File
Blob: tools/unix/apply-big-move.sh
| 1 | #! /bin/bash |
| 2 | # |
| 3 | # This script applies the most recent "big change". |
| 4 | # |
| 5 | # This script is designed so that it can help you rebase an in-flight PR across this change. Once |
| 6 | # the big move has been merged, you may simply run this script inside your branch, and it will |
| 7 | # take care of updating your change in order to avoid any merge conflicts. |
| 8 | # |
| 9 | # How it does this: |
| 10 | # 1. Rebases your change to the commit just before the Big Move. |
| 11 | # 2. Applies the same logical changes that the Big Move did, but now applies this to the changes |
| 12 | # on your branch too. |
| 13 | # 3. Rewrites git history so that your commits are now based on the commit immediately after the |
| 14 | # Big Move. |
| 15 | # |
| 16 | # To use the script, just run it in your branch. It'll take care of everything, and you'll end up |
| 17 | # with the changes in your branch all based on the commit after The Big Move. |
| 18 | |
| 19 | set -euo pipefail |
| 20 | |
| 21 | AFTER_MOVE_TAG=after-big-move-1 |
| 22 | BEFORE_MOVE_TAG=before-big-move-1 |
| 23 | NEXT_BIG_MOVE=before-big-move-2 |
| 24 | ORIG_BRANCH=$(git branch --show-current) |
| 25 | |
| 26 | fail() { |
| 27 | echo -e '\e[0;1;31mFAILED:\e[0m '"$@" >&2 |
| 28 | exit 1 |
| 29 | } |
| 30 | |
| 31 | check_big_move_dependencies() { |
| 32 | source "$(dirname -- $BASH_SOURCE)/find-python3.sh" |
| 33 | PYTHON_PATH=$(get_python3) |
| 34 | if [[ -z "$PYTHON_PATH" ]]; then |
| 35 | fail "Cannot find python3. Run install python3 first." |
| 36 | fi |
| 37 | } |
| 38 | |
| 39 | apply_big_move() { |
| 40 | echo "Applying format..." |
| 41 | |
| 42 | source "$(dirname -- $BASH_SOURCE)/find-python3.sh" |
| 43 | PYTHON_PATH=$(get_python3) |
| 44 | $PYTHON_PATH "$(dirname -- $BASH_SOURCE)/../cross/format.py" |
| 45 | |
| 46 | echo "Changes applied successfully." |
| 47 | } |
| 48 | |
| 49 | rebase_and_rerun() { |
| 50 | # Rebase on top of tag $1 and then run the script again. |
| 51 | |
| 52 | BASE=$1 |
| 53 | |
| 54 | echo -e '\e[0;1;34mRebasing onto tag '"$BASE"'...\e[0m' |
| 55 | git rebase $BASE || \ |
| 56 | fail "Rebase had conflicts. Please resolve them and run the script again." |
| 57 | git submodule update |
| 58 | |
| 59 | # Make sure we're using the correct version of the script by starting it over. |
| 60 | exec ./apply-big-move.sh |
| 61 | } |
| 62 | |
| 63 | check_for_next_move() { |
| 64 | # Check if there's a new "Big Move" in the future and, if so, prompt the user to apply it too. |
| 65 | |
| 66 | if git describe $NEXT_BIG_MOVE >/dev/null 2>&1; then |
| 67 | while true; do |
| 68 | 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 ' |
| 69 | read -n1 CHOICE |
| 70 | echo |
| 71 | case "$CHOICE" in |
| 72 | y | Y ) |
| 73 | rebase_and_rerun $NEXT_BIG_MOVE |
| 74 | ;; |
| 75 | n | N ) |
| 76 | echo "OK. When you are ready, please run the script again to apply the next move." |
| 77 | exit 0 |
| 78 | ;; |
| 79 | esac |
| 80 | echo "??? Please press either y or n." |
| 81 | done |
| 82 | fi |
| 83 | } |
| 84 | |
| 85 | main() { |
| 86 | if [ "x$(git status --untracked-files=no --porcelain)" != "x" ]; then |
| 87 | fail "You have uncommitted changes. Please commit or discard them first." |
| 88 | fi |
| 89 | |
| 90 | check_big_move_dependencies |
| 91 | |
| 92 | # Use --apply to just apply the Big Move changes to the current tree without committing or |
| 93 | # rewriting any history. This is intended to be used to create the Big Move commit in the first |
| 94 | # place. |
| 95 | if [ "${1:-}" = "--apply" ]; then |
| 96 | apply_big_move |
| 97 | exit 0 |
| 98 | fi |
| 99 | |
| 100 | # Make sure our tags are up-to-date so we can find the relevant Big Move tags. |
| 101 | echo "Fetching tags..." |
| 102 | git fetch origin --tags --force |
| 103 | |
| 104 | # Check that the Big Move is actually ready. (This script will be committed to the repo before |
| 105 | # the Big Move itself is, so this checks if someone is running the script too early.) |
| 106 | if ! git describe $BEFORE_MOVE_TAG >/dev/null 2>&1; then |
| 107 | fail "The Big Move hasn't happened yet (tags not found)." |
| 108 | fi |
| 109 | |
| 110 | # Check if we already applied The Big Move in this branch, as indicated by $AFTER_MOVE_TAG being |
| 111 | # in our history. |
| 112 | if git merge-base --is-ancestor $AFTER_MOVE_TAG HEAD; then |
| 113 | # This branch already includes The Big Move, but maybe there is a future Big Move and the |
| 114 | # user was actually intending to apply that? |
| 115 | if git describe $NEXT_BIG_MOVE >/dev/null 2>&1; then |
| 116 | # Indeed there is, let's skip forward to that. |
| 117 | rebase_and_rerun $NEXT_BIG_MOVE |
| 118 | fi |
| 119 | |
| 120 | echo "The Big Move has already been applied to this branch." |
| 121 | exit 0 |
| 122 | fi |
| 123 | |
| 124 | # Check if $BEFORE_MOVE_TAG is in this branch's history. If not, we need to rebase first. |
| 125 | if ! git merge-base --is-ancestor $BEFORE_MOVE_TAG HEAD; then |
| 126 | # Branch is not yet based on $BEFORE_MOVE_TAG, so rebase onto it. |
| 127 | rebase_and_rerun $BEFORE_MOVE_TAG |
| 128 | fail "(can't get here -- rebase_and_rerun should not return)" |
| 129 | fi |
| 130 | |
| 131 | # Get the list of commits that we need to rebase over The Big Move. |
| 132 | COMMITS=($(git log --reverse --format='%H' $BEFORE_MOVE_TAG..HEAD)) |
| 133 | |
| 134 | # Checkout $AFTER_MOVE_TAG in order to start building on it. |
| 135 | git checkout -q $AFTER_MOVE_TAG |
| 136 | git submodule update |
| 137 | |
| 138 | # Apply each commit. |
| 139 | for COMMIT in ${COMMITS[@]}; do |
| 140 | git log -1 --format='%CblueRewriting commit %h:%Creset %s' $COMMIT |
| 141 | |
| 142 | # Update the working tree to match the code from the source commit. |
| 143 | git checkout $COMMIT . |
| 144 | git submodule update |
| 145 | |
| 146 | # Apply the Big Move on top of that. |
| 147 | apply_big_move |
| 148 | |
| 149 | # Commit to edgeworker (including updating the workerd submodule), reusing the old commit |
| 150 | # message. It's possible that the diff is now empty, in which case we skip this commit, much |
| 151 | # like `git rebase` does by default. |
| 152 | if git commit -a --dry-run > /dev/null 2>&1; then |
| 153 | git commit -aC $COMMIT |
| 154 | fi |
| 155 | done |
| 156 | |
| 157 | # Record the final commit. |
| 158 | FINAL_COMMIT=$(git log -1 --format=%H) |
| 159 | |
| 160 | # Check out the original branch, and reset it to the final commit. |
| 161 | git checkout -q "$ORIG_BRANCH" |
| 162 | git reset --hard "$FINAL_COMMIT" |
| 163 | |
| 164 | # Success! |
| 165 | echo -e '\e[0;1;32mSUCCESS:\e[0m Applied Big Move.' |
| 166 | |
| 167 | # Check if there's another move upcoming that we should apply next. |
| 168 | check_for_next_move |
| 169 | |
| 170 | exit 0 |
| 171 | } |
| 172 | |
| 173 | # Ensure that if the file changes while bash is reading it, bash won't go off the rails. Normally, |
| 174 | # bash reads one line from the file at a time and then executes it before reading the next. |
| 175 | # Wrapping the whole script body in a function ensures that bash buffers it in memory. Placing |
| 176 | # `exit 0` on the same line ensures that bash won't attempt to read anything more after the |
| 177 | # function returns. |
| 178 | # |
| 179 | # This ensures that if further changes are made to this file before the Big Move actually happens, |
| 180 | # then the rebase command that rebases to the commit before the Big Move won't confuse bash. |
| 181 | main "$@"; exit 0 |