Skip to main content

chronicle/hooks/
mod.rs

1pub mod post_rewrite;
2pub mod prepare_commit_msg;
3
4use crate::error::chronicle_error::IoSnafu;
5use crate::error::Result;
6use snafu::ResultExt;
7use std::path::Path;
8
9const HOOK_BEGIN_MARKER: &str = "# --- chronicle hook begin ---";
10const HOOK_END_MARKER: &str = "# --- chronicle hook end ---";
11
12const POST_COMMIT_SCRIPT: &str = r#"# --- chronicle hook begin ---
13# Installed by chronicle. Do not edit between these markers.
14if command -v git-chronicle >/dev/null 2>&1; then
15    git-chronicle annotate --auto --commit HEAD &
16fi
17# --- chronicle hook end ---"#;
18
19const PREPARE_COMMIT_MSG_SCRIPT: &str = r#"# --- chronicle hook begin ---
20# Installed by chronicle. Do not edit between these markers.
21if command -v git-chronicle >/dev/null 2>&1; then
22    git-chronicle hook prepare-commit-msg "$@"
23fi
24# --- chronicle hook end ---"#;
25
26const POST_REWRITE_SCRIPT: &str = r#"# --- chronicle hook begin ---
27# Installed by chronicle. Do not edit between these markers.
28if command -v git-chronicle >/dev/null 2>&1; then
29    git-chronicle hook post-rewrite "$@"
30fi
31# --- chronicle hook end ---"#;
32
33/// Install a single hook script into the hooks directory.
34fn install_single_hook(hooks_dir: &Path, hook_name: &str, script: &str) -> Result<()> {
35    let hook_path = hooks_dir.join(hook_name);
36
37    let existing = if hook_path.exists() {
38        std::fs::read_to_string(&hook_path).context(IoSnafu)?
39    } else {
40        String::new()
41    };
42
43    let new_content = if existing.contains(HOOK_BEGIN_MARKER) {
44        // Replace existing chronicle section
45        let mut result = String::new();
46        let mut in_section = false;
47        for line in existing.lines() {
48            if line.contains(HOOK_BEGIN_MARKER) {
49                in_section = true;
50                result.push_str(script);
51                result.push('\n');
52                continue;
53            }
54            if line.contains(HOOK_END_MARKER) {
55                in_section = false;
56                continue;
57            }
58            if !in_section {
59                result.push_str(line);
60                result.push('\n');
61            }
62        }
63        result
64    } else if existing.is_empty() {
65        format!("#!/bin/sh\n{script}\n")
66    } else {
67        let mut content = existing.clone();
68        if !content.ends_with('\n') {
69            content.push('\n');
70        }
71        content.push('\n');
72        content.push_str(script);
73        content.push('\n');
74        content
75    };
76
77    std::fs::write(&hook_path, &new_content).context(IoSnafu)?;
78
79    #[cfg(unix)]
80    {
81        use std::os::unix::fs::PermissionsExt;
82        let perms = std::fs::Permissions::from_mode(0o755);
83        std::fs::set_permissions(&hook_path, perms).context(IoSnafu)?;
84    }
85
86    Ok(())
87}
88
89/// Install all chronicle hooks: post-commit, prepare-commit-msg, and post-rewrite.
90pub fn install_hooks(git_dir: &Path) -> Result<()> {
91    let hooks_dir = git_dir.join("hooks");
92    std::fs::create_dir_all(&hooks_dir).context(IoSnafu)?;
93
94    install_single_hook(&hooks_dir, "post-commit", POST_COMMIT_SCRIPT)?;
95    install_single_hook(&hooks_dir, "prepare-commit-msg", PREPARE_COMMIT_MSG_SCRIPT)?;
96    install_single_hook(&hooks_dir, "post-rewrite", POST_REWRITE_SCRIPT)?;
97
98    Ok(())
99}