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