about summary refs log tree commit diff
path: root/third_party/git/t/t3430-rebase-merges.sh
blob: a1bc3e20016b37492c4f351970a7d8f8da44d5e8 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
#!/bin/sh
#
# Copyright (c) 2018 Johannes E. Schindelin
#

test_description='git rebase -i --rebase-merges

This test runs git rebase "interactively", retaining the branch structure by
recreating merge commits.

Initial setup:

    -- B --                   (first)
   /       \
 A - C - D - E - H            (master)
   \    \       /
    \    F - G                (second)
     \
      Conflicting-G
'
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-rebase.sh
. "$TEST_DIRECTORY"/lib-log-graph.sh

test_cmp_graph () {
	cat >expect &&
	lib_test_cmp_graph --boundary --format=%s "$@"
}

test_expect_success 'setup' '
	write_script replace-editor.sh <<-\EOF &&
	mv "$1" "$(git rev-parse --git-path ORIGINAL-TODO)"
	cp script-from-scratch "$1"
	EOF

	test_commit A &&
	git checkout -b first &&
	test_commit B &&
	b=$(git rev-parse --short HEAD) &&
	git checkout master &&
	test_commit C &&
	c=$(git rev-parse --short HEAD) &&
	test_commit D &&
	d=$(git rev-parse --short HEAD) &&
	git merge --no-commit B &&
	test_tick &&
	git commit -m E &&
	git tag -m E E &&
	e=$(git rev-parse --short HEAD) &&
	git checkout -b second C &&
	test_commit F &&
	f=$(git rev-parse --short HEAD) &&
	test_commit G &&
	g=$(git rev-parse --short HEAD) &&
	git checkout master &&
	git merge --no-commit G &&
	test_tick &&
	git commit -m H &&
	h=$(git rev-parse --short HEAD) &&
	git tag -m H H &&
	git checkout A &&
	test_commit conflicting-G G.t
'

test_expect_success 'create completely different structure' '
	cat >script-from-scratch <<-\EOF &&
	label onto

	# onebranch
	pick G
	pick D
	label onebranch

	# second
	reset onto
	pick B
	label second

	reset onto
	merge -C H second
	merge onebranch # Merge the topic branch '\''onebranch'\''
	EOF
	test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
	test_tick &&
	git rebase -i -r A master &&
	test_cmp_graph <<-\EOF
	*   Merge the topic branch '\''onebranch'\''
	|\
	| * D
	| * G
	* |   H
	|\ \
	| |/
	|/|
	| * B
	|/
	* A
	EOF
'

test_expect_success 'generate correct todo list' '
	cat >expect <<-EOF &&
	label onto

	reset onto
	pick $b B
	label E

	reset onto
	pick $c C
	label branch-point
	pick $f F
	pick $g G
	label H

	reset branch-point # C
	pick $d D
	merge -C $e E # E
	merge -C $h H # H

	EOF

	grep -v "^#" <.git/ORIGINAL-TODO >output &&
	test_cmp expect output
'

test_expect_success '`reset` refuses to overwrite untracked files' '
	git checkout -b refuse-to-reset &&
	test_commit dont-overwrite-untracked &&
	git checkout @{-1} &&
	: >dont-overwrite-untracked.t &&
	echo "reset refs/tags/dont-overwrite-untracked" >script-from-scratch &&
	test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
	test_must_fail git rebase -ir HEAD &&
	git rebase --abort
'

test_expect_success 'failed `merge -C` writes patch (may be rescheduled, too)' '
	test_when_finished "test_might_fail git rebase --abort" &&
	git checkout -b conflicting-merge A &&

	: fail because of conflicting untracked file &&
	>G.t &&
	echo "merge -C H G" >script-from-scratch &&
	test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
	test_tick &&
	test_must_fail git rebase -ir HEAD &&
	grep "^merge -C .* G$" .git/rebase-merge/done &&
	grep "^merge -C .* G$" .git/rebase-merge/git-rebase-todo &&
	test_path_is_file .git/rebase-merge/patch &&

	: fail because of merge conflict &&
	rm G.t .git/rebase-merge/patch &&
	git reset --hard conflicting-G &&
	test_must_fail git rebase --continue &&
	! grep "^merge -C .* G$" .git/rebase-merge/git-rebase-todo &&
	test_path_is_file .git/rebase-merge/patch
