diff --git a/githooks/pre-push b/githooks/pre-push new file mode 100644 index 00000000000..179a9b86343 --- /dev/null +++ b/githooks/pre-push @@ -0,0 +1,45 @@ +#!/usr/bin/env bash + +set -euo pipefail + + +source "$(dirname -- $BASH_SOURCE)/../tools/unix/find-python3.sh" +PYTHON_PATH=$(get_python3) +if [[ -z "$PYTHON_PATH" ]]; then + echo + echo "python3 is required for formatting and was not found" + echo + echo "ERROR: you must either install python3 and try pushing again or run `git push` with `--no-verify`" + exit 1 +fi + +while read LOCAL_REF LOCAL_SHA REMOTE_REF REMOTE_SHA +do + git fetch origin master &>/dev/null + # Check all local changes, not present in origin/master, for lint. + set +e + $PYTHON_PATH "$(dirname -- $BASH_SOURCE)/../tools/cross/format.py" --check git --source $LOCAL_SHA --target origin/master + EXIT_CODE=$? + set -e + case $EXIT_CODE in + 0) + # No lint. + ;; + 1) + echo + echo "ERROR: changes in $LOCAL_REF have lint which may fail CI." + echo + echo "To fix lint:" + echo " python3 ./tools/cross/format.py" + echo + exit 1 + ;; + 2) + echo + echo "ERROR: failed to run format.py, Pass '--no-verify' or '-n' to skip." + echo + exit 1 + ;; + esac +done + diff --git a/tools/unix/apply-big-move.sh b/tools/unix/apply-big-move.sh new file mode 100644 index 00000000000..810572f34a2 --- /dev/null +++ b/tools/unix/apply-big-move.sh @@ -0,0 +1,181 @@ +#! /bin/bash +# +# This script applies the most recent "big change". +# +# This script is designed so that it can help you rebase an in-flight PR across this change. Once +# the big move has been merged, you may simply run this script inside your branch, and it will +# take care of updating your change in order to avoid any merge conflicts. +# +# How it does this: +# 1. Rebases your change to the commit just before the Big Move. +# 2. Applies the same logical changes that the Big Move did, but now applies this to the changes +# on your branch too. +# 3. Rewrites git history so that your commits are now based on the commit immediately after the +# Big Move. +# +# To use the script, just run it in your branch. It'll take care of everything, and you'll end up +# with the changes in your branch all based on the commit after The Big Move. + +set -euo pipefail + +AFTER_MOVE_TAG=after-big-move-1 +BEFORE_MOVE_TAG=before-big-move-1 +NEXT_BIG_MOVE=before-big-move-2 +ORIG_BRANCH=$(git branch --show-current) + +fail() { + echo -e '\e[0;1;31mFAILED:\e[0m '"$@" >&2 + exit 1 +} + +check_big_move_dependencies() { + source "$(dirname -- $BASH_SOURCE)/find-python3.sh" + PYTHON_PATH=$(get_python3) + if [[ -z "$PYTHON_PATH" ]]; then + fail "Cannot find python3. Run install python3 first." + fi +} + +apply_big_move() { + echo "Applying format..." + + source "$(dirname -- $BASH_SOURCE)/find-python3.sh" + PYTHON_PATH=$(get_python3) + $PYTHON_PATH "$(dirname -- $BASH_SOURCE)/../cross/format.py" + + echo "Changes applied successfully." +} + +rebase_and_rerun() { + # Rebase on top of tag $1 and then run the script again. + + BASE=$1 + + echo -e '\e[0;1;34mRebasing onto tag '"$BASE"'...\e[0m' + git rebase $BASE || \ + fail "Rebase had conflicts. Please resolve them and run the script again." + git submodule update + + # Make sure we're using the correct version of the script by starting it over. + exec ./apply-big-move.sh +} + +check_for_next_move() { + # Check if there's a new "Big Move" in the future and, if so, prompt the user to apply it too. + + if git describe $NEXT_BIG_MOVE >/dev/null 2>&1; then + while true; do + echo -en '\e[0;1;33mIt looks like there is another big move after this one. Should we apply it now? [y/n]\e[0m ' + read -n1 CHOICE + echo + case "$CHOICE" in + y | Y ) + rebase_and_rerun $NEXT_BIG_MOVE + ;; + n | N ) + echo "OK. When you are ready, please run the script again to apply the next move." + exit 0 + ;; + esac + echo "??? Please press either y or n." + done + fi +} + +main() { + if [ "x$(git status --untracked-files=no --porcelain)" != "x" ]; then + fail "You have uncommitted changes. Please commit or discard them first." + fi + + check_big_move_dependencies + + # Use --apply to just apply the Big Move changes to the current tree without committing or + # rewriting any history. This is intended to be used to create the Big Move commit in the first + # place. + if [ "${1:-}" = "--apply" ]; then + apply_big_move + exit 0 + fi + + # Make sure our tags are up-to-date so we can find the relevant Big Move tags. + echo "Fetching tags..." + git fetch origin --tags --force + + # Check that the Big Move is actually ready. (This script will be committed to the repo before + # the Big Move itself is, so this checks if someone is running the script too early.) + if ! git describe $BEFORE_MOVE_TAG >/dev/null 2>&1; then + fail "The Big Move hasn't happened yet (tags not found)." + fi + + # Check if we already applied The Big Move in this branch, as indicated by $AFTER_MOVE_TAG being + # in our history. + if git merge-base --is-ancestor $AFTER_MOVE_TAG HEAD; then + # This branch already includes The Big Move, but maybe there is a future Big Move and the + # user was actually intending to apply that? + if git describe $NEXT_BIG_MOVE >/dev/null 2>&1; then + # Indeed there is, let's skip forward to that. + rebase_and_rerun $NEXT_BIG_MOVE + fi + + echo "The Big Move has already been applied to this branch." + exit 0 + fi + + # Check if $BEFORE_MOVE_TAG is in this branch's history. If not, we need to rebase first. + if ! git merge-base --is-ancestor $BEFORE_MOVE_TAG HEAD; then + # Branch is not yet based on $BEFORE_MOVE_TAG, so rebase onto it. + rebase_and_rerun $BEFORE_MOVE_TAG + fail "(can't get here -- rebase_and_rerun should not return)" + fi + + # Get the list of commits that we need to rebase over The Big Move. + COMMITS=($(git log --reverse --format='%H' $BEFORE_MOVE_TAG..HEAD)) + + # Checkout $AFTER_MOVE_TAG in order to start building on it. + git checkout -q $AFTER_MOVE_TAG + git submodule update + + # Apply each commit. + for COMMIT in ${COMMITS[@]}; do + git log -1 --format='%CblueRewriting commit %h:%Creset %s' $COMMIT + + # Update the working tree to match the code from the source commit. + git checkout $COMMIT . + git submodule update + + # Apply the Big Move on top of that. + apply_big_move + + # Commit to edgeworker (including updating the workerd submodule), reusing the old commit + # message. It's possible that the diff is now empty, in which case we skip this commit, much + # like `git rebase` does by default. + if git commit -a --dry-run > /dev/null 2>&1; then + git commit -aC $COMMIT + fi + done + + # Record the final commit. + FINAL_COMMIT=$(git log -1 --format=%H) + + # Check out the original branch, and reset it to the final commit. + git checkout -q "$ORIG_BRANCH" + git reset --hard "$FINAL_COMMIT" + + # Success! + echo -e '\e[0;1;32mSUCCESS:\e[0m Applied Big Move.' + + # Check if there's another move upcoming that we should apply next. + check_for_next_move + + exit 0 +} + +# Ensure that if the file changes while bash is reading it, bash won't go off the rails. Normally, +# bash reads one line from the file at a time and then executes it before reading the next. +# Wrapping the whole script body in a function ensures that bash buffers it in memory. Placing +# `exit 0` on the same line ensures that bash won't attempt to read anything more after the +# function returns. +# +# This ensures that if further changes are made to this file before the Big Move actually happens, +# then the rebase command that rebases to the commit before the Big Move won't confuse bash. +main "$@"; exit 0 diff --git a/tools/unix/find-python3.sh b/tools/unix/find-python3.sh new file mode 100644 index 00000000000..6966fcb81b3 --- /dev/null +++ b/tools/unix/find-python3.sh @@ -0,0 +1,21 @@ + +get_python3() { + # Search for python3 + python=$(which python3 2>/dev/null) + + # If not found, search for python + if [ -z "$python" ]; then + python=$(which python 2>/dev/null) + if [ -n "$python" ]; then + local python_version=$($python -V 2>&1 | head -n 1 | cut -d ' ' -f 2- | cut -d '.' -f1) + if [ "$python_version" != "3" ]; then + unset python + fi + fi + fi + + if [ -z "$python" ]; then + return + fi + echo "$python" +}