diff --git a/clippy_dev/src/lib.rs b/clippy_dev/src/lib.rs index a9098695df9..72bdaf8d592 100644 --- a/clippy_dev/src/lib.rs +++ b/clippy_dev/src/lib.rs @@ -14,9 +14,9 @@ use walkdir::WalkDir; pub mod bless; pub mod fmt; -pub mod setup; pub mod new_lint; pub mod serve; +pub mod setup; pub mod stderr_length_check; pub mod update_lints; diff --git a/clippy_dev/src/main.rs b/clippy_dev/src/main.rs index bd4c137ec6d..b20c40bc556 100644 --- a/clippy_dev/src/main.rs +++ b/clippy_dev/src/main.rs @@ -3,7 +3,7 @@ #![warn(rust_2018_idioms, unused_lifetimes)] use clap::{App, Arg, ArgMatches, SubCommand}; -use clippy_dev::{bless, fmt, setup, new_lint, serve, stderr_length_check, update_lints}; +use clippy_dev::{bless, fmt, new_lint, serve, setup, stderr_length_check, update_lints}; fn main() { let matches = get_clap_config(); @@ -36,7 +36,11 @@ fn main() { ("limit_stderr_length", _) => { stderr_length_check::check(); }, - ("ide_setup", Some(matches)) => setup::intellij::run(matches.value_of("rustc-repo-path")), + ("setup", Some(sub_command)) => match sub_command.subcommand() { + ("intellij", Some(matches)) => setup::intellij::run(matches.value_of("rustc-repo-path")), + ("git-hook", Some(matches)) => setup::git_hook::run(matches.is_present("force-override")), + _ => {}, + }, ("serve", Some(matches)) => { let port = matches.value_of("port").unwrap().parse().unwrap(); let lint = matches.value_of("lint"); @@ -140,16 +144,31 @@ fn get_clap_config<'a>() -> ArgMatches<'a> { .about("Ensures that stderr files do not grow longer than a certain amount of lines."), ) .subcommand( - SubCommand::with_name("ide_setup") - .about("Alter dependencies so Intellij Rust can find rustc internals") - .arg( - Arg::with_name("rustc-repo-path") - .long("repo-path") - .short("r") - .help("The path to a rustc repo that will be used for setting the dependencies") - .takes_value(true) - .value_name("path") - .required(true), + SubCommand::with_name("setup") + .about("Support for setting up your personal development environment") + .subcommand( + SubCommand::with_name("intellij") + .about("Alter dependencies so Intellij Rust can find rustc internals") + .arg( + Arg::with_name("rustc-repo-path") + .long("repo-path") + .short("r") + .help("The path to a rustc repo that will be used for setting the dependencies") + .takes_value(true) + .value_name("path") + .required(true), + ), + ) + .subcommand( + SubCommand::with_name("git-hook") + .about("Add a pre-commit git hook that formats your code to make it look pretty") + .arg( + Arg::with_name("force-override") + .long("force-override") + .short("f") + .help("Forces the override of an existing git pre-commit hook") + .required(false), + ), ), ) .subcommand( 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 { + 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() +} diff --git a/util/etc/pre-commit.sh b/util/etc/pre-commit.sh new file mode 100755 index 00000000000..3c76e924b34 --- /dev/null +++ b/util/etc/pre-commit.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +cargo dev fmt