'

test_expect_success 'failed `merge <branch>` does not crash' '
	test_when_finished "test_might_fail git rebase --abort" &&
	git checkout conflicting-G &&

	echo "merge G" >script-from-scratch &&
	test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
	test_tick &&
	test_must_fail git rebase -ir HEAD &&
	! grep "^merge G$" .git/rebase-merge/git-rebase-todo &&
	grep "^Merge branch ${SQ}G${SQ}$" .git/rebase-merge/message
'

test_expect_success 'fast-forward merge -c still rewords' '
	git checkout -b fast-forward-merge-c H &&
	(
		set_fake_editor &&
		FAKE_COMMIT_MESSAGE=edited \
			GIT_SEQUENCE_EDITOR="echo merge -c H G >" \
			git rebase -ir @^
	) &&
	echo edited >expected &&
	git log --pretty=format:%B -1 >actual &&
	test_cmp expected actual
'

test_expect_success 'with a branch tip that was cherry-picked already' '
	git checkout -b already-upstream master &&
	base="$(git rev-parse --verify HEAD)" &&

	test_commit A1 &&
	test_commit A2 &&
	git reset --hard $base &&
	test_commit B1 &&
	test_tick &&
	git merge -m "Merge branch A" A2 &&

	git checkout -b upstream-with-a2 $base &&
	test_tick &&
	git cherry-pick A2 &&

	git checkout already-upstream &&
	test_tick &&
	git rebase -i -r upstream-with-a2 &&
	test_cmp_graph upstream-with-a2.. <<-\EOF
	*   Merge branch A
	|\
	| * A1
	* | B1
	|/
	o A2
	EOF
'

test_expect_success 'do not rebase cousins unless asked for' '
	git checkout -b cousins master &&
	before="$(git rev-parse --verify HEAD)" &&
	test_tick &&
	git rebase -r HEAD^ &&
	test_cmp_rev HEAD $before &&
	test_tick &&
	git rebase --rebase-merges=rebase-cousins HEAD^ &&
	test_cmp_graph HEAD^.. <<-\EOF
	*   Merge the topic branch '\''onebranch'\''
	|\
	| * D
	| * G
	|/
	o H
	EOF
'

test_expect_success 'refs/rewritten/* is worktree-local' '
	git worktree add wt &&
	cat >wt/script-from-scratch <<-\EOF &&
	label xyz
	exec GIT_DIR=../.git git rev-parse --verify refs/rewritten/xyz >a || :
	exec git rev-parse --verify refs/rewritten/xyz >b
	EOF

	test_config -C wt sequence.editor \""$PWD"/replace-editor.sh\" &&
	git -C wt rebase -i HEAD &&
	test_must_be_empty wt/a &&
	test_cmp_rev HEAD "$(cat wt/b)"
'

test_expect_success '--abort cleans up refs/rewritten' '
	git checkout -b abort-cleans-refs-rewritten H &&
	GIT_SEQUENCE_EDITOR="echo break >>" git rebase -ir @^ &&
	git rev-parse --verify refs/rewritten/onto &&
	git rebase --abort &&
	test_must_fail git rev-parse --verify refs/rewritten/onto
'

test_expect_success '--quit cleans up refs/rewritten' '
	git checkout -b quit-cleans-refs-rewritten H &&
	GIT_SEQUENCE_EDITOR="echo break >>" git rebase -ir @^ &&
	git rev-parse --verify refs/rewritten/onto &&
	git rebase --quit &&
	test_must_fail git rev-parse --verify refs/rewritten/onto
'

