use std::path::Path;
use git_lfs_git::git_dir;
use crate::install;
const HOOKS: &[&str] = &["pre-push", "post-checkout", "post-commit", "post-merge"];
#[derive(Debug, thiserror::Error)]
pub enum UpdateError {
#[error(transparent)]
Install(#[from] install::InstallError),
#[error(transparent)]
Io(#[from] std::io::Error),
#[error("Not in a Git repository.")]
NotInRepo,
}
pub fn run(cwd: &Path, force: bool, manual: bool) -> Result<u8, UpdateError> {
let git_dir = git_dir(cwd).map_err(|_| UpdateError::NotInRepo)?;
let hooks_dir = install::effective_hooks_dir(cwd)?;
let display_dir = display_hooks_dir(cwd, &git_dir, &hooks_dir);
if manual {
print_manual(&display_dir);
return Ok(0);
}
let opts = install::InstallOptions {
scope: install::InstallScope::Local,
force,
skip_repo: false,
skip_smudge: false,
};
match install::install_all_hooks(cwd, &opts) {
Ok(()) => {
println!("Updated Git hooks.");
Ok(0)
}
Err(install::InstallError::HookConflict { hook, existing }) => {
install::print_hook_conflict(&hook, &existing);
Ok(2)
}
Err(e) => Err(e.into()),
}
}
fn display_hooks_dir(cwd: &Path, git_dir: &Path, hooks_dir: &Path) -> String {
let work_root = git_dir.parent().unwrap_or(git_dir);
if let Ok(rel) = hooks_dir.strip_prefix(work_root) {
return rel.display().to_string();
}
if let Ok(rel) = hooks_dir.strip_prefix(cwd) {
return rel.display().to_string();
}
hooks_dir.display().to_string()
}
fn print_manual(display_dir: &str) {
let mut first = true;
for hook in HOOKS {
if !first {
println!();
}
first = false;
println!("Add the following to '{display_dir}/{hook}':");
println!();
for line in install::current_template(hook).lines() {
println!("\t{line}");
}
}
}