1#!/bin/sh
2
3test_description="remember regular & dir renames in sequence of merges"
4
5. ./test-lib.sh
6
7#
8# NOTE 1: this testfile tends to not only rename files, but modify on both
9#         sides; without modifying on both sides, optimizations can kick in
10#         which make rename detection irrelevant or trivial.  We want to make
11#         sure that we are triggering rename caching rather than rename
12#         bypassing.
13#
14# NOTE 2: this testfile uses 'test-tool fast-rebase' instead of either
15#         cherry-pick or rebase.  sequencer.c is only superficially
16#         integrated with merge-ort; it calls merge_switch_to_result()
17#         after EACH merge, which updates the index and working copy AND
18#         throws away the cached results (because merge_switch_to_result()
19#         is only supposed to be called at the end of the sequence).
20#         Integrating them more deeply is a big task, so for now the tests
21#         use 'test-tool fast-rebase'.
22#
23
24
25#
26# In the following simple testcase:
27#   Base:     numbers_1, values_1
28#   Upstream: numbers_2, values_2
29#   Topic_1:  sequence_3
30#   Topic_2:  scruples_3
31# or, in english, rename numbers -> sequence in the first commit, and rename
32# values -> scruples in the second commit.
33#
34# This shouldn't be a challenge, it's just verifying that cached renames isn't
35# preventing us from finding new renames.
36#
37test_expect_success 'caching renames does not preclude finding new ones' '
38	test_create_repo caching-renames-and-new-renames &&
39	(
40		cd caching-renames-and-new-renames &&
41
42		test_seq 2 10 >numbers &&
43		test_seq 2 10 >values &&
44		git add numbers values &&
45		git commit -m orig &&
46
47		git branch upstream &&
48		git branch topic &&
49
50		git switch upstream &&
51		test_seq 1 10 >numbers &&
52		test_seq 1 10 >values &&
53		git add numbers values &&
54		git commit -m "Tweaked both files" &&
55
56		git switch topic &&
57
58		test_seq 2 12 >numbers &&
59		git add numbers &&
60		git mv numbers sequence &&
61		git commit -m A &&
62
63		test_seq 2 12 >values &&
64		git add values &&
65		git mv values scruples &&
66		git commit -m B &&
67
68		#
69		# Actual testing
70		#
71
72		git switch upstream &&
73
74		test-tool fast-rebase --onto HEAD upstream~1 topic &&
75		#git cherry-pick upstream~1..topic
76
77		git ls-files >tracked-files &&
78		test_line_count = 2 tracked-files &&
79		test_seq 1 12 >expect &&
80		test_cmp expect sequence &&
81		test_cmp expect scruples
82	)
83'
84
85#
86# In the following testcase:
87#   Base:     numbers_1
88#   Upstream: rename numbers_1 -> sequence_2
89#   Topic_1:  numbers_3
90#   Topic_2:  numbers_1
91# or, in english, the first commit on the topic branch modifies numbers by
92# shrinking it (dramatically) and the second commit on topic reverts its
93# parent.
94#
95# Can git apply both patches?
96#
97# Traditional cherry-pick/rebase will fail to apply the second commit, the
98# one that reverted its parent, because despite detecting the rename from
99# 'numbers' to 'sequence' for the first commit, it fails to detect that
100# rename when picking the second commit.  That's "reasonable" given the
101# dramatic change in size of the file, but remembering the rename and
102# reusing it is reasonable too.
103#
104# We do test here that we expect rename detection to only be run once total
105# (the topic side of history doesn't need renames, and with caching we
106# should be able to only run rename detection on the upstream side one
107# time.)
108test_expect_success 'cherry-pick both a commit and its immediate revert' '
109	test_create_repo pick-commit-and-its-immediate-revert &&
110	(
111		cd pick-commit-and-its-immediate-revert &&
112
113		test_seq 11 30 >numbers &&
114		git add numbers &&
115		git commit -m orig &&
116
117		git branch upstream &&
118		git branch topic &&
119
120		git switch upstream &&
121		test_seq 1 30 >numbers &&
122		git add numbers &&
123		git mv numbers sequence &&
124		git commit -m "Renamed (and modified) numbers -> sequence" &&
125
126		git switch topic &&
127
128		test_seq 11 13 >numbers &&
129		git add numbers &&
130		git commit -m A &&
131
132		git revert HEAD &&
133
134		#
135		# Actual testing
136		#
137
138		git switch upstream &&
139
140		GIT_TRACE2_PERF="$(pwd)/trace.output" &&
141		export GIT_TRACE2_PERF &&
142
143		test-tool fast-rebase --onto HEAD upstream~1 topic &&
144		#git cherry-pick upstream~1..topic &&
145
146		grep region_enter.*diffcore_rename trace.output >calls &&
147		test_line_count = 1 calls
148	)
149'
150
151#
152# In the following testcase:
153#   Base:     sequence_1
154#   Upstream: rename sequence_1 -> values_2
155#   Topic_1:  rename sequence_1 -> values_3
156#   Topic_2:  add unrelated sequence_4
157# or, in english, both sides rename sequence -> values, and then the second
158# commit on the topic branch adds an unrelated file called sequence.
159#
160# This testcase presents no problems for git traditionally, but having both
161# sides do the same rename in effect "uses it up" and if it remains cached,
162# could cause a spurious rename/add conflict.
163#
164test_expect_success 'rename same file identically, then reintroduce it' '
165	test_create_repo rename-rename-1to1-then-add-old-filename &&
166	(
167		cd rename-rename-1to1-then-add-old-filename &&
168
169		test_seq 3 8 >sequence &&
170		git add sequence &&
171		git commit -m orig &&
172
173		git branch upstream &&
174		git branch topic &&
175
176		git switch upstream &&
177		test_seq 1 8 >sequence &&
178		git add sequence &&
179		git mv sequence values &&
180		git commit -m "Renamed (and modified) sequence -> values" &&
181
182		git switch topic &&
183
184		test_seq 3 10 >sequence &&
185		git add sequence &&
186		git mv sequence values &&
187		git commit -m A &&
188
189		test_write_lines A B C D E F G H I J >sequence &&
190		git add sequence &&
191		git commit -m B &&
192
193		#
194		# Actual testing
195		#
196
197		git switch upstream &&
198
199		GIT_TRACE2_PERF="$(pwd)/trace.output" &&
200		export GIT_TRACE2_PERF &&
201
202		test-tool fast-rebase --onto HEAD upstream~1 topic &&
203		#git cherry-pick upstream~1..topic &&
204
205		git ls-files >tracked &&
206		test_line_count = 2 tracked &&
207		test_path_is_file values &&
208		test_path_is_file sequence &&
209
210		grep region_enter.*diffcore_rename trace.output >calls &&
211		test_line_count = 2 calls
212	)
213'
214
215#
216# In the following testcase:
217#   Base:     olddir/{valuesZ_1, valuesY_1, valuesX_1}
218#   Upstream: rename olddir/valuesZ_1 -> dirA/valuesZ_2
219#             rename olddir/valuesY_1 -> dirA/valuesY_2
220#             rename olddir/valuesX_1 -> dirB/valuesX_2
221#   Topic_1:  rename olddir/valuesZ_1 -> dirA/valuesZ_3
222#             rename olddir/valuesY_1 -> dirA/valuesY_3
223#   Topic_2:  add olddir/newfile
224#   Expected Pick1: dirA/{valuesZ, valuesY}, dirB/valuesX
225#   Expected Pick2: dirA/{valuesZ, valuesY}, dirB/{valuesX, newfile}
226#
227# This testcase presents no problems for git traditionally, but having both
228# sides do the same renames in effect "use it up" but if the renames remain
229# cached, the directory rename could put newfile in the wrong directory.
230#
231test_expect_success 'rename same file identically, then add file to old dir' '
232	test_create_repo rename-rename-1to1-then-add-file-to-old-dir &&
233	(
234		cd rename-rename-1to1-then-add-file-to-old-dir &&
235
236		mkdir olddir/ &&
237		test_seq 3 8 >olddir/valuesZ &&
238		test_seq 3 8 >olddir/valuesY &&
239		test_seq 3 8 >olddir/valuesX &&
240		git add olddir &&
241		git commit -m orig &&
242
243		git branch upstream &&
244		git branch topic &&
245
246		git switch upstream &&
247		test_seq 1 8 >olddir/valuesZ &&
248		test_seq 1 8 >olddir/valuesY &&
249		test_seq 1 8 >olddir/valuesX &&
250		git add olddir &&
251		mkdir dirA &&
252		git mv olddir/valuesZ olddir/valuesY dirA &&
253		git mv olddir/ dirB/ &&
254		git commit -m "Renamed (and modified) values*" &&
255
256		git switch topic &&
257
258		test_seq 3 10 >olddir/valuesZ &&
259		test_seq 3 10 >olddir/valuesY &&
260		git add olddir &&
261		mkdir dirA &&
262		git mv olddir/valuesZ olddir/valuesY dirA &&
263		git commit -m A &&
264
265		>olddir/newfile &&
266		git add olddir/newfile &&
267		git commit -m B &&
268
269		#
270		# Actual testing
271		#
272
273		git switch upstream &&
274		git config merge.directoryRenames true &&
275
276		GIT_TRACE2_PERF="$(pwd)/trace.output" &&
277		export GIT_TRACE2_PERF &&
278
279		test-tool fast-rebase --onto HEAD upstream~1 topic &&
280		#git cherry-pick upstream~1..topic &&
281
282		git ls-files >tracked &&
283		test_line_count = 4 tracked &&
284		test_path_is_file dirA/valuesZ &&
285		test_path_is_file dirA/valuesY &&
286		test_path_is_file dirB/valuesX &&
287		test_path_is_file dirB/newfile &&
288
289		grep region_enter.*diffcore_rename trace.output >calls &&
290		test_line_count = 3 calls
291	)
292'
293
294#
295# In the following testcase, upstream renames a directory, and the topic branch
296# first adds a file to the directory, then later renames the directory
297# differently:
298#   Base:     olddir/a
299#             olddir/b
300#   Upstream: rename olddir/ -> newdir/
301#   Topic_1:  add olddir/newfile
302#   Topic_2:  rename olddir/ -> otherdir/
303#
304# Here we are just concerned that cached renames might prevent us from seeing
305# the rename conflict, and we want to ensure that we do get a conflict.
306#
307# While at it, though, we do test that we only try to detect renames 2
308# times and not three.  (The first merge needs to detect renames on the
309# upstream side.  Traditionally, the second merge would need to detect
310# renames on both sides of history, but our caching of upstream renames
311# should avoid the need to re-detect upstream renames.)
312#
313test_expect_success 'cached dir rename does not prevent noticing later conflict' '
314	test_create_repo dir-rename-cache-not-occluding-later-conflict &&
315	(
316		cd dir-rename-cache-not-occluding-later-conflict &&
317
318		mkdir olddir &&
319		test_seq 3 10 >olddir/a &&
320		test_seq 3 10 >olddir/b &&
321		git add olddir &&
322		git commit -m orig &&
323
324		git branch upstream &&
325		git branch topic &&
326
327		git switch upstream &&
328		test_seq 3 10 >olddir/a &&
329		test_seq 3 10 >olddir/b &&
330		git add olddir &&
331		git mv olddir newdir &&
332		git commit -m "Dir renamed" &&
333
334		git switch topic &&
335
336		>olddir/newfile &&
337		git add olddir/newfile &&
338		git commit -m A &&
339
340		test_seq 1 8 >olddir/a &&
341		test_seq 1 8 >olddir/b &&
342		git add olddir &&
343		git mv olddir otherdir &&
344		git commit -m B &&
345
346		#
347		# Actual testing
348		#
349
350		git switch upstream &&
351		git config merge.directoryRenames true &&
352
353		GIT_TRACE2_PERF="$(pwd)/trace.output" &&
354		export GIT_TRACE2_PERF &&
355
356		test_must_fail test-tool fast-rebase --onto HEAD upstream~1 topic >output &&
357		#git cherry-pick upstream..topic &&
358
359		grep CONFLICT..rename/rename output &&
360
361		grep region_enter.*diffcore_rename trace.output >calls &&
362		test_line_count = 2 calls
363	)
364'
365
366# Helper for the next two tests
367test_setup_upstream_rename () {
368	test_create_repo $1 &&
369	(
370		cd $1 &&
371
372		test_seq 3 8 >somefile &&
373		test_seq 3 8 >relevant-rename &&
374		git add somefile relevant-rename &&
375		mkdir olddir &&
376		test_write_lines a b c d e f g >olddir/a &&
377		test_write_lines z y x w v u t >olddir/b &&
378		git add olddir &&
379		git commit -m orig &&
380
381		git branch upstream &&
382		git branch topic &&
383
384		git switch upstream &&
385		test_seq 1 8 >somefile &&
386		test_seq 1 8 >relevant-rename &&
387		git add somefile relevant-rename &&
388		git mv relevant-rename renamed &&
389		echo h >>olddir/a &&
390		echo s >>olddir/b &&
391		git add olddir &&
392		git mv olddir newdir &&
393		git commit -m "Dir renamed"
394	)
395}
396
397#
398# In the following testcase, upstream renames a file in the toplevel directory
399# as well as its only directory:
400#   Base:     relevant-rename_1
401#             somefile
402#             olddir/a
403#             olddir/b
404#   Upstream: rename relevant-rename_1 -> renamed_2
405#             rename olddir/           -> newdir/
406#   Topic_1:  relevant-rename_3
407#   Topic_2:  olddir/newfile_1
408#   Topic_3:  olddir/newfile_2
409#
410# In this testcase, since the first commit being picked only modifies a
411# file in the toplevel directory, the directory rename is irrelevant for
412# that first merge.  However, we need to notice the directory rename for
413# the merge that picks the second commit, and we don't want the third
414# commit to mess up its location either.  We want to make sure that
415# olddir/newfile doesn't exist in the result and that newdir/newfile does.
416#
417# We also test that we only do rename detection twice.  We never need
418# rename detection on the topic side of history, but we do need it twice on
419# the upstream side of history.  For the first topic commit, we only need
420# the
421#   relevant-rename -> renamed
422# rename, because olddir is unmodified by Topic_1.  For Topic_2, however,
423# the new file being added to olddir means files that were previously
424# irrelevant for rename detection are now relevant, forcing us to repeat
425# rename detection for the paths we don't already have cached.  Topic_3 also
426# tweaks olddir/newfile, but the renames in olddir/ will have been cached
427# from the second rename detection run.
428#
429test_expect_success 'dir rename unneeded, then add new file to old dir' '
430	test_setup_upstream_rename dir-rename-unneeded-until-new-file &&
431	(
432		cd dir-rename-unneeded-until-new-file &&
433
434		git switch topic &&
435
436		test_seq 3 10 >relevant-rename &&
437		git add relevant-rename &&
438		git commit -m A &&
439
440		echo foo >olddir/newfile &&
441		git add olddir/newfile &&
442		git commit -m B &&
443
444		echo bar >>olddir/newfile &&
445		git add olddir/newfile &&
446		git commit -m C &&
447
448		#
449		# Actual testing
450		#
451
452		git switch upstream &&
453		git config merge.directoryRenames true &&
454
455		GIT_TRACE2_PERF="$(pwd)/trace.output" &&
456		export GIT_TRACE2_PERF &&
457
458		test-tool fast-rebase --onto HEAD upstream~1 topic &&
459		#git cherry-pick upstream..topic &&
460
461		grep region_enter.*diffcore_rename trace.output >calls &&
462		test_line_count = 2 calls &&
463
464		git ls-files >tracked &&
465		test_line_count = 5 tracked &&
466		test_path_is_missing olddir/newfile &&
467		test_path_is_file newdir/newfile
468	)
469'
470
471#
472# The following testcase is *very* similar to the last one, but instead of
473# adding a new olddir/newfile, it renames somefile -> olddir/newfile:
474#   Base:     relevant-rename_1
475#             somefile_1
476#             olddir/a
477#             olddir/b
478#   Upstream: rename relevant-rename_1 -> renamed_2
479#             rename olddir/           -> newdir/
480#   Topic_1:  relevant-rename_3
481#   Topic_2:  rename somefile -> olddir/newfile_2
482#   Topic_3:  modify olddir/newfile_3
483#
484# In this testcase, since the first commit being picked only modifies a
485# file in the toplevel directory, the directory rename is irrelevant for
486# that first merge.  However, we need to notice the directory rename for
487# the merge that picks the second commit, and we don't want the third
488# commit to mess up its location either.  We want to make sure that
489# neither somefile or olddir/newfile exists in the result and that
490# newdir/newfile does.
491#
492# This testcase needs one more call to rename detection than the last
493# testcase, because of the somefile -> olddir/newfile rename in Topic_2.
494test_expect_success 'dir rename unneeded, then rename existing file into old dir' '
495	test_setup_upstream_rename dir-rename-unneeded-until-file-moved-inside &&
496	(
497		cd dir-rename-unneeded-until-file-moved-inside &&
498
499		git switch topic &&
500
501		test_seq 3 10 >relevant-rename &&
502		git add relevant-rename &&
503		git commit -m A &&
504
505		test_seq 1 10 >somefile &&
506		git add somefile &&
507		git mv somefile olddir/newfile &&
508		git commit -m B &&
509
510		test_seq 1 12 >olddir/newfile &&
511		git add olddir/newfile &&
512		git commit -m C &&
513
514		#
515		# Actual testing
516		#
517
518		git switch upstream &&
519		git config merge.directoryRenames true &&
520
521		GIT_TRACE2_PERF="$(pwd)/trace.output" &&
522		export GIT_TRACE2_PERF &&
523
524		test-tool fast-rebase --onto HEAD upstream~1 topic &&
525		#git cherry-pick upstream..topic &&
526
527		grep region_enter.*diffcore_rename trace.output >calls &&
528		test_line_count = 3 calls &&
529
530		test_path_is_missing somefile &&
531		test_path_is_missing olddir/newfile &&
532		test_path_is_file newdir/newfile &&
533		git ls-files >tracked &&
534		test_line_count = 4 tracked
535	)
536'
537
538# Helper for the next two tests
539test_setup_topic_rename () {
540	test_create_repo $1 &&
541	(
542		cd $1 &&
543
544		test_seq 3 8 >somefile &&
545		mkdir olddir &&
546		test_seq 3 8 >olddir/a &&
547		echo b >olddir/b &&
548		git add olddir somefile &&
549		git commit -m orig &&
550
551		git branch upstream &&
552		git branch topic &&
553
554		git switch topic &&
555		test_seq 1 8 >somefile &&
556		test_seq 1 8 >olddir/a &&
557		git add somefile olddir/a &&
558		git mv olddir newdir &&
559		git commit -m "Dir renamed" &&
560
561		test_seq 1 10 >somefile &&
562		git add somefile &&
563		mkdir olddir &&
564		>olddir/unrelated-file &&
565		git add olddir &&
566		git commit -m "Unrelated file in recreated old dir"
567	)
568}
569
570#
571# In the following testcase, the first commit on the topic branch renames
572# a directory, while the second recreates the old directory and places a
573# file into it:
574#   Base:     somefile
575#             olddir/a
576#             olddir/b
577#   Upstream: olddir/newfile
578#   Topic_1:  somefile_2
579#             rename olddir/ -> newdir/
580#   Topic_2:  olddir/unrelated-file
581#
582# Note that the first pick should merge:
583#   Base:     somefile
584#             olddir/{a,b}
585#   Upstream: olddir/newfile
586#   Topic_1:  rename olddir/ -> newdir/
587# For which the expected result (assuming merge.directoryRenames=true) is
588# clearly:
589#   Result:   somefile
590#             newdir/{a, b, newfile}
591#
592# While the second pick does the following three-way merge:
593#   Base (Topic_1):           somefile
594#                             newdir/{a,b}
595#   Upstream (Result from 1): same files as base, but adds newdir/newfile
596#   Topic_2:                  same files as base, but adds olddir/unrelated-file
597#
598# The second merge is pretty trivial; upstream adds newdir/newfile, and
599# topic_2 adds olddir/unrelated-file.  We're just testing that we don't
600# accidentally cache directory renames somehow and rename
601# olddir/unrelated-file to newdir/unrelated-file.
602#
603# This testcase should only need one call to diffcore_rename_extended().
604test_expect_success 'caching renames only on upstream side, part 1' '
605	test_setup_topic_rename cache-renames-only-upstream-add-file &&
606	(
607		cd cache-renames-only-upstream-add-file &&
608
609		git switch upstream &&
610
611		>olddir/newfile &&
612		git add olddir/newfile &&
613		git commit -m "Add newfile" &&
614
615		#
616		# Actual testing
617		#
618
619		git switch upstream &&
620
621		git config merge.directoryRenames true &&
622
623		GIT_TRACE2_PERF="$(pwd)/trace.output" &&
624		export GIT_TRACE2_PERF &&
625
626		test-tool fast-rebase --onto HEAD upstream~1 topic &&
627		#git cherry-pick upstream..topic &&
628
629		grep region_enter.*diffcore_rename trace.output >calls &&
630		test_line_count = 1 calls &&
631
632		git ls-files >tracked &&
633		test_line_count = 5 tracked &&
634		test_path_is_missing newdir/unrelated-file &&
635		test_path_is_file olddir/unrelated-file &&
636		test_path_is_file newdir/newfile &&
637		test_path_is_file newdir/b &&
638		test_path_is_file newdir/a &&
639		test_path_is_file somefile
640	)
641'
642
643#
644# The following testcase is *very* similar to the last one, but instead of
645# adding a new olddir/newfile, it renames somefile -> olddir/newfile:
646#   Base:     somefile
647#             olddir/a
648#             olddir/b
649#   Upstream: somefile_1 -> olddir/newfile
650#   Topic_1:  rename olddir/ -> newdir/
651#             somefile_2
652#   Topic_2:  olddir/unrelated-file
653#             somefile_3
654#
655# Much like the previous test, this case is actually trivial and we are just
656# making sure there isn't some spurious directory rename caching going on
657# for the wrong side of history.
658#
659#
660# This testcase should only need two calls to diffcore_rename_extended(),
661# both for the first merge, one for each side of history.
662#
663test_expect_success 'caching renames only on upstream side, part 2' '
664	test_setup_topic_rename cache-renames-only-upstream-rename-file &&
665	(
666		cd cache-renames-only-upstream-rename-file &&
667
668		git switch upstream &&
669
670		git mv somefile olddir/newfile &&
671		git commit -m "Add newfile" &&
672
673		#
674		# Actual testing
675		#
676
677		git switch upstream &&
678
679		git config merge.directoryRenames true &&
680
681		GIT_TRACE2_PERF="$(pwd)/trace.output" &&
682		export GIT_TRACE2_PERF &&
683
684		test-tool fast-rebase --onto HEAD upstream~1 topic &&
685		#git cherry-pick upstream..topic &&
686
687		grep region_enter.*diffcore_rename trace.output >calls &&
688		test_line_count = 2 calls &&
689
690		git ls-files >tracked &&
691		test_line_count = 4 tracked &&
692		test_path_is_missing newdir/unrelated-file &&
693		test_path_is_file olddir/unrelated-file &&
694		test_path_is_file newdir/newfile &&
695		test_path_is_file newdir/b &&
696		test_path_is_file newdir/a
697	)
698'
699
700test_done
701