Raw
1 #!/bin/sh
2 #
3 # Copyright (c) 2018 Johannes E. Schindelin
4 #
5
6 test_description='git rebase -i --rebase-merges
7
8 This test runs git rebase "interactively", retaining the branch structure by
9 recreating merge commits.
10
11 Initial setup:
12
13 -- B -- (first)
14 / \
15 A - C - D - E - H (main)
16 \ \ /
17 \ F - G (second)
18 \
19 Conflicting-G
20 '
21 GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
22 export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
23
24 . ./test-lib.sh
25 . "$TEST_DIRECTORY"/lib-rebase.sh
26 . "$TEST_DIRECTORY"/lib-log-graph.sh
27
28 test_cmp_graph () {
29 cat >expect &&
30 lib_test_cmp_graph --boundary --format=%s "$@"
31 }
32
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
38
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
66 '
67
68 test_expect_success 'create completely different structure' '
69 cat >script-from-scratch <<-\EOF &&
70 label onto
71
72 # onebranch
73 pick G
74 pick D
75 label onebranch
76
77 # second
78 reset onto
79 pick B
80 label second
81
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'\''
91 |\
92 | * D
93 | * G
94 * | H
95 |\ \
96 | |/
97 |/|
98 | * B
99 |/
100 * A
101 EOF
102
103 head="$(git show-ref --verify -s --abbrev HEAD)" &&
104 cat >expect <<-EOF &&
105 $head HEAD@{0}: rebase (finish): returning to refs/heads/main
106 $head HEAD@{1}: rebase (merge): Merge the topic branch ${SQ}onebranch${SQ}
107 EOF
108
109 git reflog -n2 HEAD >actual &&
110 test_cmp expect actual
111 '
112
113 test_expect_success 'generate correct todo list' '
114 cat >expect <<-EOF &&
115 label onto
116
117 reset onto
118 pick $b # B
119 label first
120
121 reset onto
122 pick $c # C
123 label branch-point
124 pick $f # F
125 pick $g # G
126 label second
127
128 reset branch-point # C
129 pick $d # D
130 merge -C $e first # E
131 merge -C $h second # H
132
133 EOF
134
135 grep -v "^#" <.git/ORIGINAL-TODO >output &&
136 test_cmp expect output
137 '
138
139 test_expect_success '`reset` refuses to overwrite untracked files' '
140 git checkout B &&
141 test_commit dont-overwrite-untracked &&
142 cat >script-from-scratch <<-EOF &&
143 exec >dont-overwrite-untracked.t
144 pick $(git rev-parse B) B
145 reset refs/tags/dont-overwrite-untracked
146 pick $(git rev-parse C) C
147 exec cat .git/rebase-merge/done >actual
148 EOF
149 test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
150 test_must_fail git rebase -ir A &&
151 test_cmp_rev HEAD B &&
152 head -n3 script-from-scratch >expect &&
153 test_cmp expect .git/rebase-merge/done &&
154 rm dont-overwrite-untracked.t &&
155 git rebase --continue &&
156 tail -n3 script-from-scratch >>expect &&
157 test_cmp expect actual
158 '
159
160 test_expect_success '`reset` rejects trees' '
161 test_when_finished "test_might_fail git rebase --abort" &&
162 test_must_fail env GIT_SEQUENCE_EDITOR="echo reset A^{tree} >" \
163 git rebase -i B C >out 2>err &&
164 test_grep "object .* is a tree" err &&
165 test_must_be_empty out
166 '
167
168 test_expect_success '`reset` only looks for labels under refs/rewritten/' '
169 test_when_finished "test_might_fail git rebase --abort" &&
170 git branch refs/rewritten/my-label A &&
171 test_must_fail env GIT_SEQUENCE_EDITOR="echo reset my-label >" \
172 git rebase -i B C >out 2>err &&
173 test_grep "could not resolve ${SQ}my-label${SQ}" err &&
174 test_must_be_empty out
175 '
176
177 test_expect_success 'failed `merge -C` writes patch (may be rescheduled, too)' '
178 test_when_finished "test_might_fail git rebase --abort" &&
179 git checkout -b conflicting-merge A &&
180
181 : fail because of conflicting untracked file &&
182 >G.t &&
183 echo "merge -C H G" >script-from-scratch &&
184 test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
185 test_tick &&
186 test_must_fail git rebase -ir HEAD &&
187 test_cmp_rev REBASE_HEAD H^0 &&
188 test_grep "^merge -C .* G$" .git/rebase-merge/done &&
189 test_grep "^merge -C .* G$" .git/rebase-merge/git-rebase-todo &&
190 test_path_is_missing .git/rebase-merge/patch &&
191 echo changed >file1 &&
192 git add file1 &&
193 test_must_fail git rebase --continue 2>err &&
194 test_grep "error: you have staged changes in your working tree" err &&
195
196 : fail because of merge conflict &&
197 git reset --hard conflicting-G &&
198 test_must_fail git rebase --continue &&
199 test_grep ! "^merge -C .* G$" .git/rebase-merge/git-rebase-todo &&
200 test_path_is_file .git/rebase-merge/patch
201 '
202
203 test_expect_success 'failed `merge <branch>` does not crash' '
204 test_when_finished "test_might_fail git rebase --abort" &&
205 git checkout conflicting-G &&
206
207 echo "merge G" >script-from-scratch &&
208 test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
209 test_tick &&
210 test_must_fail git rebase -ir HEAD &&
211 test_grep ! "^merge G$" .git/rebase-merge/git-rebase-todo &&
212 test_grep "^Merge branch ${SQ}G${SQ}$" .git/rebase-merge/message
213 '
214
215 test_expect_success 'merge -c commits before rewording and reloads todo-list' '
216 cat >script-from-scratch <<-\EOF &&
217 merge -c E B
218 merge -c H G
219 EOF
220
221 git checkout -b merge-c H &&
222 (
223 set_reword_editor &&
224 GIT_SEQUENCE_EDITOR="\"$PWD/replace-editor.sh\"" \
225 git rebase -i -r D
226 ) &&
227 check_reworded_commits E H
228 '
229
230 test_expect_success 'merge -c rewords when a strategy is given' '
231 git checkout -b merge-c-with-strategy H &&
232 write_script git-merge-override <<-\EOF &&
233 echo overridden$1 >G.t
234 git add G.t
235 EOF
236
237 PATH="$PWD:$PATH" \
238 GIT_SEQUENCE_EDITOR="echo merge -c H G >" \
239 GIT_EDITOR="echo edited >>" \
240 git rebase --no-ff -ir -s override -Xxopt E &&
241 test_write_lines overridden--xopt >expect &&
242 test_cmp expect G.t &&
243 test_write_lines H "" edited "" >expect &&
244 git log --format=%B -1 >actual &&
245 test_cmp expect actual
246
247 '
248 test_expect_success 'with a branch tip that was cherry-picked already' '
249 git checkout -b already-upstream main &&
250 base="$(git rev-parse --verify HEAD)" &&
251
252 test_commit A1 &&
253 test_commit A2 &&
254 git reset --hard $base &&
255 test_commit B1 &&
256 test_tick &&
257 git merge -m "Merge branch A" A2 &&
258
259 git checkout -b upstream-with-a2 $base &&
260 test_tick &&
261 git cherry-pick A2 &&
262
263 git checkout already-upstream &&
264 test_tick &&
265 git rebase -i -r upstream-with-a2 &&
266 test_cmp_graph upstream-with-a2.. <<-\EOF
267 * Merge branch A
268 |\
269 | * A1
270 * | B1
271 |/
272 o A2
273 EOF
274 '
275
276 test_expect_success '--no-rebase-merges countermands --rebase-merges' '
277 git checkout -b no-rebase-merges E &&
278 git rebase --rebase-merges --no-rebase-merges C &&
279 test_cmp_graph C.. <<-\EOF
280 * B
281 * D
282 o C
283 EOF
284 '
285
286 test_expect_success 'do not rebase cousins unless asked for' '
287 git checkout -b cousins main &&
288 before="$(git rev-parse --verify HEAD)" &&
289 test_tick &&
290 git rebase -r HEAD^ &&
291 test_cmp_rev HEAD $before &&
292 test_tick &&
293 git rebase --rebase-merges=rebase-cousins HEAD^ &&
294 test_cmp_graph HEAD^.. <<-\EOF
295 * Merge the topic branch '\''onebranch'\''
296 |\
297 | * D
298 | * G
299 |/
300 o H
301 EOF
302 '
303
304 test_expect_success 'rebase.rebaseMerges=rebase-cousins is equivalent to --rebase-merges=rebase-cousins' '
305 test_config rebase.rebaseMerges rebase-cousins &&
306 git checkout -b config-rebase-cousins main &&
307 git rebase HEAD^ &&
308 test_cmp_graph HEAD^.. <<-\EOF
309 * Merge the topic branch '\''onebranch'\''
310 |\
311 | * D
312 | * G
313 |/
314 o H
315 EOF
316 '
317
318 test_expect_success '--no-rebase-merges overrides rebase.rebaseMerges=no-rebase-cousins' '
319 test_config rebase.rebaseMerges no-rebase-cousins &&
320 git checkout -b override-config-no-rebase-cousins E &&
321 git rebase --no-rebase-merges C &&
322 test_cmp_graph C.. <<-\EOF
323 * B
324 * D
325 o C
326 EOF
327 '
328
329 test_expect_success '--rebase-merges overrides rebase.rebaseMerges=rebase-cousins' '
330 test_config rebase.rebaseMerges rebase-cousins &&
331 git checkout -b override-config-rebase-cousins E &&
332 before="$(git rev-parse --verify HEAD)" &&
333 test_tick &&
334 git rebase --rebase-merges C &&
335 test_cmp_rev HEAD $before
336 '
337
338 test_expect_success 'refs/rewritten/* is worktree-local' '
339 git worktree add wt &&
340 cat >wt/script-from-scratch <<-\EOF &&
341 label xyz
342 exec GIT_DIR=../.git git rev-parse --verify refs/rewritten/xyz >a || :
343 exec git rev-parse --verify refs/rewritten/xyz >b
344 EOF
345
346 test_config -C wt sequence.editor \""$PWD"/replace-editor.sh\" &&
347 git -C wt rebase -i HEAD &&
348 test_must_be_empty wt/a &&
349 test_cmp_rev HEAD "$(cat wt/b)"
350 '
351
352 test_expect_success '--abort cleans up refs/rewritten' '
353 git checkout -b abort-cleans-refs-rewritten H &&
354 GIT_SEQUENCE_EDITOR="echo break >>" git rebase -ir @^ &&
355 git rev-parse --verify refs/rewritten/onto &&
356 git rebase --abort &&
357 test_must_fail git rev-parse --verify refs/rewritten/onto
358 '
359
360 test_expect_success '--quit cleans up refs/rewritten' '
361 git checkout -b quit-cleans-refs-rewritten H &&
362 GIT_SEQUENCE_EDITOR="echo break >>" git rebase -ir @^ &&
363 git rev-parse --verify refs/rewritten/onto &&
364 git rebase --quit &&
365 test_must_fail git rev-parse --verify refs/rewritten/onto
366 '
367
368 test_expect_success 'post-rewrite hook and fixups work for merges' '
369 git checkout -b post-rewrite H &&
370 test_commit same1 &&
371 git reset --hard HEAD^ &&
372 test_commit same2 &&
373 git merge -m "to fix up" same1 &&
374 echo same old same old >same2.t &&
375 test_tick &&
376 git commit --fixup HEAD same2.t &&
377 fixup="$(git rev-parse HEAD)" &&
378
379 test_hook post-rewrite <<-\EOF &&
380 cat >actual
381 EOF
382
383 test_tick &&
384 git rebase -i --autosquash -r HEAD^^^ &&
385 printf "%s %s\n%s %s\n%s %s\n%s %s\n" >expect $(git rev-parse \
386 $fixup^^2 HEAD^2 \
387 $fixup^^ HEAD^ \
388 $fixup^ HEAD \
389 $fixup HEAD) &&
390 test_cmp expect actual
391 '
392
393 test_expect_success 'refuse to merge ancestors of HEAD' '
394 echo "merge HEAD^" >script-from-scratch &&
395 test_config -C wt sequence.editor \""$PWD"/replace-editor.sh\" &&
396 before="$(git rev-parse HEAD)" &&
397 git rebase -i HEAD &&
398 test_cmp_rev HEAD $before
399 '
400
401 test_expect_success 'root commits' '
402 git checkout --orphan unrelated &&
403 test_commit --author "Parsnip <root@example.com>" second-root &&
404 test_commit third-root &&
405 cat >script-from-scratch <<-\EOF &&
406 pick third-root
407 label first-branch
408 reset [new root]
409 pick second-root
410 merge first-branch # Merge the 3rd root
411 EOF
412 test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
413 test_tick &&
414 git rebase -i --force-rebase --root -r &&
415 test "Parsnip" = "$(git show -s --format=%an HEAD^)" &&
416 test $(git rev-parse second-root^0) != $(git rev-parse HEAD^) &&
417 test $(git rev-parse second-root:second-root.t) = \
418 $(git rev-parse HEAD^:second-root.t) &&
419 test_cmp_graph HEAD <<-\EOF &&
420 * Merge the 3rd root
421 |\
422 | * third-root
423 * second-root
424 EOF
425
426 : fast forward if possible &&
427 before="$(git rev-parse --verify HEAD)" &&
428 test_might_fail git config --unset sequence.editor &&
429 test_tick &&
430 git rebase -i --root -r &&
431 test_cmp_rev HEAD $before
432 '
433
434 test_expect_success 'a "merge" into a root commit is a fast-forward' '
435 head=$(git rev-parse HEAD) &&
436 cat >script-from-scratch <<-EOF &&
437 reset [new root]
438 merge $head
439 EOF
440 test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
441 test_tick &&
442 git rebase -i -r HEAD^ &&
443 test_cmp_rev HEAD $head
444 '
445
446 test_expect_success 'A root commit can be a cousin, treat it that way' '
447 git checkout --orphan khnum &&
448 test_commit yama &&
449 git checkout -b asherah main &&
450 test_commit shamkat &&
451 git merge --allow-unrelated-histories khnum &&
452 test_tick &&
453 git rebase -f -r HEAD^ &&
454 test_cmp_rev ! HEAD^2 khnum &&
455 test_cmp_graph HEAD^.. <<-\EOF &&
456 * Merge branch '\''khnum'\'' into asherah
457 |\
458 | * yama
459 o shamkat
460 EOF
461 test_tick &&
462 git rebase --rebase-merges=rebase-cousins HEAD^ &&
463 test_cmp_graph HEAD^.. <<-\EOF
464 * Merge branch '\''khnum'\'' into asherah
465 |\
466 | * yama
467 |/
468 o shamkat
469 EOF
470 '
471
472 test_expect_success 'labels that are object IDs are rewritten' '
473 git checkout --detach B &&
474 test_commit I &&
475 third=$(git rev-parse HEAD) &&
476 git checkout -b labels main &&
477 git merge --no-commit $third &&
478 test_tick &&
479 git commit -m "Merge commit '\''$third'\'' into labels" &&
480 echo noop >script-from-scratch &&
481 test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
482 test_tick &&
483 git rebase -i -r A &&
484 test_grep "^label $third-" .git/ORIGINAL-TODO &&
485 test_grep ! "^label $third$" .git/ORIGINAL-TODO
486 '
487
488 test_expect_success 'octopus merges' '
489 git checkout -b three &&
490 test_commit before-octopus &&
491 test_commit three &&
492 git checkout -b two HEAD^ &&
493 test_commit two &&
494 git checkout -b one HEAD^ &&
495 test_commit one &&
496 test_tick &&
497 (GIT_AUTHOR_NAME="Hank" GIT_AUTHOR_EMAIL="hank@sea.world" \
498 git merge -m "Tüntenfüsch" two three) &&
499
500 : fast forward if possible &&
501 before="$(git rev-parse --verify HEAD)" &&
502 test_tick &&
503 git rebase -i -r HEAD^^ &&
504 test_cmp_rev HEAD $before &&
505
506 test_tick &&
507 git rebase -i --force-rebase -r HEAD^^ &&
508 test "Hank" = "$(git show -s --format=%an HEAD)" &&
509 test "$before" != $(git rev-parse HEAD) &&
510 # NOTE: do not quote this heredoc, Dash 0.5.13 has a bug with heredocs
511 # that contain multibyte chars.
512 test_cmp_graph HEAD^^.. <<-EOF
513 *-. Tüntenfüsch
514 |\\ \\
515 | | * three
516 | * | two
517 | |/
518 * / one
519 |/
520 o before-octopus
521 EOF
522 '
523
524 test_expect_success 'with --autosquash and --exec' '
525 git checkout -b with-exec H &&
526 echo Booh >B.t &&
527 test_tick &&
528 git commit --fixup B B.t &&
529 write_script show.sh <<-\EOF &&
530 subject="$(git show -s --format=%s HEAD)"
531 content="$(git diff HEAD^ HEAD | tail -n 1)"
532 echo "$subject: $content"
533 EOF
534 test_tick &&
535 git rebase -ir --autosquash --exec ./show.sh A >actual &&
536 test_grep "B: +Booh" actual &&
537 test_grep "E: +Booh" actual &&
538 test_grep "G: +G" actual
539 '
540
541 test_expect_success '--continue after resolving conflicts after a merge' '
542 git checkout -b already-has-g E &&
543 git cherry-pick E..G &&
544 test_commit H2 &&
545
546 git checkout -b conflicts-in-merge H &&
547 test_commit H2 H2.t conflicts H2-conflict &&
548 test_must_fail git rebase -r already-has-g &&
549 test_grep conflicts H2.t &&
550 echo resolved >H2.t &&
551 git add -u &&
552 git rebase --continue &&
553 test_must_fail git rev-parse --verify HEAD^2 &&
554 test_path_is_missing .git/MERGE_HEAD
555 '
556
557 test_expect_success '--rebase-merges with strategies' '
558 git checkout -b with-a-strategy F &&
559 test_tick &&
560 git merge -m "Merge conflicting-G" conflicting-G &&
561
562 : first, test with a merge strategy option &&
563 git rebase -ir -Xtheirs G &&
564 echo conflicting-G >expect &&
565 test_cmp expect G.t &&
566
567 : now, try with a merge strategy other than recursive &&
568 git reset --hard @{1} &&
569 write_script git-merge-override <<-\EOF &&
570 echo overridden$1 >>G.t
571 git add G.t
572 EOF
573 PATH="$PWD:$PATH" git rebase -ir -s override -Xxopt G &&
574 test_write_lines G overridden--xopt >expect &&
575 test_cmp expect G.t
576 '
577
578 test_expect_success '--rebase-merges with commit that can generate bad characters for filename' '
579 git checkout -b colon-in-label E &&
580 git merge -m "colon: this should work" G &&
581 git rebase --rebase-merges --force-rebase E
582 '
583
584 test_expect_success '--rebase-merges with message matched with onto label' '
585 git checkout -b onto-label E &&
586 git merge -m onto G &&
587 git rebase --rebase-merges --force-rebase E &&
588 test_cmp_graph <<-\EOF
589 * onto
590 |\
591 | * G
592 | * F
593 * | E
594 |\ \
595 | * | B
596 * | | D
597 | |/
598 |/|
599 * | C
600 |/
601 * A
602 EOF
603 '
604
605 test_expect_success 'progress shows the correct total' '
606 git checkout -b progress H &&
607 git rebase --rebase-merges --force-rebase --verbose A 2> err &&
608 # Expecting "Rebasing (N/14)" here, no bogus total number
609 grep "^Rebasing.*/14.$" err >progress &&
610 test_line_count = 14 progress
611 '
612
613 test_expect_success 'truncate label names' '
614 commit=$(git commit-tree -p HEAD^ -p HEAD -m "0123456789 我 123" HEAD^{tree}) &&
615 git merge --ff-only $commit &&
616
617 done="$(git rev-parse --git-path rebase-merge/done)" &&
618 git -c rebase.maxLabelLength=14 rebase --rebase-merges -x "cp \"$done\" out" --root &&
619 test_grep "label 0123456789-我$" out &&
620 git -c rebase.maxLabelLength=13 rebase --rebase-merges -x "cp \"$done\" out" --root &&
621 test_grep "label 0123456789-$" out
622 '
623
624 test_expect_success 'reword fast-forwarded empty merge commit' '
625 oid="$(git commit-tree -m "D1" -p A D^{tree})" &&
626 oid="$(git commit-tree -m "empty merge" -p D -p $oid D^{tree})" &&
627
628 write_script sequence-editor.sh <<-\EOF &&
629 sed /^merge/s/-C/-c/ "$1" >"$1.tmp"
630 mv "$1.tmp" "$1"
631 EOF
632
633 (
634 test_set_sequence_editor "$(pwd)/sequence-editor.sh" &&
635 GIT_EDITOR="echo edited >>" git rebase -i -r D $oid
636 ) &&
637 test_commit_message HEAD <<-\EOF
638 empty merge
639
640 edited
641 EOF
642 '
643
644 test_done