| use std::fs; |
| use std::path::Path; |
| |
| use super::verify_inside_clippy_dir; |
| |
| /// 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 therefore only be used in a normal clone of clippy |
| const REPO_GIT_DIR: &str = ".git"; |
| const HOOK_SOURCE_FILE: &str = "util/etc/pre-commit.sh"; |
| const HOOK_TARGET_FILE: &str = ".git/hooks/pre-commit"; |
| |
| pub fn install_hook(force_override: bool) { |
| if !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_FILE, HOOK_TARGET_FILE) { |
| Ok(_) => { |
| println!("info: the hook can be removed with `cargo dev remove git-hook`"); |
| println!("git hook successfully installed"); |
| }, |
| Err(err) => eprintln!("error: unable to copy `{HOOK_SOURCE_FILE}` to `{HOOK_TARGET_FILE}` ({err})"), |
| } |
| } |
| |
| fn check_precondition(force_override: bool) -> bool { |
| if !verify_inside_clippy_dir() { |
| return false; |
| } |
| |
| // 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() { |
| eprintln!("error: clippy_dev was unable to find the `.git` directory"); |
| return false; |
| } |
| |
| // Make sure that we don't override an existing hook by accident |
| let path = Path::new(HOOK_TARGET_FILE); |
| if path.exists() { |
| if force_override { |
| return delete_git_hook_file(path); |
| } |
| |
| eprintln!("error: there is already a pre-commit hook installed"); |
| println!("info: use the `--force-override` flag to override the existing hook"); |
| return false; |
| } |
| |
| true |
| } |
| |
| pub fn remove_hook() { |
| let path = Path::new(HOOK_TARGET_FILE); |
| if path.exists() { |
| if delete_git_hook_file(path) { |
| println!("git hook successfully removed"); |
| } |
| } else { |
| println!("no pre-commit hook was found"); |
| } |
| } |
| |
| fn delete_git_hook_file(path: &Path) -> bool { |
| if let Err(err) = fs::remove_file(path) { |
| eprintln!("error: unable to delete existing pre-commit git hook ({err})"); |
| false |
| } else { |
| true |
| } |
| } |