1#!/bin/sh
2
3test_description="merge cases"
4
5# The setup for all of them, pictorially, is:
6#
7# A
8# o
9# / \
10# O o ?
11# \ /
12# o
13# B
14#
15# To help make it easier to follow the flow of tests, they have been
16# divided into sections and each test will start with a quick explanation
17# of what commits O, A, and B contain.
18#
19# Notation:
20# z/{b,c} means files z/b and z/c both exist
21# x/d_1 means file x/d exists with content d1. (Purpose of the
22# underscore notation is to differentiate different
23# files that might be renamed into each other's paths.)
24
25. ./test-lib.sh
26
27
28###########################################################################
29# SECTION 1: Cases involving no renames (one side has subset of changes of
30# the other side)
31###########################################################################
32
33# Testcase 1a, Changes on A, subset of changes on B
34# Commit O: b_1
35# Commit A: b_2
36# Commit B: b_3
37# Expected: b_2
38
39test_expect_success '1a-setup: Modify(A)/Modify(B), change on B subset of A' '
40 test_create_repo 1a &&
41 (
42 cd 1a &&
43
44 test_write_lines 1 2 3 4 5 6 7 8 9 10 >b &&
45 git add b &&
46 test_tick &&
47 git commit -m "O" &&
48
49 git branch O &&
50 git branch A &&
51 git branch B &&
52
53 git checkout A &&
54 test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 10.5 >b &&
55 git add b &&
56 test_tick &&
57 git commit -m "A" &&
58
59 git checkout B &&
60 test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 >b &&
61 git add b &&
62 test_tick &&
63 git commit -m "B"
64 )
65'
66
67test_expect_success '1a-check-L: Modify(A)/Modify(B), change on B subset of A' '
68 test_when_finished "git -C 1a reset --hard" &&
69 test_when_finished "git -C 1a clean -fd" &&
70 (
71 cd 1a &&
72
73 git checkout A^0 &&
74
75 test-tool chmtime =31337 b &&
76 test-tool chmtime -v +0 b >expected-mtime &&
77
78 GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err &&
79
80 test_i18ngrep "Skipped b" out &&
81 test_must_be_empty err &&
82
83 test-tool chmtime -v +0 b >actual-mtime &&
84 test_cmp expected-mtime actual-mtime &&
85
86 git ls-files -s >index_files &&
87 test_line_count = 1 index_files &&
88
89 git rev-parse >actual HEAD:b &&
90 git rev-parse >expect A:b &&
91 test_cmp expect actual &&
92
93 git hash-object b >actual &&
94 git rev-parse A:b >expect &&
95 test_cmp expect actual
96 )
97'
98
99test_expect_success '1a-check-R: Modify(A)/Modify(B), change on B subset of A' '
100 test_when_finished "git -C 1a reset --hard" &&
101 test_when_finished "git -C 1a clean -fd" &&
102 (
103 cd 1a &&
104
105 git checkout B^0 &&
106
107 GIT_MERGE_VERBOSITY=3 git merge -s recursive A^0 >out 2>err &&
108
109 test_i18ngrep "Auto-merging b" out &&
110 test_must_be_empty err &&
111
112 git ls-files -s >index_files &&
113 test_line_count = 1 index_files &&
114
115 git rev-parse >actual HEAD:b &&
116 git rev-parse >expect A:b &&
117 test_cmp expect actual &&
118
119 git hash-object b >actual &&
120 git rev-parse A:b >expect &&
121 test_cmp expect actual
122 )
123'
124
125
126###########################################################################
127# SECTION 2: Cases involving basic renames
128###########################################################################
129
130# Testcase 2a, Changes on A, rename on B
131# Commit O: b_1
132# Commit A: b_2
133# Commit B: c_1
134# Expected: c_2
135
136test_expect_success '2a-setup: Modify(A)/rename(B)' '
137 test_create_repo 2a &&
138 (
139 cd 2a &&
140
141 test_seq 1 10 >b &&
142 git add b &&
143 test_tick &&
144 git commit -m "O" &&
145
146 git branch O &&
147 git branch A &&
148 git branch B &&
149
150 git checkout A &&
151 test_seq 1 11 >b &&
152 git add b &&
153 test_tick &&
154 git commit -m "A" &&
155
156 git checkout B &&
157 git mv b c &&
158 test_tick &&
159 git commit -m "B"
160 )
161'
162
163test_expect_success '2a-check-L: Modify/rename, merge into modify side' '
164 test_when_finished "git -C 2a reset --hard" &&
165 test_when_finished "git -C 2a clean -fd" &&
166 (
167 cd 2a &&
168
169 git checkout A^0 &&
170
171 GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err &&
172
173 test_i18ngrep ! "Skipped c" out &&
174 test_must_be_empty err &&
175
176 git ls-files -s >index_files &&
177 test_line_count = 1 index_files &&
178
179 git rev-parse >actual HEAD:c &&
180 git rev-parse >expect A:b &&
181 test_cmp expect actual &&
182
183 git hash-object c >actual &&
184 git rev-parse A:b >expect &&
185 test_cmp expect actual &&
186
187 test_must_fail git rev-parse HEAD:b &&
188 test_path_is_missing b
189 )
190'
191
192test_expect_success '2a-check-R: Modify/rename, merge into rename side' '
193 test_when_finished "git -C 2a reset --hard" &&
194 test_when_finished "git -C 2a clean -fd" &&
195 (
196 cd 2a &&
197
198 git checkout B^0 &&
199
200 GIT_MERGE_VERBOSITY=3 git merge -s recursive A^0 >out 2>err &&
201
202 test_i18ngrep ! "Skipped c" out &&
203 test_must_be_empty err &&
204
205 git ls-files -s >index_files &&
206 test_line_count = 1 index_files &&
207
208 git rev-parse >actual HEAD:c &&
209 git rev-parse >expect A:b &&
210 test_cmp expect actual &&
211
212 git hash-object c >actual &&
213 git rev-parse A:b >expect &&
214 test_cmp expect actual &&
215
216 test_must_fail git rev-parse HEAD:b &&
217 test_path_is_missing b
218 )
219'
220
221# Testcase 2b, Changed and renamed on A, subset of changes on B
222# Commit O: b_1
223# Commit A: c_2
224# Commit B: b_3
225# Expected: c_2
226
227test_expect_success '2b-setup: Rename+Mod(A)/Mod(B), B mods subset of A' '
228 test_create_repo 2b &&
229 (
230 cd 2b &&
231
232 test_write_lines 1 2 3 4 5 6 7 8 9 10 >b &&
233 git add b &&
234 test_tick &&
235 git commit -m "O" &&
236
237 git branch O &&
238 git branch A &&
239 git branch B &&
240
241 git checkout A &&
242 test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 10.5 >b &&
243 git add b &&
244 git mv b c &&
245 test_tick &&
246 git commit -m "A" &&
247
248 git checkout B &&
249 test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 >b &&
250 git add b &&
251 test_tick &&
252 git commit -m "B"
253 )
254'
255
256test_expect_success '2b-check-L: Rename+Mod(A)/Mod(B), B mods subset of A' '
257 test_when_finished "git -C 2b reset --hard" &&
258 test_when_finished "git -C 2b clean -fd" &&
259 (
260 cd 2b &&
261
262 git checkout A^0 &&
263
264 test-tool chmtime =31337 c &&
265 test-tool chmtime -v +0 c >expected-mtime &&
266
267 GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err &&
268
269 test_i18ngrep "Skipped c" out &&
270 test_must_be_empty err &&
271
272 test-tool chmtime -v +0 c >actual-mtime &&
273 test_cmp expected-mtime actual-mtime &&
274
275 git ls-files -s >index_files &&
276 test_line_count = 1 index_files &&
277
278 git rev-parse >actual HEAD:c &&
279 git rev-parse >expect A:c &&
280 test_cmp expect actual &&
281
282 git hash-object c >actual &&
283 git rev-parse A:c >expect &&
284 test_cmp expect actual &&
285
286 test_must_fail git rev-parse HEAD:b &&
287 test_path_is_missing b
288 )
289'
290
291test_expect_success '2b-check-R: Rename+Mod(A)/Mod(B), B mods subset of A' '
292 test_when_finished "git -C 2b reset --hard" &&
293 test_when_finished "git -C 2b clean -fd" &&
294 (
295 cd 2b &&
296
297 git checkout B^0 &&
298
299 GIT_MERGE_VERBOSITY=3 git merge -s recursive A^0 >out 2>err &&
300
301 test_i18ngrep "Auto-merging c" out &&
302 test_must_be_empty err &&
303
304 git ls-files -s >index_files &&
305 test_line_count = 1 index_files &&
306
307 git rev-parse >actual HEAD:c &&
308 git rev-parse >expect A:c &&
309 test_cmp expect actual &&
310
311 git hash-object c >actual &&
312 git rev-parse A:c >expect &&
313 test_cmp expect actual &&
314
315 test_must_fail git rev-parse HEAD:b &&
316 test_path_is_missing b
317 )
318'
319
320# Testcase 2c, Changes on A, rename on B
321# Commit O: b_1
322# Commit A: b_2, c_3
323# Commit B: c_1
324# Expected: rename/add conflict c_2 vs c_3
325#
326# NOTE: Since A modified b_1->b_2, and B renamed b_1->c_1, the threeway
327# merge of those files should result in c_2. We then should have a
328# rename/add conflict between c_2 and c_3. However, if we note in
329# merge_content() that A had the right contents (b_2 has same
330# contents as c_2, just at a different name), and that A had the
331# right path present (c_3 existed) and thus decides that it can
332# skip the update, then we're in trouble. This test verifies we do
333# not make that particular mistake.
334
335test_expect_success '2c-setup: Modify b & add c VS rename b->c' '
336 test_create_repo 2c &&
337 (
338 cd 2c &&
339
340 test_seq 1 10 >b &&
341 git add b &&
342 test_tick &&
343 git commit -m "O" &&
344
345 git branch O &&
346 git branch A &&
347 git branch B &&
348
349 git checkout A &&
350 test_seq 1 11 >b &&
351 echo whatever >c &&
352 git add b c &&
353 test_tick &&
354 git commit -m "A" &&
355
356 git checkout B &&
357 git mv b c &&
358 test_tick &&
359 git commit -m "B"
360 )
361'
362
363test_expect_success '2c-check: Modify b & add c VS rename b->c' '
364 (
365 cd 2c &&
366
367 git checkout A^0 &&
368
369 GIT_MERGE_VERBOSITY=3 &&
370 export GIT_MERGE_VERBOSITY &&
371 test_must_fail git merge -s recursive B^0 >out 2>err &&
372
373 test_i18ngrep "CONFLICT (rename/add): Rename b->c" out &&
374 test_i18ngrep ! "Skipped c" out &&
375 test_must_be_empty err
376
377 # FIXME: rename/add conflicts are horribly broken right now;
378 # when I get back to my patch series fixing it and
379 # rename/rename(2to1) conflicts to bring them in line with
380 # how add/add conflicts behave, then checks like the below
381 # could be added. But that patch series is waiting until
382 # the rename-directory-detection series lands, which this
383 # is part of. And in the mean time, I do not want to further
384 # enforce broken behavior. So for now, the main test is the
385 # one above that err is an empty file.
386
387 #git ls-files -s >index_files &&
388 #test_line_count = 2 index_files &&
389
390 #git rev-parse >actual :2:c :3:c &&
391 #git rev-parse >expect A:b A:c &&
392 #test_cmp expect actual &&
393
394 #git cat-file -p A:b >>merged &&
395 #git cat-file -p A:c >>merge-me &&
396 #>empty &&
397 #test_must_fail git merge-file \
398 # -L "Temporary merge branch 1" \
399 # -L "" \
400 # -L "Temporary merge branch 2" \
401 # merged empty merge-me &&
402 #sed -e "s/^\([<=>]\)/\1\1\1/" merged >merged-internal &&
403
404 #git hash-object c >actual &&
405 #git hash-object merged-internal >expect &&
406 #test_cmp expect actual &&
407
408 #test_path_is_missing b
409 )
410'
411
412
413###########################################################################
414# SECTION 3: Cases involving directory renames
415#
416# NOTE:
417# Directory renames only apply when one side renames a directory, and the
418# other side adds or renames a path into that directory. Applying the
419# directory rename to that new path creates a new pathname that didn't
420# exist on either side of history. Thus, it is impossible for the
421# merge contents to already be at the right path, so all of these checks
422# exist just to make sure that updates are not skipped.
423###########################################################################
424
425# Testcase 3a, Change + rename into dir foo on A, dir rename foo->bar on B
426# Commit O: bq_1, foo/whatever
427# Commit A: foo/{bq_2, whatever}
428# Commit B: bq_1, bar/whatever
429# Expected: bar/{bq_2, whatever}
430
431test_expect_success '3a-setup: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
432 test_create_repo 3a &&
433 (
434 cd 3a &&
435
436 mkdir foo &&
437 test_seq 1 10 >bq &&
438 test_write_lines a b c d e f g h i j k >foo/whatever &&
439 git add bq foo/whatever &&
440 test_tick &&
441 git commit -m "O" &&
442
443 git branch O &&
444 git branch A &&
445 git branch B &&
446
447 git checkout A &&
448 test_seq 1 11 >bq &&
449 git add bq &&
450 git mv bq foo/ &&
451 test_tick &&
452 git commit -m "A" &&
453
454 git checkout B &&
455 git mv foo/ bar/ &&
456 test_tick &&
457 git commit -m "B"
458 )
459'
460
461test_expect_success '3a-check-L: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
462 test_when_finished "git -C 3a reset --hard" &&
463 test_when_finished "git -C 3a clean -fd" &&
464 (
465 cd 3a &&
466
467 git checkout A^0 &&
468
469 GIT_MERGE_VERBOSITY=3 git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err &&
470
471 test_i18ngrep ! "Skipped bar/bq" out &&
472 test_must_be_empty err &&
473
474 git ls-files -s >index_files &&
475 test_line_count = 2 index_files &&
476
477 git rev-parse >actual HEAD:bar/bq HEAD:bar/whatever &&
478 git rev-parse >expect A:foo/bq A:foo/whatever &&
479 test_cmp expect actual &&
480
481 git hash-object bar/bq bar/whatever >actual &&
482 git rev-parse A:foo/bq A:foo/whatever >expect &&
483 test_cmp expect actual &&
484
485 test_must_fail git rev-parse HEAD:bq HEAD:foo/bq &&
486 test_path_is_missing bq foo/bq foo/whatever
487 )
488'
489
490test_expect_success '3a-check-R: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
491 test_when_finished "git -C 3a reset --hard" &&
492 test_when_finished "git -C 3a clean -fd" &&
493 (
494 cd 3a &&
495
496 git checkout B^0 &&
497
498 GIT_MERGE_VERBOSITY=3 git -c merge.directoryRenames=true merge -s recursive A^0 >out 2>err &&
499
500 test_i18ngrep ! "Skipped bar/bq" out &&
501 test_must_be_empty err &&
502
503 git ls-files -s >index_files &&
504 test_line_count = 2 index_files &&
505
506 git rev-parse >actual HEAD:bar/bq HEAD:bar/whatever &&
507 git rev-parse >expect A:foo/bq A:foo/whatever &&
508 test_cmp expect actual &&
509
510 git hash-object bar/bq bar/whatever >actual &&
511 git rev-parse A:foo/bq A:foo/whatever >expect &&
512 test_cmp expect actual &&
513
514 test_must_fail git rev-parse HEAD:bq HEAD:foo/bq &&
515 test_path_is_missing bq foo/bq foo/whatever
516 )
517'
518
519# Testcase 3b, rename into dir foo on A, dir rename foo->bar + change on B
520# Commit O: bq_1, foo/whatever
521# Commit A: foo/{bq_1, whatever}
522# Commit B: bq_2, bar/whatever
523# Expected: bar/{bq_2, whatever}
524
525test_expect_success '3b-setup: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
526 test_create_repo 3b &&
527 (
528 cd 3b &&
529
530 mkdir foo &&
531 test_seq 1 10 >bq &&
532 test_write_lines a b c d e f g h i j k >foo/whatever &&
533 git add bq foo/whatever &&
534 test_tick &&
535 git commit -m "O" &&
536
537 git branch O &&
538 git branch A &&
539 git branch B &&
540
541 git checkout A &&
542 git mv bq foo/ &&
543 test_tick &&
544 git commit -m "A" &&
545
546 git checkout B &&
547 test_seq 1 11 >bq &&
548 git add bq &&
549 git mv foo/ bar/ &&
550 test_tick &&
551 git commit -m "B"
552 )
553'
554
555test_expect_success '3b-check-L: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
556 test_when_finished "git -C 3b reset --hard" &&
557 test_when_finished "git -C 3b clean -fd" &&
558 (
559 cd 3b &&
560
561 git checkout A^0 &&
562
563 GIT_MERGE_VERBOSITY=3 git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err &&
564
565 test_i18ngrep ! "Skipped bar/bq" out &&
566 test_must_be_empty err &&
567
568 git ls-files -s >index_files &&
569 test_line_count = 2 index_files &&
570
571 git rev-parse >actual HEAD:bar/bq HEAD:bar/whatever &&
572 git rev-parse >expect B:bq A:foo/whatever &&
573 test_cmp expect actual &&
574
575 git hash-object bar/bq bar/whatever >actual &&
576 git rev-parse B:bq A:foo/whatever >expect &&
577 test_cmp expect actual &&
578
579 test_must_fail git rev-parse HEAD:bq HEAD:foo/bq &&
580 test_path_is_missing bq foo/bq foo/whatever
581 )
582'
583
584test_expect_success '3b-check-R: bq_1->foo/bq_2 on A, foo/->bar/ on B' '
585 test_when_finished "git -C 3b reset --hard" &&
586 test_when_finished "git -C 3b clean -fd" &&
587 (
588 cd 3b &&
589
590 git checkout B^0 &&
591
592 GIT_MERGE_VERBOSITY=3 git -c merge.directoryRenames=true merge -s recursive A^0 >out 2>err &&
593
594 test_i18ngrep ! "Skipped bar/bq" out &&
595 test_must_be_empty err &&
596
597 git ls-files -s >index_files &&
598 test_line_count = 2 index_files &&
599
600 git rev-parse >actual HEAD:bar/bq HEAD:bar/whatever &&
601 git rev-parse >expect B:bq A:foo/whatever &&
602 test_cmp expect actual &&
603
604 git hash-object bar/bq bar/whatever >actual &&
605 git rev-parse B:bq A:foo/whatever >expect &&
606 test_cmp expect actual &&
607
608 test_must_fail git rev-parse HEAD:bq HEAD:foo/bq &&
609 test_path_is_missing bq foo/bq foo/whatever
610 )
611'
612
613###########################################################################
614# SECTION 4: Cases involving dirty changes
615###########################################################################
616
617# Testcase 4a, Changed on A, subset of changes on B, locally modified
618# Commit O: b_1
619# Commit A: b_2
620# Commit B: b_3
621# Working copy: b_4
622# Expected: b_2 for merge, b_4 in working copy
623
624test_expect_success '4a-setup: Change on A, change on B subset of A, dirty mods present' '
625 test_create_repo 4a &&
626 (
627 cd 4a &&
628
629 test_write_lines 1 2 3 4 5 6 7 8 9 10 >b &&
630 git add b &&
631 test_tick &&
632 git commit -m "O" &&
633
634 git branch O &&
635 git branch A &&
636 git branch B &&
637
638 git checkout A &&
639 test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 10.5 >b &&
640 git add b &&
641 test_tick &&
642 git commit -m "A" &&
643
644 git checkout B &&
645 test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 >b &&
646 git add b &&
647 test_tick &&
648 git commit -m "B"
649 )
650'
651
652# NOTE: For as long as we continue using unpack_trees() without index_only
653# set to true, it will error out on a case like this claiming the the locally
654# modified file would be overwritten by the merge. Getting this testcase
655# correct requires doing the merge in-memory first, then realizing that no
656# updates to the file are necessary, and thus that we can just leave the path
657# alone.
658test_expect_failure '4a-check: Change on A, change on B subset of A, dirty mods present' '
659 test_when_finished "git -C 4a reset --hard" &&
660 test_when_finished "git -C 4a clean -fd" &&
661 (
662 cd 4a &&
663
664 git checkout A^0 &&
665 echo "File rewritten" >b &&
666
667 test-tool chmtime =31337 b &&
668 test-tool chmtime -v +0 b >expected-mtime &&
669
670 GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err &&
671
672 test_i18ngrep "Skipped b" out &&
673 test_must_be_empty err &&
674
675 test-tool chmtime -v +0 b >actual-mtime &&
676 test_cmp expected-mtime actual-mtime &&
677
678 git ls-files -s >index_files &&
679 test_line_count = 1 index_files &&
680
681 git rev-parse >actual :0:b &&
682 git rev-parse >expect A:b &&
683 test_cmp expect actual &&
684
685 git hash-object b >actual &&
686 echo "File rewritten" | git hash-object --stdin >expect &&
687 test_cmp expect actual
688 )
689'
690
691# Testcase 4b, Changed+renamed on A, subset of changes on B, locally modified
692# Commit O: b_1
693# Commit A: c_2
694# Commit B: b_3
695# Working copy: c_4
696# Expected: c_2
697
698test_expect_success '4b-setup: Rename+Mod(A)/Mod(B), change on B subset of A, dirty mods present' '
699 test_create_repo 4b &&
700 (
701 cd 4b &&
702
703 test_write_lines 1 2 3 4 5 6 7 8 9 10 >b &&
704 git add b &&
705 test_tick &&
706 git commit -m "O" &&
707
708 git branch O &&
709 git branch A &&
710 git branch B &&
711
712 git checkout A &&
713 test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 10.5 >b &&
714 git add b &&
715 git mv b c &&
716 test_tick &&
717 git commit -m "A" &&
718
719 git checkout B &&
720 test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 >b &&
721 git add b &&
722 test_tick &&
723 git commit -m "B"
724 )
725'
726
727test_expect_success '4b-check: Rename+Mod(A)/Mod(B), change on B subset of A, dirty mods present' '
728 test_when_finished "git -C 4b reset --hard" &&
729 test_when_finished "git -C 4b clean -fd" &&
730 (
731 cd 4b &&
732
733 git checkout A^0 &&
734 echo "File rewritten" >c &&
735
736 test-tool chmtime =31337 c &&
737 test-tool chmtime -v +0 c >expected-mtime &&
738
739 GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err &&
740
741 test_i18ngrep "Skipped c" out &&
742 test_must_be_empty err &&
743
744 test-tool chmtime -v +0 c >actual-mtime &&
745 test_cmp expected-mtime actual-mtime &&
746
747 git ls-files -s >index_files &&
748 test_line_count = 1 index_files &&
749
750 git rev-parse >actual :0:c &&
751 git rev-parse >expect A:c &&
752 test_cmp expect actual &&
753
754 git hash-object c >actual &&
755 echo "File rewritten" | git hash-object --stdin >expect &&
756 test_cmp expect actual &&
757
758 test_must_fail git rev-parse HEAD:b &&
759 test_path_is_missing b
760 )
761'
762
763test_done