hooky/
lib.rs

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}