hooky/
lib.rs

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}