forked from NixOS/nixpkgs
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce script to automatically resolve conflicts after treewide ch…
…anges (such as reformats) (NixOS#363759)
- Loading branch information
Showing
7 changed files
with
263 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# Auto rebase script | ||
|
||
The [`./run.sh` script](./run.sh) in this directory rebases the current branch onto a target branch, | ||
while automatically resolving merge conflicts caused by marked commits in [`.git-blame-ignore-revs`](../../../.git-blame-ignore-revs). | ||
See the header comment of that file to understand how to mark commits. | ||
|
||
This is convenient for resolving merge conflicts for pull requests after e.g. treewide reformats. | ||
|
||
## Testing | ||
|
||
To run the tests in the [test directory](./test): | ||
``` | ||
$ cd test | ||
$ nix-shell | ||
nix-shell> ./run.sh | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
#!/usr/bin/env bash | ||
set -euo pipefail | ||
|
||
if (( $# < 1 )); then | ||
echo "Usage: $0 TARGET_BRANCH" | ||
echo "" | ||
echo "TARGET_BRANCH: Branch to rebase the current branch onto, e.g. master or release-24.11" | ||
exit 1 | ||
fi | ||
|
||
targetBranch=$1 | ||
|
||
# Loop through all autorebase-able commits in .git-blame-ignore-revs on the base branch | ||
readarray -t autoLines < <( | ||
git show "$targetBranch":.git-blame-ignore-revs \ | ||
| sed -n 's/^\([0-9a-f]\+\).*!autorebase \(.*\)$/\1 \2/p' | ||
) | ||
for line in "${autoLines[@]}"; do | ||
read -r autoCommit autoCmd <<< "$line" | ||
|
||
if ! git cat-file -e "$autoCommit"; then | ||
echo "Not a valid commit: $autoCommit" | ||
exit 1 | ||
elif git merge-base --is-ancestor "$autoCommit" HEAD; then | ||
# Skip commits that we have already | ||
continue | ||
fi | ||
|
||
echo -e "\e[32mAuto-rebasing commit $autoCommit with command '$autoCmd'\e[0m" | ||
|
||
# The commit before the commit | ||
parent=$(git rev-parse "$autoCommit"~) | ||
|
||
echo "Rebasing on top of the previous commit, might need to manually resolve conflicts" | ||
if ! git rebase --onto "$parent" "$(git merge-base "$targetBranch" HEAD)"; then | ||
echo -e "\e[33m\e[1mRestart this script after resolving the merge conflict as described above\e[0m" | ||
exit 1 | ||
fi | ||
|
||
echo "Reapplying the commit on each commit of our branch" | ||
# This does two things: | ||
# - The parent filter inserts the auto commit between its parent and | ||
# and our first commit. By itself, this causes our first commit to | ||
# effectively "undo" the auto commit, since the tree of our first | ||
# commit is unchanged. This is why the following is also necessary: | ||
# - The tree filter runs the command on each of our own commits, | ||
# effectively reapplying it. | ||
FILTER_BRANCH_SQUELCH_WARNING=1 git filter-branch \ | ||
--parent-filter "sed 's/$parent/$autoCommit/'" \ | ||
--tree-filter "$autoCmd" \ | ||
"$autoCommit"..HEAD | ||
|
||
# A tempting alternative is something along the lines of | ||
# git rebase --strategy-option=theirs --onto "$rev" "$parent" \ | ||
# --exec '$autoCmd && git commit --all --amend --no-edit' \ | ||
# but this causes problems because merges are not guaranteed to maintain the formatting. | ||
# The ./test.sh exercises such a case. | ||
done | ||
|
||
echo "Rebasing on top of the latest target branch commit" | ||
git rebase --onto "$targetBranch" "$(git merge-base "$targetBranch" HEAD)" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
let | ||
pkgs = import ../../../.. { | ||
config = { }; | ||
overlays = [ ]; | ||
}; | ||
|
||
inherit (pkgs) | ||
lib | ||
stdenvNoCC | ||
gitMinimal | ||
treefmt | ||
nixfmt-rfc-style | ||
; | ||
in | ||
|
||
stdenvNoCC.mkDerivation { | ||
name = "test"; | ||
src = lib.fileset.toSource { | ||
root = ./..; | ||
fileset = lib.fileset.unions [ | ||
../run.sh | ||
./run.sh | ||
./first.diff | ||
./second.diff | ||
]; | ||
}; | ||
nativeBuildInputs = [ | ||
gitMinimal | ||
treefmt | ||
nixfmt-rfc-style | ||
]; | ||
patchPhase = '' | ||
patchShebangs . | ||
''; | ||
|
||
buildPhase = '' | ||
export HOME=$(mktemp -d) | ||
export PAGER=true | ||
git config --global user.email "Your Name" | ||
git config --global user.name "[email protected]" | ||
./test/run.sh | ||
''; | ||
installPhase = '' | ||
touch $out | ||
''; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
diff --git a/b.nix b/b.nix | ||
index 9d18f25..67b0466 100644 | ||
--- a/b.nix | ||
+++ b/b.nix | ||
@@ -1,5 +1,5 @@ | ||
{ | ||
this = "is"; | ||
|
||
- some = "set"; | ||
+ some = "value"; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
#!/usr/bin/env bash | ||
|
||
set -euo pipefail | ||
|
||
# https://stackoverflow.com/a/246128/6605742 | ||
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) | ||
|
||
# Allows using a local directory for temporary files, | ||
# which can then be inspected after the run | ||
if (( $# > 0 )); then | ||
tmp=$(realpath "$1/tmp") | ||
if [[ -e "$tmp" ]]; then | ||
rm -rf "$tmp" | ||
fi | ||
mkdir -p "$tmp" | ||
else | ||
tmp=$(mktemp -d) | ||
trap 'rm -rf "$tmp"' exit | ||
fi | ||
|
||
# Tests a scenario where two poorly formatted files were modified on both the | ||
# main branch and the feature branch, while the main branch also did a treewide | ||
# format. | ||
|
||
git init "$tmp/repo" | ||
cd "$tmp/repo" || exit | ||
git branch -m main | ||
|
||
# Some initial poorly-formatted files | ||
cat > a.nix <<EOF | ||
{ x | ||
, y | ||
, z | ||
}: | ||
null | ||
EOF | ||
|
||
cat > b.nix <<EOF | ||
{ | ||
this = "is"; | ||
some="set" ; | ||
} | ||
EOF | ||
|
||
git add -A | ||
git commit -m "init" | ||
|
||
git switch -c feature | ||
|
||
# Some changes | ||
sed 's/set/value/' -i b.nix | ||
git commit -a -m "change b" | ||
sed '/, y/d' -i a.nix | ||
git commit -a -m "change a" | ||
|
||
git switch main | ||
|
||
# A change to cause a merge conflict | ||
sed 's/y/why/' -i a.nix | ||
git commit -a -m "change a" | ||
|
||
cat > treefmt.toml <<EOF | ||
[formatter.nix] | ||
command = "nixfmt" | ||
includes = [ "*.nix" ] | ||
EOF | ||
git add -A | ||
git commit -a -m "introduce treefmt" | ||
|
||
# Treewide reformat | ||
treefmt | ||
git commit -a -m "format" | ||
|
||
echo "$(git rev-parse HEAD) # !autorebase treefmt" > .git-blame-ignore-revs | ||
git add -A | ||
git commit -a -m "update ignored revs" | ||
|
||
git switch feature | ||
|
||
# Setup complete | ||
|
||
git log --graph --oneline feature main | ||
|
||
# This expectedly fails with a merge conflict that has to be manually resolved | ||
"$SCRIPT_DIR"/../run.sh main && exit 1 | ||
sed '/<<</,/>>>/d' -i a.nix | ||
git add a.nix | ||
GIT_EDITOR=true git rebase --continue | ||
|
||
"$SCRIPT_DIR"/../run.sh main | ||
|
||
git log --graph --oneline feature main | ||
|
||
checkDiff() { | ||
local ref=$1 | ||
local file=$2 | ||
expectedDiff=$(cat "$file") | ||
actualDiff=$(git diff "$ref"~ "$ref") | ||
if [[ "$expectedDiff" != "$actualDiff" ]]; then | ||
echo -e "Expected this diff:\n$expectedDiff" | ||
echo -e "But got this diff:\n$actualDiff" | ||
exit 1 | ||
fi | ||
} | ||
|
||
checkDiff HEAD~ "$SCRIPT_DIR"/first.diff | ||
checkDiff HEAD "$SCRIPT_DIR"/second.diff | ||
|
||
echo "Success!" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
diff --git a/a.nix b/a.nix | ||
index 18ba7ce..bcf38bc 100644 | ||
--- a/a.nix | ||
+++ b/a.nix | ||
@@ -1,6 +1,5 @@ | ||
{ | ||
x, | ||
- why, | ||
|
||
z, | ||
}: |