Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 77 additions & 17 deletions src/bootstrap/src/core/build_steps/setup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,52 @@ impl Step for Hook {
}
}

/// It handles Git hook setup
#[derive(Clone, Debug, Eq, PartialEq)]
enum GitPrePushHookKind {
Tidy,
TidyWithSpellcheck,
}

impl GitPrePushHookKind {
fn prompt_user() -> io::Result<Option<GitPrePushHookKind>> {
let prompt_str = "Available options:
1. Set tidy as pre-push hook
2. Set tidy with spellcheck as pre-push hook
Please select [default: None]:";

let mut input = String::new();
loop {
print!("{prompt_str}");
io::stdout().flush()?;
io::stdin().read_line(&mut input)?;

let mut modified_input = input.to_lowercase();
modified_input.retain(|ch| !ch.is_whitespace());

match modified_input.as_str() {
"1" => return Ok(Some(GitPrePushHookKind::Tidy)),
"2" => return Ok(Some(GitPrePushHookKind::TidyWithSpellcheck)),
"" | "none" => return Ok(None),
_ => {
eprintln!("ERROR: unrecognized option '{}'", input.trim());
eprintln!("NOTE: press Ctrl+C to exit");
}
}

input.clear();
}
}

fn settings_path(&self) -> PathBuf {
PathBuf::new().join("src").join("etc").join(match self {
GitPrePushHookKind::Tidy => "pre-push.sh",
GitPrePushHookKind::TidyWithSpellcheck => "pre-push-spellcheck.sh",
})
}
}

// install a git hook to automatically run tidy, if they want
fn install_git_hook_maybe(builder: &Builder<'_>, config: &Config) -> io::Result<()> {
let git = helpers::git(Some(&config.src))
Expand All @@ -493,37 +539,51 @@ fn install_git_hook_maybe(builder: &Builder<'_>, config: &Config) -> io::Result<
let git = PathBuf::from(git.trim());
let hooks_dir = git.join("hooks");
let dst = hooks_dir.join("pre-push");
if dst.exists() {
// The git hook has already been set up, or the user already has a custom hook.
return Ok(());
}

println!(
"\nRust's CI will automatically fail if it doesn't pass `tidy`, the internal tool for ensuring code quality.
If you'd like, x.py can install a git hook for you that will automatically run `test tidy` before
pushing your code to ensure your code is up to par. If you decide later that this behavior is
undesirable, simply delete the `pre-push` file from .git/hooks."
undesirable, simply delete the `pre-push` file from .git/hooks.
You have two choices of hooks, the first just runs `test tidy`, the second runs the tidy command with spellcheck.
Since the spellcheck will be installed if the binary doesn't exist under `build/`, we'll recommend you to choose the first one if you frequently clean up the build directory.
It overrides the existing pre-push hook if you already have."
);

if prompt_user("Would you like to install the git hook?: [y/N]")? != Some(PromptResult::Yes) {
println!("Ok, skipping installation!");
return Ok(());
}
let src = match GitPrePushHookKind::prompt_user() {
Ok(git_hook_kind) => {
if let Some(git_hook_kind) = git_hook_kind {
git_hook_kind.settings_path()
} else {
println!("Skip setting pre-push hook");
return Ok(());
}
}
Err(e) => {
eprintln!("ERROR: could not determine pre push hook: {e}");
return Err(e);
}
};

if !hooks_dir.exists() {
// We need to (try to) create the hooks directory first.
let _ = fs::create_dir(hooks_dir);
}
let src = config.src.join("src").join("etc").join("pre-push.sh");
match fs::hard_link(src, &dst) {

if let Ok(true) = fs::exists(&dst) {
// Remove the existing pre-push file.
if let Err(e) = fs::remove_file(&dst) {
eprintln!("ERROR: could not remove the existing hook\n{}", e);
return Err(e);
}
}

match fs::hard_link(config.src.join(&src), &dst) {
Err(e) => {
eprintln!(
"ERROR: could not create hook {}: do you already have the git hook installed?\n{}",
dst.display(),
e
);
eprintln!("ERROR: could not create hook {}:\n{}", dst.display(), e);
return Err(e);
}
Ok(_) => println!("Linked `src/etc/pre-push.sh` to `.git/hooks/pre-push`"),
Ok(_) => println!("Linked `{}` to `{}`", src.display(), dst.display()),
};
Ok(())
}
Expand Down
36 changes: 36 additions & 0 deletions src/etc/pre-push-spellcheck.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/usr/bin/env bash
#
# Call `tidy` before git push
# Copy this script to .git/hooks to activate,
# and remove it from .git/hooks to deactivate.
#

set -Euo pipefail

# Check if the push is doing anything other than deleting remote branches
SKIP=true
while read LOCAL_REF LOCAL_SHA REMOTE_REF REMOTE_SHA; do
if [[ "$LOCAL_REF" != "(delete)" || \
"$LOCAL_SHA" != "0000000000000000000000000000000000000000" ]]; then
SKIP=false
fi
done

if $SKIP; then
echo "Skipping tidy check for branch deletion"
exit 0
fi

ROOT_DIR="$(git rev-parse --show-toplevel)"

echo "Running pre-push script $ROOT_DIR/x test tidy"

cd "$ROOT_DIR"
# The env var is necessary for printing diffs in py (fmt/lint) and cpp.
TIDY_PRINT_DIFF=1 ./x test tidy \
--set build.locked-deps=true \
--extra-checks auto:py,auto:cpp,auto:js,spellcheck
if [ $? -ne 0 ]; then
echo "You may use \`git push --no-verify\` to skip this check."
exit 1
fi
Loading