Skip to content

Commit 85155e8

Browse files
committed
fix(hooks): Improve fidelity with git
1 parent 7e42705 commit 85155e8

File tree

1 file changed

+48
-5
lines changed

1 file changed

+48
-5
lines changed

src/hooks.rs

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,25 @@ impl Hooks {
2222
&self.root
2323
}
2424

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+
2544
pub fn run_hook(
2645
&self,
2746
repo: &git2::Repository,
@@ -30,10 +49,16 @@ impl Hooks {
3049
stdin: Option<&[u8]>,
3150
env: &[(&str, &str)],
3251
) -> 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 {
3555
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");
3762

3863
let path = {
3964
let mut path_components: Vec<std::path::PathBuf> =
@@ -62,11 +87,12 @@ impl Hooks {
6287

6388
let mut cmd = std::process::Command::new(sh_path);
6489
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.
6792
.args(args)
6893
.env("PATH", path)
6994
.current_dir(cwd)
95+
// Technically, git maps stdout to stderr when running hooks
7096
.stdin(std::process::Stdio::piped());
7197
for (key, value) in env.iter().copied() {
7298
cmd.env(key, value);
@@ -121,3 +147,20 @@ const PUSH_HOOKS: &[&str] = &[
121147
"post-update",
122148
"push-to-checkout",
123149
];
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

Comments
 (0)