set_git_hooks_dir/
installer.rs1use std::env;
2use std::fs::File;
3use std::io::{self, BufRead, BufReader};
4use std::path::{Path, PathBuf};
5use std::process::Command;
6
7const CI_ENV_VARS: &[&str] = &[
8 "SET_GIT_HOOKS_DIR_SKIP",
9 "GITHUB_ACTION",
10 "CI",
11 "JENKINS_URL",
12];
13
14fn find_dot_git(hooks_dir: &Path, base_dir: &Path) -> io::Result<PathBuf> {
15 let mut cur = base_dir;
16 loop {
17 let dir = cur.join(hooks_dir);
18 if dir.is_dir() {
19 let dot_git = cur.join(".git");
20 if dot_git.exists() {
21 return Ok(dot_git);
22 }
23 }
24 let Some(parent) = cur.parent() else {
25 let msg = format!(
26 "Directory {hooks_dir:?} is not found at any root of Git repository in {base_dir:?}",
27 );
28 return Err(io::Error::new(io::ErrorKind::Other, msg));
29 };
30 cur = parent;
31 }
32}
33
34pub fn setup(hooks_dir: impl AsRef<Path>, base_dir: impl AsRef<Path>) -> io::Result<()> {
36 for var in CI_ENV_VARS {
37 if matches!(env::var(var), Ok(v) if !v.is_empty()) {
38 return Ok(());
39 }
40 }
41
42 let hooks_dir = hooks_dir.as_ref();
43
44 let dot_git = find_dot_git(hooks_dir, base_dir.as_ref())?;
45 if dot_git.is_dir() {
46 let config = File::open(dot_git.join("config"))?;
47 for line in BufReader::new(config).lines() {
48 if line?.starts_with("\thooksPath = ") {
49 return Ok(());
50 }
51 }
52 }
53
54 let git_var = env::var("SET_GIT_HOOKS_DIR_GIT");
55 let git = match &git_var {
56 Ok(var) if !var.is_empty() => var.as_str(),
57 _ => "git",
58 };
59
60 let mut cmd = Command::new(git);
61 cmd.arg("config").arg("core.hooksPath").arg(hooks_dir);
62 if let Some(root) = dot_git.parent() {
63 cmd.current_dir(root);
64 }
65
66 let output = cmd.output()?;
67 if !output.status.success() {
68 let stderr = String::from_utf8_lossy(&output.stderr);
69 return Err(io::Error::new(
70 io::ErrorKind::Other,
71 format!("`{git} config core.hooksPath {hooks_dir:?}` failed: {stderr}"),
72 ));
73 }
74
75 Ok(())
76}