summaryrefslogtreecommitdiff
path: root/t/t6429-merge-sequence-rename-caching.sh
blob: 035edc40b1ebba46305fb555c903d8b44220f8de (plain)
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
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
#!/bin/sh

test_description="remember regular & dir renames in sequence of merges"

. ./test-lib.sh

#
# NOTE 1: this testfile tends to not only rename files, but modify on both
#         sides; without modifying on both sides, optimizations can kick in
#         which make rename detection irrelevant or trivial.  We want to make
#         sure that we are triggering rename caching rather than rename
#         bypassing.
#
# NOTE 2: this testfile uses 'test-tool fast-rebase' instead of either
#         cherry-pick or rebase.  sequencer.c is only superficially
#         integrated with merge-ort; it calls merge_switch_to_result()
#         after EACH merge, which updates the index and working copy AND
#         throws away the cached results (because merge_switch_to_result()
#         is only supposed to be called at the end of the sequence).
#         Integrating them more deeply is a big task, so for now the tests
#         use 'test-tool fast-rebase'.
#


#
# In the following simple testcase:
#   Base:     numbers_1, values_1
#   Upstream: numbers_2, values_2
#   Topic_1:  sequence_3
#   Topic_2:  scruples_3
# or, in english, rename numbers -> sequence in the first commit, and rename
# values -> scruples in the second commit.
#
# This shouldn't be a challenge, it's just verifying that cached renames isn't
# preventing us from finding new renames.
#
test_expect_success 'caching renames does not preclude finding new ones' '
	test_create_repo caching-renames-and-new-renames &&
	(
		cd caching-renames-and-new-renames &&

		test_seq 2 10 >numbers &&
		test_seq 2 10 >values &&
		git add numbers values &&
		git commit -m orig &&

		git branch upstream &&
		git branch topic &&

		git switch upstream &&
		test_seq 1 10 >numbers &&
		test_seq 1 10 >values &&
		git add numbers values &&
		git commit -m "Tweaked both files" &&

		git switch topic &&

		test_seq 2 12 >numbers &&
		git add numbers &&
		git mv numbers sequence &&
		git commit -m A &&

		test_seq 2 12 >values &&
		git add values &&
		git mv values scruples &&
		git commit -m B &&

		#
		# Actual testing
		#

		git switch upstream &&

		test-tool fast-rebase --onto HEAD upstream~1 topic &&
		#git cherry-pick upstream~1..topic

		git ls-files >tracked-files &&
		test_line_count = 2 tracked-files &&
		test_seq 1 12 >expect &&
		test_cmp expect sequence &&
		test_cmp expect scruples
	)
'

#
# In the following testcase:
#   Base:     numbers_1
#   Upstream: rename numbers_1 -> sequence_2
#   Topic_1:  numbers_3
#   Topic_2:  numbers_1
# or, in english, the first commit on the topic branch modifies numbers by
# shrinking it (dramatically) and the second commit on topic reverts its
# parent.
#
# Can git apply both patches?
#
# Traditional cherry-pick/rebase will fail to apply the second commit, the
# one that reverted its parent, because despite detecting the rename from
# 'numbers' to 'sequence' for the first commit, it fails to detect that
# rename when picking the second commit.  That's "reasonable" given the
# dramatic change in size of the file, but remembering the rename and
# reusing it is reasonable too.
#
# We do test here that we expect rename detection to only be run once total
# (the topic side of history doesn't need renames, and with caching we
# should be able to only run rename detection on the upstream side one
# time.)
test_expect_success 'cherry-pick both a commit and its immediate revert' '
	test_create_repo pick-commit-and-its-immediate-revert &&
	(
		cd pick-commit-and-its-immediate-revert &&

		test_seq 11 30 >numbers &&
		git add numbers &&
		git commit -m orig &&

		git branch upstream &&
		git branch topic &&

		git switch upstream &&
		test_seq 1 30 >numbers &&
		git add numbers &&
		git mv numbers sequence &&
		git commit -m "Renamed (and modified) numbers -> sequence" &&

		git switch topic &&

		test_seq 11 13 >numbers &&
		git add numbers &&
		git commit -m A &&

		git revert HEAD &&

		#
		# Actual testing
		#

		git switch upstream &&

		GIT_TRACE2_PERF="$(pwd)/trace.output" &&
		export GIT_TRACE2_PERF &&

		test-tool fast-rebase --onto HEAD upstream~1 topic &&
		#git cherry-pick upstream~1..topic &&

		grep region_enter.*diffcore_rename trace.output >calls &&
		test_line_count = 1 calls
	)
