The second batch
[git] / t / t6429-merge-sequence-rename-caching.sh
1 #!/bin/sh
2
3 test_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 #
37 test_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.)
108 test_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 #
164 test_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 #
231 test_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 #
313 test_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
367 test_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 #
429 test_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.
494 test_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
539 test_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().
604 test_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 #
663 test_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
700 test_done