mirror of
https://github.com/git/git
synced 2024-08-27 11:39:22 +00:00
![Alex Henrie](/assets/img/avatar_default.png)
The purpose of the new option is to accommodate users who would like --rebase-merges to be on by default and to facilitate turning on --rebase-merges by default without configuration in a future version of Git. Name the new option rebase.rebaseMerges, even though it is a little redundant, for consistency with the name of the command line option and to be clear when scrolling through values in the [rebase] section of .gitconfig. Support setting rebase.rebaseMerges to the nonspecific value "true" for users who don't need to or don't want to learn about the difference between rebase-cousins and no-rebase-cousins. Make --rebase-merges without an argument on the command line override any value of rebase.rebaseMerges in the configuration, for consistency with other command line flags with optional arguments that have an associated config option. Signed-off-by: Alex Henrie <alexhenrie24@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
582 lines
14 KiB
Bash
Executable file
582 lines
14 KiB
Bash
Executable file
#!/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 (main)
|
|
\ \ /
|
|
\ F - G (second)
|
|
\
|
|
Conflicting-G
|
|
'
|
|
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
|
|
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
|
|
|
|
. ./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 main &&
|
|
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 main &&
|
|
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 main &&
|
|
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 '`reset` rejects trees' '
|
|
test_when_finished "test_might_fail git rebase --abort" &&
|
|
test_must_fail env GIT_SEQUENCE_EDITOR="echo reset A^{tree} >" \
|
|
git rebase -i B C >out 2>err &&
|
|
grep "object .* is a tree" err &&
|
|
test_must_be_empty out
|
|
'
|
|
|
|
test_expect_success '`reset` only looks for labels under refs/rewritten/' '
|
|
test_when_finished "test_might_fail git rebase --abort" &&
|
|
git branch refs/rewritten/my-label A &&
|
|
test_must_fail env GIT_SEQUENCE_EDITOR="echo reset my-label >" \
|
|
git rebase -i B C >out 2>err &&
|
|
grep "could not resolve ${SQ}my-label${SQ}" err &&
|
|
test_must_be_empty out
|
|
'
|
|
|
|
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 'merge -c commits before rewording and reloads todo-list' '
|
|
cat >script-from-scratch <<-\EOF &&
|
|
merge -c E B
|
|
merge -c H G
|
|
EOF
|
|
|
|
git checkout -b merge-c H &&
|
|
(
|
|
set_reword_editor &&
|
|
GIT_SEQUENCE_EDITOR="\"$PWD/replace-editor.sh\"" \
|
|
git rebase -i -r D
|
|
) &&
|
|
check_reworded_commits E H
|
|
'
|
|
|
|
test_expect_success 'merge -c rewords when a strategy is given' '
|
|
git checkout -b merge-c-with-strategy H &&
|
|
write_script git-merge-override <<-\EOF &&
|
|
echo overridden$1 >G.t
|
|
git add G.t
|
|
EOF
|
|
|
|
PATH="$PWD:$PATH" \
|
|
GIT_SEQUENCE_EDITOR="echo merge -c H G >" \
|
|
GIT_EDITOR="echo edited >>" \
|
|
git rebase --no-ff -ir -s override -Xxopt E &&
|
|
test_write_lines overridden--xopt >expect &&
|
|
test_cmp expect G.t &&
|
|
test_write_lines H "" edited "" >expect &&
|
|
git log --format=%B -1 >actual &&
|
|
test_cmp expect actual
|
|
|
|
'
|
|
test_expect_success 'with a branch tip that was cherry-picked already' '
|
|
git checkout -b already-upstream main &&
|
|
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 '--no-rebase-merges countermands --rebase-merges' '
|
|
git checkout -b no-rebase-merges E &&
|
|
git rebase --rebase-merges --no-rebase-merges C &&
|
|
test_cmp_graph C.. <<-\EOF
|
|
* B
|
|
* D
|
|
o C
|
|
EOF
|
|
'
|
|
|
|
test_expect_success 'do not rebase cousins unless asked for' '
|
|
git checkout -b cousins main &&
|
|
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 'rebase.rebaseMerges=rebase-cousins is equivalent to --rebase-merges=rebase-cousins' '
|
|
test_config rebase.rebaseMerges rebase-cousins &&
|
|
git checkout -b config-rebase-cousins main &&
|
|
git rebase HEAD^ &&
|
|
test_cmp_graph HEAD^.. <<-\EOF
|
|
* Merge the topic branch '\''onebranch'\''
|
|
|\
|
|
| * D
|
|
| * G
|
|
|/
|
|
o H
|
|
EOF
|
|
'
|
|
|
|
test_expect_success '--no-rebase-merges overrides rebase.rebaseMerges=no-rebase-cousins' '
|
|
test_config rebase.rebaseMerges no-rebase-cousins &&
|
|
git checkout -b override-config-no-rebase-cousins E &&
|
|
git rebase --no-rebase-merges C &&
|
|
test_cmp_graph C.. <<-\EOF
|
|
* B
|
|
* D
|
|
o C
|
|
EOF
|
|
'
|
|
|
|
test_expect_success '--rebase-merges overrides rebase.rebaseMerges=rebase-cousins' '
|
|
test_config rebase.rebaseMerges rebase-cousins &&
|
|
git checkout -b override-config-rebase-cousins E &&
|
|
before="$(git rev-parse --verify HEAD)" &&
|
|
test_tick &&
|
|
git rebase --rebase-merges C &&
|
|
test_cmp_rev HEAD $before
|
|
'
|
|
|
|
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)" &&
|
|
|
|
test_hook post-rewrite <<-\EOF &&
|
|
cat >actual
|
|
EOF
|
|
|
|
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 main &&
|
|
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 main &&
|
|
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
|