t/perf: avoid unnecessary test_export() recursion
[git] / t / t3430-rebase-merges.sh
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            (master)
16    \    \       /
17     \    F - G                (second)
18      \
19       Conflicting-G
20 '
21 . ./test-lib.sh
22 . "$TEST_DIRECTORY"/lib-rebase.sh
23 . "$TEST_DIRECTORY"/lib-log-graph.sh
24
25 test_cmp_graph () {
26         cat >expect &&
27         lib_test_cmp_graph --boundary --format=%s "$@"
28 }
29
30 test_expect_success 'setup' '
31         write_script replace-editor.sh <<-\EOF &&
32         mv "$1" "$(git rev-parse --git-path ORIGINAL-TODO)"
33         cp script-from-scratch "$1"
34         EOF
35
36         test_commit A &&
37         git checkout -b first &&
38         test_commit B &&
39         b=$(git rev-parse --short HEAD) &&
40         git checkout master &&
41         test_commit C &&
42         c=$(git rev-parse --short HEAD) &&
43         test_commit D &&
44         d=$(git rev-parse --short HEAD) &&
45         git merge --no-commit B &&
46         test_tick &&
47         git commit -m E &&
48         git tag -m E E &&
49         e=$(git rev-parse --short HEAD) &&
50         git checkout -b second C &&
51         test_commit F &&
52         f=$(git rev-parse --short HEAD) &&
53         test_commit G &&
54         g=$(git rev-parse --short HEAD) &&
55         git checkout master &&
56         git merge --no-commit G &&
57         test_tick &&
58         git commit -m H &&
59         h=$(git rev-parse --short HEAD) &&
60         git tag -m H H &&
61         git checkout A &&
62         test_commit conflicting-G G.t
63 '
64
65 test_expect_success 'create completely different structure' '
66         cat >script-from-scratch <<-\EOF &&
67         label onto
68
69         # onebranch
70         pick G
71         pick D
72         label onebranch
73
74         # second
75         reset onto
76         pick B
77         label second
78
79         reset onto
80         merge -C H second
81         merge onebranch # Merge the topic branch '\''onebranch'\''
82         EOF
83         test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
84         test_tick &&
85         git rebase -i -r A master &&
86         test_cmp_graph <<-\EOF
87         *   Merge the topic branch '\''onebranch'\''
88         |\
89         | * D
90         | * G
91         * |   H
92         |\ \
93         | |/
94         |/|
95         | * B
96         |/
97         * A
98         EOF
99 '
100
101 test_expect_success 'generate correct todo list' '
102         cat >expect <<-EOF &&
103         label onto
104
105         reset onto
106         pick $b B
107         label E
108
109         reset onto
110         pick $c C
111         label branch-point
112         pick $f F
113         pick $g G
114         label H
115
116         reset branch-point # C
117         pick $d D
118         merge -C $e E # E
119         merge -C $h H # H
120
121         EOF
122
123         grep -v "^#" <.git/ORIGINAL-TODO >output &&
124         test_cmp expect output
125 '
126
127 test_expect_success '`reset` refuses to overwrite untracked files' '
128         git checkout -b refuse-to-reset &&
129         test_commit dont-overwrite-untracked &&
130         git checkout @{-1} &&
131         : >dont-overwrite-untracked.t &&
132         echo "reset refs/tags/dont-overwrite-untracked" >script-from-scratch &&
133         test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
134         test_must_fail git rebase -ir HEAD &&
135         git rebase --abort
136 '
137
138 test_expect_success 'failed `merge -C` writes patch (may be rescheduled, too)' '
139         test_when_finished "test_might_fail git rebase --abort" &&
140         git checkout -b conflicting-merge A &&
141
142         : fail because of conflicting untracked file &&
143         >G.t &&
144         echo "merge -C H G" >script-from-scratch &&
145         test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
146         test_tick &&
147         test_must_fail git rebase -ir HEAD &&
148         grep "^merge -C .* G$" .git/rebase-merge/done &&
149         grep "^merge -C .* G$" .git/rebase-merge/git-rebase-todo &&
150         test_path_is_file .git/rebase-merge/patch &&
151
152         : fail because of merge conflict &&
153         rm G.t .git/rebase-merge/patch &&
154         git reset --hard conflicting-G &&
155         test_must_fail git rebase --continue &&
156         ! grep "^merge -C .* G$" .git/rebase-merge/git-rebase-todo &&
157         test_path_is_file .git/rebase-merge/patch
158 '
159
160 test_expect_success 'failed `merge <branch>` does not crash' '
161         test_when_finished "test_might_fail git rebase --abort" &&
162         git checkout conflicting-G &&
163
164         echo "merge G" >script-from-scratch &&
165         test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
166         test_tick &&
167         test_must_fail git rebase -ir HEAD &&
168         ! grep "^merge G$" .git/rebase-merge/git-rebase-todo &&
169         grep "^Merge branch ${SQ}G${SQ}$" .git/rebase-merge/message
170 '
171
172 test_expect_success 'fast-forward merge -c still rewords' '
173         git checkout -b fast-forward-merge-c H &&
174         (
175                 set_fake_editor &&
176                 FAKE_COMMIT_MESSAGE=edited \
177                         GIT_SEQUENCE_EDITOR="echo merge -c H G >" \
178                         git rebase -ir @^
179         ) &&
180         echo edited >expected &&
181         git log --pretty=format:%B -1 >actual &&
182         test_cmp expected actual
183 '
184
185 test_expect_success 'with a branch tip that was cherry-picked already' '
186         git checkout -b already-upstream master &&
187         base="$(git rev-parse --verify HEAD)" &&
188
189         test_commit A1 &&
190         test_commit A2 &&
191         git reset --hard $base &&
192         test_commit B1 &&
193         test_tick &&
194         git merge -m "Merge branch A" A2 &&
195
196         git checkout -b upstream-with-a2 $base &&
197         test_tick &&
198         git cherry-pick A2 &&
199
200         git checkout already-upstream &&
201         test_tick &&
202         git rebase -i -r upstream-with-a2 &&
203         test_cmp_graph upstream-with-a2.. <<-\EOF
204         *   Merge branch A
205         |\
206         | * A1
207         * | B1
208         |/
209         o A2
210         EOF
211 '
212
213 test_expect_success 'do not rebase cousins unless asked for' '
214         git checkout -b cousins master &&
215         before="$(git rev-parse --verify HEAD)" &&
216         test_tick &&
217         git rebase -r HEAD^ &&
218         test_cmp_rev HEAD $before &&
219         test_tick &&
220         git rebase --rebase-merges=rebase-cousins HEAD^ &&
221         test_cmp_graph HEAD^.. <<-\EOF
222         *   Merge the topic branch '\''onebranch'\''
223         |\
224         | * D
225         | * G
226         |/
227         o H
228         EOF
229 '
230
231 test_expect_success 'refs/rewritten/* is worktree-local' '
232         git worktree add wt &&
233         cat >wt/script-from-scratch <<-\EOF &&
234         label xyz
235         exec GIT_DIR=../.git git rev-parse --verify refs/rewritten/xyz >a || :
236         exec git rev-parse --verify refs/rewritten/xyz >b
237         EOF
238
239         test_config -C wt sequence.editor \""$PWD"/replace-editor.sh\" &&
240         git -C wt rebase -i HEAD &&
241         test_must_be_empty wt/a &&
242         test_cmp_rev HEAD "$(cat wt/b)"
243 '
244
245 test_expect_success '--abort cleans up refs/rewritten' '
246         git checkout -b abort-cleans-refs-rewritten H &&
247         GIT_SEQUENCE_EDITOR="echo break >>" git rebase -ir @^ &&
248         git rev-parse --verify refs/rewritten/onto &&
249         git rebase --abort &&
250         test_must_fail git rev-parse --verify refs/rewritten/onto
251 '
252
253 test_expect_success '--quit cleans up refs/rewritten' '
254         git checkout -b quit-cleans-refs-rewritten H &&
255         GIT_SEQUENCE_EDITOR="echo break >>" git rebase -ir @^ &&
256         git rev-parse --verify refs/rewritten/onto &&
257         git rebase --quit &&
258         test_must_fail git rev-parse --verify refs/rewritten/onto
259 '
260
261 test_expect_success 'post-rewrite hook and fixups work for merges' '
262         git checkout -b post-rewrite H &&
263         test_commit same1 &&
264         git reset --hard HEAD^ &&
265         test_commit same2 &&
266         git merge -m "to fix up" same1 &&
267         echo same old same old >same2.t &&
268         test_tick &&
269         git commit --fixup HEAD same2.t &&
270         fixup="$(git rev-parse HEAD)" &&
271
272         mkdir -p .git/hooks &&
273         test_when_finished "rm .git/hooks/post-rewrite" &&
274         echo "cat >actual" | write_script .git/hooks/post-rewrite &&
275
276         test_tick &&
277         git rebase -i --autosquash -r HEAD^^^ &&
278         printf "%s %s\n%s %s\n%s %s\n%s %s\n" >expect $(git rev-parse \
279                 $fixup^^2 HEAD^2 \
280                 $fixup^^ HEAD^ \
281                 $fixup^ HEAD \
282                 $fixup HEAD) &&
283         test_cmp expect actual
284 '
285
286 test_expect_success 'refuse to merge ancestors of HEAD' '
287         echo "merge HEAD^" >script-from-scratch &&
288         test_config -C wt sequence.editor \""$PWD"/replace-editor.sh\" &&
289         before="$(git rev-parse HEAD)" &&
290         git rebase -i HEAD &&
291         test_cmp_rev HEAD $before
292 '
293
294 test_expect_success 'root commits' '
295         git checkout --orphan unrelated &&
296         (GIT_AUTHOR_NAME="Parsnip" GIT_AUTHOR_EMAIL="root@example.com" \
297          test_commit second-root) &&
298         test_commit third-root &&
299         cat >script-from-scratch <<-\EOF &&
300         pick third-root
301         label first-branch
302         reset [new root]
303         pick second-root
304         merge first-branch # Merge the 3rd root
305         EOF
306         test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
307         test_tick &&
308         git rebase -i --force-rebase --root -r &&
309         test "Parsnip" = "$(git show -s --format=%an HEAD^)" &&
310         test $(git rev-parse second-root^0) != $(git rev-parse HEAD^) &&
311         test $(git rev-parse second-root:second-root.t) = \
312                 $(git rev-parse HEAD^:second-root.t) &&
313         test_cmp_graph HEAD <<-\EOF &&
314         *   Merge the 3rd root
315         |\
316         | * third-root
317         * second-root
318         EOF
319
320         : fast forward if possible &&
321         before="$(git rev-parse --verify HEAD)" &&
322         test_might_fail git config --unset sequence.editor &&
323         test_tick &&
324         git rebase -i --root -r &&
325         test_cmp_rev HEAD $before
326 '
327
328 test_expect_success 'a "merge" into a root commit is a fast-forward' '
329         head=$(git rev-parse HEAD) &&
330         cat >script-from-scratch <<-EOF &&
331         reset [new root]
332         merge $head
333         EOF
334         test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
335         test_tick &&
336         git rebase -i -r HEAD^ &&
337         test_cmp_rev HEAD $head
338 '
339
340 test_expect_success 'A root commit can be a cousin, treat it that way' '
341         git checkout --orphan khnum &&
342         test_commit yama &&
343         git checkout -b asherah master &&
344         test_commit shamkat &&
345         git merge --allow-unrelated-histories khnum &&
346         test_tick &&
347         git rebase -f -r HEAD^ &&
348         test_cmp_rev ! HEAD^2 khnum &&
349         test_cmp_graph HEAD^.. <<-\EOF &&
350         *   Merge branch '\''khnum'\'' into asherah
351         |\
352         | * yama
353         o shamkat
354         EOF
355         test_tick &&
356         git rebase --rebase-merges=rebase-cousins HEAD^ &&
357         test_cmp_graph HEAD^.. <<-\EOF
358         *   Merge branch '\''khnum'\'' into asherah
359         |\
360         | * yama
361         |/
362         o shamkat
363         EOF
364 '
365
366 test_expect_success 'labels that are object IDs are rewritten' '
367         git checkout -b third B &&
368         test_commit I &&
369         third=$(git rev-parse HEAD) &&
370         git checkout -b labels master &&
371         git merge --no-commit third &&
372         test_tick &&
373         git commit -m "Merge commit '\''$third'\'' into labels" &&
374         echo noop >script-from-scratch &&
375         test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
376         test_tick &&
377         git rebase -i -r A &&
378         grep "^label $third-" .git/ORIGINAL-TODO &&
379         ! grep "^label $third$" .git/ORIGINAL-TODO
380 '
381
382 test_expect_success 'octopus merges' '
383         git checkout -b three &&
384         test_commit before-octopus &&
385         test_commit three &&
386         git checkout -b two HEAD^ &&
387         test_commit two &&
388         git checkout -b one HEAD^ &&
389         test_commit one &&
390         test_tick &&
391         (GIT_AUTHOR_NAME="Hank" GIT_AUTHOR_EMAIL="hank@sea.world" \
392          git merge -m "Tüntenfüsch" two three) &&
393
394         : fast forward if possible &&
395         before="$(git rev-parse --verify HEAD)" &&
396         test_tick &&
397         git rebase -i -r HEAD^^ &&
398         test_cmp_rev HEAD $before &&
399
400         test_tick &&
401         git rebase -i --force-rebase -r HEAD^^ &&
402         test "Hank" = "$(git show -s --format=%an HEAD)" &&
403         test "$before" != $(git rev-parse HEAD) &&
404         test_cmp_graph HEAD^^.. <<-\EOF
405         *-.   Tüntenfüsch
406         |\ \
407         | | * three
408         | * | two
409         | |/
410         * / one
411         |/
412         o before-octopus
413         EOF
414 '
415
416 test_expect_success 'with --autosquash and --exec' '
417         git checkout -b with-exec H &&
418         echo Booh >B.t &&
419         test_tick &&
420         git commit --fixup B B.t &&
421         write_script show.sh <<-\EOF &&
422         subject="$(git show -s --format=%s HEAD)"
423         content="$(git diff HEAD^ HEAD | tail -n 1)"
424         echo "$subject: $content"
425         EOF
426         test_tick &&
427         git rebase -ir --autosquash --exec ./show.sh A >actual &&
428         grep "B: +Booh" actual &&
429         grep "E: +Booh" actual &&
430         grep "G: +G" actual
431 '
432
433 test_expect_success '--continue after resolving conflicts after a merge' '
434         git checkout -b already-has-g E &&
435         git cherry-pick E..G &&
436         test_commit H2 &&
437
438         git checkout -b conflicts-in-merge H &&
439         test_commit H2 H2.t conflicts H2-conflict &&
440         test_must_fail git rebase -r already-has-g &&
441         grep conflicts H2.t &&
442         echo resolved >H2.t &&
443         git add -u &&
444         git rebase --continue &&
445         test_must_fail git rev-parse --verify HEAD^2 &&
446         test_path_is_missing .git/MERGE_HEAD
447 '
448
449 test_expect_success '--rebase-merges with strategies' '
450         git checkout -b with-a-strategy F &&
451         test_tick &&
452         git merge -m "Merge conflicting-G" conflicting-G &&
453
454         : first, test with a merge strategy option &&
455         git rebase -ir -Xtheirs G &&
456         echo conflicting-G >expect &&
457         test_cmp expect G.t &&
458
459         : now, try with a merge strategy other than recursive &&
460         git reset --hard @{1} &&
461         write_script git-merge-override <<-\EOF &&
462         echo overridden$1 >>G.t
463         git add G.t
464         EOF
465         PATH="$PWD:$PATH" git rebase -ir -s override -Xxopt G &&
466         test_write_lines G overridden--xopt >expect &&
467         test_cmp expect G.t
468 '
469
470 test_expect_success '--rebase-merges with commit that can generate bad characters for filename' '
471         git checkout -b colon-in-label E &&
472         git merge -m "colon: this should work" G &&
473         git rebase --rebase-merges --force-rebase E
474 '
475
476 test_expect_success '--rebase-merges with message matched with onto label' '
477         git checkout -b onto-label E &&
478         git merge -m onto G &&
479         git rebase --rebase-merges --force-rebase E &&
480         test_cmp_graph <<-\EOF
481         *   onto
482         |\
483         | * G
484         | * F
485         * |   E
486         |\ \
487         | * | B
488         * | | D
489         | |/
490         |/|
491         * | C
492         |/
493         * A
494         EOF
495 '
496
497 test_done