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
33fn 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 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
89pub 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}