git-prompt: use here-doc instead of here-string
[git/gitster.git] / t / t3430-rebase-merges.sh
blob36ca126bcdf9a5b68d883bb97bd1d5eadfab613b
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_PASSES_SANITIZE_LEAK=true
25 . ./test-lib.sh
26 . "$TEST_DIRECTORY"/lib-rebase.sh
27 . "$TEST_DIRECTORY"/lib-log-graph.sh
29 test_cmp_graph () {
30 cat >expect &&
31 lib_test_cmp_graph --boundary --format=%s "$@"
34 test_expect_success 'setup' '
35 write_script replace-editor.sh <<-\EOF &&
36 mv "$1" "$(git rev-parse --git-path ORIGINAL-TODO)"
37 cp script-from-scratch "$1"
38 EOF
40 test_commit A &&
41 git checkout -b first &&
42 test_commit B &&
43 b=$(git rev-parse --short HEAD) &&
44 git checkout main &&
45 test_commit C &&
46 c=$(git rev-parse --short HEAD) &&
47 test_commit D &&
48 d=$(git rev-parse --short HEAD) &&
49 git merge --no-commit B &&
50 test_tick &&
51 git commit -m E &&
52 git tag -m E E &&
53 e=$(git rev-parse --short HEAD) &&
54 git checkout -b second C &&
55 test_commit F &&
56 f=$(git rev-parse --short HEAD) &&
57 test_commit G &&
58 g=$(git rev-parse --short HEAD) &&
59 git checkout main &&
60 git merge --no-commit G &&
61 test_tick &&
62 git commit -m H &&
63 h=$(git rev-parse --short HEAD) &&
64 git tag -m H H &&
65 git checkout A &&
66 test_commit conflicting-G G.t
69 test_expect_success 'create completely different structure' '
70 cat >script-from-scratch <<-\EOF &&
71 label onto
73 # onebranch
74 pick G
75 pick D
76 label onebranch
78 # second
79 reset onto
80 pick B
81 label second
83 reset onto
84 merge -C H second
85 merge onebranch # Merge the topic branch '\''onebranch'\''
86 EOF
87 test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
88 test_tick &&
89 git rebase -i -r A main &&
90 test_cmp_graph <<-\EOF
91 * Merge the topic branch '\''onebranch'\''
93 | * D
94 | * G
95 * | H
96 |\ \
97 | |/
98 |/|
99 | * B
105 test_expect_success 'generate correct todo list' '
106 cat >expect <<-EOF &&
107 label onto
109 reset onto
110 pick $b B
111 label E
113 reset onto
114 pick $c C
115 label branch-point
116 pick $f F
117 pick $g G
118 label H
120 reset branch-point # C
121 pick $d D
122 merge -C $e E # E
123 merge -C $h H # H
127 grep -v "^#" <.git/ORIGINAL-TODO >output &&
128 test_cmp expect output
131 test_expect_success '`reset` refuses to overwrite untracked files' '
132 git checkout B &&
133 test_commit dont-overwrite-untracked &&
134 cat >script-from-scratch <<-EOF &&
135 exec >dont-overwrite-untracked.t
136 pick $(git rev-parse B) B
137 reset refs/tags/dont-overwrite-untracked
138 pick $(git rev-parse C) C
139 exec cat .git/rebase-merge/done >actual
141 test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
142 test_must_fail git rebase -ir A &&
143 test_cmp_rev HEAD B &&
144 head -n3 script-from-scratch >expect &&
145 test_cmp expect .git/rebase-merge/done &&
146 rm dont-overwrite-untracked.t &&
147 git rebase --continue &&
148 tail -n3 script-from-scratch >>expect &&
149 test_cmp expect actual
152 test_expect_success '`reset` rejects trees' '
153 test_when_finished "test_might_fail git rebase --abort" &&
154 test_must_fail env GIT_SEQUENCE_EDITOR="echo reset A^{tree} >" \
155 git rebase -i B C >out 2>err &&
156 grep "object .* is a tree" err &&
157 test_must_be_empty out
160 test_expect_success '`reset` only looks for labels under refs/rewritten/' '
161 test_when_finished "test_might_fail git rebase --abort" &&
162 git branch refs/rewritten/my-label A &&
163 test_must_fail env GIT_SEQUENCE_EDITOR="echo reset my-label >" \
164 git rebase -i B C >out 2>err &&
165 grep "could not resolve ${SQ}my-label${SQ}" err &&
166 test_must_be_empty out
169 test_expect_success 'failed `merge -C` writes patch (may be rescheduled, too)' '
170 test_when_finished "test_might_fail git rebase --abort" &&
171 git checkout -b conflicting-merge A &&
173 : fail because of conflicting untracked file &&
174 >G.t &&
175 echo "merge -C H G" >script-from-scratch &&
176 test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
177 test_tick &&
178 test_must_fail git rebase -ir HEAD &&
179 test_cmp_rev REBASE_HEAD H^0 &&
180 grep "^merge -C .* G$" .git/rebase-merge/done &&
181 grep "^merge -C .* G$" .git/rebase-merge/git-rebase-todo &&
182 test_path_is_missing .git/rebase-merge/patch &&
183 echo changed >file1 &&
184 git add file1 &&
185 test_must_fail git rebase --continue 2>err &&
186 grep "error: you have staged changes in your working tree" err &&
188 : fail because of merge conflict &&
189 git reset --hard conflicting-G &&
190 test_must_fail git rebase --continue &&
191 ! grep "^merge -C .* G$" .git/rebase-merge/git-rebase-todo &&
192 test_path_is_file .git/rebase-merge/patch
195 test_expect_success 'failed `merge <branch>` does not crash' '
196 test_when_finished "test_might_fail git rebase --abort" &&
197 git checkout conflicting-G &&
199 echo "merge G" >script-from-scratch &&
200 test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
201 test_tick &&
202 test_must_fail git rebase -ir HEAD &&
203 ! grep "^merge G$" .git/rebase-merge/git-rebase-todo &&
204 grep "^Merge branch ${SQ}G${SQ}$" .git/rebase-merge/message
207 test_expect_success 'merge -c commits before rewording and reloads todo-list' '
208 cat >script-from-scratch <<-\EOF &&
209 merge -c E B
210 merge -c H G
213 git checkout -b merge-c H &&
215 set_reword_editor &&
216 GIT_SEQUENCE_EDITOR="\"$PWD/replace-editor.sh\"" \
217 git rebase -i -r D
218 ) &&
219 check_reworded_commits E H
222 test_expect_success 'merge -c rewords when a strategy is given' '
223 git checkout -b merge-c-with-strategy H &&
224 write_script git-merge-override <<-\EOF &&
225 echo overridden$1 >G.t
226 git add G.t
229 PATH="$PWD:$PATH" \
230 GIT_SEQUENCE_EDITOR="echo merge -c H G >" \
231 GIT_EDITOR="echo edited >>" \
232 git rebase --no-ff -ir -s override -Xxopt E &&
233 test_write_lines overridden--xopt >expect &&
234 test_cmp expect G.t &&
235 test_write_lines H "" edited "" >expect &&
236 git log --format=%B -1 >actual &&
237 test_cmp expect actual
240 test_expect_success 'with a branch tip that was cherry-picked already' '
241 git checkout -b already-upstream main &&
242 base="$(git rev-parse --verify HEAD)" &&
244 test_commit A1 &&
245 test_commit A2 &&
246 git reset --hard $base &&
247 test_commit B1 &&
248 test_tick &&
249 git merge -m "Merge branch A" A2 &&
251 git checkout -b upstream-with-a2 $base &&
252 test_tick &&
253 git cherry-pick A2 &&
255 git checkout already-upstream &&
256 test_tick &&
257 git rebase -i -r upstream-with-a2 &&
258 test_cmp_graph upstream-with-a2.. <<-\EOF
259 * Merge branch A
261 | * A1
262 * | B1
264 o A2
268 test_expect_success '--no-rebase-merges countermands --rebase-merges' '
269 git checkout -b no-rebase-merges E &&
270 git rebase --rebase-merges --no-rebase-merges C &&
271 test_cmp_graph C.. <<-\EOF
278 test_expect_success 'do not rebase cousins unless asked for' '
279 git checkout -b cousins main &&
280 before="$(git rev-parse --verify HEAD)" &&
281 test_tick &&
282 git rebase -r HEAD^ &&
283 test_cmp_rev HEAD $before &&
284 test_tick &&
285 git rebase --rebase-merges=rebase-cousins HEAD^ &&
286 test_cmp_graph HEAD^.. <<-\EOF
287 * Merge the topic branch '\''onebranch'\''
289 | * D
290 | * G
296 test_expect_success 'rebase.rebaseMerges=rebase-cousins is equivalent to --rebase-merges=rebase-cousins' '
297 test_config rebase.rebaseMerges rebase-cousins &&
298 git checkout -b config-rebase-cousins main &&
299 git rebase HEAD^ &&
300 test_cmp_graph HEAD^.. <<-\EOF
301 * Merge the topic branch '\''onebranch'\''
303 | * D
304 | * G
310 test_expect_success '--no-rebase-merges overrides rebase.rebaseMerges=no-rebase-cousins' '
311 test_config rebase.rebaseMerges no-rebase-cousins &&
312 git checkout -b override-config-no-rebase-cousins E &&
313 git rebase --no-rebase-merges C &&
314 test_cmp_graph C.. <<-\EOF
321 test_expect_success '--rebase-merges overrides rebase.rebaseMerges=rebase-cousins' '
322 test_config rebase.rebaseMerges rebase-cousins &&
323 git checkout -b override-config-rebase-cousins E &&
324 before="$(git rev-parse --verify HEAD)" &&
325 test_tick &&
326 git rebase --rebase-merges C &&
327 test_cmp_rev HEAD $before
330 test_expect_success 'refs/rewritten/* is worktree-local' '
331 git worktree add wt &&
332 cat >wt/script-from-scratch <<-\EOF &&
333 label xyz
334 exec GIT_DIR=../.git git rev-parse --verify refs/rewritten/xyz >a || :
335 exec git rev-parse --verify refs/rewritten/xyz >b
338 test_config -C wt sequence.editor \""$PWD"/replace-editor.sh\" &&
339 git -C wt rebase -i HEAD &&
340 test_must_be_empty wt/a &&
341 test_cmp_rev HEAD "$(cat wt/b)"
344 test_expect_success '--abort cleans up refs/rewritten' '
345 git checkout -b abort-cleans-refs-rewritten H &&
346 GIT_SEQUENCE_EDITOR="echo break >>" git rebase -ir @^ &&
347 git rev-parse --verify refs/rewritten/onto &&
348 git rebase --abort &&
349 test_must_fail git rev-parse --verify refs/rewritten/onto
352 test_expect_success '--quit cleans up refs/rewritten' '
353 git checkout -b quit-cleans-refs-rewritten H &&
354 GIT_SEQUENCE_EDITOR="echo break >>" git rebase -ir @^ &&
355 git rev-parse --verify refs/rewritten/onto &&
356 git rebase --quit &&
357 test_must_fail git rev-parse --verify refs/rewritten/onto
360 test_expect_success 'post-rewrite hook and fixups work for merges' '
361 git checkout -b post-rewrite H &&
362 test_commit same1 &&
363 git reset --hard HEAD^ &&
364 test_commit same2 &&
365 git merge -m "to fix up" same1 &&
366 echo same old same old >same2.t &&
367 test_tick &&
368 git commit --fixup HEAD same2.t &&
369 fixup="$(git rev-parse HEAD)" &&
371 test_hook post-rewrite <<-\EOF &&
372 cat >actual
375 test_tick &&
376 git rebase -i --autosquash -r HEAD^^^ &&
377 printf "%s %s\n%s %s\n%s %s\n%s %s\n" >expect $(git rev-parse \
378 $fixup^^2 HEAD^2 \
379 $fixup^^ HEAD^ \
380 $fixup^ HEAD \
381 $fixup HEAD) &&
382 test_cmp expect actual
385 test_expect_success 'refuse to merge ancestors of HEAD' '
386 echo "merge HEAD^" >script-from-scratch &&
387 test_config -C wt sequence.editor \""$PWD"/replace-editor.sh\" &&
388 before="$(git rev-parse HEAD)" &&
389 git rebase -i HEAD &&
390 test_cmp_rev HEAD $before
393 test_expect_success 'root commits' '
394 git checkout --orphan unrelated &&
395 (GIT_AUTHOR_NAME="Parsnip" GIT_AUTHOR_EMAIL="root@example.com" \
396 test_commit second-root) &&
397 test_commit third-root &&
398 cat >script-from-scratch <<-\EOF &&
399 pick third-root
400 label first-branch
401 reset [new root]
402 pick second-root
403 merge first-branch # Merge the 3rd root
405 test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
406 test_tick &&
407 git rebase -i --force-rebase --root -r &&
408 test "Parsnip" = "$(git show -s --format=%an HEAD^)" &&
409 test $(git rev-parse second-root^0) != $(git rev-parse HEAD^) &&
410 test $(git rev-parse second-root:second-root.t) = \
411 $(git rev-parse HEAD^:second-root.t) &&
412 test_cmp_graph HEAD <<-\EOF &&
413 * Merge the 3rd root
415 | * third-root
416 * second-root
419 : fast forward if possible &&
420 before="$(git rev-parse --verify HEAD)" &&
421 test_might_fail git config --unset sequence.editor &&
422 test_tick &&
423 git rebase -i --root -r &&
424 test_cmp_rev HEAD $before
427 test_expect_success 'a "merge" into a root commit is a fast-forward' '
428 head=$(git rev-parse HEAD) &&
429 cat >script-from-scratch <<-EOF &&
430 reset [new root]
431 merge $head
433 test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
434 test_tick &&
435 git rebase -i -r HEAD^ &&
436 test_cmp_rev HEAD $head
439 test_expect_success 'A root commit can be a cousin, treat it that way' '
440 git checkout --orphan khnum &&
441 test_commit yama &&
442 git checkout -b asherah main &&
443 test_commit shamkat &&
444 git merge --allow-unrelated-histories khnum &&
445 test_tick &&
446 git rebase -f -r HEAD^ &&
447 test_cmp_rev ! HEAD^2 khnum &&
448 test_cmp_graph HEAD^.. <<-\EOF &&
449 * Merge branch '\''khnum'\'' into asherah
451 | * yama
452 o shamkat
454 test_tick &&
455 git rebase --rebase-merges=rebase-cousins HEAD^ &&
456 test_cmp_graph HEAD^.. <<-\EOF
457 * Merge branch '\''khnum'\'' into asherah
459 | * yama
461 o shamkat
465 test_expect_success 'labels that are object IDs are rewritten' '
466 git checkout -b third B &&
467 test_commit I &&
468 third=$(git rev-parse HEAD) &&
469 git checkout -b labels main &&
470 git merge --no-commit third &&
471 test_tick &&
472 git commit -m "Merge commit '\''$third'\'' into labels" &&
473 echo noop >script-from-scratch &&
474 test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
475 test_tick &&
476 git rebase -i -r A &&
477 grep "^label $third-" .git/ORIGINAL-TODO &&
478 ! grep "^label $third$" .git/ORIGINAL-TODO
481 test_expect_success 'octopus merges' '
482 git checkout -b three &&
483 test_commit before-octopus &&
484 test_commit three &&
485 git checkout -b two HEAD^ &&
486 test_commit two &&
487 git checkout -b one HEAD^ &&
488 test_commit one &&
489 test_tick &&
490 (GIT_AUTHOR_NAME="Hank" GIT_AUTHOR_EMAIL="hank@sea.world" \
491 git merge -m "Tüntenfüsch" two three) &&
493 : fast forward if possible &&
494 before="$(git rev-parse --verify HEAD)" &&
495 test_tick &&
496 git rebase -i -r HEAD^^ &&
497 test_cmp_rev HEAD $before &&
499 test_tick &&
500 git rebase -i --force-rebase -r HEAD^^ &&
501 test "Hank" = "$(git show -s --format=%an HEAD)" &&
502 test "$before" != $(git rev-parse HEAD) &&
503 test_cmp_graph HEAD^^.. <<-\EOF
504 *-. Tüntenfüsch
505 |\ \
506 | | * three
507 | * | two
508 | |/
509 * / one
511 o before-octopus
515 test_expect_success 'with --autosquash and --exec' '
516 git checkout -b with-exec H &&
517 echo Booh >B.t &&
518 test_tick &&
519 git commit --fixup B B.t &&
520 write_script show.sh <<-\EOF &&
521 subject="$(git show -s --format=%s HEAD)"
522 content="$(git diff HEAD^ HEAD | tail -n 1)"
523 echo "$subject: $content"
525 test_tick &&
526 git rebase -ir --autosquash --exec ./show.sh A >actual &&
527 grep "B: +Booh" actual &&
528 grep "E: +Booh" actual &&
529 grep "G: +G" actual
532 test_expect_success '--continue after resolving conflicts after a merge' '
533 git checkout -b already-has-g E &&
534 git cherry-pick E..G &&
535 test_commit H2 &&
537 git checkout -b conflicts-in-merge H &&
538 test_commit H2 H2.t conflicts H2-conflict &&
539 test_must_fail git rebase -r already-has-g &&
540 grep conflicts H2.t &&
541 echo resolved >H2.t &&
542 git add -u &&
543 git rebase --continue &&
544 test_must_fail git rev-parse --verify HEAD^2 &&
545 test_path_is_missing .git/MERGE_HEAD
548 test_expect_success '--rebase-merges with strategies' '
549 git checkout -b with-a-strategy F &&
550 test_tick &&
551 git merge -m "Merge conflicting-G" conflicting-G &&
553 : first, test with a merge strategy option &&
554 git rebase -ir -Xtheirs G &&
555 echo conflicting-G >expect &&
556 test_cmp expect G.t &&
558 : now, try with a merge strategy other than recursive &&
559 git reset --hard @{1} &&
560 write_script git-merge-override <<-\EOF &&
561 echo overridden$1 >>G.t
562 git add G.t
564 PATH="$PWD:$PATH" git rebase -ir -s override -Xxopt G &&
565 test_write_lines G overridden--xopt >expect &&
566 test_cmp expect G.t
569 test_expect_success '--rebase-merges with commit that can generate bad characters for filename' '
570 git checkout -b colon-in-label E &&
571 git merge -m "colon: this should work" G &&
572 git rebase --rebase-merges --force-rebase E
575 test_expect_success '--rebase-merges with message matched with onto label' '
576 git checkout -b onto-label E &&
577 git merge -m onto G &&
578 git rebase --rebase-merges --force-rebase E &&
579 test_cmp_graph <<-\EOF
580 * onto
582 | * G
583 | * F
584 * | E
585 |\ \
586 | * | B
587 * | | D
588 | |/
590 * | C
596 test_expect_success 'progress shows the correct total' '
597 git checkout -b progress H &&
598 git rebase --rebase-merges --force-rebase --verbose A 2> err &&
599 # Expecting "Rebasing (N/14)" here, no bogus total number
600 grep "^Rebasing.*/14.$" err >progress &&
601 test_line_count = 14 progress
604 test_expect_success 'truncate label names' '
605 commit=$(git commit-tree -p HEAD^ -p HEAD -m "0123456789 我 123" HEAD^{tree}) &&
606 git merge --ff-only $commit &&
608 done="$(git rev-parse --git-path rebase-merge/done)" &&
609 git -c rebase.maxLabelLength=14 rebase --rebase-merges -x "cp \"$done\" out" --root &&
610 grep "label 0123456789-我$" out &&
611 git -c rebase.maxLabelLength=13 rebase --rebase-merges -x "cp \"$done\" out" --root &&
612 grep "label 0123456789-$" out
615 test_done