'

#
# In the following testcase:
#   Base:     sequence_1
#   Upstream: rename sequence_1 -> values_2
#   Topic_1:  rename sequence_1 -> values_3
#   Topic_2:  add unrelated sequence_4
# or, in english, both sides rename sequence -> values, and then the second
# commit on the topic branch adds an unrelated file called sequence.
#
# This testcase presents no problems for git traditionally, but having both
# sides do the same rename in effect "uses it up" and if it remains cached,
# could cause a spurious rename/add conflict.
#
test_expect_success 'rename same file identically, then reintroduce it' '
	test_create_repo rename-rename-1to1-then-add-old-filename &&
	(
		cd rename-rename-1to1-then-add-old-filename &&

		test_seq 3 8 >sequence &&
		git add sequence &&
		git commit -m orig &&

		git branch upstream &&
		git branch topic &&

		git switch upstream &&
		test_seq 1 8 >sequence &&
		git add sequence &&
		git mv sequence values &&
		git commit -m "Renamed (and modified) sequence -> values" &&

		git switch topic &&

		test_seq 3 10 >sequence &&
		git add sequence &&
		git mv sequence values &&
		git commit -m A &&

		test_write_lines A B C D E F G H I J >sequence &&
		git add sequence &&
		git commit -m B &&

		#
		# Actual testing
		#

		git switch upstream &&

		GIT_TRACE2_PERF="$(pwd)/trace.output" &&
		export GIT_TRACE2_PERF &&

		test-tool fast-rebase --onto HEAD upstream~1 topic &&
		#git cherry-pick upstream~1..topic &&

		git ls-files >tracked &&
		test_line_count = 2 tracked &&
		test_path_is_file values &&
		test_path_is_file sequence &&

		grep region_enter.*diffcore_rename trace.output >calls &&
		test_line_count = 2 calls
	)
'

#
# In the following testcase:
#   Base:     olddir/{valuesZ_1, valuesY_1, valuesX_1}
#   Upstream: rename olddir/valuesZ_1 -> dirA/valuesZ_2
#             rename olddir/valuesY_1 -> dirA/valuesY_2
#             rename olddir/valuesX_1 -> dirB/valuesX_2
#   Topic_1:  rename olddir/valuesZ_1 -> dirA/valuesZ_3
#             rename olddir/valuesY_1 -> dirA/valuesY_3
#   Topic_2:  add olddir/newfile
#   Expected Pick1: dirA/{valuesZ, valuesY}, dirB/valuesX
#   Expected Pick2: dirA/{valuesZ, valuesY}, dirB/{valuesX, newfile}
#
# This testcase presents no problems for git traditionally, but having both
# sides do the same renames in effect "use it up" but if the renames remain
# cached, the directory rename could put newfile in the wrong directory.
#
test_expect_success 'rename same file identically, then add file to old dir' '
	test_create_repo rename-rename-1to1-then-add-file-to-old-dir &&
	(
		cd rename-rename-1to1-then-add-file-to-old-dir &&

		mkdir olddir/ &&
		test_seq 3 8 >olddir/valuesZ &&
		test_seq 3 8 >olddir/valuesY &&
		test_seq 3 8 >olddir/valuesX &&
		git add olddir &&
		git commit -m orig &&

		git branch upstream &&
		git branch topic &&

		git switch upstream &&
		test_seq 1 8 >olddir/valuesZ &&
		test_seq 1 8 >olddir/valuesY &&
		test_seq 1 8 >olddir/valuesX &&
		git add olddir &&
		mkdir dirA &&
		git mv olddir/valuesZ olddir/valuesY dirA &&
		git mv olddir/ dirB/ &&
		git commit -m "Renamed (and modified) values*" &&

		git switch topic &&

		test_seq 3 10 >olddir/valuesZ &&
		test_seq 3 10 >olddir/valuesY &&
		git add olddir &&
		mkdir dirA &&
		git mv olddir/valuesZ olddir/valuesY dirA &&
		git commit -m A &&

		>olddir/newfile &&
		git add olddir/newfile &&
		git commit -m B &&

		#
		# Actual testing
		#

		git switch upstream &&
		git config merge.directoryRenames true &&

		GIT_TRACE2_PERF="$(pwd)/trace.output" &&
		export GIT_TRACE2_PERF &&

		test-tool fast-rebase --onto HEAD upstream~1 topic &&
		#git cherry-pick upstream~1..topic &&

		git ls-files >tracked &&
		test_line_count = 4 tracked &&
		test_path_is_file dirA/valuesZ &&
		test_path_is_file dirA/valuesY &&
		test_path_is_file dirB/valuesX &&
		test_path_is_file dirB/newfile &&

		grep region_enter.*diffcore_rename trace.output >calls &&
		test_line_count = 3 calls
	)
