@@ -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 ( ) )
118
84
}
119
85
120
86
/// 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 (
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
@@ -198,11 +168,9 @@ mod tests {
198
168
let subfolder = root. join ( "foo/" ) ;
199
169
std:: fs:: create_dir_all ( & subfolder) . unwrap ( ) ;
200
170
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 ( ) ;
206
174
207
175
assert_eq ! (
208
176
res,
@@ -233,8 +201,7 @@ mod tests {
233
201
git2_hooks:: HOOK_PRE_COMMIT ,
234
202
hook,
235
203
) ;
236
- let res =
237
- hooks_pre_commit ( repo_path, Duration :: ZERO ) . unwrap ( ) ;
204
+ let res = hooks_pre_commit ( repo_path) . unwrap ( ) ;
238
205
if let HookResult :: NotOk ( res) = res {
239
206
assert_eq ! (
240
207
std:: path:: Path :: new( res. trim_end( ) ) ,
@@ -269,7 +236,6 @@ mod tests {
269
236
let res = hooks_commit_msg (
270
237
& subfolder. to_str ( ) . unwrap ( ) . into ( ) ,
271
238
& mut msg,
272
- Duration :: ZERO ,
273
239
)
274
240
. unwrap ( ) ;
275
241
@@ -296,16 +262,13 @@ mod tests {
296
262
hook,
297
263
) ;
298
264
299
- let res = hooks_pre_commit (
265
+ let res = hooks_pre_commit_with_timeout (
300
266
& root. to_str ( ) . unwrap ( ) . into ( ) ,
301
267
Duration :: from_millis ( 200 ) ,
302
268
)
303
269
. unwrap ( ) ;
304
270
305
- assert_eq ! (
306
- res,
307
- HookResult :: NotOk ( "hook timed out" . to_string( ) )
308
- ) ;
271
+ assert_eq ! ( res, HookResult :: TimedOut ) ;
309
272
}
310
273
311
274
#[ test]
@@ -323,7 +286,7 @@ mod tests {
323
286
hook,
324
287
) ;
325
288
326
- let res = hooks_pre_commit (
289
+ let res = hooks_pre_commit_with_timeout (
327
290
& root. to_str ( ) . unwrap ( ) . into ( ) ,
328
291
Duration :: from_millis ( 110 ) ,
329
292
)
@@ -347,12 +310,43 @@ mod tests {
347
310
hook,
348
311
) ;
349
312
350
- let res = hooks_post_commit (
313
+ let res = hooks_post_commit_with_timeout (
351
314
& root. to_str ( ) . unwrap ( ) . into ( ) ,
352
315
Duration :: ZERO ,
353
316
)
354
317
. unwrap ( ) ;
355
318
356
319
assert_eq ! ( res, HookResult :: Ok ) ;
357
320
}
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
+ }
358
352
}
0 commit comments