mirror of
https://github.com/NixOS/nixpkgs.git
synced 2025-03-06 23:31:34 +00:00
Introduce script to automatically resolve conflicts after treewide changes (such as reformats) (#363759)
This commit is contained in:
commit
3425323bb4
.git-blame-ignore-revs
maintainers/scripts/auto-rebase
|
@ -1,5 +1,11 @@
|
|||
# This file contains a list of commits that are not likely what you
|
||||
# are looking for in a blame, such as mass reformatting or renaming.
|
||||
#
|
||||
# If a commit's line ends with `# !autorebase <command>`,
|
||||
# where <command> is an idempotent bash command that reapplies the changes from the commit,
|
||||
# the `maintainers/scripts/auto-rebase/run.sh` script can be used to rebase
|
||||
# across that commit while automatically resolving merge conflicts caused by the commit.
|
||||
#
|
||||
# You can set this file as a default ignore file for blame by running
|
||||
# the following command.
|
||||
#
|
||||
|
|
16
maintainers/scripts/auto-rebase/README.md
Normal file
16
maintainers/scripts/auto-rebase/README.md
Normal file
|
@ -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
|
||||
```
|
61
maintainers/scripts/auto-rebase/run.sh
Executable file
61
maintainers/scripts/auto-rebase/run.sh
Executable file
|
@ -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)"
|
46
maintainers/scripts/auto-rebase/test/default.nix
Normal file
46
maintainers/scripts/auto-rebase/test/default.nix
Normal file
|
@ -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 "your.name@example.com"
|
||||
./test/run.sh
|
||||
'';
|
||||
installPhase = ''
|
||||
touch $out
|
||||
'';
|
||||
}
|
11
maintainers/scripts/auto-rebase/test/first.diff
Normal file
11
maintainers/scripts/auto-rebase/test/first.diff
Normal file
|
@ -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";
|
||||
}
|
112
maintainers/scripts/auto-rebase/test/run.sh
Executable file
112
maintainers/scripts/auto-rebase/test/run.sh
Executable file
|
@ -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!"
|
11
maintainers/scripts/auto-rebase/test/second.diff
Normal file
11
maintainers/scripts/auto-rebase/test/second.diff
Normal file
|
@ -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,
|
||||
}:
|
Loading…
Reference in a new issue