You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
497 lines
12 KiB
497 lines
12 KiB
#!/bin/sh |
|
# |
|
# Copyright (c) 2018 Johannes E. Schindelin |
|
# |
|
|
|
test_description='git rebase -i --rebase-merges |
|
|
|
This test runs git rebase "interactively", retaining the branch structure by |
|
recreating merge commits. |
|
|
|
Initial setup: |
|
|
|
-- B -- (first) |
|
/ \ |
|
A - C - D - E - H (master) |
|
\ \ / |
|
\ F - G (second) |
|
\ |
|
Conflicting-G |
|
' |
|
. ./test-lib.sh |
|
. "$TEST_DIRECTORY"/lib-rebase.sh |
|
. "$TEST_DIRECTORY"/lib-log-graph.sh |
|
|
|
test_cmp_graph () { |
|
cat >expect && |
|
lib_test_cmp_graph --boundary --format=%s "$@" |
|
} |
|
|
|
test_expect_success 'setup' ' |
|
write_script replace-editor.sh <<-\EOF && |
|
mv "$1" "$(git rev-parse --git-path ORIGINAL-TODO)" |
|
cp script-from-scratch "$1" |
|
EOF |
|
|
|
test_commit A && |
|
git checkout -b first && |
|
test_commit B && |
|
b=$(git rev-parse --short HEAD) && |
|
git checkout master && |
|
test_commit C && |
|
c=$(git rev-parse --short HEAD) && |
|
test_commit D && |
|
d=$(git rev-parse --short HEAD) && |
|
git merge --no-commit B && |
|
test_tick && |
|
git commit -m E && |
|
git tag -m E E && |
|
e=$(git rev-parse --short HEAD) && |
|
git checkout -b second C && |
|
test_commit F && |
|
f=$(git rev-parse --short HEAD) && |
|
test_commit G && |
|
g=$(git rev-parse --short HEAD) && |
|
git checkout master && |
|
git merge --no-commit G && |
|
test_tick && |
|
git commit -m H && |
|
h=$(git rev-parse --short HEAD) && |
|
git tag -m H H && |
|
git checkout A && |
|
test_commit conflicting-G G.t |
|
' |
|
|
|
test_expect_success 'create completely different structure' ' |
|
cat >script-from-scratch <<-\EOF && |
|
label onto |
|
|
|
# onebranch |
|
pick G |
|
pick D |
|
label onebranch |
|
|
|
# second |
|
reset onto |
|
pick B |
|
label second |
|
|
|
reset onto |
|
merge -C H second |
|
merge onebranch # Merge the topic branch '\''onebranch'\'' |
|
EOF |
|
test_config sequence.editor \""$PWD"/replace-editor.sh\" && |
|
test_tick && |
|
git rebase -i -r A master && |
|
test_cmp_graph <<-\EOF |
|
* Merge the topic branch '\''onebranch'\'' |
|
|\ |
|
| * D |
|
| * G |
|
* | H |
|
|\ \ |
|
| |/ |
|
|/| |
|
| * B |
|
|/ |
|
* A |
|
EOF |
|
' |
|
|
|
test_expect_success 'generate correct todo list' ' |
|
cat >expect <<-EOF && |
|
label onto |
|
|
|
reset onto |
|
pick $b B |
|
label E |
|
|
|
reset onto |
|
pick $c C |
|
label branch-point |
|
pick $f F |
|
pick $g G |
|
label H |
|
|
|
reset branch-point # C |
|
pick $d D |
|
merge -C $e E # E |
|
merge -C $h H # H |
|
|
|
EOF |
|
|
|
grep -v "^#" <.git/ORIGINAL-TODO >output && |
|
test_cmp expect output |
|
' |
|
|
|
test_expect_success '`reset` refuses to overwrite untracked files' ' |
|
git checkout -b refuse-to-reset && |
|
test_commit dont-overwrite-untracked && |
|
git checkout @{-1} && |
|
: >dont-overwrite-untracked.t && |
|
echo "reset refs/tags/dont-overwrite-untracked" >script-from-scratch && |
|
test_config sequence.editor \""$PWD"/replace-editor.sh\" && |
|
test_must_fail git rebase -ir HEAD && |
|
git rebase --abort |
|
' |
|
|
|
test_expect_success 'failed `merge -C` writes patch (may be rescheduled, too)' ' |
|
test_when_finished "test_might_fail git rebase --abort" && |
|
git checkout -b conflicting-merge A && |
|
|
|
: fail because of conflicting untracked file && |
|
>G.t && |
|
echo "merge -C H G" >script-from-scratch && |
|
test_config sequence.editor \""$PWD"/replace-editor.sh\" && |
|
test_tick && |
|
test_must_fail git rebase -ir HEAD && |
|
grep "^merge -C .* G$" .git/rebase-merge/done && |
|
grep "^merge -C .* G$" .git/rebase-merge/git-rebase-todo && |
|
test_path_is_file .git/rebase-merge/patch && |
|
|
|
: fail because of merge conflict && |
|
rm G.t .git/rebase-merge/patch && |
|
git reset --hard conflicting-G && |
|
test_must_fail git rebase --continue && |
|
! grep "^merge -C .* G$" .git/rebase-merge/git-rebase-todo && |
|
test_path_is_file .git/rebase-merge/patch |
|
' |
|
|
|
test_expect_success 'failed `merge <branch>` does not crash' ' |
|
test_when_finished "test_might_fail git rebase --abort" && |
|
git checkout conflicting-G && |
|
|
|
echo "merge G" >script-from-scratch && |
|
test_config sequence.editor \""$PWD"/replace-editor.sh\" && |
|
test_tick && |
|
test_must_fail git rebase -ir HEAD && |
|
! grep "^merge G$" .git/rebase-merge/git-rebase-todo && |
|
grep "^Merge branch ${SQ}G${SQ}$" .git/rebase-merge/message |
|
' |
|
|
|
test_expect_success 'fast-forward merge -c still rewords' ' |
|
git checkout -b fast-forward-merge-c H && |
|
( |
|
set_fake_editor && |
|
FAKE_COMMIT_MESSAGE=edited \ |
|
GIT_SEQUENCE_EDITOR="echo merge -c H G >" \ |
|
git rebase -ir @^ |
|
) && |
|
echo edited >expected && |
|
git log --pretty=format:%B -1 >actual && |
|
test_cmp expected actual |
|
' |
|
|
|
test_expect_success 'with a branch tip that was cherry-picked already' ' |
|
git checkout -b already-upstream master && |
|
base="$(git rev-parse --verify HEAD)" && |
|
|
|
test_commit A1 && |
|
test_commit A2 && |
|
git reset --hard $base && |
|
test_commit B1 && |
|
test_tick && |
|
git merge -m "Merge branch A" A2 && |
|
|
|
git checkout -b upstream-with-a2 $base && |
|
test_tick && |
|
git cherry-pick A2 && |
|
|
|
git checkout already-upstream && |
|
test_tick && |
|
git rebase -i -r upstream-with-a2 && |
|
test_cmp_graph upstream-with-a2.. <<-\EOF |
|
* Merge branch A |
|
|\ |
|
| * A1 |
|
* | B1 |
|
|/ |
|
o A2 |
|
EOF |
|
' |
|
|
|
test_expect_success 'do not rebase cousins unless asked for' ' |
|
git checkout -b cousins master && |
|
before="$(git rev-parse --verify HEAD)" && |
|
test_tick && |
|
git rebase -r HEAD^ && |
|
test_cmp_rev HEAD $before && |
|
test_tick && |
|
git rebase --rebase-merges=rebase-cousins HEAD^ && |
|
test_cmp_graph HEAD^.. <<-\EOF |
|
* Merge the topic branch '\''onebranch'\'' |
|
|\ |
|
| * D |
|
| * G |
|
|/ |
|
o H |
|
EOF |
|
' |
|
|
|
test_expect_success 'refs/rewritten/* is worktree-local' ' |
|
git worktree add wt && |
|
cat >wt/script-from-scratch <<-\EOF && |
|
label xyz |
|
exec GIT_DIR=../.git git rev-parse --verify refs/rewritten/xyz >a || : |
|
exec git rev-parse --verify refs/rewritten/xyz >b |
|
EOF |
|
|
|
test_config -C wt sequence.editor \""$PWD"/replace-editor.sh\" && |
|
git -C wt rebase -i HEAD && |
|
test_must_be_empty wt/a && |
|
test_cmp_rev HEAD "$(cat wt/b)" |
|
' |
|
|
|
test_expect_success '--abort cleans up refs/rewritten' ' |
|
git checkout -b abort-cleans-refs-rewritten H && |
|
GIT_SEQUENCE_EDITOR="echo break >>" git rebase -ir @^ && |
|
git rev-parse --verify refs/rewritten/onto && |
|
git rebase --abort && |
|
test_must_fail git rev-parse --verify refs/rewritten/onto |
|
' |
|
|
|
test_expect_success '--quit cleans up refs/rewritten' ' |
|
git checkout -b quit-cleans-refs-rewritten H && |
|
GIT_SEQUENCE_EDITOR="echo break >>" git rebase -ir @^ && |
|
git rev-parse --verify refs/rewritten/onto && |
|
git rebase --quit && |
|
test_must_fail git rev-parse --verify refs/rewritten/onto |
|
' |
|
|
|
test_expect_success 'post-rewrite hook and fixups work for merges' ' |
|
git checkout -b post-rewrite H && |
|
test_commit same1 && |
|
git reset --hard HEAD^ && |
|
test_commit same2 && |
|
git merge -m "to fix up" same1 && |
|
echo same old same old >same2.t && |
|
test_tick && |
|
git commit --fixup HEAD same2.t && |
|
fixup="$(git rev-parse HEAD)" && |
|
|
|
mkdir -p .git/hooks && |
|
test_when_finished "rm .git/hooks/post-rewrite" && |
|
echo "cat >actual" | write_script .git/hooks/post-rewrite && |
|
|
|
test_tick && |
|
git rebase -i --autosquash -r HEAD^^^ && |
|
printf "%s %s\n%s %s\n%s %s\n%s %s\n" >expect $(git rev-parse \ |
|
$fixup^^2 HEAD^2 \ |
|
$fixup^^ HEAD^ \ |
|
$fixup^ HEAD \ |
|
$fixup HEAD) && |
|
test_cmp expect actual |
|
' |
|
|
|
test_expect_success 'refuse to merge ancestors of HEAD' ' |
|
echo "merge HEAD^" >script-from-scratch && |
|
test_config -C wt sequence.editor \""$PWD"/replace-editor.sh\" && |
|
before="$(git rev-parse HEAD)" && |
|
git rebase -i HEAD && |
|
test_cmp_rev HEAD $before |
|
' |
|
|
|
test_expect_success 'root commits' ' |
|
git checkout --orphan unrelated && |
|
(GIT_AUTHOR_NAME="Parsnip" GIT_AUTHOR_EMAIL="root@example.com" \ |
|
test_commit second-root) && |
|
test_commit third-root && |
|
cat >script-from-scratch <<-\EOF && |
|
pick third-root |
|
label first-branch |
|
reset [new root] |
|
pick second-root |
|
merge first-branch # Merge the 3rd root |
|
EOF |
|
test_config sequence.editor \""$PWD"/replace-editor.sh\" && |
|
test_tick && |
|
git rebase -i --force-rebase --root -r && |
|
test "Parsnip" = "$(git show -s --format=%an HEAD^)" && |
|
test $(git rev-parse second-root^0) != $(git rev-parse HEAD^) && |
|
test $(git rev-parse second-root:second-root.t) = \ |
|
$(git rev-parse HEAD^:second-root.t) && |
|
test_cmp_graph HEAD <<-\EOF && |
|
* Merge the 3rd root |
|
|\ |
|
| * third-root |
|
* second-root |
|
EOF |
|
|
|
: fast forward if possible && |
|
before="$(git rev-parse --verify HEAD)" && |
|
test_might_fail git config --unset sequence.editor && |
|
test_tick && |
|
git rebase -i --root -r && |
|
test_cmp_rev HEAD $before |
|
' |
|
|
|
test_expect_success 'a "merge" into a root commit is a fast-forward' ' |
|
head=$(git rev-parse HEAD) && |
|
cat >script-from-scratch <<-EOF && |
|
reset [new root] |
|
merge $head |
|
EOF |
|
test_config sequence.editor \""$PWD"/replace-editor.sh\" && |
|
test_tick && |
|
git rebase -i -r HEAD^ && |
|
test_cmp_rev HEAD $head |
|
' |
|
|
|
test_expect_success 'A root commit can be a cousin, treat it that way' ' |
|
git checkout --orphan khnum && |
|
test_commit yama && |
|
git checkout -b asherah master && |
|
test_commit shamkat && |
|
git merge --allow-unrelated-histories khnum && |
|
test_tick && |
|
git rebase -f -r HEAD^ && |
|
test_cmp_rev ! HEAD^2 khnum && |
|
test_cmp_graph HEAD^.. <<-\EOF && |
|
* Merge branch '\''khnum'\'' into asherah |
|
|\ |
|
| * yama |
|
o shamkat |
|
EOF |
|
test_tick && |
|
git rebase --rebase-merges=rebase-cousins HEAD^ && |
|
test_cmp_graph HEAD^.. <<-\EOF |
|
* Merge branch '\''khnum'\'' into asherah |
|
|\ |
|
| * yama |
|
|/ |
|
o shamkat |
|
EOF |
|
' |
|
|
|
test_expect_success 'labels that are object IDs are rewritten' ' |
|
git checkout -b third B && |
|
test_commit I && |
|
third=$(git rev-parse HEAD) && |
|
git checkout -b labels master && |
|
git merge --no-commit third && |
|
test_tick && |
|
git commit -m "Merge commit '\''$third'\'' into labels" && |
|
echo noop >script-from-scratch && |
|
test_config sequence.editor \""$PWD"/replace-editor.sh\" && |
|
test_tick && |
|
git rebase -i -r A && |
|
grep "^label $third-" .git/ORIGINAL-TODO && |
|
! grep "^label $third$" .git/ORIGINAL-TODO |
|
' |
|
|
|
test_expect_success 'octopus merges' ' |
|
git checkout -b three && |
|
test_commit before-octopus && |
|
test_commit three && |
|
git checkout -b two HEAD^ && |
|
test_commit two && |
|
git checkout -b one HEAD^ && |
|
test_commit one && |
|
test_tick && |
|
(GIT_AUTHOR_NAME="Hank" GIT_AUTHOR_EMAIL="hank@sea.world" \ |
|
git merge -m "Tüntenfüsch" two three) && |
|
|
|
: fast forward if possible && |
|
before="$(git rev-parse --verify HEAD)" && |
|
test_tick && |
|
git rebase -i -r HEAD^^ && |
|
test_cmp_rev HEAD $before && |
|
|
|
test_tick && |
|
git rebase -i --force-rebase -r HEAD^^ && |
|
test "Hank" = "$(git show -s --format=%an HEAD)" && |
|
test "$before" != $(git rev-parse HEAD) && |
|
test_cmp_graph HEAD^^.. <<-\EOF |
|
*-. Tüntenfüsch |
|
|\ \ |
|
| | * three |
|
| * | two |
|
| |/ |
|
* / one |
|
|/ |
|
o before-octopus |
|
EOF |
|
' |
|
|
|
test_expect_success 'with --autosquash and --exec' ' |
|
git checkout -b with-exec H && |
|
echo Booh >B.t && |
|
test_tick && |
|
git commit --fixup B B.t && |
|
write_script show.sh <<-\EOF && |
|
subject="$(git show -s --format=%s HEAD)" |
|
content="$(git diff HEAD^ HEAD | tail -n 1)" |
|
echo "$subject: $content" |
|
EOF |
|
test_tick && |
|
git rebase -ir --autosquash --exec ./show.sh A >actual && |
|
grep "B: +Booh" actual && |
|
grep "E: +Booh" actual && |
|
grep "G: +G" actual |
|
' |
|
|
|
test_expect_success '--continue after resolving conflicts after a merge' ' |
|
git checkout -b already-has-g E && |
|
git cherry-pick E..G && |
|
test_commit H2 && |
|
|
|
git checkout -b conflicts-in-merge H && |
|
test_commit H2 H2.t conflicts H2-conflict && |
|
test_must_fail git rebase -r already-has-g && |
|
grep conflicts H2.t && |
|
echo resolved >H2.t && |
|
git add -u && |
|
git rebase --continue && |
|
test_must_fail git rev-parse --verify HEAD^2 && |
|
test_path_is_missing .git/MERGE_HEAD |
|
' |
|
|
|
test_expect_success '--rebase-merges with strategies' ' |
|
git checkout -b with-a-strategy F && |
|
test_tick && |
|
git merge -m "Merge conflicting-G" conflicting-G && |
|
|
|
: first, test with a merge strategy option && |
|
git rebase -ir -Xtheirs G && |
|
echo conflicting-G >expect && |
|
test_cmp expect G.t && |
|
|
|
: now, try with a merge strategy other than recursive && |
|
git reset --hard @{1} && |
|
write_script git-merge-override <<-\EOF && |
|
echo overridden$1 >>G.t |
|
git add G.t |
|
EOF |
|
PATH="$PWD:$PATH" git rebase -ir -s override -Xxopt G && |
|
test_write_lines G overridden--xopt >expect && |
|
test_cmp expect G.t |
|
' |
|
|
|
test_expect_success '--rebase-merges with commit that can generate bad characters for filename' ' |
|
git checkout -b colon-in-label E && |
|
git merge -m "colon: this should work" G && |
|
git rebase --rebase-merges --force-rebase E |
|
' |
|
|
|
test_expect_success '--rebase-merges with message matched with onto label' ' |
|
git checkout -b onto-label E && |
|
git merge -m onto G && |
|
git rebase --rebase-merges --force-rebase E && |
|
test_cmp_graph <<-\EOF |
|
* onto |
|
|\ |
|
| * G |
|
| * F |
|
* | E |
|
|\ \ |
|
| * | B |
|
* | | D |
|
| |/ |
|
|/| |
|
* | C |
|
|/ |
|
* A |
|
EOF |
|
' |
|
|
|
test_done
|
|
|