git_commit_helper_cli/
install.rs1use anyhow::{Context, Result};
2use dialoguer::{Confirm, Select};
3use std::fs;
4use std::path::{Path, PathBuf};
5use std::process::Command;
6
7pub fn install_git_hook(repo_path: Option<PathBuf>, force: bool) -> Result<()> {
8 let repo_path = repo_path.unwrap_or_else(|| PathBuf::from("."));
9 let git_dir = find_git_dir(&repo_path)?;
10 let hooks_dir = git_dir.join("hooks");
11 let commit_msg_hook = hooks_dir.join("commit-msg");
12
13 let (use_backup, run_before) = if commit_msg_hook.exists() {
15 if force {
16 let (keep_old, run_before) = handle_existing_hook(&commit_msg_hook)?;
17 (keep_old, if keep_old { Some(run_before) } else { None })
18 } else {
19 return Err(anyhow::anyhow!(
20 "Hook 文件已存在: {}。使用 --force 选项进行处理。",
21 commit_msg_hook.display()
22 ));
23 }
24 } else {
25 (false, None)
26 };
27
28 let current_exe = std::env::current_exe()?;
30 let binary_path = current_exe.canonicalize()?;
31
32 let hook_content = create_hook_content(&binary_path, use_backup, run_before)?;
34
35 fs::create_dir_all(&hooks_dir)?;
37
38 fs::write(&commit_msg_hook, hook_content)?;
40
41 #[cfg(unix)]
43 {
44 use std::os::unix::fs::PermissionsExt;
45 fs::set_permissions(&commit_msg_hook, fs::Permissions::from_mode(0o755))?;
46 }
47
48 println!("Git hook 已安装到: {}", commit_msg_hook.display());
49 Ok(())
50}
51
52fn handle_existing_hook(hook_path: &Path) -> Result<(bool, bool)> {
53 println!("检测到已存在的 commit-msg hook");
54
55 let keep_old = Confirm::with_theme(&dialoguer::theme::ColorfulTheme::default())
56 .with_prompt("是否保留已存在的 hook 功能?")
57 .default(true)
58 .interact()?;
59
60 if !keep_old {
61 fs::remove_file(hook_path)?;
62 return Ok((false, false));
63 }
64
65 let hooks_dir = hook_path.parent().unwrap();
67 let backup_path = hooks_dir.join("commit-msg.old");
68 fs::rename(hook_path, &backup_path)?;
69 println!("已存在的 hook 已备份到: {}", backup_path.display());
70
71 let options = vec![
72 "先执行翻译程序,再执行原 hook",
73 "先执行原 hook,再执行翻译程序"
74 ];
75 let selection = Select::with_theme(&dialoguer::theme::ColorfulTheme::default())
76 .with_prompt("请选择执行顺序")
77 .items(&options)
78 .default(0)
79 .interact()?;
80
81 Ok((true, selection == 0))
82}
83
84fn create_hook_content(binary_path: &Path, use_backup: bool, run_before: Option<bool>) -> Result<String> {
85 if !use_backup {
86 return Ok(format!(
87 r#"#!/bin/sh
88# 检查是否在命令行中使用了 --no-review 选项
89if git config --bool git-commit-helper.disable-review >/dev/null 2>&1; then
90 exec "{}" --no-review "$1"
91else
92 exec "{}" "$1"
93fi
94"#,
95 binary_path.display(),
96 binary_path.display()
97 ));
98 }
99
100 let git_hooks_dir = binary_path.parent().unwrap().parent().unwrap().join("hooks");
101 let backup_path = git_hooks_dir.join("commit-msg.old");
102
103 match run_before {
104 Some(true) => Ok(format!(
105 r#"#!/bin/sh
106# 检查是否在命令行中使用了 --no-review 选项
107if git config --bool git-commit-helper.disable-review >/dev/null 2>&1; then
108 # 先运行当前程序,如果失败则中止
109 "{}" --no-review "$1" || exit $?
110else
111 # 先运行当前程序,如果失败则中止
112 "{}" "$1" || exit $?
113fi
114
115# 如果存在旧的 hook,则运行它
116if [ -x "{}" ]; then
117 exec "{}" "$1"
118fi
119"#,
120 binary_path.display(),
121 binary_path.display(),
122 backup_path.display(),
123 backup_path.display()
124 )),
125 Some(false) => Ok(format!(
126 r#"#!/bin/sh
127# 如果存在旧的 hook,先运行它
128if [ -x "{}" ]; then
129 "{}" "$1" || exit $?
130fi
131
132# 检查是否在命令行中使用了 --no-review 选项
133if git config --bool git-commit-helper.disable-review >/dev/null 2>&1; then
134 # 运行当前程序
135 exec "{}" --no-review "$1"
136else
137 # 运行当前程序
138 exec "{}" "$1"
139fi
140"#,
141 backup_path.display(),
142 backup_path.display(),
143 binary_path.display(),
144 binary_path.display()
145 )),
146 None => unreachable!("使用备份时必须指定运行顺序"),
147 }
148}
149
150fn find_git_dir(start_path: &Path) -> Result<PathBuf> {
151 let output = Command::new("git")
152 .arg("rev-parse")
153 .arg("--git-dir")
154 .current_dir(start_path)
155 .output()
156 .context("执行 git 命令失败")?;
157
158 if !output.status.success() {
159 return Err(anyhow::anyhow!("当前目录不是 git 仓库"));
160 }
161
162 let git_dir = String::from_utf8(output.stdout)?;
163 Ok(PathBuf::from(git_dir.trim()))
164}