Skip to content
File

Blob: tools/unix/apply-big-move.sh

bash182 lines
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 
19set -euo pipefail
20 
21AFTER_MOVE_TAG=after-big-move-1
22BEFORE_MOVE_TAG=before-big-move-1
23NEXT_BIG_MOVE=before-big-move-2
24ORIG_BRANCH=$(git branch --show-current)
25 
26fail() {
27 echo -e '\e[0;1;31mFAILED:\e[0m '"$@" >&2
28 exit 1
29}
30 
31check_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 
39apply_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 
49rebase_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 
63check_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 
85main() {
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.
181main "$@"; exit 0