'

#
# In the following testcase, upstream renames a directory, and the topic branch
# first adds a file to the directory, then later renames the directory
# differently:
#   Base:     olddir/a
#             olddir/b
#   Upstream: rename olddir/ -> newdir/
#   Topic_1:  add olddir/newfile
#   Topic_2:  rename olddir/ -> otherdir/
#
# Here we are just concerned that cached renames might prevent us from seeing
# the rename conflict, and we want to ensure that we do get a conflict.
#
# While at it, though, we do test that we only try to detect renames 2
# times and not three.  (The first merge needs to detect renames on the
# upstream side.  Traditionally, the second merge would need to detect
# renames on both sides of history, but our caching of upstream renames
# should avoid the need to re-detect upstream renames.)
#
test_expect_success 'cached dir rename does not prevent noticing later conflict' '
	test_create_repo dir-rename-cache-not-occluding-later-conflict &&
	(
		cd dir-rename-cache-not-occluding-later-conflict &&

		mkdir olddir &&
		test_seq 3 10 >olddir/a &&
		test_seq 3 10 >olddir/b &&
		git add olddir &&
		git commit -m orig &&

		git branch upstream &&
		git branch topic &&

		git switch upstream &&
		test_seq 3 10 >olddir/a &&
		test_seq 3 10 >olddir/b &&
		git add olddir &&
		git mv olddir newdir &&
		git commit -m "Dir renamed" &&

		git switch topic &&

		>olddir/newfile &&
		git add olddir/newfile &&
		git commit -m A &&

		test_seq 1 8 >olddir/a &&
		test_seq 1 8 >olddir/b &&
		git add olddir &&
		git mv olddir otherdir &&
		git commit -m B &&

		#
		# Actual testing
		#

		git switch upstream &&
		git config merge.directoryRenames true &&

		GIT_TRACE2_PERF="$(pwd)/trace.output" &&
		export GIT_TRACE2_PERF &&

		test_must_fail test-tool fast-rebase --onto HEAD upstream~1 topic >output &&
		#git cherry-pick upstream..topic &&

		grep CONFLICT..rename/rename output &&

		grep region_enter.*diffcore_rename trace.output >calls &&
		test_line_count = 2 calls
	)
'

# Helper for the next two tests
test_setup_upstream_rename () {
	test_create_repo $1 &&
	(
		cd $1 &&

		test_seq 3 8 >somefile &&
		test_seq 3 8 >relevant-rename &&
		git add somefile relevant-rename &&
		mkdir olddir &&
		test_write_lines a b c d e f g >olddir/a &&
		test_write_lines z y x w v u t >olddir/b &&
		git add olddir &&
		git commit -m orig &&

		git branch upstream &&
		git branch topic &&

		git switch upstream &&
		test_seq 1 8 >somefile &&
		test_seq 1 8 >relevant-rename &&
		git add somefile relevant-rename &&
		git mv relevant-rename renamed &&
		echo h >>olddir/a &&
		echo s >>olddir/b &&
		git add olddir &&
		git mv olddir newdir &&
		git commit -m "Dir renamed"
	)
}

