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