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