test_expect_success 'post-rewrite hook and fixups work for merges' '
	git checkout -b post-rewrite H &&
	test_commit same1 &&
	git reset --hard HEAD^ &&
	test_commit same2 &&
	git merge -m "to fix up" same1 &&
	echo same old same old >same2.t &&
	test_tick &&
	git commit --fixup HEAD same2.t &&
	fixup="$(git rev-parse HEAD)" &&

	mkdir -p .git/hooks &&
	test_when_finished "rm .git/hooks/post-rewrite" &&
	echo "cat >actual" | write_script .git/hooks/post-rewrite &&

	test_tick &&
	git rebase -i --autosquash -r HEAD^^^ &&
	printf "%s %s\n%s %s\n%s %s\n%s %s\n" >expect $(git rev-parse \
		$fixup^^2 HEAD^2 \
		$fixup^^ HEAD^ \
		$fixup^ HEAD \
		$fixup HEAD) &&
	test_cmp expect actual
'

test_expect_success 'refuse to merge ancestors of HEAD' '
	echo "merge HEAD^" >script-from-scratch &&
	test_config -C wt sequence.editor \""$PWD"/replace-editor.sh\" &&
	before="$(git rev-parse HEAD)" &&
	git rebase -i HEAD &&
	test_cmp_rev HEAD $before
'

test_expect_success 'root commits' '
	git checkout --orphan unrelated &&
	(GIT_AUTHOR_NAME="Parsnip" GIT_AUTHOR_EMAIL="root@example.com" \
	 test_commit second-root) &&
	test_commit third-root &&
	cat >script-from-scratch <<-\EOF &&
	pick third-root
	label first-branch
	reset [new root]
	pick second-root
	merge first-branch # Merge the 3rd root
	EOF
	test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
	test_tick &&
	git rebase -i --force-rebase --root -r &&
	test "Parsnip" = "$(git show -s --format=%an HEAD^)" &&
	test $(git rev-parse second-root^0) != $(git rev-parse HEAD^) &&
	test $(git rev-parse second-root:second-root.t) = \
		$(git rev-parse HEAD^:second-root.t) &&
	test_cmp_graph HEAD <<-\EOF &&
	*   Merge the 3rd root
	|\
	| * third-root
	* second-root
	EOF

	: fast forward if possible &&
	before="$(git rev-parse --verify HEAD)" &&
	test_might_fail git config --unset sequence.editor &&
	test_tick &&
	git rebase -i --root -r &&
	test_cmp_rev HEAD $before
'

test_expect_success 'a "merge" into a root commit is a fast-forward' '
	head=$(git rev-parse HEAD) &&
	cat >script-from-scratch <<-EOF &&
	reset [new root]
	merge $head
	EOF
	test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
	test_tick &&
	git rebase -i -r HEAD^ &&
	test_cmp_rev HEAD $head
'

test_expect_success 'A root commit can be a cousin, treat it that way' '
	git checkout --orphan khnum &&
	test_commit yama &&
	git checkout -b asherah master &&
	test_commit shamkat &&
	git merge --allow-unrelated-histories khnum &&
	test_tick &&
	git rebase -f -r HEAD^ &&
	test_cmp_rev ! HEAD^2 khnum &&
	test_cmp_graph HEAD^.. <<-\EOF &&
	*   Merge branch '\''khnum'\'' into asherah
	|\
	| * yama
	o shamkat
	EOF
	test_tick &&
	git rebase --rebase-merges=rebase-cousins HEAD^ &&
	test_cmp_graph HEAD^.. <<-\EOF
	*   Merge branch '\''khnum'\'' into asherah
	|\
	| * yama
	|/
	o shamkat
	EOF
'

