1#!/bin/sh
2#
3# Copyright (c) 2018 Johannes E. Schindelin
4#
5
6test_description='git rebase -i --rebase-merges
7
8This test runs git rebase "interactively", retaining the branch structure by
9recreating merge commits.
10
11Initial 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
24test_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
31test_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
66test_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
102test_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
128test_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
139test_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
161SQ="'"
162test_expect_success 'failed `merge <branch>` does not crash' '
163 test_when_finished "test_might_fail git rebase --abort" &&
164 git checkout conflicting-G &&
165
166 echo "merge G" >script-from-scratch &&
167 test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
168 test_tick &&
169 test_must_fail git rebase -ir HEAD &&
170 ! grep "^merge G$" .git/rebase-merge/git-rebase-todo &&
171 grep "^Merge branch ${SQ}G${SQ}$" .git/rebase-merge/message
172'
173
174test_expect_success 'fast-forward merge -c still rewords' '
175 git checkout -b fast-forward-merge-c H &&
176 (
177 set_fake_editor &&
178 FAKE_COMMIT_MESSAGE=edited \
179 GIT_SEQUENCE_EDITOR="echo merge -c H G >" \
180 git rebase -ir @^
181 ) &&
182 echo edited >expected &&
183 git log --pretty=format:%B -1 >actual &&
184 test_cmp expected actual
185'
186
187test_expect_success 'with a branch tip that was cherry-picked already' '
188 git checkout -b already-upstream master &&
189 base="$(git rev-parse --verify HEAD)" &&
190
191 test_commit A1 &&
192 test_commit A2 &&
193 git reset --hard $base &&
194 test_commit B1 &&
195 test_tick &&
196 git merge -m "Merge branch A" A2 &&
197
198 git checkout -b upstream-with-a2 $base &&
199 test_tick &&
200 git cherry-pick A2 &&
201
202 git checkout already-upstream &&
203 test_tick &&
204 git rebase -i -r upstream-with-a2 &&
205 test_cmp_graph upstream-with-a2.. <<-\EOF
206 * Merge branch A
207 |\
208 | * A1
209 * | B1
210 |/
211 o A2
212 EOF
213'
214
215test_expect_success 'do not rebase cousins unless asked for' '
216 git checkout -b cousins master &&
217 before="$(git rev-parse --verify HEAD)" &&
218 test_tick &&
219 git rebase -r HEAD^ &&
220 test_cmp_rev HEAD $before &&
221 test_tick &&
222 git rebase --rebase-merges=rebase-cousins HEAD^ &&
223 test_cmp_graph HEAD^.. <<-\EOF
224 * Merge the topic branch '\''onebranch'\''
225 |\
226 | * D
227 | * G
228 |/
229 o H
230 EOF
231'
232
233test_expect_success 'refs/rewritten/* is worktree-local' '
234 git worktree add wt &&
235 cat >wt/script-from-scratch <<-\EOF &&
236 label xyz
237 exec GIT_DIR=../.git git rev-parse --verify refs/rewritten/xyz >a || :
238 exec git rev-parse --verify refs/rewritten/xyz >b
239 EOF
240
241 test_config -C wt sequence.editor \""$PWD"/replace-editor.sh\" &&
242 git -C wt rebase -i HEAD &&
243 test_must_be_empty wt/a &&
244 test_cmp_rev HEAD "$(cat wt/b)"
245'
246
247test_expect_success '--abort cleans up refs/rewritten' '
248 git checkout -b abort-cleans-refs-rewritten H &&
249 GIT_SEQUENCE_EDITOR="echo break >>" git rebase -ir @^ &&
250 git rev-parse --verify refs/rewritten/onto &&
251 git rebase --abort &&
252 test_must_fail git rev-parse --verify refs/rewritten/onto
253'
254
255test_expect_success '--quit cleans up refs/rewritten' '
256 git checkout -b quit-cleans-refs-rewritten H &&
257 GIT_SEQUENCE_EDITOR="echo break >>" git rebase -ir @^ &&
258 git rev-parse --verify refs/rewritten/onto &&
259 git rebase --quit &&
260 test_must_fail git rev-parse --verify refs/rewritten/onto
261'
262
263test_expect_success 'post-rewrite hook and fixups work for merges' '
264 git checkout -b post-rewrite H &&
265 test_commit same1 &&
266 git reset --hard HEAD^ &&
267 test_commit same2 &&
268 git merge -m "to fix up" same1 &&
269 echo same old same old >same2.t &&
270 test_tick &&
271 git commit --fixup HEAD same2.t &&
272 fixup="$(git rev-parse HEAD)" &&
273
274 mkdir -p .git/hooks &&
275 test_when_finished "rm .git/hooks/post-rewrite" &&
276 echo "cat >actual" | write_script .git/hooks/post-rewrite &&
277
278 test_tick &&
279 git rebase -i --autosquash -r HEAD^^^ &&
280 printf "%s %s\n%s %s\n%s %s\n%s %s\n" >expect $(git rev-parse \
281 $fixup^^2 HEAD^2 \
282 $fixup^^ HEAD^ \
283 $fixup^ HEAD \
284 $fixup HEAD) &&
285 test_cmp expect actual
286'
287
288test_expect_success 'refuse to merge ancestors of HEAD' '
289 echo "merge HEAD^" >script-from-scratch &&
290 test_config -C wt sequence.editor \""$PWD"/replace-editor.sh\" &&
291 before="$(git rev-parse HEAD)" &&
292 git rebase -i HEAD &&
293 test_cmp_rev HEAD $before
294'
295
296test_expect_success 'root commits' '
297 git checkout --orphan unrelated &&
298 (GIT_AUTHOR_NAME="Parsnip" GIT_AUTHOR_EMAIL="root@example.com" \
299 test_commit second-root) &&
300 test_commit third-root &&
301 cat >script-from-scratch <<-\EOF &&
302 pick third-root
303 label first-branch
304 reset [new root]
305 pick second-root
306 merge first-branch # Merge the 3rd root
307 EOF
308 test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
309 test_tick &&
310 git rebase -i --force-rebase --root -r &&
311 test "Parsnip" = "$(git show -s --format=%an HEAD^)" &&
312 test $(git rev-parse second-root^0) != $(git rev-parse HEAD^) &&
313 test $(git rev-parse second-root:second-root.t) = \
314 $(git rev-parse HEAD^:second-root.t) &&
315 test_cmp_graph HEAD <<-\EOF &&
316 * Merge the 3rd root
317 |\
318 | * third-root
319 * second-root
320 EOF
321
322 : fast forward if possible &&
323 before="$(git rev-parse --verify HEAD)" &&
324 test_might_fail git config --unset sequence.editor &&
325 test_tick &&
326 git rebase -i --root -r &&
327 test_cmp_rev HEAD $before
328'
329
330test_expect_success 'a "merge" into a root commit is a fast-forward' '
331 head=$(git rev-parse HEAD) &&
332 cat >script-from-scratch <<-EOF &&
333 reset [new root]
334 merge $head
335 EOF
336 test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
337 test_tick &&
338 git rebase -i -r HEAD^ &&
339 test_cmp_rev HEAD $head
340'
341
342test_expect_success 'A root commit can be a cousin, treat it that way' '
343 git checkout --orphan khnum &&
344 test_commit yama &&
345 git checkout -b asherah master &&
346 test_commit shamkat &&
347 git merge --allow-unrelated-histories khnum &&
348 test_tick &&
349 git rebase -f -r HEAD^ &&
350 ! test_cmp_rev HEAD^2 khnum &&
351 test_cmp_graph HEAD^.. <<-\EOF &&
352 * Merge branch '\''khnum'\'' into asherah
353 |\
354 | * yama
355 o shamkat
356 EOF
357 test_tick &&
358 git rebase --rebase-merges=rebase-cousins HEAD^ &&
359 test_cmp_graph HEAD^.. <<-\EOF
360 * Merge branch '\''khnum'\'' into asherah
361 |\
362 | * yama
363 |/
364 o shamkat
365 EOF
366'
367
368test_expect_success 'labels that are object IDs are rewritten' '
369 git checkout -b third B &&
370 test_commit I &&
371 third=$(git rev-parse HEAD) &&
372 git checkout -b labels master &&
373 git merge --no-commit third &&
374 test_tick &&
375 git commit -m "Merge commit '\''$third'\'' into labels" &&
376 echo noop >script-from-scratch &&
377 test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
378 test_tick &&
379 git rebase -i -r A &&
380 grep "^label $third-" .git/ORIGINAL-TODO &&
381 ! grep "^label $third$" .git/ORIGINAL-TODO
382'
383
384test_expect_success 'octopus merges' '
385 git checkout -b three &&
386 test_commit before-octopus &&
387 test_commit three &&
388 git checkout -b two HEAD^ &&
389 test_commit two &&
390 git checkout -b one HEAD^ &&
391 test_commit one &&
392 test_tick &&
393 (GIT_AUTHOR_NAME="Hank" GIT_AUTHOR_EMAIL="hank@sea.world" \
394 git merge -m "Tüntenfüsch" two three) &&
395
396 : fast forward if possible &&
397 before="$(git rev-parse --verify HEAD)" &&
398 test_tick &&
399 git rebase -i -r HEAD^^ &&
400 test_cmp_rev HEAD $before &&
401
402 test_tick &&
403 git rebase -i --force-rebase -r HEAD^^ &&
404 test "Hank" = "$(git show -s --format=%an HEAD)" &&
405 test "$before" != $(git rev-parse HEAD) &&
406 test_cmp_graph HEAD^^.. <<-\EOF
407 *-. Tüntenfüsch
408 |\ \
409 | | * three
410 | * | two
411 | |/
412 * | one
413 |/
414 o before-octopus
415 EOF
416'
417
418test_expect_success 'with --autosquash and --exec' '
419 git checkout -b with-exec H &&
420 echo Booh >B.t &&
421 test_tick &&
422 git commit --fixup B B.t &&
423 write_script show.sh <<-\EOF &&
424 subject="$(git show -s --format=%s HEAD)"
425 content="$(git diff HEAD^! | tail -n 1)"
426 echo "$subject: $content"
427 EOF
428 test_tick &&
429 git rebase -ir --autosquash --exec ./show.sh A >actual &&
430 grep "B: +Booh" actual &&
431 grep "E: +Booh" actual &&
432 grep "G: +G" actual
433'
434
435test_expect_success '--continue after resolving conflicts after a merge' '
436 git checkout -b already-has-g E &&
437 git cherry-pick E..G &&
438 test_commit H2 &&
439
440 git checkout -b conflicts-in-merge H &&
441 test_commit H2 H2.t conflicts H2-conflict &&
442 test_must_fail git rebase -r already-has-g &&
443 grep conflicts H2.t &&
444 echo resolved >H2.t &&
445 git add -u &&
446 git rebase --continue &&
447 test_must_fail git rev-parse --verify HEAD^2 &&
448 test_path_is_missing .git/MERGE_HEAD
449'
450
451test_expect_success '--rebase-merges with strategies' '
452 git checkout -b with-a-strategy F &&
453 test_tick &&
454 git merge -m "Merge conflicting-G" conflicting-G &&
455
456 : first, test with a merge strategy option &&
457 git rebase -ir -Xtheirs G &&
458 echo conflicting-G >expect &&
459 test_cmp expect G.t &&
460
461 : now, try with a merge strategy other than recursive &&
462 git reset --hard @{1} &&
463 write_script git-merge-override <<-\EOF &&
464 echo overridden$1 >>G.t
465 git add G.t
466 EOF
467 PATH="$PWD:$PATH" git rebase -ir -s override -Xxopt G &&
468 test_write_lines G overridden--xopt >expect &&
469 test_cmp expect G.t
470'
471
472test_done