#
# In the following testcase, upstream renames a file in the toplevel directory
# as well as its only directory:
#   Base:     relevant-rename_1
#             somefile
#             olddir/a
#             olddir/b
#   Upstream: rename relevant-rename_1 -> renamed_2
#             rename olddir/           -> newdir/
#   Topic_1:  relevant-rename_3
#   Topic_2:  olddir/newfile_1
#   Topic_3:  olddir/newfile_2
#
# In this testcase, since the first commit being picked only modifies a
# file in the toplevel directory, the directory rename is irrelevant for
# that first merge.  However, we need to notice the directory rename for
# the merge that picks the second commit, and we don't want the third
# commit to mess up its location either.  We want to make sure that
# olddir/newfile doesn't exist in the result and that newdir/newfile does.
#
# We also test that we only do rename detection twice.  We never need
# rename detection on the topic side of history, but we do need it twice on
# the upstream side of history.  For the first topic commit, we only need
# the
#   relevant-rename -> renamed
# rename, because olddir is unmodified by Topic_1.  For Topic_2, however,
# the new file being added to olddir means files that were previously
# irrelevant for rename detection are now relevant, forcing us to repeat
# rename detection for the paths we don't already have cached.  Topic_3 also
# tweaks olddir/newfile, but the renames in olddir/ will have been cached
# from the second rename detection run.
#
test_expect_success 'dir rename unneeded, then add new file to old dir' '
	test_setup_upstream_rename dir-rename-unneeded-until-new-file &&
	(
		cd dir-rename-unneeded-until-new-file &&

		git switch topic &&

		test_seq 3 10 >relevant-rename &&
		git add relevant-rename &&
		git commit -m A &&

		echo foo >olddir/newfile &&
		git add olddir/newfile &&
		git commit -m B &&

		echo bar >>olddir/newfile &&
		git add olddir/newfile &&
		git commit -m C &&

		#
		# Actual testing
		#

		git switch upstream &&
		git config merge.directoryRenames true &&

		GIT_TRACE2_PERF="$(pwd)/trace.output" &&
		export GIT_TRACE2_PERF &&

		test-tool fast-rebase --onto HEAD upstream~1 topic &&
		#git cherry-pick upstream..topic &&

		grep region_enter.*diffcore_rename trace.output >calls &&
		test_line_count = 2 calls &&

		git ls-files >tracked &&
		test_line_count = 5 tracked &&
		test_path_is_missing olddir/newfile &&
		test_path_is_file newdir/newfile
	)
'

#
# The following testcase is *very* similar to the last one, but instead of
# adding a new olddir/newfile, it renames somefile -> olddir/newfile:
#   Base:     relevant-rename_1
#             somefile_1
#             olddir/a
#             olddir/b
#   Upstream: rename relevant-rename_1 -> renamed_2
#             rename olddir/           -> newdir/
#   Topic_1:  relevant-rename_3
#   Topic_2:  rename somefile -> olddir/newfile_2
#   Topic_3:  modify olddir/newfile_3
#
# In this testcase, since the first commit being picked only modifies a
# file in the toplevel directory, the directory rename is irrelevant for
# that first merge.  However, we need to notice the directory rename for
# the merge that picks the second commit, and we don't want the third
# commit to mess up its location either.  We want to make sure that
# neither somefile or olddir/newfile exists in the result and that
# newdir/newfile does.
#
# This testcase needs one more call to rename detection than the last
# testcase, because of the somefile -> olddir/newfile rename in Topic_2.
test_expect_success 'dir rename unneeded, then rename existing file into old dir' '
	test_setup_upstream_rename dir-rename-unneeded-until-file-moved-inside &&
	(
		cd dir-rename-unneeded-until-file-moved-inside &&

		git switch topic &&

		test_seq 3 10 >relevant-rename &&
		git add relevant-rename &&
		git commit -m A &&

		test_seq 1 10 >somefile &&
		git add somefile &&
		git mv somefile olddir/newfile &&
		git commit -m B &&

		test_seq 1 12 >olddir/newfile &&
		git add olddir/newfile &&
		git commit -m C &&

		#
		# Actual testing
		#

		git switch upstream &&
		git config merge.directoryRenames true &&

		GIT_TRACE2_PERF="$(pwd)/trace.output" &&
		export GIT_TRACE2_PERF &&

		test-tool fast-rebase --onto HEAD upstream~1 topic &&
		#git cherry-pick upstream..topic &&

		grep region_enter.*diffcore_rename trace.output >calls &&
		test_line_count = 3 calls &&

		test_path_is_missing somefile &&
		test_path_is_missing olddir/newfile &&
		test_path_is_file newdir/newfile &&
		git ls-files >tracked &&
		test_line_count = 4 tracked
	)
'

# Helper for the next two tests
test_setup_topic_rename () {
	test_create_repo $1 &&
	(
		cd $1 &&

		test_seq 3 8 >somefile &&
		mkdir olddir &&
		test_seq 3 8 >olddir/a &&
		echo b >olddir/b &&
		git add olddir somefile &&
		git commit -m orig &&

		git branch upstream &&
		git branch topic &&

		git switch topic &&
		test_seq 1 8 >somefile &&
		test_seq 1 8 >olddir/a &&
		git add somefile olddir/a &&
		git mv olddir newdir &&
		git commit -m "Dir renamed" &&

		test_seq 1 10 >somefile &&
		git add somefile &&
		mkdir olddir &&
		>olddir/unrelated-file &&
		git add olddir &&
		git commit -m "Unrelated file in recreated old dir"
	)
}

