rebase: use correct base for --keep-base when a branch is given
[alt-git.git] / t / t3430-rebase-merges.sh
blobf351701fec281aa6a88e9ee7185101639c8bbcd6
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 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 &&
138 git rebase --abort
141 test_expect_success 'failed `merge -C` writes patch (may be rescheduled, too)' '
142 test_when_finished "test_might_fail git rebase --abort" &&
143 git checkout -b conflicting-merge A &&
145 : fail because of conflicting untracked file &&
146 >G.t &&
147 echo "merge -C H G" >script-from-scratch &&
148 test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
149 test_tick &&
150 test_must_fail git rebase -ir HEAD &&
151 grep "^merge -C .* G$" .git/rebase-merge/done &&
152 grep "^merge -C .* G$" .git/rebase-merge/git-rebase-todo &&
153 test_path_is_file .git/rebase-merge/patch &&
155 : fail because of merge conflict &&
156 rm G.t .git/rebase-merge/patch &&
157 git reset --hard conflicting-G &&
158 test_must_fail git rebase --continue &&
159 ! grep "^merge -C .* G$" .git/rebase-merge/git-rebase-todo &&
160 test_path_is_file .git/rebase-merge/patch
163 test_expect_success 'failed `merge <branch>` does not crash' '
164 test_when_finished "test_might_fail git rebase --abort" &&
165 git checkout conflicting-G &&
167 echo "merge G" >script-from-scratch &&
168 test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
169 test_tick &&
170 test_must_fail git rebase -ir HEAD &&
171 ! grep "^merge G$" .git/rebase-merge/git-rebase-todo &&
172 grep "^Merge branch ${SQ}G${SQ}$" .git/rebase-merge/message
175 test_expect_success 'merge -c commits before rewording and reloads todo-list' '
176 cat >script-from-scratch <<-\EOF &&
177 merge -c E B
178 merge -c H G
181 git checkout -b merge-c H &&
183 set_reword_editor &&
184 GIT_SEQUENCE_EDITOR="\"$PWD/replace-editor.sh\"" \
185 git rebase -i -r D
186 ) &&
187 check_reworded_commits E H
190 test_expect_success 'merge -c rewords when a strategy is given' '
191 git checkout -b merge-c-with-strategy H &&
192 write_script git-merge-override <<-\EOF &&
193 echo overridden$1 >G.t
194 git add G.t
197 PATH="$PWD:$PATH" \
198 GIT_SEQUENCE_EDITOR="echo merge -c H G >" \
199 GIT_EDITOR="echo edited >>" \
200 git rebase --no-ff -ir -s override -Xxopt E &&
201 test_write_lines overridden--xopt >expect &&
202 test_cmp expect G.t &&
203 test_write_lines H "" edited "" >expect &&
204 git log --format=%B -1 >actual &&
205 test_cmp expect actual
208 test_expect_success 'with a branch tip that was cherry-picked already' '
209 git checkout -b already-upstream main &&
210 base="$(git rev-parse --verify HEAD)" &&
212 test_commit A1 &&
213 test_commit A2 &&
214 git reset --hard $base &&
215 test_commit B1 &&
216 test_tick &&
217 git merge -m "Merge branch A" A2 &&
219 git checkout -b upstream-with-a2 $base &&
220 test_tick &&
221 git cherry-pick A2 &&
223 git checkout already-upstream &&
224 test_tick &&
225 git rebase -i -r upstream-with-a2 &&
226 test_cmp_graph upstream-with-a2.. <<-\EOF
227 * Merge branch A
229 | * A1
230 * | B1
232 o A2
236 test_expect_success 'do not rebase cousins unless asked for' '
237 git checkout -b cousins main &&
238 before="$(git rev-parse --verify HEAD)" &&
239 test_tick &&
240 git rebase -r HEAD^ &&
241 test_cmp_rev HEAD $before &&
242 test_tick &&
243 git rebase --rebase-merges=rebase-cousins HEAD^ &&
244 test_cmp_graph HEAD^.. <<-\EOF
245 * Merge the topic branch '\''onebranch'\''
247 | * D
248 | * G
254 test_expect_success 'refs/rewritten/* is worktree-local' '
255 git worktree add wt &&
256 cat >wt/script-from-scratch <<-\EOF &&
257 label xyz
258 exec GIT_DIR=../.git git rev-parse --verify refs/rewritten/xyz >a || :
259 exec git rev-parse --verify refs/rewritten/xyz >b
262 test_config -C wt sequence.editor \""$PWD"/replace-editor.sh\" &&
263 git -C wt rebase -i HEAD &&
264 test_must_be_empty wt/a &&
265 test_cmp_rev HEAD "$(cat wt/b)"
268 test_expect_success '--abort cleans up refs/rewritten' '
269 git checkout -b abort-cleans-refs-rewritten H &&
270 GIT_SEQUENCE_EDITOR="echo break >>" git rebase -ir @^ &&
271 git rev-parse --verify refs/rewritten/onto &&
272 git rebase --abort &&
273 test_must_fail git rev-parse --verify refs/rewritten/onto
276 test_expect_success '--quit cleans up refs/rewritten' '
277 git checkout -b quit-cleans-refs-rewritten H &&
278 GIT_SEQUENCE_EDITOR="echo break >>" git rebase -ir @^ &&
279 git rev-parse --verify refs/rewritten/onto &&
280 git rebase --quit &&
281 test_must_fail git rev-parse --verify refs/rewritten/onto
284 test_expect_success 'post-rewrite hook and fixups work for merges' '
285 git checkout -b post-rewrite H &&
286 test_commit same1 &&
287 git reset --hard HEAD^ &&
288 test_commit same2 &&
289 git merge -m "to fix up" same1 &&
290 echo same old same old >same2.t &&
291 test_tick &&
292 git commit --fixup HEAD same2.t &&
293 fixup="$(git rev-parse HEAD)" &&
295 test_hook post-rewrite <<-\EOF &&
296 cat >actual
299 test_tick &&
300 git rebase -i --autosquash -r HEAD^^^ &&
301 printf "%s %s\n%s %s\n%s %s\n%s %s\n" >expect $(git rev-parse \
302 $fixup^^2 HEAD^2 \
303 $fixup^^ HEAD^ \
304 $fixup^ HEAD \
305 $fixup HEAD) &&
306 test_cmp expect actual
309 test_expect_success 'refuse to merge ancestors of HEAD' '
310 echo "merge HEAD^" >script-from-scratch &&
311 test_config -C wt sequence.editor \""$PWD"/replace-editor.sh\" &&
312 before="$(git rev-parse HEAD)" &&
313 git rebase -i HEAD &&
314 test_cmp_rev HEAD $before
317 test_expect_success 'root commits' '
318 git checkout --orphan unrelated &&
319 (GIT_AUTHOR_NAME="Parsnip" GIT_AUTHOR_EMAIL="root@example.com" \
320 test_commit second-root) &&
321 test_commit third-root &&
322 cat >script-from-scratch <<-\EOF &&
323 pick third-root
324 label first-branch
325 reset [new root]
326 pick second-root
327 merge first-branch # Merge the 3rd root
329 test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
330 test_tick &&
331 git rebase -i --force-rebase --root -r &&
332 test "Parsnip" = "$(git show -s --format=%an HEAD^)" &&
333 test $(git rev-parse second-root^0) != $(git rev-parse HEAD^) &&
334 test $(git rev-parse second-root:second-root.t) = \
335 $(git rev-parse HEAD^:second-root.t) &&
336 test_cmp_graph HEAD <<-\EOF &&
337 * Merge the 3rd root
339 | * third-root
340 * second-root
343 : fast forward if possible &&
344 before="$(git rev-parse --verify HEAD)" &&
345 test_might_fail git config --unset sequence.editor &&
346 test_tick &&
347 git rebase -i --root -r &&
348 test_cmp_rev HEAD $before
351 test_expect_success 'a "merge" into a root commit is a fast-forward' '
352 head=$(git rev-parse HEAD) &&
353 cat >script-from-scratch <<-EOF &&
354 reset [new root]
355 merge $head
357 test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
358 test_tick &&
359 git rebase -i -r HEAD^ &&
360 test_cmp_rev HEAD $head
363 test_expect_success 'A root commit can be a cousin, treat it that way' '
364 git checkout --orphan khnum &&
365 test_commit yama &&
366 git checkout -b asherah main &&
367 test_commit shamkat &&
368 git merge --allow-unrelated-histories khnum &&
369 test_tick &&
370 git rebase -f -r HEAD^ &&
371 test_cmp_rev ! HEAD^2 khnum &&
372 test_cmp_graph HEAD^.. <<-\EOF &&
373 * Merge branch '\''khnum'\'' into asherah
375 | * yama
376 o shamkat
378 test_tick &&
379 git rebase --rebase-merges=rebase-cousins HEAD^ &&
380 test_cmp_graph HEAD^.. <<-\EOF
381 * Merge branch '\''khnum'\'' into asherah
383 | * yama
385 o shamkat
389 test_expect_success 'labels that are object IDs are rewritten' '
390 git checkout -b third B &&
391 test_commit I &&
392 third=$(git rev-parse HEAD) &&
393 git checkout -b labels main &&
394 git merge --no-commit third &&
395 test_tick &&
396 git commit -m "Merge commit '\''$third'\'' into labels" &&
397 echo noop >script-from-scratch &&
398 test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
399 test_tick &&
400 git rebase -i -r A &&
401 grep "^label $third-" .git/ORIGINAL-TODO &&
402 ! grep "^label $third$" .git/ORIGINAL-TODO
405 test_expect_success 'octopus merges' '
406 git checkout -b three &&
407 test_commit before-octopus &&
408 test_commit three &&
409 git checkout -b two HEAD^ &&
410 test_commit two &&
411 git checkout -b one HEAD^ &&
412 test_commit one &&
413 test_tick &&
414 (GIT_AUTHOR_NAME="Hank" GIT_AUTHOR_EMAIL="hank@sea.world" \
415 git merge -m "Tüntenfüsch" two three) &&
417 : fast forward if possible &&
418 before="$(git rev-parse --verify HEAD)" &&
419 test_tick &&
420 git rebase -i -r HEAD^^ &&
421 test_cmp_rev HEAD $before &&
423 test_tick &&
424 git rebase -i --force-rebase -r HEAD^^ &&
425 test "Hank" = "$(git show -s --format=%an HEAD)" &&
426 test "$before" != $(git rev-parse HEAD) &&
427 test_cmp_graph HEAD^^.. <<-\EOF
428 *-. Tüntenfüsch
429 |\ \
430 | | * three
431 | * | two
432 | |/
433 * / one
435 o before-octopus
439 test_expect_success 'with --autosquash and --exec' '
440 git checkout -b with-exec H &&
441 echo Booh >B.t &&
442 test_tick &&
443 git commit --fixup B B.t &&
444 write_script show.sh <<-\EOF &&
445 subject="$(git show -s --format=%s HEAD)"
446 content="$(git diff HEAD^ HEAD | tail -n 1)"
447 echo "$subject: $content"
449 test_tick &&
450 git rebase -ir --autosquash --exec ./show.sh A >actual &&
451 grep "B: +Booh" actual &&
452 grep "E: +Booh" actual &&
453 grep "G: +G" actual
456 test_expect_success '--continue after resolving conflicts after a merge' '
457 git checkout -b already-has-g E &&
458 git cherry-pick E..G &&
459 test_commit H2 &&
461 git checkout -b conflicts-in-merge H &&
462 test_commit H2 H2.t conflicts H2-conflict &&
463 test_must_fail git rebase -r already-has-g &&
464 grep conflicts H2.t &&
465 echo resolved >H2.t &&
466 git add -u &&
467 git rebase --continue &&
468 test_must_fail git rev-parse --verify HEAD^2 &&
469 test_path_is_missing .git/MERGE_HEAD
472 test_expect_success '--rebase-merges with strategies' '
473 git checkout -b with-a-strategy F &&
474 test_tick &&
475 git merge -m "Merge conflicting-G" conflicting-G &&
477 : first, test with a merge strategy option &&
478 git rebase -ir -Xtheirs G &&
479 echo conflicting-G >expect &&
480 test_cmp expect G.t &&
482 : now, try with a merge strategy other than recursive &&
483 git reset --hard @{1} &&
484 write_script git-merge-override <<-\EOF &&
485 echo overridden$1 >>G.t
486 git add G.t
488 PATH="$PWD:$PATH" git rebase -ir -s override -Xxopt G &&
489 test_write_lines G overridden--xopt >expect &&
490 test_cmp expect G.t
493 test_expect_success '--rebase-merges with commit that can generate bad characters for filename' '
494 git checkout -b colon-in-label E &&
495 git merge -m "colon: this should work" G &&
496 git rebase --rebase-merges --force-rebase E
499 test_expect_success '--rebase-merges with message matched with onto label' '
500 git checkout -b onto-label E &&
501 git merge -m onto G &&
502 git rebase --rebase-merges --force-rebase E &&
503 test_cmp_graph <<-\EOF
504 * onto
506 | * G
507 | * F
508 * | E
509 |\ \
510 | * | B
511 * | | D
512 | |/
514 * | C
520 test_done