1 use std::fs; 2 use std::path::Path; 3 4 use super::verify_inside_clippy_dir; 5 6 /// Rusts setup uses `git rev-parse --git-common-dir` to get the root directory of the repo. 7 /// I've decided against this for the sake of simplicity and to make sure that it doesn't install 8 /// the hook if `clippy_dev` would be used in the rust tree. The hook also references this tool 9 /// for formatting and should therefore only be used in a normal clone of clippy 10 const REPO_GIT_DIR: &str = ".git"; 11 const HOOK_SOURCE_FILE: &str = "util/etc/pre-commit.sh"; 12 const HOOK_TARGET_FILE: &str = ".git/hooks/pre-commit"; 13 install_hook(force_override: bool)14pub fn install_hook(force_override: bool) { 15 if !check_precondition(force_override) { 16 return; 17 } 18 19 // So a little bit of a funny story. Git on unix requires the pre-commit file 20 // to have the `execute` permission to be set. The Rust functions for modifying 21 // these flags doesn't seem to work when executed with normal user permissions. 22 // 23 // However, there is a little hack that is also being used by Rust itself in their 24 // setup script. Git saves the `execute` flag when syncing files. This means 25 // that we can check in a file with execution permissions and the sync it to create 26 // a file with the flag set. We then copy this file here. The copy function will also 27 // include the `execute` permission. 28 match fs::copy(HOOK_SOURCE_FILE, HOOK_TARGET_FILE) { 29 Ok(_) => { 30 println!("info: the hook can be removed with `cargo dev remove git-hook`"); 31 println!("git hook successfully installed"); 32 }, 33 Err(err) => eprintln!("error: unable to copy `{HOOK_SOURCE_FILE}` to `{HOOK_TARGET_FILE}` ({err})"), 34 } 35 } 36 check_precondition(force_override: bool) -> bool37fn check_precondition(force_override: bool) -> bool { 38 if !verify_inside_clippy_dir() { 39 return false; 40 } 41 42 // Make sure that we can find the git repository 43 let git_path = Path::new(REPO_GIT_DIR); 44 if !git_path.exists() || !git_path.is_dir() { 45 eprintln!("error: clippy_dev was unable to find the `.git` directory"); 46 return false; 47 } 48 49 // Make sure that we don't override an existing hook by accident 50 let path = Path::new(HOOK_TARGET_FILE); 51 if path.exists() { 52 if force_override { 53 return delete_git_hook_file(path); 54 } 55 56 eprintln!("error: there is already a pre-commit hook installed"); 57 println!("info: use the `--force-override` flag to override the existing hook"); 58 return false; 59 } 60 61 true 62 } 63 remove_hook()64pub fn remove_hook() { 65 let path = Path::new(HOOK_TARGET_FILE); 66 if path.exists() { 67 if delete_git_hook_file(path) { 68 println!("git hook successfully removed"); 69 } 70 } else { 71 println!("no pre-commit hook was found"); 72 } 73 } 74 delete_git_hook_file(path: &Path) -> bool75fn delete_git_hook_file(path: &Path) -> bool { 76 if let Err(err) = fs::remove_file(path) { 77 eprintln!("error: unable to delete existing pre-commit git hook ({err})"); 78 false 79 } else { 80 true 81 } 82 } 83