mirror of
https://github.com/NixOS/nixpkgs.git
synced 2025-06-11 20:25:32 +03:00
Introduce script to automatically resolve conflicts after treewide changes (such as reformats) (#363759)
This commit is contained in:
commit
3425323bb4
7 changed files with 263 additions and 0 deletions
|
@ -1,5 +1,11 @@
|
||||||
# This file contains a list of commits that are not likely what you
|
# 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.
|
# 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
|
# You can set this file as a default ignore file for blame by running
|
||||||
# the following command.
|
# 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…
Add table
Add a link
Reference in a new issue