about summary refs log tree commit diff
path: root/clippy_dev/src/setup
diff options
context:
space:
mode:
authorxFrednet <xFrednet@gmail.com>2021-06-11 01:05:51 +0200
committerflip1995 <philipp.krones@embecosm.com>2021-06-25 11:16:59 +0200
commit0a5f28c4b0c78c030956afe77b7a5c0c3e33ef5b (patch)
tree02ce45765b6286d7991ad67d7e61815fff505b3a /clippy_dev/src/setup
parent0941d9f14d9c446f9d7a465485652a3dc131de1e (diff)
Added `cargo dev setup git-hook`
Diffstat (limited to 'clippy_dev/src/setup')
-rw-r--r--clippy_dev/src/setup/git_hook.rs61
-rw-r--r--clippy_dev/src/setup/mod.rs31
2 files changed, 91 insertions, 1 deletions
diff --git a/clippy_dev/src/setup/git_hook.rs b/clippy_dev/src/setup/git_hook.rs
new file mode 100644
index 00000000000..741738e37fb
--- /dev/null
+++ b/clippy_dev/src/setup/git_hook.rs
@@ -0,0 +1,61 @@
+use std::fs;
+use std::path::Path;
+
+/// Rusts setup uses `git rev-parse --git-common-dir` to get the root directory of the repo.
+/// I've decided against this for the sake of simplicity and to make sure that it doesn't install
+/// the hook if `clippy_dev` would be used in the rust tree. The hook also references this tool
+/// for formatting and should therefor only be used in a normal clone of clippy
+const REPO_GIT_DIR: &str = ".git";
+const HOOK_SOURCE_PATH: &str = "util/etc/pre-commit.sh";
+const HOOK_TARGET_PATH: &str = ".git/hooks/pre-commit";
+
+pub fn run(force_override: bool) {
+    if let Err(_) = check_precondition(force_override) {
+        return;
+    }
+
+    // So a little bit of a funny story. Git on unix requires the pre-commit file
+    // to have the `execute` permission to be set. The Rust functions for modifying
+    // these flags doesn't seem to work when executed with normal user permissions.
+    //
+    // However, there is a little hack that is also being used by Rust itself in their
+    // setup script. Git saves the `execute` flag when syncing files. This means
+    // that we can check in a file with execution permissions and the sync it to create
+    // a file with the flag set. We then copy this file here. The copy function will also
+    // include the `execute` permission.
+    match fs::copy(HOOK_SOURCE_PATH, HOOK_TARGET_PATH) {
+        Ok(_) => println!("Git hook successfully installed :)"),
+        Err(err) => println!(
+            "error: unable to copy `{}` to `{}` ({})",
+            HOOK_SOURCE_PATH, HOOK_TARGET_PATH, err
+        ),
+    }
+}
+
+fn check_precondition(force_override: bool) -> Result<(), ()> {
+    // Make sure that we can find the git repository
+    let git_path = Path::new(REPO_GIT_DIR);
+    if !git_path.exists() || !git_path.is_dir() {
+        println!("error: clippy_dev was unable to find the `.git` directory");
+        return Err(());
+    }
+
+    // Make sure that we don't override an existing hook by accident
+    let path = Path::new(HOOK_TARGET_PATH);
+    if path.exists() {
+        if !force_override {
+            println!("warn: The found `.git` directory already has a commit hook");
+        }
+
+        if force_override || super::ask_yes_no_question("Do you want to override it?") {
+            if fs::remove_file(path).is_err() {
+                println!("error: unable to delete existing pre-commit git hook");
+                return Err(());
+            }
+        } else {
+            return Err(());
+        }
+    }
+
+    Ok(())
+}
diff --git a/clippy_dev/src/setup/mod.rs b/clippy_dev/src/setup/mod.rs
index cab4e386fc2..5db545c0ff1 100644
--- a/clippy_dev/src/setup/mod.rs
+++ b/clippy_dev/src/setup/mod.rs
@@ -1 +1,30 @@
-pub mod intellij;
\ No newline at end of file
+use std::io::{self, Write};
+pub mod git_hook;
+pub mod intellij;
+
+/// This function will asked the user the given question and wait for user input
+/// either `true` for yes and `false` for no.
+fn ask_yes_no_question(question: &str) -> bool {
+    // This code was proudly stolen from rusts bootstrapping tool.
+
+    fn ask_with_result(question: &str) -> io::Result<bool> {
+        let mut input = String::new();
+        Ok(loop {
+            print!("{}: [y/N] ", question);
+            io::stdout().flush()?;
+            input.clear();
+            io::stdin().read_line(&mut input)?;
+            break match input.trim().to_lowercase().as_str() {
+                "y" | "yes" => true,
+                "n" | "no" | "" => false,
+                _ => {
+                    println!("error: unrecognized option '{}'", input.trim());
+                    println!("note: press Ctrl+C to exit");
+                    continue;
+                },
+            };
+        })
+    }
+
+    ask_with_result(question).unwrap_or_default()
+}