use std::process::Command;
use standard_githooks::{HookCommand, Prefix};
use crate::{git, ui};
pub(super) fn run_lifecycle_hook(hook_name: &str, extra_args: &[&str]) -> Result<(), i32> {
if let Ok(val) = std::env::var("GIT_STD_SKIP_HOOKS")
&& (val == "1" || val.eq_ignore_ascii_case("true"))
{
return Ok(());
}
let cwd = std::env::current_dir().unwrap_or_default();
let hooks_dir = match git::workdir(&cwd) {
Ok(root) => root.join(".githooks"),
Err(_) => {
ui::error("bare repository not supported");
return Err(1);
}
};
let hooks_file = hooks_dir.join(format!("{hook_name}.hooks"));
if !hooks_file.exists() {
return Ok(());
}
let content = match std::fs::read_to_string(&hooks_file) {
Ok(c) => c,
Err(e) => {
ui::error(&format!("cannot read {}: {e}", hooks_file.display()));
return Err(2);
}
};
let commands = standard_githooks::parse(&content);
if commands.is_empty() {
return Ok(());
}
for cmd in &commands {
let exit_code = execute_hook_command(cmd, extra_args);
let success = exit_code == Some(0);
let is_advisory = cmd.prefix == Prefix::Advisory;
if success {
ui::info(&format!("{} {}", ui::pass(), cmd.command));
} else if is_advisory {
let info = match exit_code {
Some(code) => format!("(advisory, exit {code})"),
None => "(advisory, killed)".to_string(),
};
ui::info(&format!("{} {} {}", ui::warn(), cmd.command, info));
} else {
let info = match exit_code {
Some(code) => format!("(exit {code})"),
None => "(killed)".to_string(),
};
ui::error(&format!("hook command failed: {} {info}", cmd.command));
ui::hint(&format!(
"to disable this command: comment it out in .githooks/{hook_name}.hooks"
));
return Err(1);
}
}
Ok(())
}
fn execute_hook_command(cmd: &HookCommand, extra_args: &[&str]) -> Option<i32> {
let status = Command::new("sh")
.arg("-c")
.arg(&cmd.command)
.arg("_")
.args(extra_args)
.status();
match status {
Ok(s) => s.code(),
Err(_) => Some(127),
}
}