@@ -2,10 +2,7 @@ use super::{repository::repo, RepoPath};
2
2
use crate :: error:: Result ;
3
3
pub use git2_hooks:: PrepareCommitMsgSource ;
4
4
use scopetime:: scope_time;
5
- use std:: {
6
- sync:: mpsc:: { channel, RecvTimeoutError } ,
7
- time:: Duration ,
8
- } ;
5
+ use std:: time:: Duration ;
9
6
10
7
///
11
8
#[ derive( Debug , PartialEq , Eq ) ]
@@ -14,6 +11,8 @@ pub enum HookResult {
14
11
Ok ,
15
12
/// Hook returned error
16
13
NotOk ( String ) ,
14
+ /// Hook timed out
15
+ TimedOut ,
17
16
}
18
17
19
18
impl From < git2_hooks:: HookResult > for HookResult {
@@ -26,156 +25,127 @@ impl From<git2_hooks::HookResult> for HookResult {
26
25
stderr,
27
26
..
28
27
} => Self :: NotOk ( format ! ( "{stdout}{stderr}" ) ) ,
28
+ git2_hooks:: HookResult :: TimedOut { .. } => Self :: TimedOut ,
29
29
}
30
30
}
31
31
}
32
32
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" ) ;
46
39
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 ( ) )
63
43
}
64
44
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 (
67
48
repo_path : & RepoPath ,
68
49
msg : & mut String ,
69
50
timeout : Duration ,
70
51
) -> Result < HookResult > {
71
- scope_time ! ( "hooks_commit_msg " ) ;
52
+ scope_time ! ( "hooks_prepare_commit_msg " ) ;
72
53
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
+ }
95
60
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 ( ) )
97
68
}
98
69
99
70
/// see `git2_hooks::hooks_pre_commit`
100
- pub fn hooks_pre_commit (
71
+ #[ allow( unused) ]
72
+ pub fn hooks_pre_commit_with_timeout (
101
73
repo_path : & RepoPath ,
102
74
timeout : Duration ,
103
75
) -> Result < HookResult > {
104
76
scope_time ! ( "hooks_pre_commit" ) ;
105
77
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 ( ) )
118
93
}
119
94
120
95
/// see `git2_hooks::hooks_post_commit`
121
- pub fn hooks_post_commit (
96
+ #[ allow( unused) ]
97
+ pub fn hooks_post_commit_with_timeout (
122
98
repo_path : & RepoPath ,
123
99
timeout : Duration ,
124
100
) -> Result < HookResult > {
125
101
scope_time ! ( "hooks_post_commit" ) ;
126
102
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 ( ) )
139
109
}
140
110
141
111
/// see `git2_hooks::hooks_prepare_commit_msg`
142
112
pub fn hooks_prepare_commit_msg (
143
113
repo_path : & RepoPath ,
144
114
source : PrepareCommitMsgSource ,
145
115
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 ,
146
133
timeout : Duration ,
147
134
) -> Result < HookResult > {
148
135
scope_time ! ( "hooks_prepare_commit_msg" ) ;
149
136
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) ?;
173
138
174
- Ok ( result)
139
+ Ok ( git2_hooks:: hooks_prepare_commit_msg_with_timeout (
140
+ & repo, None , source, msg, timeout,
141
+ ) ?
142
+ . into ( ) )
175
143
}
176
144
177
145
#[ cfg( test) ]
178
146
mod tests {
147
+ use tempfile:: tempdir;
148
+
179
149
use super :: * ;
180
150
use crate :: sync:: tests:: repo_init;
181
151
use std:: fs:: File ;
@@ -215,11 +185,9 @@ mod tests {
215
185
let subfolder = root. join ( "foo/" ) ;
216
186
std:: fs:: create_dir_all ( & subfolder) . unwrap ( ) ;
217
187
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 ( ) ;
223
191
224
192
assert_eq ! (
225
193
res,
@@ -250,8 +218,7 @@ mod tests {
250
218
git2_hooks:: HOOK_PRE_COMMIT ,
251
219
hook,
252
220
) ;
253
- let res =
254
- hooks_pre_commit ( repo_path, Duration :: ZERO ) . unwrap ( ) ;
221
+ let res = hooks_pre_commit ( repo_path) . unwrap ( ) ;
255
222
if let HookResult :: NotOk ( res) = res {
256
223
assert_eq ! (
257
224
std:: path:: Path :: new( res. trim_end( ) ) ,
@@ -286,7 +253,6 @@ mod tests {
286
253
let res = hooks_commit_msg (
287
254
& subfolder. to_str ( ) . unwrap ( ) . into ( ) ,
288
255
& mut msg,
289
- Duration :: ZERO ,
290
256
)
291
257
. unwrap ( ) ;
292
258
@@ -346,16 +312,13 @@ mod tests {
346
312
hook,
347
313
) ;
348
314
349
- let res = hooks_pre_commit (
315
+ let res = hooks_pre_commit_with_timeout (
350
316
& root. to_str ( ) . unwrap ( ) . into ( ) ,
351
317
Duration :: from_millis ( 200 ) ,
352
318
)
353
319
. unwrap ( ) ;
354
320
355
- assert_eq ! (
356
- res,
357
- HookResult :: NotOk ( "hook timed out" . to_string( ) )
358
- ) ;
321
+ assert_eq ! ( res, HookResult :: TimedOut ) ;
359
322
}
360
323
361
324
#[ test]
@@ -373,7 +336,7 @@ mod tests {
373
336
hook,
374
337
) ;
375
338
376
- let res = hooks_pre_commit (
339
+ let res = hooks_pre_commit_with_timeout (
377
340
& root. to_str ( ) . unwrap ( ) . into ( ) ,
378
341
Duration :: from_millis ( 110 ) ,
379
342
)
@@ -397,12 +360,43 @@ mod tests {
397
360
hook,
398
361
) ;
399
362
400
- let res = hooks_post_commit (
363
+ let res = hooks_post_commit_with_timeout (
401
364
& root. to_str ( ) . unwrap ( ) . into ( ) ,
402
365
Duration :: ZERO ,
403
366
)
404
367
. unwrap ( ) ;
405
368
406
369
assert_eq ! ( res, HookResult :: Ok ) ;
407
370
}
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
+ }
408
402
}
0 commit comments