@@ -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 ( ) )
11884}
11985
12086/// see `git2_hooks::hooks_post_commit`
121- pub fn 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 ( ) )
93+ }
94+
95+ /// see `git2_hooks::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
@@ -198,11 +168,9 @@ mod tests {
198168 let subfolder = root. join ( "foo/" ) ;
199169 std:: fs:: create_dir_all ( & subfolder) . unwrap ( ) ;
200170
201- let res = hooks_post_commit (
202- & subfolder. to_str ( ) . unwrap ( ) . into ( ) ,
203- Duration :: ZERO ,
204- )
205- . unwrap ( ) ;
171+ let res =
172+ hooks_post_commit ( & subfolder. to_str ( ) . unwrap ( ) . into ( ) )
173+ . unwrap ( ) ;
206174
207175 assert_eq ! (
208176 res,
@@ -233,8 +201,7 @@ mod tests {
233201 git2_hooks:: HOOK_PRE_COMMIT ,
234202 hook,
235203 ) ;
236- let res =
237- hooks_pre_commit ( repo_path, Duration :: ZERO ) . unwrap ( ) ;
204+ let res = hooks_pre_commit ( repo_path) . unwrap ( ) ;
238205 if let HookResult :: NotOk ( res) = res {
239206 assert_eq ! (
240207 std:: path:: Path :: new( res. trim_end( ) ) ,
@@ -269,7 +236,6 @@ mod tests {
269236 let res = hooks_commit_msg (
270237 & subfolder. to_str ( ) . unwrap ( ) . into ( ) ,
271238 & mut msg,
272- Duration :: ZERO ,
273239 )
274240 . unwrap ( ) ;
275241
@@ -296,16 +262,13 @@ mod tests {
296262 hook,
297263 ) ;
298264
299- let res = hooks_pre_commit (
265+ let res = hooks_pre_commit_with_timeout (
300266 & root. to_str ( ) . unwrap ( ) . into ( ) ,
301267 Duration :: from_millis ( 200 ) ,
302268 )
303269 . unwrap ( ) ;
304270
305- assert_eq ! (
306- res,
307- HookResult :: NotOk ( "hook timed out" . to_string( ) )
308- ) ;
271+ assert_eq ! ( res, HookResult :: TimedOut ) ;
309272 }
310273
311274 #[ test]
@@ -323,7 +286,7 @@ mod tests {
323286 hook,
324287 ) ;
325288
326- let res = hooks_pre_commit (
289+ let res = hooks_pre_commit_with_timeout (
327290 & root. to_str ( ) . unwrap ( ) . into ( ) ,
328291 Duration :: from_millis ( 110 ) ,
329292 )
@@ -347,12 +310,43 @@ mod tests {
347310 hook,
348311 ) ;
349312
350- let res = hooks_post_commit (
313+ let res = hooks_post_commit_with_timeout (
351314 & root. to_str ( ) . unwrap ( ) . into ( ) ,
352315 Duration :: ZERO ,
353316 )
354317 . unwrap ( ) ;
355318
356319 assert_eq ! ( res, HookResult :: Ok ) ;
357320 }
321+
322+ #[ test]
323+ fn test_run_with_timeout_kills ( ) {
324+ let ( _td, repo) = repo_init ( ) . unwrap ( ) ;
325+ let root = repo. path ( ) . parent ( ) . unwrap ( ) ;
326+
327+ let temp_dir = tempdir ( ) . expect ( "temp dir" ) ;
328+ let file = temp_dir. path ( ) . join ( "test" ) ;
329+ let hook = format ! (
330+ "#!/usr/bin/env sh
331+ sleep 1
332+
333+ echo 'after sleep' > {}
334+ " ,
335+ file. as_path( ) . to_str( ) . unwrap( )
336+ ) ;
337+
338+ git2_hooks:: create_hook (
339+ & repo,
340+ git2_hooks:: HOOK_PRE_COMMIT ,
341+ hook. as_bytes ( ) ,
342+ ) ;
343+
344+ let res = hooks_pre_commit_with_timeout (
345+ & root. to_str ( ) . unwrap ( ) . into ( ) ,
346+ Duration :: from_millis ( 100 ) ,
347+ ) ;
348+
349+ assert ! ( res. is_ok( ) ) ;
350+ assert ! ( !file. exists( ) ) ;
351+ }
358352}
0 commit comments