@@ -2,10 +2,7 @@ use super::{repository::repo, RepoPath};
22use crate :: error:: Result ;
33pub use git2_hooks:: PrepareCommitMsgSource ;
44use scopetime:: scope_time;
5- use std:: {
6- sync:: mpsc:: { channel, RecvTimeoutError } ,
7- time:: Duration ,
8- } ;
5+ use std:: time:: Duration ;
96
107///
118#[ derive( Debug , PartialEq , Eq ) ]
@@ -14,6 +11,8 @@ pub enum HookResult {
1411 Ok ,
1512 /// Hook returned error
1613 NotOk ( String ) ,
14+ /// Hook timed out
15+ TimedOut ,
1716}
1817
1918impl From < git2_hooks:: HookResult > for HookResult {
@@ -26,156 +25,127 @@ impl From<git2_hooks::HookResult> for HookResult {
2625 stderr,
2726 ..
2827 } => Self :: NotOk ( format ! ( "{stdout}{stderr}" ) ) ,
28+ git2_hooks:: HookResult :: TimedOut { .. } => Self :: TimedOut ,
2929 }
3030 }
3131}
3232
33- fn run_with_timeout < F > (
34- f : F ,
35- timeout : Duration ,
36- ) -> Result < ( HookResult , Option < String > ) >
37- where
38- F : FnOnce ( ) -> Result < ( HookResult , Option < String > ) >
39- + Send
40- + Sync
41- + ' static ,
42- {
43- if timeout. is_zero ( ) {
44- return f ( ) ; // Don't bother with threads if we don't have a timeout
45- }
33+ /// see `git2_hooks::hooks_commit_msg`
34+ pub fn hooks_commit_msg (
35+ repo_path : & RepoPath ,
36+ msg : & mut String ,
37+ ) -> Result < HookResult > {
38+ scope_time ! ( "hooks_commit_msg" ) ;
4639
47- let ( tx, rx) = channel ( ) ;
48- let _ = std:: thread:: spawn ( move || {
49- let result = f ( ) ;
50- tx. send ( result)
51- } ) ;
52-
53- match rx. recv_timeout ( timeout) {
54- Ok ( result) => result,
55- Err ( RecvTimeoutError :: Timeout ) => Ok ( (
56- HookResult :: NotOk ( "hook timed out" . to_string ( ) ) ,
57- None ,
58- ) ) ,
59- Err ( RecvTimeoutError :: Disconnected ) => {
60- unreachable ! ( )
61- }
62- }
40+ let repo = repo ( repo_path) ?;
41+
42+ Ok ( git2_hooks:: hooks_commit_msg ( & repo, None , msg) ?. into ( ) )
6343}
6444
65- /// see `git2_hooks::hooks_commit_msg`
66- pub fn hooks_commit_msg (
45+ /// see `git2_hooks::hooks_prepare_commit_msg`
46+ #[ allow( unused) ]
47+ pub fn hooks_commit_msg_with_timeout (
6748 repo_path : & RepoPath ,
6849 msg : & mut String ,
6950 timeout : Duration ,
7051) -> Result < HookResult > {
71- scope_time ! ( "hooks_commit_msg " ) ;
52+ scope_time ! ( "hooks_prepare_commit_msg " ) ;
7253
73- let repo_path = repo_path. clone ( ) ;
74- let mut msg_clone = msg. clone ( ) ;
75- let ( result, msg_opt) = run_with_timeout (
76- move || {
77- let repo = repo ( & repo_path) ?;
78- Ok ( (
79- git2_hooks:: hooks_commit_msg (
80- & repo,
81- None ,
82- & mut msg_clone,
83- ) ?
84- . into ( ) ,
85- Some ( msg_clone) ,
86- ) )
87- } ,
88- timeout,
89- ) ?;
90-
91- if let Some ( updated_msg) = msg_opt {
92- msg. clear ( ) ;
93- msg. push_str ( & updated_msg) ;
94- }
54+ let repo = repo ( repo_path) ?;
55+ Ok ( git2_hooks:: hooks_commit_msg_with_timeout (
56+ & repo, None , msg, timeout,
57+ ) ?
58+ . into ( ) )
59+ }
9560
96- Ok ( result)
61+ /// see `git2_hooks::hooks_pre_commit`
62+ pub fn hooks_pre_commit ( repo_path : & RepoPath ) -> Result < HookResult > {
63+ scope_time ! ( "hooks_pre_commit" ) ;
64+
65+ let repo = repo ( repo_path) ?;
66+
67+ Ok ( git2_hooks:: hooks_pre_commit ( & repo, None ) ?. into ( ) )
9768}
9869
9970/// see `git2_hooks::hooks_pre_commit`
100- pub fn hooks_pre_commit (
71+ #[ allow( unused) ]
72+ pub fn hooks_pre_commit_with_timeout (
10173 repo_path : & RepoPath ,
10274 timeout : Duration ,
10375) -> Result < HookResult > {
10476 scope_time ! ( "hooks_pre_commit" ) ;
10577
106- let repo_path = repo_path. clone ( ) ;
107- run_with_timeout (
108- move || {
109- let repo = repo ( & repo_path) ?;
110- Ok ( (
111- git2_hooks:: hooks_pre_commit ( & repo, None ) ?. into ( ) ,
112- None ,
113- ) )
114- } ,
115- timeout,
116- )
117- . map ( |res| res. 0 )
78+ let repo = repo ( repo_path) ?;
79+
80+ Ok ( git2_hooks:: hooks_pre_commit_with_timeout (
81+ & repo, None , timeout,
82+ ) ?
83+ . into ( ) )
84+ }
85+
86+ /// see `git2_hooks::hooks_post_commit`
87+ pub fn hooks_post_commit ( repo_path : & RepoPath ) -> Result < HookResult > {
88+ scope_time ! ( "hooks_post_commit" ) ;
89+
90+ let repo = repo ( repo_path) ?;
91+
92+ Ok ( git2_hooks:: hooks_post_commit ( & repo, None ) ?. into ( ) )
11893}
11994
12095/// see `git2_hooks::hooks_post_commit`
121- pub fn hooks_post_commit (
96+ #[ allow( unused) ]
97+ pub fn hooks_post_commit_with_timeout (
12298 repo_path : & RepoPath ,
12399 timeout : Duration ,
124100) -> Result < HookResult > {
125101 scope_time ! ( "hooks_post_commit" ) ;
126102
127- let repo_path = repo_path. clone ( ) ;
128- run_with_timeout (
129- move || {
130- let repo = repo ( & repo_path) ?;
131- Ok ( (
132- git2_hooks:: hooks_post_commit ( & repo, None ) ?. into ( ) ,
133- None ,
134- ) )
135- } ,
136- timeout,
137- )
138- . map ( |res| res. 0 )
103+ let repo = repo ( repo_path) ?;
104+
105+ Ok ( git2_hooks:: hooks_post_commit_with_timeout (
106+ & repo, None , timeout,
107+ ) ?
108+ . into ( ) )
139109}
140110
141111/// see `git2_hooks::hooks_prepare_commit_msg`
142112pub fn hooks_prepare_commit_msg (
143113 repo_path : & RepoPath ,
144114 source : PrepareCommitMsgSource ,
145115 msg : & mut String ,
116+ ) -> Result < HookResult > {
117+ scope_time ! ( "hooks_prepare_commit_msg" ) ;
118+
119+ let repo = repo ( repo_path) ?;
120+
121+ Ok ( git2_hooks:: hooks_prepare_commit_msg (
122+ & repo, None , source, msg,
123+ ) ?
124+ . into ( ) )
125+ }
126+
127+ /// see `git2_hooks::hooks_prepare_commit_msg`
128+ #[ allow( unused) ]
129+ pub fn hooks_prepare_commit_msg_with_timeout (
130+ repo_path : & RepoPath ,
131+ source : PrepareCommitMsgSource ,
132+ msg : & mut String ,
146133 timeout : Duration ,
147134) -> Result < HookResult > {
148135 scope_time ! ( "hooks_prepare_commit_msg" ) ;
149136
150- let repo_path = repo_path. clone ( ) ;
151- let mut msg_cloned = msg. clone ( ) ;
152- let ( result, msg_opt) = run_with_timeout (
153- move || {
154- let repo = repo ( & repo_path) ?;
155- Ok ( (
156- git2_hooks:: hooks_prepare_commit_msg (
157- & repo,
158- None ,
159- source,
160- & mut msg_cloned,
161- ) ?
162- . into ( ) ,
163- Some ( msg_cloned) ,
164- ) )
165- } ,
166- timeout,
167- ) ?;
168-
169- if let Some ( updated_msg) = msg_opt {
170- msg. clear ( ) ;
171- msg. push_str ( & updated_msg) ;
172- }
137+ let repo = repo ( repo_path) ?;
173138
174- Ok ( result)
139+ Ok ( git2_hooks:: hooks_prepare_commit_msg_with_timeout (
140+ & repo, None , source, msg, timeout,
141+ ) ?
142+ . into ( ) )
175143}
176144
177145#[ cfg( test) ]
178146mod tests {
147+ use tempfile:: tempdir;
148+
179149 use super :: * ;
180150 use crate :: sync:: tests:: repo_init;
181151 use std:: fs:: File ;
@@ -215,11 +185,9 @@ mod tests {
215185 let subfolder = root. join ( "foo/" ) ;
216186 std:: fs:: create_dir_all ( & subfolder) . unwrap ( ) ;
217187
218- let res = hooks_post_commit (
219- & subfolder. to_str ( ) . unwrap ( ) . into ( ) ,
220- Duration :: ZERO ,
221- )
222- . unwrap ( ) ;
188+ let res =
189+ hooks_post_commit ( & subfolder. to_str ( ) . unwrap ( ) . into ( ) )
190+ . unwrap ( ) ;
223191
224192 assert_eq ! (
225193 res,
@@ -250,8 +218,7 @@ mod tests {
250218 git2_hooks:: HOOK_PRE_COMMIT ,
251219 hook,
252220 ) ;
253- let res =
254- hooks_pre_commit ( repo_path, Duration :: ZERO ) . unwrap ( ) ;
221+ let res = hooks_pre_commit ( repo_path) . unwrap ( ) ;
255222 if let HookResult :: NotOk ( res) = res {
256223 assert_eq ! (
257224 std:: path:: Path :: new( res. trim_end( ) ) ,
@@ -286,7 +253,6 @@ mod tests {
286253 let res = hooks_commit_msg (
287254 & subfolder. to_str ( ) . unwrap ( ) . into ( ) ,
288255 & mut msg,
289- Duration :: ZERO ,
290256 )
291257 . unwrap ( ) ;
292258
@@ -346,16 +312,13 @@ mod tests {
346312 hook,
347313 ) ;
348314
349- let res = hooks_pre_commit (
315+ let res = hooks_pre_commit_with_timeout (
350316 & root. to_str ( ) . unwrap ( ) . into ( ) ,
351317 Duration :: from_millis ( 200 ) ,
352318 )
353319 . unwrap ( ) ;
354320
355- assert_eq ! (
356- res,
357- HookResult :: NotOk ( "hook timed out" . to_string( ) )
358- ) ;
321+ assert_eq ! ( res, HookResult :: TimedOut ) ;
359322 }
360323
361324 #[ test]
@@ -373,7 +336,7 @@ mod tests {
373336 hook,
374337 ) ;
375338
376- let res = hooks_pre_commit (
339+ let res = hooks_pre_commit_with_timeout (
377340 & root. to_str ( ) . unwrap ( ) . into ( ) ,
378341 Duration :: from_millis ( 110 ) ,
379342 )
@@ -397,12 +360,43 @@ mod tests {
397360 hook,
398361 ) ;
399362
400- let res = hooks_post_commit (
363+ let res = hooks_post_commit_with_timeout (
401364 & root. to_str ( ) . unwrap ( ) . into ( ) ,
402365 Duration :: ZERO ,
403366 )
404367 . unwrap ( ) ;
405368
406369 assert_eq ! ( res, HookResult :: Ok ) ;
407370 }
371+
372+ #[ test]
373+ fn test_run_with_timeout_kills ( ) {
374+ let ( _td, repo) = repo_init ( ) . unwrap ( ) ;
375+ let root = repo. path ( ) . parent ( ) . unwrap ( ) ;
376+
377+ let temp_dir = tempdir ( ) . expect ( "temp dir" ) ;
378+ let file = temp_dir. path ( ) . join ( "test" ) ;
379+ let hook = format ! (
380+ "#!/usr/bin/env sh
381+ sleep 1
382+
383+ echo 'after sleep' > {}
384+ " ,
385+ file. as_path( ) . to_str( ) . unwrap( )
386+ ) ;
387+
388+ git2_hooks:: create_hook (
389+ & repo,
390+ git2_hooks:: HOOK_PRE_COMMIT ,
391+ hook. as_bytes ( ) ,
392+ ) ;
393+
394+ let res = hooks_pre_commit_with_timeout (
395+ & root. to_str ( ) . unwrap ( ) . into ( ) ,
396+ Duration :: from_millis ( 100 ) ,
397+ ) ;
398+
399+ assert ! ( res. is_ok( ) ) ;
400+ assert ! ( !file. exists( ) ) ;
401+ }
408402}
0 commit comments