@@ -12,56 +12,106 @@ pub struct HookPaths {
1212pub pwd : PathBuf ,
1313}
1414
15+ const CONFIG_HOOKS_PATH : & str = "core.hooksPath" ;
16+ const DEFAULT_HOOKS_PATH : & str = "hooks" ;
17+
1518impl HookPaths {
16- pub fn new ( repo : & Repository , hook : & str ) -> Result < Self > {
19+ /// `core.hooksPath` always takes precendence.
20+ /// If its defined and there is no hook `hook` this is not considered
21+ /// an error or a reason to search in other paths.
22+ /// If the config is not set we go into search mode and
23+ /// first check standard `.git/hooks` folder and any sub path provided in `other_paths`.
24+ ///
25+ /// Note: we try to model as closely as possible what git shell is doing.
26+ pub fn new (
27+ repo : & Repository ,
28+ other_paths : Option < & [ & str ] > ,
29+ hook : & str ,
30+ ) -> Result < Self > {
1731let pwd = repo
1832. workdir ( )
1933. unwrap_or_else ( || repo. path ( ) )
2034. to_path_buf ( ) ;
2135
2236let git_dir = repo. path ( ) . to_path_buf ( ) ;
23- let hooks_path = repo
24- . config ( )
25- . and_then ( |config| config. get_string ( "core.hooksPath" ) )
26- . map_or_else (
27- |e| {
28- log:: error!( "hookspath error: {}" , e) ;
29- repo. path ( ) . to_path_buf ( ) . join ( "hooks/" )
30- } ,
31- PathBuf :: from,
32- ) ;
3337
34- let hook = hooks_path. join ( hook) ;
38+ if let Some ( config_path) = Self :: config_hook_path ( repo) ? {
39+ let hooks_path = PathBuf :: from ( config_path) ;
40+
41+ let hook = hooks_path. join ( hook) ;
42+
43+ let hook = shellexpand:: full (
44+ hook. as_os_str ( )
45+ . to_str ( )
46+ . ok_or ( HooksError :: PathToString ) ?,
47+ ) ?;
3548
36- let hook = shellexpand:: full (
37- hook. as_os_str ( )
38- . to_str ( )
39- . ok_or ( HooksError :: PathToString ) ?,
40- ) ?;
49+ let hook = PathBuf :: from_str ( hook. as_ref ( ) )
50+ . map_err ( |_| HooksError :: PathToString ) ?;
4151
42- let hook = PathBuf :: from_str ( hook. as_ref ( ) )
43- . map_err ( |_| HooksError :: PathToString ) ?;
52+ return Ok ( Self {
53+ git : git_dir,
54+ hook,
55+ pwd,
56+ } ) ;
57+ }
4458
4559Ok ( Self {
4660git : git_dir,
47- hook,
61+ hook : Self :: find_hook ( repo , other_paths , hook ) ,
4862pwd,
4963} )
5064}
5165
52- pub fn is_executable ( & self ) -> bool {
66+ fn config_hook_path ( repo : & Repository ) -> Result < Option < String > > {
67+ Ok ( repo. config ( ) ?. get_string ( CONFIG_HOOKS_PATH ) . ok ( ) )
68+ }
69+
70+ /// check default hook path first and then followed by `other_paths`.
71+ /// if no hook is found we return the default hook path
72+ fn find_hook (
73+ repo : & Repository ,
74+ other_paths : Option < & [ & str ] > ,
75+ hook : & str ,
76+ ) -> PathBuf {
77+ let mut paths = vec ! [ DEFAULT_HOOKS_PATH . to_string( ) ] ;
78+ if let Some ( others) = other_paths {
79+ paths. extend (
80+ others
81+ . iter ( )
82+ . map ( |p| p. trim_end_matches ( '/' ) . to_string ( ) ) ,
83+ ) ;
84+ }
85+
86+ for p in paths {
87+ let p = repo. path ( ) . to_path_buf ( ) . join ( p) . join ( hook) ;
88+ if p. exists ( ) {
89+ return p;
90+ }
91+ }
92+
93+ repo. path ( )
94+ . to_path_buf ( )
95+ . join ( DEFAULT_HOOKS_PATH )
96+ . join ( hook)
97+ }
98+
99+ /// was a hook file found and is it executable
100+ pub fn found ( & self ) -> bool {
53101self . hook . exists ( ) && is_executable ( & self . hook )
54102}
55103
56104/// this function calls hook scripts based on conventions documented here
57105/// see <https://git-scm.com/docs/githooks>
58106pub fn run_hook ( & self , args : & [ & str ] ) -> Result < HookResult > {
59- let arg_str = format ! ( "{:?} {}" , self . hook, args. join( " " ) ) ;
107+ let hook = self . hook . clone ( ) ;
108+
109+ let arg_str = format ! ( "{:?} {}" , hook, args. join( " " ) ) ;
60110// Use -l to avoid "command not found" on Windows.
61111let bash_args =
62112vec ! [ "-l" . to_string( ) , "-c" . to_string( ) , arg_str] ;
63113
64- log:: trace!( "run hook '{:?}' in '{:?}'" , self . hook, self . pwd) ;
114+ log:: trace!( "run hook '{:?}' in '{:?}'" , hook, self . pwd) ;
65115
66116let git_bash = find_bash_executable ( )
67117. unwrap_or_else ( || PathBuf :: from ( "bash" ) ) ;
@@ -78,19 +128,24 @@ impl HookPaths {
78128. output ( ) ?;
79129
80130if output. status . success ( ) {
81- Ok ( HookResult :: Ok )
131+ Ok ( HookResult :: Ok { hook } )
82132} else {
83133let stderr =
84134String :: from_utf8_lossy ( & output. stderr ) . to_string ( ) ;
85135let stdout =
86136String :: from_utf8_lossy ( & output. stdout ) . to_string ( ) ;
87137
88- Ok ( HookResult :: NotOk { stdout, stderr } )
138+ Ok ( HookResult :: RunNotSuccessful {
139+ code : output. status . code ( ) ,
140+ stdout,
141+ stderr,
142+ hook,
143+ } )
89144}
90145}
91146}
92147
93- #[ cfg( not ( windows ) ) ]
148+ #[ cfg( unix ) ]
94149fn is_executable ( path : & Path ) -> bool {
95150use std:: os:: unix:: fs:: PermissionsExt ;
96151
0 commit comments