3 # Copyright (c) 2018 Johannes E. Schindelin
6 test_description
='git rebase -i --rebase-merges
8 This test runs git rebase "interactively", retaining the branch structure by
9 recreating merge commits.
15 A - C - D - E - H (main)
21 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
=main
22 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
25 .
"$TEST_DIRECTORY"/lib-rebase.sh
26 .
"$TEST_DIRECTORY"/lib-log-graph.sh
30 lib_test_cmp_graph
--boundary --format=%s
"$@"
33 test_expect_success
'setup' '
34 write_script replace-editor.sh <<-\EOF &&
35 mv "$1" "$(git rev-parse --git-path ORIGINAL-TODO)"
36 cp script-from-scratch "$1"
40 git checkout -b first &&
42 b=$(git rev-parse --short HEAD) &&
45 c=$(git rev-parse --short HEAD) &&
47 d=$(git rev-parse --short HEAD) &&
48 git merge --no-commit B &&
52 e=$(git rev-parse --short HEAD) &&
53 git checkout -b second C &&
55 f=$(git rev-parse --short HEAD) &&
57 g=$(git rev-parse --short HEAD) &&
59 git merge --no-commit G &&
62 h=$(git rev-parse --short HEAD) &&
65 test_commit conflicting-G G.t
68 test_expect_success
'create completely different structure' '
69 cat >script-from-scratch <<-\EOF &&
84 merge onebranch # Merge the topic branch '\''onebranch'\''
86 test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
88 git rebase -i -r A main &&
89 test_cmp_graph <<-\EOF
90 * Merge the topic branch '\''onebranch'\''
104 test_expect_success
'generate correct todo list' '
105 cat >expect <<-EOF &&
119 reset branch-point # C
126 grep -v "^#" <.git/ORIGINAL-TODO >output &&
127 test_cmp expect output
130 test_expect_success
'`reset` refuses to overwrite untracked files' '
131 git checkout -b refuse-to-reset &&
132 test_commit dont-overwrite-untracked &&
133 git checkout @{-1} &&
134 : >dont-overwrite-untracked.t &&
135 echo "reset refs/tags/dont-overwrite-untracked" >script-from-scratch &&
136 test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
137 test_must_fail git rebase -ir HEAD &&
141 test_expect_success
'`reset` rejects trees' '
142 test_when_finished "test_might_fail git rebase --abort" &&
143 test_must_fail env GIT_SEQUENCE_EDITOR="echo reset A^{tree} >" \
144 git rebase -i B C >out 2>err &&
145 grep "object .* is a tree" err &&
146 test_must_be_empty out
149 test_expect_success
'`reset` only looks for labels under refs/rewritten/' '
150 test_when_finished "test_might_fail git rebase --abort" &&
151 git branch refs/rewritten/my-label A &&
152 test_must_fail env GIT_SEQUENCE_EDITOR="echo reset my-label >" \
153 git rebase -i B C >out 2>err &&
154 grep "could not resolve ${SQ}my-label${SQ}" err &&
155 test_must_be_empty out
158 test_expect_success
'failed `merge -C` writes patch (may be rescheduled, too)' '
159 test_when_finished "test_might_fail git rebase --abort" &&
160 git checkout -b conflicting-merge A &&
162 : fail because of conflicting untracked file &&
164 echo "merge -C H G" >script-from-scratch &&
165 test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
167 test_must_fail git rebase -ir HEAD &&
168 grep "^merge -C .* G$" .git/rebase-merge/done &&
169 grep "^merge -C .* G$" .git/rebase-merge/git-rebase-todo &&
170 test_path_is_file .git/rebase-merge/patch &&
172 : fail because of merge conflict &&
173 rm G.t .git/rebase-merge/patch &&
174 git reset --hard conflicting-G &&
175 test_must_fail git rebase --continue &&
176 ! grep "^merge -C .* G$" .git/rebase-merge/git-rebase-todo &&
177 test_path_is_file .git/rebase-merge/patch
180 test_expect_success
'failed `merge <branch>` does not crash' '
181 test_when_finished "test_might_fail git rebase --abort" &&
182 git checkout conflicting-G &&
184 echo "merge G" >script-from-scratch &&
185 test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
187 test_must_fail git rebase -ir HEAD &&
188 ! grep "^merge G$" .git/rebase-merge/git-rebase-todo &&
189 grep "^Merge branch ${SQ}G${SQ}$" .git/rebase-merge/message
192 test_expect_success
'merge -c commits before rewording and reloads todo-list' '
193 cat >script-from-scratch <<-\EOF &&
198 git checkout -b merge-c H &&
201 GIT_SEQUENCE_EDITOR="\"$PWD/replace-editor.sh\"" \
204 check_reworded_commits E H
207 test_expect_success
'merge -c rewords when a strategy is given' '
208 git checkout -b merge-c-with-strategy H &&
209 write_script git-merge-override <<-\EOF &&
210 echo overridden$1 >G.t
215 GIT_SEQUENCE_EDITOR="echo merge -c H G >" \
216 GIT_EDITOR="echo edited >>" \
217 git rebase --no-ff -ir -s override -Xxopt E &&
218 test_write_lines overridden--xopt >expect &&
219 test_cmp expect G.t &&
220 test_write_lines H "" edited "" >expect &&
221 git log --format=%B -1 >actual &&
222 test_cmp expect actual
225 test_expect_success
'with a branch tip that was cherry-picked already' '
226 git checkout -b already-upstream main &&
227 base="$(git rev-parse --verify HEAD)" &&
231 git reset --hard $base &&
234 git merge -m "Merge branch A" A2 &&
236 git checkout -b upstream-with-a2 $base &&
238 git cherry-pick A2 &&
240 git checkout already-upstream &&
242 git rebase -i -r upstream-with-a2 &&
243 test_cmp_graph upstream-with-a2.. <<-\EOF
253 test_expect_success
'do not rebase cousins unless asked for' '
254 git checkout -b cousins main &&
255 before="$(git rev-parse --verify HEAD)" &&
257 git rebase -r HEAD^ &&
258 test_cmp_rev HEAD $before &&
260 git rebase --rebase-merges=rebase-cousins HEAD^ &&
261 test_cmp_graph HEAD^.. <<-\EOF
262 * Merge the topic branch '\''onebranch'\''
271 test_expect_success
'refs/rewritten/* is worktree-local' '
272 git worktree add wt &&
273 cat >wt/script-from-scratch <<-\EOF &&
275 exec GIT_DIR=../.git git rev-parse --verify refs/rewritten/xyz >a || :
276 exec git rev-parse --verify refs/rewritten/xyz >b
279 test_config -C wt sequence.editor \""$PWD"/replace-editor.sh\" &&
280 git -C wt rebase -i HEAD &&
281 test_must_be_empty wt/a &&
282 test_cmp_rev HEAD "$(cat wt/b)"
285 test_expect_success
'--abort cleans up refs/rewritten' '
286 git checkout -b abort-cleans-refs-rewritten H &&
287 GIT_SEQUENCE_EDITOR="echo break >>" git rebase -ir @^ &&
288 git rev-parse --verify refs/rewritten/onto &&
289 git rebase --abort &&
290 test_must_fail git rev-parse --verify refs/rewritten/onto
293 test_expect_success
'--quit cleans up refs/rewritten' '
294 git checkout -b quit-cleans-refs-rewritten H &&
295 GIT_SEQUENCE_EDITOR="echo break >>" git rebase -ir @^ &&
296 git rev-parse --verify refs/rewritten/onto &&
298 test_must_fail git rev-parse --verify refs/rewritten/onto
301 test_expect_success
'post-rewrite hook and fixups work for merges' '
302 git checkout -b post-rewrite H &&
304 git reset --hard HEAD^ &&
306 git merge -m "to fix up" same1 &&
307 echo same old same old >same2.t &&
309 git commit --fixup HEAD same2.t &&
310 fixup="$(git rev-parse HEAD)" &&
312 test_hook post-rewrite <<-\EOF &&
317 git rebase -i --autosquash -r HEAD^^^ &&
318 printf "%s %s\n%s %s\n%s %s\n%s %s\n" >expect $(git rev-parse \
323 test_cmp expect actual
326 test_expect_success
'refuse to merge ancestors of HEAD' '
327 echo "merge HEAD^" >script-from-scratch &&
328 test_config -C wt sequence.editor \""$PWD"/replace-editor.sh\" &&
329 before="$(git rev-parse HEAD)" &&
330 git rebase -i HEAD &&
331 test_cmp_rev HEAD $before
334 test_expect_success
'root commits' '
335 git checkout --orphan unrelated &&
336 (GIT_AUTHOR_NAME="Parsnip" GIT_AUTHOR_EMAIL="root@example.com" \
337 test_commit second-root) &&
338 test_commit third-root &&
339 cat >script-from-scratch <<-\EOF &&
344 merge first-branch # Merge the 3rd root
346 test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
348 git rebase -i --force-rebase --root -r &&
349 test "Parsnip" = "$(git show -s --format=%an HEAD^)" &&
350 test $(git rev-parse second-root^0) != $(git rev-parse HEAD^) &&
351 test $(git rev-parse second-root:second-root.t) = \
352 $(git rev-parse HEAD^:second-root.t) &&
353 test_cmp_graph HEAD <<-\EOF &&
360 : fast forward if possible &&
361 before="$(git rev-parse --verify HEAD)" &&
362 test_might_fail git config --unset sequence.editor &&
364 git rebase -i --root -r &&
365 test_cmp_rev HEAD $before
368 test_expect_success
'a "merge" into a root commit is a fast-forward' '
369 head=$(git rev-parse HEAD) &&
370 cat >script-from-scratch <<-EOF &&
374 test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
376 git rebase -i -r HEAD^ &&
377 test_cmp_rev HEAD $head
380 test_expect_success
'A root commit can be a cousin, treat it that way' '
381 git checkout --orphan khnum &&
383 git checkout -b asherah main &&
384 test_commit shamkat &&
385 git merge --allow-unrelated-histories khnum &&
387 git rebase -f -r HEAD^ &&
388 test_cmp_rev ! HEAD^2 khnum &&
389 test_cmp_graph HEAD^.. <<-\EOF &&
390 * Merge branch '\''khnum'\'' into asherah
396 git rebase --rebase-merges=rebase-cousins HEAD^ &&
397 test_cmp_graph HEAD^.. <<-\EOF
398 * Merge branch '\''khnum'\'' into asherah
406 test_expect_success
'labels that are object IDs are rewritten' '
407 git checkout -b third B &&
409 third=$(git rev-parse HEAD) &&
410 git checkout -b labels main &&
411 git merge --no-commit third &&
413 git commit -m "Merge commit '\''$third'\'' into labels" &&
414 echo noop >script-from-scratch &&
415 test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
417 git rebase -i -r A &&
418 grep "^label $third-" .git/ORIGINAL-TODO &&
419 ! grep "^label $third$" .git/ORIGINAL-TODO
422 test_expect_success
'octopus merges' '
423 git checkout -b three &&
424 test_commit before-octopus &&
426 git checkout -b two HEAD^ &&
428 git checkout -b one HEAD^ &&
431 (GIT_AUTHOR_NAME="Hank" GIT_AUTHOR_EMAIL="hank@sea.world" \
432 git merge -m "Tüntenfüsch" two three) &&
434 : fast forward if possible &&
435 before="$(git rev-parse --verify HEAD)" &&
437 git rebase -i -r HEAD^^ &&
438 test_cmp_rev HEAD $before &&
441 git rebase -i --force-rebase -r HEAD^^ &&
442 test "Hank" = "$(git show -s --format=%an HEAD)" &&
443 test "$before" != $(git rev-parse HEAD) &&
444 test_cmp_graph HEAD^^.. <<-\EOF
456 test_expect_success
'with --autosquash and --exec' '
457 git checkout -b with-exec H &&
460 git commit --fixup B B.t &&
461 write_script show.sh <<-\EOF &&
462 subject="$(git show -s --format=%s HEAD)"
463 content="$(git diff HEAD^ HEAD | tail -n 1)"
464 echo "$subject: $content"
467 git rebase -ir --autosquash --exec ./show.sh A >actual &&
468 grep "B: +Booh" actual &&
469 grep "E: +Booh" actual &&
473 test_expect_success
'--continue after resolving conflicts after a merge' '
474 git checkout -b already-has-g E &&
475 git cherry-pick E..G &&
478 git checkout -b conflicts-in-merge H &&
479 test_commit H2 H2.t conflicts H2-conflict &&
480 test_must_fail git rebase -r already-has-g &&
481 grep conflicts H2.t &&
482 echo resolved >H2.t &&
484 git rebase --continue &&
485 test_must_fail git rev-parse --verify HEAD^2 &&
486 test_path_is_missing .git/MERGE_HEAD
489 test_expect_success
'--rebase-merges with strategies' '
490 git checkout -b with-a-strategy F &&
492 git merge -m "Merge conflicting-G" conflicting-G &&
494 : first, test with a merge strategy option &&
495 git rebase -ir -Xtheirs G &&
496 echo conflicting-G >expect &&
497 test_cmp expect G.t &&
499 : now, try with a merge strategy other than recursive &&
500 git reset --hard @{1} &&
501 write_script git-merge-override <<-\EOF &&
502 echo overridden$1 >>G.t
505 PATH="$PWD:$PATH" git rebase -ir -s override -Xxopt G &&
506 test_write_lines G overridden--xopt >expect &&
510 test_expect_success
'--rebase-merges with commit that can generate bad characters for filename' '
511 git checkout -b colon-in-label E &&
512 git merge -m "colon: this should work" G &&
513 git rebase --rebase-merges --force-rebase E
516 test_expect_success
'--rebase-merges with message matched with onto label' '
517 git checkout -b onto-label E &&
518 git merge -m onto G &&
519 git rebase --rebase-merges --force-rebase E &&
520 test_cmp_graph <<-\EOF