1use std::fs;
2use std::io::prelude::*;
3use std::path;
4use std::process::Command;
5
6#[cfg(target_family = "unix")]
7use std::os::unix::prelude::OpenOptionsExt;
8
9#[cfg(target_family = "windows")]
10use std::os::windows::fs::OpenOptionsExt;
11
12pub const ALLOWED_HOOKS: [&str; 13] = [
13 "applypatch-msg",
14 "commit-msg",
15 "post-update",
16 "pre-applypatch",
17 "pre-commit",
18 "pre-merge-commit",
19 "pre-push",
20 "pre-rebase",
21 "pre-receive",
22 "prepare-commit-msg",
23 "push-to-checkout",
24 "sendemail-validate",
25 "update",
26];
27
28pub fn init(no_pre_commit: bool) {
29 let pre_commit_dir = path::Path::new(".hooky");
30
31 if !path::Path::new(".git").exists() {
32 println!("[error] git cannot be found");
33
34 return;
35 }
36
37 if pre_commit_dir.exists() {
38 println!("[info] hooky is already installed");
39
40 return;
41 }
42
43 Command::new("git")
44 .arg("config")
45 .arg("core.hooksPath")
46 .arg(pre_commit_dir)
47 .spawn()
48 .expect("Failed to set hooks path");
49
50 println!("[info] Hooks path set to .hooky directory");
51
52 fs::create_dir_all(pre_commit_dir).expect("Failed to create .hooky directory");
53
54 if !no_pre_commit {
55 let mut file = create_file(pre_commit_dir.join("pre-commit"));
56
57 file.write_all(b"#!/usr/bin/env sh\n# Run pre-commit hooks\n\nexit 0")
58 .expect("Failed to write to pre-commit file");
59
60 println!("[info] Created pre-commit file");
61 }
62}
63
64pub fn uninstall() {
65 let pre_commit_dir = path::Path::new(".hooky");
66
67 if !path::Path::new(".git").exists() {
68 println!("[error] git cannot be found");
69
70 return;
71 }
72
73 if !pre_commit_dir.exists() {
74 println!("[info] hooky is not installed");
75
76 return;
77 }
78
79 Command::new("git")
80 .arg("config")
81 .arg("--unset")
82 .arg("core.hooksPath")
83 .spawn()
84 .expect("Failed to unset hooks path");
85
86 fs::remove_dir_all(pre_commit_dir).expect("Failed to remove .hooky directory");
87
88 println!("[info] Uninstalled hooky");
89}
90
91pub fn add_hook(hook: &str) {
92 if !ALLOWED_HOOKS.contains(&hook) {
93 println!("[error] Hook not allowed");
94
95 return;
96 }
97
98 let pre_commit_dir = path::Path::new(".hooky");
99
100 if !pre_commit_dir.exists() {
101 println!("[error] .hooky directory not found");
102 println!("[hint] try running `hooky init` first");
103
104 return;
105 }
106
107 if !path::Path::new(".git").exists() {
108 println!("[error] git cannot be found");
109
110 return;
111 }
112 let mut file = create_file(pre_commit_dir.join(hook));
113
114 let hook_content = format!("#!/usr/bin/env sh\n# Run {} hook\n\nexit 0", hook);
115 file.write_all(hook_content.as_bytes())
116 .expect("Failed to write to hook file");
117
118 println!("[info] Created hook file");
119}
120
121fn create_file<P>(path: P) -> fs::File
122where
123 P: AsRef<path::Path>,
124{
125 let mut options = fs::OpenOptions::new();
126
127 options.create(true).write(true);
128
129 #[cfg(target_family = "windows")]
130 {
131 options.access_mode(0o755);
132 }
133
134 #[cfg(target_family = "unix")]
135 {
136 options.mode(0o755);
137 }
138
139 options.open(path).expect("Failed to create file")
140}