Merge branch 'cp/git-flush-is-an-env-bool'
[alt-git.git] / t / t3430-rebase-merges.sh
blob59b5d6b6f276c367862df92f847db37906715004
1 #!/bin/sh
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.
11 Initial setup:
13 -- B -- (first)
14 / \
15 A - C - D - E - H (main)
16 \ \ /
17 \ F - G (second)
19 Conflicting-G
21 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
22 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
24 . ./test-lib.sh
25 . "$TEST_DIRECTORY"/lib-rebase.sh
26 . "$TEST_DIRECTORY"/lib-log-graph.sh
28 test_cmp_graph () {
29 cat >expect &&
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"
37 EOF
39 test_commit A &&
40 git checkout -b first &&
41 test_commit B &&
42 b=$(git rev-parse --short HEAD) &&
43 git checkout main &&
44 test_commit C &&
45 c=$(git rev-parse --short HEAD) &&
46 test_commit D &&
47 d=$(git rev-parse --short HEAD) &&
48 git merge --no-commit B &&
49 test_tick &&
50 git commit -m E &&
51 git tag -m E E &&
52 e=$(git rev-parse --short HEAD) &&
53 git checkout -b second C &&
54 test_commit F &&
55 f=$(git rev-parse --short HEAD) &&
56 test_commit G &&
57 g=$(git rev-parse --short HEAD) &&
58 git checkout main &&
59 git merge --no-commit G &&
60 test_tick &&
61 git commit -m H &&
62 h=$(git rev-parse --short HEAD) &&
63 git tag -m H H &&
64 git checkout A &&
65 test_commit conflicting-G G.t
68 test_expect_success 'create completely different structure' '
69 cat >script-from-scratch <<-\EOF &&
70 label onto
72 # onebranch
73 pick G
74 pick D
75 label onebranch
77 # second
78 reset onto
79 pick B
80 label second
82 reset onto
83 merge -C H second
84 merge onebranch # Merge the topic branch '\''onebranch'\''
85 EOF
86 test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
87 test_tick &&
88 git rebase -i -r A main &&
89 test_cmp_graph <<-\EOF
90 * Merge the topic branch '\''onebranch'\''
92 | * D
93 | * G
94 * | H
95 |\ \
96 | |/
97 |/|
98 | * B
104 test_expect_success 'generate correct todo list' '
105 cat >expect <<-EOF &&
106 label onto
108 reset onto
109 pick $b B
110 label E
112 reset onto
113 pick $c C
114 label branch-point
115 pick $f F
116 pick $g G
117 label H
119 reset branch-point # C
120 pick $d D
121 merge -C $e E # E
122 merge -C $h H # H
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 &&
132 test_commit dont-overwrite-untracked &&
133 cat >script-from-scratch <<-EOF &&
134 exec >dont-overwrite-untracked.t
135 pick $(git rev-parse B) B
136 reset refs/tags/dont-overwrite-untracked
137 pick $(git rev-parse C) C
138 exec cat .git/rebase-merge/done >actual
140 test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
141 test_must_fail git rebase -ir A &&
142 test_cmp_rev HEAD B &&
143 head -n3 script-from-scratch >expect &&
144 test_cmp expect .git/rebase-merge/done &&
145 rm dont-overwrite-untracked.t &&
146 git rebase --continue &&
147 tail -n3 script-from-scratch >>expect &&
148 test_cmp expect actual
151 test_expect_success '`reset` rejects trees' '
152 test_when_finished "test_might_fail git rebase --abort" &&
153 test_must_fail env GIT_SEQUENCE_EDITOR="echo reset A^{tree} >" \
154 git rebase -i B C >out 2>err &&
155 grep "object .* is a tree" err &&
156 test_must_be_empty out
159 test_expect_success '`reset` only looks for labels under refs/rewritten/' '
160 test_when_finished "test_might_fail git rebase --abort" &&
161 git branch refs/rewritten/my-label A &&
162 test_must_fail env GIT_SEQUENCE_EDITOR="echo reset my-label >" \
163 git rebase -i B C >out 2>err &&
164 grep "could not resolve ${SQ}my-label${SQ}" err &&
165 test_must_be_empty out
168 test_expect_success 'failed `merge -C` writes patch (may be rescheduled, too)' '
169 test_when_finished "test_might_fail git rebase --abort" &&
170 git checkout -b conflicting-merge A &&
172 : fail because of conflicting untracked file &&
173 >G.t &&
174 echo "merge -C H G" >script-from-scratch &&
175 test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
176 test_tick &&
177 test_must_fail git rebase -ir HEAD &&
178 test_cmp_rev REBASE_HEAD H^0 &&
179 grep "^merge -C .* G$" .git/rebase-merge/done &&
180 grep "^merge -C .* G$" .git/rebase-merge/git-rebase-todo &&
181 test_path_is_missing .git/rebase-merge/patch &&
182 echo changed >file1 &&
183 git add file1 &&
184 test_must_fail git rebase --continue 2>err &&
185 grep "error: you have staged changes in your working tree" err &&
187 : fail because of merge conflict &&
188 git reset --hard conflicting-G &&
189 test_must_fail git rebase --continue &&
190 ! grep "^merge -C .* G$" .git/rebase-merge/git-rebase-todo &&
191 test_path_is_file .git/rebase-merge/patch
194 test_expect_success 'failed `merge <branch>` does not crash' '
195 test_when_finished "test_might_fail git rebase --abort" &&
196 git checkout conflicting-G &&
198 echo "merge G" >script-from-scratch &&
199 test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
200 test_tick &&
201 test_must_fail git rebase -ir HEAD &&
202 ! grep "^merge G$" .git/rebase-merge/git-rebase-todo &&
203 grep "^Merge branch ${SQ}G${SQ}$" .git/rebase-merge/message
206 test_expect_success 'merge -c commits before rewording and reloads todo-list' '
207 cat >script-from-scratch <<-\EOF &&
208 merge -c E B
209 merge -c H G
212 git checkout -b merge-c H &&
214 set_reword_editor &&
215 GIT_SEQUENCE_EDITOR="\"$PWD/replace-editor.sh\"" \
216 git rebase -i -r D
217 ) &&
218 check_reworded_commits E H
221 test_expect_success 'merge -c rewords when a strategy is given' '
222 git checkout -b merge-c-with-strategy H &&
223 write_script git-merge-override <<-\EOF &&
224 echo overridden$1 >G.t
225 git add G.t
228 PATH="$PWD:$PATH" \
229 GIT_SEQUENCE_EDITOR="echo merge -c H G >" \
230 GIT_EDITOR="echo edited >>" \
231 git rebase --no-ff -ir -s override -Xxopt E &&
232 test_write_lines overridden--xopt >expect &&
233 test_cmp expect G.t &&
234 test_write_lines H "" edited "" >expect &&
235 git log --format=%B -1 >actual &&
236 test_cmp expect actual
239 test_expect_success 'with a branch tip that was cherry-picked already' '
240 git checkout -b already-upstream main &&
241 base="$(git rev-parse --verify HEAD)" &&
243 test_commit A1 &&
244 test_commit A2 &&
245 git reset --hard $base &&
246 test_commit B1 &&
247 test_tick &&
248 git merge -m "Merge branch A" A2 &&
250 git checkout -b upstream-with-a2 $base &&
251 test_tick &&
252 git cherry-pick A2 &&
254 git checkout already-upstream &&
255 test_tick &&
256 git rebase -i -r upstream-with-a2 &&
257 test_cmp_graph upstream-with-a2.. <<-\EOF
258 * Merge branch A
260 | * A1
261 * | B1
263 o A2
267 test_expect_success '--no-rebase-merges countermands --rebase-merges' '
268 git checkout -b no-rebase-merges E &&
269 git rebase --rebase-merges --no-rebase-merges C &&
270 test_cmp_graph C.. <<-\EOF
277 test_expect_success 'do not rebase cousins unless asked for' '
278 git checkout -b cousins main &&
279 before="$(git rev-parse --verify HEAD)" &&
280 test_tick &&
281 git rebase -r HEAD^ &&
282 test_cmp_rev HEAD $before &&
283 test_tick &&
284 git rebase --rebase-merges=rebase-cousins HEAD^ &&
285 test_cmp_graph HEAD^.. <<-\EOF
286 * Merge the topic branch '\''onebranch'\''
288 | * D
289 | * G
295 test_expect_success 'rebase.rebaseMerges=rebase-cousins is equivalent to --rebase-merges=rebase-cousins' '
296 test_config rebase.rebaseMerges rebase-cousins &&
297 git checkout -b config-rebase-cousins main &&
298 git rebase HEAD^ &&
299 test_cmp_graph HEAD^.. <<-\EOF
300 * Merge the topic branch '\''onebranch'\''
302 | * D
303 | * G
309 test_expect_success '--no-rebase-merges overrides rebase.rebaseMerges=no-rebase-cousins' '
310 test_config rebase.rebaseMerges no-rebase-cousins &&
311 git checkout -b override-config-no-rebase-cousins E &&
312 git rebase --no-rebase-merges C &&
313 test_cmp_graph C.. <<-\EOF
320 test_expect_success '--rebase-merges overrides rebase.rebaseMerges=rebase-cousins' '
321 test_config rebase.rebaseMerges rebase-cousins &&
322 git checkout -b override-config-rebase-cousins E &&
323 before="$(git rev-parse --verify HEAD)" &&
324 test_tick &&
325 git rebase --rebase-merges C &&
326 test_cmp_rev HEAD $before
329 test_expect_success 'refs/rewritten/* is worktree-local' '
330 git worktree add wt &&
331 cat >wt/script-from-scratch <<-\EOF &&
332 label xyz
333 exec GIT_DIR=../.git git rev-parse --verify refs/rewritten/xyz >a || :
334 exec git rev-parse --verify refs/rewritten/xyz >b
337 test_config -C wt sequence.editor \""$PWD"/replace-editor.sh\" &&
338 git -C wt rebase -i HEAD &&
339 test_must_be_empty wt/a &&
340 test_cmp_rev HEAD "$(cat wt/b)"
343 test_expect_success '--abort cleans up refs/rewritten' '
344 git checkout -b abort-cleans-refs-rewritten H &&
345 GIT_SEQUENCE_EDITOR="echo break >>" git rebase -ir @^ &&
346 git rev-parse --verify refs/rewritten/onto &&
347 git rebase --abort &&
348 test_must_fail git rev-parse --verify refs/rewritten/onto
351 test_expect_success '--quit cleans up refs/rewritten' '
352 git checkout -b quit-cleans-refs-rewritten H &&
353 GIT_SEQUENCE_EDITOR="echo break >>" git rebase -ir @^ &&
354 git rev-parse --verify refs/rewritten/onto &&
355 git rebase --quit &&
356 test_must_fail git rev-parse --verify refs/rewritten/onto
359 test_expect_success 'post-rewrite hook and fixups work for merges' '
360 git checkout -b post-rewrite H &&
361 test_commit same1 &&
362 git reset --hard HEAD^ &&
363 test_commit same2 &&
364 git merge -m "to fix up" same1 &&
365 echo same old same old >same2.t &&
366 test_tick &&
367 git commit --fixup HEAD same2.t &&
368 fixup="$(git rev-parse HEAD)" &&
370 test_hook post-rewrite <<-\EOF &&
371 cat >actual
374 test_tick &&
375 git rebase -i --autosquash -r HEAD^^^ &&
376 printf "%s %s\n%s %s\n%s %s\n%s %s\n" >expect $(git rev-parse \
377 $fixup^^2 HEAD^2 \
378 $fixup^^ HEAD^ \
379 $fixup^ HEAD \
380 $fixup HEAD) &&
381 test_cmp expect actual
384 test_expect_success 'refuse to merge ancestors of HEAD' '
385 echo "merge HEAD^" >script-from-scratch &&
386 test_config -C wt sequence.editor \""$PWD"/replace-editor.sh\" &&
387 before="$(git rev-parse HEAD)" &&
388 git rebase -i HEAD &&
389 test_cmp_rev HEAD $before
392 test_expect_success 'root commits' '
393 git checkout --orphan unrelated &&
394 (GIT_AUTHOR_NAME="Parsnip" GIT_AUTHOR_EMAIL="root@example.com" \
395 test_commit second-root) &&
396 test_commit third-root &&
397 cat >script-from-scratch <<-\EOF &&
398 pick third-root
399 label first-branch
400 reset [new root]
401 pick second-root
402 merge first-branch # Merge the 3rd root
404 test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
405 test_tick &&
406 git rebase -i --force-rebase --root -r &&
407 test "Parsnip" = "$(git show -s --format=%an HEAD^)" &&
408 test $(git rev-parse second-root^0) != $(git rev-parse HEAD^) &&
409 test $(git rev-parse second-root:second-root.t) = \
410 $(git rev-parse HEAD^:second-root.t) &&
411 test_cmp_graph HEAD <<-\EOF &&
412 * Merge the 3rd root
414 | * third-root
415 * second-root
418 : fast forward if possible &&
419 before="$(git rev-parse --verify HEAD)" &&
420 test_might_fail git config --unset sequence.editor &&
421 test_tick &&
422 git rebase -i --root -r &&
423 test_cmp_rev HEAD $before
426 test_expect_success 'a "merge" into a root commit is a fast-forward' '
427 head=$(git rev-parse HEAD) &&
428 cat >script-from-scratch <<-EOF &&
429 reset [new root]
430 merge $head
432 test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
433 test_tick &&
434 git rebase -i -r HEAD^ &&
435 test_cmp_rev HEAD $head
438 test_expect_success 'A root commit can be a cousin, treat it that way' '
439 git checkout --orphan khnum &&
440 test_commit yama &&
441 git checkout -b asherah main &&
442 test_commit shamkat &&
443 git merge --allow-unrelated-histories khnum &&
444 test_tick &&
445 git rebase -f -r HEAD^ &&
446 test_cmp_rev ! HEAD^2 khnum &&
447 test_cmp_graph HEAD^.. <<-\EOF &&
448 * Merge branch '\''khnum'\'' into asherah
450 | * yama
451 o shamkat
453 test_tick &&
454 git rebase --rebase-merges=rebase-cousins HEAD^ &&
455 test_cmp_graph HEAD^.. <<-\EOF
456 * Merge branch '\''khnum'\'' into asherah
458 | * yama
460 o shamkat
464 test_expect_success 'labels that are object IDs are rewritten' '
465 git checkout -b third B &&
466 test_commit I &&
467 third=$(git rev-parse HEAD) &&
468 git checkout -b labels main &&
469 git merge --no-commit third &&
470 test_tick &&
471 git commit -m "Merge commit '\''$third'\'' into labels" &&
472 echo noop >script-from-scratch &&
473 test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
474 test_tick &&
475 git rebase -i -r A &&
476 grep "^label $third-" .git/ORIGINAL-TODO &&
477 ! grep "^label $third$" .git/ORIGINAL-TODO
480 test_expect_success 'octopus merges' '
481 git checkout -b three &&
482 test_commit before-octopus &&
483 test_commit three &&
484 git checkout -b two HEAD^ &&
485 test_commit two &&
486 git checkout -b one HEAD^ &&
487 test_commit one &&
488 test_tick &&
489 (GIT_AUTHOR_NAME="Hank" GIT_AUTHOR_EMAIL="hank@sea.world" \
490 git merge -m "Tüntenfüsch" two three) &&
492 : fast forward if possible &&
493 before="$(git rev-parse --verify HEAD)" &&
494 test_tick &&
495 git rebase -i -r HEAD^^ &&
496 test_cmp_rev HEAD $before &&
498 test_tick &&
499 git rebase -i --force-rebase -r HEAD^^ &&
500 test "Hank" = "$(git show -s --format=%an HEAD)" &&
501 test "$before" != $(git rev-parse HEAD) &&
502 test_cmp_graph HEAD^^.. <<-\EOF
503 *-. Tüntenfüsch
504 |\ \
505 | | * three
506 | * | two
507 | |/
508 * / one
510 o before-octopus
514 test_expect_success 'with --autosquash and --exec' '
515 git checkout -b with-exec H &&
516 echo Booh >B.t &&
517 test_tick &&
518 git commit --fixup B B.t &&
519 write_script show.sh <<-\EOF &&
520 subject="$(git show -s --format=%s HEAD)"
521 content="$(git diff HEAD^ HEAD | tail -n 1)"
522 echo "$subject: $content"
524 test_tick &&
525 git rebase -ir --autosquash --exec ./show.sh A >actual &&
526 grep "B: +Booh" actual &&
527 grep "E: +Booh" actual &&
528 grep "G: +G" actual
531 test_expect_success '--continue after resolving conflicts after a merge' '
532 git checkout -b already-has-g E &&
533 git cherry-pick E..G &&
534 test_commit H2 &&
536 git checkout -b conflicts-in-merge H &&
537 test_commit H2 H2.t conflicts H2-conflict &&
538 test_must_fail git rebase -r already-has-g &&
539 grep conflicts H2.t &&
540 echo resolved >H2.t &&
541 git add -u &&
542 git rebase --continue &&
543 test_must_fail git rev-parse --verify HEAD^2 &&
544 test_path_is_missing .git/MERGE_HEAD
547 test_expect_success '--rebase-merges with strategies' '
548 git checkout -b with-a-strategy F &&
549 test_tick &&
550 git merge -m "Merge conflicting-G" conflicting-G &&
552 : first, test with a merge strategy option &&
553 git rebase -ir -Xtheirs G &&
554 echo conflicting-G >expect &&
555 test_cmp expect G.t &&
557 : now, try with a merge strategy other than recursive &&
558 git reset --hard @{1} &&
559 write_script git-merge-override <<-\EOF &&
560 echo overridden$1 >>G.t
561 git add G.t
563 PATH="$PWD:$PATH" git rebase -ir -s override -Xxopt G &&
564 test_write_lines G overridden--xopt >expect &&
565 test_cmp expect G.t
568 test_expect_success '--rebase-merges with commit that can generate bad characters for filename' '
569 git checkout -b colon-in-label E &&
570 git merge -m "colon: this should work" G &&
571 git rebase --rebase-merges --force-rebase E
574 test_expect_success '--rebase-merges with message matched with onto label' '
575 git checkout -b onto-label E &&
576 git merge -m onto G &&
577 git rebase --rebase-merges --force-rebase E &&
578 test_cmp_graph <<-\EOF
579 * onto
581 | * G
582 | * F
583 * | E
584 |\ \
585 | * | B
586 * | | D
587 | |/
589 * | C
595 test_expect_success 'progress shows the correct total' '
596 git checkout -b progress H &&
597 git rebase --rebase-merges --force-rebase --verbose A 2> err &&
598 # Expecting "Rebasing (N/14)" here, no bogus total number
599 grep "^Rebasing.*/14.$" err >progress &&
600 test_line_count = 14 progress
603 test_expect_success 'truncate label names' '
604 commit=$(git commit-tree -p HEAD^ -p HEAD -m "0123456789 我 123" HEAD^{tree}) &&
605 git merge --ff-only $commit &&
607 done="$(git rev-parse --git-path rebase-merge/done)" &&
608 git -c rebase.maxLabelLength=14 rebase --rebase-merges -x "cp \"$done\" out" --root &&
609 grep "label 0123456789-我$" out &&
610 git -c rebase.maxLabelLength=13 rebase --rebase-merges -x "cp \"$done\" out" --root &&
611 grep "label 0123456789-$" out
614 test_done