1#!/usr/bin/env bash
2set -euo pipefail
3
4if (( $# < 1 )); then
5 echo "Usage: $0 TARGET_BRANCH"
6 echo ""
7 echo "TARGET_BRANCH: Branch to rebase the current branch onto, e.g. master or release-24.11"
8 exit 1
9fi
10
11targetBranch=$1
12
13# Loop through all autorebase-able commits in .git-blame-ignore-revs on the base branch
14readarray -t autoLines < <(
15 git show "$targetBranch":.git-blame-ignore-revs \
16 | sed -n 's/^\([0-9a-f]\+\).*!autorebase \(.*\)$/\1 \2/p'
17)
18for line in "${autoLines[@]}"; do
19 read -r autoCommit autoCmd <<< "$line"
20
21 if ! git cat-file -e "$autoCommit"; then
22 echo "Not a valid commit: $autoCommit"
23 exit 1
24 elif git merge-base --is-ancestor "$autoCommit" HEAD; then
25 # Skip commits that we have already
26 continue
27 fi
28
29 echo -e "\e[32mAuto-rebasing commit $autoCommit with command '$autoCmd'\e[0m"
30
31 # The commit before the commit
32 parent=$(git rev-parse "$autoCommit"~)
33
34 echo "Rebasing on top of the previous commit, might need to manually resolve conflicts"
35 if ! git rebase --onto "$parent" "$(git merge-base "$targetBranch" HEAD)"; then
36 echo -e "\e[33m\e[1mRestart this script after resolving the merge conflict as described above\e[0m"
37 exit 1
38 fi
39
40 echo "Reapplying the commit on each commit of our branch"
41 # This does two things:
42 # - The parent filter inserts the auto commit between its parent and
43 # and our first commit. By itself, this causes our first commit to
44 # effectively "undo" the auto commit, since the tree of our first
45 # commit is unchanged. This is why the following is also necessary:
46 # - The tree filter runs the command on each of our own commits,
47 # effectively reapplying it.
48 FILTER_BRANCH_SQUELCH_WARNING=1 git filter-branch \
49 --parent-filter "sed 's/$parent/$autoCommit/'" \
50 --tree-filter "$autoCmd" \
51 "$autoCommit"..HEAD
52
53 # A tempting alternative is something along the lines of
54 # git rebase --strategy-option=theirs --onto "$rev" "$parent" \
55 # --exec '$autoCmd && git commit --all --amend --no-edit' \
56 # but this causes problems because merges are not guaranteed to maintain the formatting.
57 # The ./test.sh exercises such a case.
58done
59
60echo "Rebasing on top of the latest target branch commit"
61git rebase --onto "$targetBranch" "$(git merge-base "$targetBranch" HEAD)"