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