test_expect_success 'labels that are object IDs are rewritten' '
	git checkout -b third B &&
	test_commit I &&
	third=$(git rev-parse HEAD) &&
	git checkout -b labels master &&
	git merge --no-commit third &&
	test_tick &&
	git commit -m "Merge commit '\''$third'\'' into labels" &&
	echo noop >script-from-scratch &&
	test_config sequence.editor \""$PWD"/replace-editor.sh\" &&
	test_tick &&
	git rebase -i -r A &&
	grep "^label $third-" .git/ORIGINAL-TODO &&
	! grep "^label $third$" .git/ORIGINAL-TODO
'

test_expect_success 'octopus merges' '
	git checkout -b three &&
	test_commit before-octopus &&
	test_commit three &&
	git checkout -b two HEAD^ &&
	test_commit two &&
	git checkout -b one HEAD^ &&
	test_commit one &&
	test_tick &&
	(GIT_AUTHOR_NAME="Hank" GIT_AUTHOR_EMAIL="hank@sea.world" \
	 git merge -m "Tüntenfüsch" two three) &&

	: fast forward if possible &&
	before="$(git rev-parse --verify HEAD)" &&
	test_tick &&
	git rebase -i -r HEAD^^ &&
	test_cmp_rev HEAD $before &&

	test_tick &&
	git rebase -i --force-rebase -r HEAD^^ &&
	test "Hank" = "$(git show -s --format=%an HEAD)" &&
	test "$before" != $(git rev-parse HEAD) &&
	test_cmp_graph HEAD^^.. <<-\EOF
	*-.   Tüntenfüsch
	|\ \
	| | * three
	| * | two
	| |/
	* / one
	|/
	o before-octopus
	EOF
'

test_expect_success 'with --autosquash and --exec' '
	git checkout -b with-exec H &&
	echo Booh >B.t &&
	test_tick &&
	git commit --fixup B B.t &&
	write_script show.sh <<-\EOF &&
	subject="$(git show -s --format=%s HEAD)"
	content="$(git diff HEAD^! | tail -n 1)"
	echo "$subject: $content"
	EOF
	test_tick &&
	git rebase -ir --autosquash --exec ./show.sh A >actual &&
	grep "B: +Booh" actual &&
	grep "E: +Booh" actual &&
	grep "G: +G" actual
'

test_expect_success '--continue after resolving conflicts after a merge' '
	git checkout -b already-has-g E &&
	git cherry-pick E..G &&
	test_commit H2 &&

	git checkout -b conflicts-in-merge H &&
	test_commit H2 H2.t conflicts H2-conflict &&
	test_must_fail git rebase -r already-has-g &&
	grep conflicts H2.t &&
	echo resolved >H2.t &&
	git add -u &&
	git rebase --continue &&
	test_must_fail git rev-parse --verify HEAD^2 &&
	test_path_is_missing .git/MERGE_HEAD
'

test_expect_success '--rebase-merges with strategies' '
	git checkout -b with-a-strategy F &&
	test_tick &&
	git merge -m "Merge conflicting-G" conflicting-G &&

	: first, test with a merge strategy option &&
	git rebase -ir -Xtheirs G &&
	echo conflicting-G >expect &&
	test_cmp expect G.t &&

	: now, try with a merge strategy other than recursive &&
	git reset --hard @{1} &&
	write_script git-merge-override <<-\EOF &&
	echo overridden$1 >>G.t
	git add G.t
	EOF
	PATH="$PWD:$PATH" git rebase -ir -s override -Xxopt G &&
	test_write_lines G overridden--xopt >expect &&
	test_cmp expect G.t
'

test_expect_success '--rebase-merges with commit that can generate bad characters for filename' '
	git checkout -b colon-in-label E &&
	git merge -m "colon: this should work" G &&
	git rebase --rebase-merges --force-rebase E
'

test_expect_success '--rebase-merges with message matched with onto label' '
	git checkout -b onto-label E &&
	git merge -m onto G &&
	git rebase --rebase-merges --force-rebase E &&
	test_cmp_graph <<-\EOF
	*   onto
	|\
	| * G
	| * F
	* |   E
	|\ \
	| * | B
	* | | D
	| |/
	|/|
	* | C
	|/
	* A
	EOF
'

test_done