@@ -22,6 +22,25 @@ impl Hooks {
22
22
& self . root
23
23
}
24
24
25
+ pub fn find_hook ( & self , _repo : & git2:: Repository , name : & str ) -> Option < std:: path:: PathBuf > {
26
+ let mut hook_path = self . root ( ) . join ( name) ;
27
+ if is_executable ( & hook_path) {
28
+ return Some ( hook_path) ;
29
+ }
30
+
31
+ if !std:: env:: consts:: EXE_SUFFIX . is_empty ( ) {
32
+ hook_path. set_extension ( std:: env:: consts:: EXE_SUFFIX ) ;
33
+ if is_executable ( & hook_path) {
34
+ return Some ( hook_path) ;
35
+ }
36
+ }
37
+
38
+ // Technically, we should check `advice.ignoredHook` and warn users if the hook is present
39
+ // but not executable. Supporting this in the future is why we accept `repo`.
40
+
41
+ None
42
+ }
43
+
25
44
pub fn run_hook (
26
45
& self ,
27
46
repo : & git2:: Repository ,
@@ -30,10 +49,16 @@ impl Hooks {
30
49
stdin : Option < & [ u8 ] > ,
31
50
env : & [ ( & str , & str ) ] ,
32
51
) -> Result < i32 , std:: io:: Error > {
33
- let hook_path = self . root ( ) . join ( name) ;
34
- if !hook_path. exists ( ) {
52
+ let hook_path = if let Some ( hook_path) = self . find_hook ( repo, name) {
53
+ hook_path
54
+ } else {
35
55
return Ok ( 0 ) ;
36
- }
56
+ } ;
57
+ let bin_name = hook_path
58
+ . file_name ( )
59
+ . expect ( "find_hook always returns a bin name" )
60
+ . to_str ( )
61
+ . expect ( "find_hook always returns a utf-8 bin name" ) ;
37
62
38
63
let path = {
39
64
let mut path_components: Vec < std:: path:: PathBuf > =
@@ -62,11 +87,12 @@ impl Hooks {
62
87
63
88
let mut cmd = std:: process:: Command :: new ( sh_path) ;
64
89
cmd. arg ( "-c" )
65
- . arg ( format ! ( "{} \" $@\" " , name ) )
66
- . arg ( name ) // "$@" expands "$1" "$2" "$3" ... but we also must specify $0.
90
+ . arg ( format ! ( "{} \" $@\" " , bin_name ) )
91
+ . arg ( bin_name ) // "$@" expands "$1" "$2" "$3" ... but we also must specify $0.
67
92
. args ( args)
68
93
. env ( "PATH" , path)
69
94
. current_dir ( cwd)
95
+ // Technically, git maps stdout to stderr when running hooks
70
96
. stdin ( std:: process:: Stdio :: piped ( ) ) ;
71
97
for ( key, value) in env. iter ( ) . copied ( ) {
72
98
cmd. env ( key, value) ;
@@ -121,3 +147,20 @@ const PUSH_HOOKS: &[&str] = &[
121
147
"post-update" ,
122
148
"push-to-checkout" ,
123
149
] ;
150
+
151
+ #[ cfg( unix) ]
152
+ fn is_executable ( path : & std:: path:: Path ) -> bool {
153
+ use std:: os:: unix:: fs:: PermissionsExt ;
154
+
155
+ let metadata = match path. metadata ( ) {
156
+ Ok ( metadata) => metadata,
157
+ Err ( _) => return false ,
158
+ } ;
159
+ let permissions = metadata. permissions ( ) ;
160
+ metadata. is_file ( ) && permissions. mode ( ) & 0o111 != 0
161
+ }
162
+
163
+ #[ cfg( not( unix) ) ]
164
+ fn is_executable ( path : & std:: path:: Path ) -> bool {
165
+ path. is_file ( )
166
+ }
0 commit comments