#
# In the following testcase, the first commit on the topic branch renames
# a directory, while the second recreates the old directory and places a
# file into it:
#   Base:     somefile
#             olddir/a
#             olddir/b
#   Upstream: olddir/newfile
#   Topic_1:  somefile_2
#             rename olddir/ -> newdir/
#   Topic_2:  olddir/unrelated-file
#
# Note that the first pick should merge:
#   Base:     somefile
#             olddir/{a,b}
#   Upstream: olddir/newfile
#   Topic_1:  rename olddir/ -> newdir/
# For which the expected result (assuming merge.directoryRenames=true) is
# clearly:
#   Result:   somefile
#             newdir/{a, b, newfile}
#
# While the second pick does the following three-way merge:
#   Base (Topic_1):           somefile
#                             newdir/{a,b}
#   Upstream (Result from 1): same files as base, but adds newdir/newfile
#   Topic_2:                  same files as base, but adds olddir/unrelated-file
#
# The second merge is pretty trivial; upstream adds newdir/newfile, and
# topic_2 adds olddir/unrelated-file.  We're just testing that we don't
# accidentally cache directory renames somehow and rename
# olddir/unrelated-file to newdir/unrelated-file.
#
# This testcase should only need one call to diffcore_rename_extended().
test_expect_success 'caching renames only on upstream side, part 1' '
	test_setup_topic_rename cache-renames-only-upstream-add-file &&
	(
		cd cache-renames-only-upstream-add-file &&

		git switch upstream &&

		>olddir/newfile &&
		git add olddir/newfile &&
		git commit -m "Add newfile" &&

		#
		# Actual testing
		#

		git switch upstream &&

		git config merge.directoryRenames true &&

		GIT_TRACE2_PERF="$(pwd)/trace.output" &&
		export GIT_TRACE2_PERF &&

		test-tool fast-rebase --onto HEAD upstream~1 topic &&
		#git cherry-pick upstream..topic &&

		grep region_enter.*diffcore_rename trace.output >calls &&
		test_line_count = 1 calls &&

		git ls-files >tracked &&
		test_line_count = 5 tracked &&
		test_path_is_missing newdir/unrelated-file &&
		test_path_is_file olddir/unrelated-file &&
		test_path_is_file newdir/newfile &&
		test_path_is_file newdir/b &&
		test_path_is_file newdir/a &&
		test_path_is_file somefile
	)
'

#
# The following testcase is *very* similar to the last one, but instead of
# adding a new olddir/newfile, it renames somefile -> olddir/newfile:
#   Base:     somefile
#             olddir/a
#             olddir/b
#   Upstream: somefile_1 -> olddir/newfile
#   Topic_1:  rename olddir/ -> newdir/
#             somefile_2
#   Topic_2:  olddir/unrelated-file
#             somefile_3
#
# Much like the previous test, this case is actually trivial and we are just
# making sure there isn't some spurious directory rename caching going on
# for the wrong side of history.
#
#
# This testcase should only need two calls to diffcore_rename_extended(),
# both for the first merge, one for each side of history.
#
test_expect_success 'caching renames only on upstream side, part 2' '
	test_setup_topic_rename cache-renames-only-upstream-rename-file &&
	(
		cd cache-renames-only-upstream-rename-file &&

		git switch upstream &&

		git mv somefile olddir/newfile &&
		git commit -m "Add newfile" &&

		#
		# Actual testing
		#

		git switch upstream &&

		git config merge.directoryRenames true &&

		GIT_TRACE2_PERF="$(pwd)/trace.output" &&
		export GIT_TRACE2_PERF &&

		test-tool fast-rebase --onto HEAD upstream~1 topic &&
		#git cherry-pick upstream..topic &&

		grep region_enter.*diffcore_rename trace.output >calls &&
		test_line_count = 2 calls &&

		git ls-files >tracked &&
		test_line_count = 4 tracked &&
		test_path_is_missing newdir/unrelated-file &&
		test_path_is_file olddir/unrelated-file &&
		test_path_is_file newdir/newfile &&
		test_path_is_file newdir/b &&
		test_path_is_file newdir/a
	)
'

test_done