| #!/bin/sh |
| |
| test_description="merge cases" |
| |
| # The setup for all of them, pictorially, is: |
| # |
| # A |
| # o |
| # / \ |
| # O o ? |
| # \ / |
| # o |
| # B |
| # |
| # To help make it easier to follow the flow of tests, they have been |
| # divided into sections and each test will start with a quick explanation |
| # of what commits O, A, and B contain. |
| # |
| # Notation: |
| # z/{b,c} means files z/b and z/c both exist |
| # x/d_1 means file x/d exists with content d1. (Purpose of the |
| # underscore notation is to differentiate different |
| # files that might be renamed into each other's paths.) |
| |
| . ./test-lib.sh |
| . "$TEST_DIRECTORY"/lib-merge.sh |
| |
| |
| ########################################################################### |
| # SECTION 1: Cases involving no renames (one side has subset of changes of |
| # the other side) |
| ########################################################################### |
| |
| # Testcase 1a, Changes on A, subset of changes on B |
| # Commit O: b_1 |
| # Commit A: b_2 |
| # Commit B: b_3 |
| # Expected: b_2 |
| |
| test_setup_1a () { |
| git init 1a_$1 && |
| ( |
| cd 1a_$1 && |
| |
| test_write_lines 1 2 3 4 5 6 7 8 9 10 >b && |
| git add b && |
| test_tick && |
| git commit -m "O" && |
| |
| git branch O && |
| git branch A && |
| git branch B && |
| |
| git checkout A && |
| test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 10.5 >b && |
| git add b && |
| test_tick && |
| git commit -m "A" && |
| |
| git checkout B && |
| test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 >b && |
| git add b && |
| test_tick && |
| git commit -m "B" |
| ) |
| } |
| |
| test_expect_success '1a-L: Modify(A)/Modify(B), change on B subset of A' ' |
| test_setup_1a L && |
| ( |
| cd 1a_L && |
| |
| git checkout A^0 && |
| |
| test-tool chmtime --get -3600 b >old-mtime && |
| |
| GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err && |
| |
| test_must_be_empty err && |
| |
| # Make sure b was NOT updated |
| test-tool chmtime --get b >new-mtime && |
| test_cmp old-mtime new-mtime && |
| |
| git ls-files -s >index_files && |
| test_line_count = 1 index_files && |
| |
| git rev-parse >actual HEAD:b && |
| git rev-parse >expect A:b && |
| test_cmp expect actual && |
| |
| git hash-object b >actual && |
| git rev-parse A:b >expect && |
| test_cmp expect actual |
| ) |
| ' |
| |
| test_expect_success '1a-R: Modify(A)/Modify(B), change on B subset of A' ' |
| test_setup_1a R && |
| ( |
| cd 1a_R && |
| |
| git checkout B^0 && |
| |
| test-tool chmtime --get -3600 b >old-mtime && |
| GIT_MERGE_VERBOSITY=3 git merge -s recursive A^0 >out 2>err && |
| |
| # Make sure b WAS updated |
| test-tool chmtime --get b >new-mtime && |
| test $(cat old-mtime) -lt $(cat new-mtime) && |
| |
| test_must_be_empty err && |
| |
| git ls-files -s >index_files && |
| test_line_count = 1 index_files && |
| |
| git rev-parse >actual HEAD:b && |
| git rev-parse >expect A:b && |
| test_cmp expect actual && |
| |
| git hash-object b >actual && |
| git rev-parse A:b >expect && |
| test_cmp expect actual |
| ) |
| ' |
| |
| |
| ########################################################################### |
| # SECTION 2: Cases involving basic renames |
| ########################################################################### |
| |
| # Testcase 2a, Changes on A, rename on B |
| # Commit O: b_1 |
| # Commit A: b_2 |
| # Commit B: c_1 |
| # Expected: c_2 |
| |
| test_setup_2a () { |
| git init 2a_$1 && |
| ( |
| cd 2a_$1 && |
| |
| test_seq 1 10 >b && |
| git add b && |
| test_tick && |
| git commit -m "O" && |
| |
| git branch O && |
| git branch A && |
| git branch B && |
| |
| git checkout A && |
| test_seq 1 11 >b && |
| git add b && |
| test_tick && |
| git commit -m "A" && |
| |
| git checkout B && |
| git mv b c && |
| test_tick && |
| git commit -m "B" |
| ) |
| } |
| |
| test_expect_success '2a-L: Modify/rename, merge into modify side' ' |
| test_setup_2a L && |
| ( |
| cd 2a_L && |
| |
| git checkout A^0 && |
| |
| test_path_is_missing c && |
| GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err && |
| |
| test_path_is_file c && |
| |
| git ls-files -s >index_files && |
| test_line_count = 1 index_files && |
| |
| git rev-parse >actual HEAD:c && |
| git rev-parse >expect A:b && |
| test_cmp expect actual && |
| |
| git hash-object c >actual && |
| git rev-parse A:b >expect && |
| test_cmp expect actual && |
| |
| test_must_fail git rev-parse HEAD:b && |
| test_path_is_missing b |
| ) |
| ' |
| |
| test_expect_success '2a-R: Modify/rename, merge into rename side' ' |
| test_setup_2a R && |
| ( |
| cd 2a_R && |
| |
| git checkout B^0 && |
| |
| test-tool chmtime --get -3600 c >old-mtime && |
| GIT_MERGE_VERBOSITY=3 git merge -s recursive A^0 >out 2>err && |
| |
| # Make sure c WAS updated |
| test-tool chmtime --get c >new-mtime && |
| test $(cat old-mtime) -lt $(cat new-mtime) && |
| |
| test_must_be_empty err && |
| |
| git ls-files -s >index_files && |
| test_line_count = 1 index_files && |
| |
| git rev-parse >actual HEAD:c && |
| git rev-parse >expect A:b && |
| test_cmp expect actual && |
| |
| git hash-object c >actual && |
| git rev-parse A:b >expect && |
| test_cmp expect actual && |
| |
| test_must_fail git rev-parse HEAD:b && |
| test_path_is_missing b |
| ) |
| ' |
| |
| # Testcase 2b, Changed and renamed on A, subset of changes on B |
| # Commit O: b_1 |
| # Commit A: c_2 |
| # Commit B: b_3 |
| # Expected: c_2 |
| |
| test_setup_2b () { |
| git init 2b_$1 && |
| ( |
| cd 2b_$1 && |
| |
| test_write_lines 1 2 3 4 5 6 7 8 9 10 >b && |
| git add b && |
| test_tick && |
| git commit -m "O" && |
| |
| git branch O && |
| git branch A && |
| git branch B && |
| |
| git checkout A && |
| test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 10.5 >b && |
| git add b && |
| git mv b c && |
| test_tick && |
| git commit -m "A" && |
| |
| git checkout B && |
| test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 >b && |
| git add b && |
| test_tick && |
| git commit -m "B" |
| ) |
| } |
| |
| test_expect_success '2b-L: Rename+Mod(A)/Mod(B), B mods subset of A' ' |
| test_setup_2b L && |
| ( |
| cd 2b_L && |
| |
| git checkout A^0 && |
| |
| test-tool chmtime --get -3600 c >old-mtime && |
| GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err && |
| |
| test_must_be_empty err && |
| |
| # Make sure c WAS updated |
| test-tool chmtime --get c >new-mtime && |
| test_cmp old-mtime new-mtime && |
| |
| git ls-files -s >index_files && |
| test_line_count = 1 index_files && |
| |
| git rev-parse >actual HEAD:c && |
| git rev-parse >expect A:c && |
| test_cmp expect actual && |
| |
| git hash-object c >actual && |
| git rev-parse A:c >expect && |
| test_cmp expect actual && |
| |
| test_must_fail git rev-parse HEAD:b && |
| test_path_is_missing b |
| ) |
| ' |
| |
| test_expect_success '2b-R: Rename+Mod(A)/Mod(B), B mods subset of A' ' |
| test_setup_2b R && |
| ( |
| cd 2b_R && |
| |
| git checkout B^0 && |
| |
| test_path_is_missing c && |
| GIT_MERGE_VERBOSITY=3 git merge -s recursive A^0 >out 2>err && |
| |
| # Make sure c now present (and thus was updated) |
| test_path_is_file c && |
| |
| test_must_be_empty err && |
| |
| git ls-files -s >index_files && |
| test_line_count = 1 index_files && |
| |
| git rev-parse >actual HEAD:c && |
| git rev-parse >expect A:c && |
| test_cmp expect actual && |
| |
| git hash-object c >actual && |
| git rev-parse A:c >expect && |
| test_cmp expect actual && |
| |
| test_must_fail git rev-parse HEAD:b && |
| test_path_is_missing b |
| ) |
| ' |
| |
| # Testcase 2c, Changes on A, rename on B |
| # Commit O: b_1 |
| # Commit A: b_2, c_3 |
| # Commit B: c_1 |
| # Expected: rename/add conflict c_2 vs c_3 |
| # |
| # NOTE: Since A modified b_1->b_2, and B renamed b_1->c_1, the threeway |
| # merge of those files should result in c_2. We then should have a |
| # rename/add conflict between c_2 and c_3. However, if we note in |
| # merge_content() that A had the right contents (b_2 has same |
| # contents as c_2, just at a different name), and that A had the |
| # right path present (c_3 existed) and thus decides that it can |
| # skip the update, then we're in trouble. This test verifies we do |
| # not make that particular mistake. |
| |
| test_setup_2c () { |
| git init 2c && |
| ( |
| cd 2c && |
| |
| test_seq 1 10 >b && |
| git add b && |
| test_tick && |
| git commit -m "O" && |
| |
| git branch O && |
| git branch A && |
| git branch B && |
| |
| git checkout A && |
| test_seq 1 11 >b && |
| echo whatever >c && |
| git add b c && |
| test_tick && |
| git commit -m "A" && |
| |
| git checkout B && |
| git mv b c && |
| test_tick && |
| git commit -m "B" |
| ) |
| } |
| |
| test_expect_success '2c: Modify b & add c VS rename b->c' ' |
| test_setup_2c && |
| ( |
| cd 2c && |
| |
| git checkout A^0 && |
| |
| test-tool chmtime --get -3600 c >old-mtime && |
| GIT_MERGE_VERBOSITY=3 && |
| export GIT_MERGE_VERBOSITY && |
| test_must_fail git merge -s recursive B^0 >out 2>err && |
| |
| test_grep "CONFLICT (.*/add):" out && |
| test_must_be_empty err && |
| |
| git ls-files -s >index_files && |
| test_line_count = 2 index_files && |
| |
| # Ensure b was removed |
| test_path_is_missing b && |
| |
| # Make sure c WAS updated... |
| test-tool chmtime --get c >new-mtime && |
| test $(cat old-mtime) -lt $(cat new-mtime) && |
| |
| # ...and has correct index entries and working tree contents |
| git rev-parse >actual :2:c :3:c && |
| git rev-parse >expect A:c A:b && |
| test_cmp expect actual && |
| |
| git cat-file -p A:b >>merge-me && |
| git cat-file -p A:c >>merged && |
| >empty && |
| test_must_fail git merge-file \ |
| -L "HEAD" \ |
| -L "" \ |
| -L "B^0" \ |
| merged empty merge-me && |
| test_cmp merged c |
| ) |
| ' |
| |
| |
| ########################################################################### |
| # SECTION 3: Cases involving directory renames |
| # |
| # NOTE: |
| # Directory renames only apply when one side renames a directory, and the |
| # other side adds or renames a path into that directory. Applying the |
| # directory rename to that new path creates a new pathname that didn't |
| # exist on either side of history. Thus, it is impossible for the |
| # merge contents to already be at the right path, so all of these checks |
| # exist just to make sure that updates are not skipped. |
| ########################################################################### |
| |
| # Testcase 3a, Change + rename into dir foo on A, dir rename foo->bar on B |
| # Commit O: bq_1, foo/whatever |
| # Commit A: foo/{bq_2, whatever} |
| # Commit B: bq_1, bar/whatever |
| # Expected: bar/{bq_2, whatever} |
| |
| test_setup_3a () { |
| git init 3a_$1 && |
| ( |
| cd 3a_$1 && |
| |
| mkdir foo && |
| test_seq 1 10 >bq && |
| test_write_lines a b c d e f g h i j k >foo/whatever && |
| git add bq foo/whatever && |
| test_tick && |
| git commit -m "O" && |
| |
| git branch O && |
| git branch A && |
| git branch B && |
| |
| git checkout A && |
| test_seq 1 11 >bq && |
| git add bq && |
| git mv bq foo/ && |
| test_tick && |
| git commit -m "A" && |
| |
| git checkout B && |
| git mv foo/ bar/ && |
| test_tick && |
| git commit -m "B" |
| ) |
| } |
| |
| test_expect_success '3a-L: bq_1->foo/bq_2 on A, foo/->bar/ on B' ' |
| test_setup_3a L && |
| ( |
| cd 3a_L && |
| |
| git checkout A^0 && |
| |
| test_path_is_missing bar/bq && |
| GIT_MERGE_VERBOSITY=3 git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err && |
| |
| test_must_be_empty err && |
| |
| test_path_is_file bar/bq && |
| |
| git ls-files -s >index_files && |
| test_line_count = 2 index_files && |
| |
| git rev-parse >actual HEAD:bar/bq HEAD:bar/whatever && |
| git rev-parse >expect A:foo/bq A:foo/whatever && |
| test_cmp expect actual && |
| |
| git hash-object bar/bq bar/whatever >actual && |
| git rev-parse A:foo/bq A:foo/whatever >expect && |
| test_cmp expect actual && |
| |
| test_must_fail git rev-parse HEAD:bq HEAD:foo/bq && |
| test_path_is_missing bq && |
| test_path_is_missing foo/bq && |
| test_path_is_missing foo/whatever |
| ) |
| ' |
| |
| test_expect_success '3a-R: bq_1->foo/bq_2 on A, foo/->bar/ on B' ' |
| test_setup_3a R && |
| ( |
| cd 3a_R && |
| |
| git checkout B^0 && |
| |
| test_path_is_missing bar/bq && |
| GIT_MERGE_VERBOSITY=3 git -c merge.directoryRenames=true merge -s recursive A^0 >out 2>err && |
| |
| test_must_be_empty err && |
| |
| test_path_is_file bar/bq && |
| |
| git ls-files -s >index_files && |
| test_line_count = 2 index_files && |
| |
| git rev-parse >actual HEAD:bar/bq HEAD:bar/whatever && |
| git rev-parse >expect A:foo/bq A:foo/whatever && |
| test_cmp expect actual && |
| |
| git hash-object bar/bq bar/whatever >actual && |
| git rev-parse A:foo/bq A:foo/whatever >expect && |
| test_cmp expect actual && |
| |
| test_must_fail git rev-parse HEAD:bq HEAD:foo/bq && |
| test_path_is_missing bq && |
| test_path_is_missing foo/bq && |
| test_path_is_missing foo/whatever |
| ) |
| ' |
| |
| # Testcase 3b, rename into dir foo on A, dir rename foo->bar + change on B |
| # Commit O: bq_1, foo/whatever |
| # Commit A: foo/{bq_1, whatever} |
| # Commit B: bq_2, bar/whatever |
| # Expected: bar/{bq_2, whatever} |
| |
| test_setup_3b () { |
| git init 3b_$1 && |
| ( |
| cd 3b_$1 && |
| |
| mkdir foo && |
| test_seq 1 10 >bq && |
| test_write_lines a b c d e f g h i j k >foo/whatever && |
| git add bq foo/whatever && |
| test_tick && |
| git commit -m "O" && |
| |
| git branch O && |
| git branch A && |
| git branch B && |
| |
| git checkout A && |
| git mv bq foo/ && |
| test_tick && |
| git commit -m "A" && |
| |
| git checkout B && |
| test_seq 1 11 >bq && |
| git add bq && |
| git mv foo/ bar/ && |
| test_tick && |
| git commit -m "B" |
| ) |
| } |
| |
| test_expect_success '3b-L: bq_1->foo/bq_2 on A, foo/->bar/ on B' ' |
| test_setup_3b L && |
| ( |
| cd 3b_L && |
| |
| git checkout A^0 && |
| |
| test_path_is_missing bar/bq && |
| GIT_MERGE_VERBOSITY=3 git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err && |
| |
| test_must_be_empty err && |
| |
| test_path_is_file bar/bq && |
| |
| git ls-files -s >index_files && |
| test_line_count = 2 index_files && |
| |
| git rev-parse >actual HEAD:bar/bq HEAD:bar/whatever && |
| git rev-parse >expect B:bq A:foo/whatever && |
| test_cmp expect actual && |
| |
| git hash-object bar/bq bar/whatever >actual && |
| git rev-parse B:bq A:foo/whatever >expect && |
| test_cmp expect actual && |
| |
| test_must_fail git rev-parse HEAD:bq HEAD:foo/bq && |
| test_path_is_missing bq && |
| test_path_is_missing foo/bq && |
| test_path_is_missing foo/whatever |
| ) |
| ' |
| |
| test_expect_success '3b-R: bq_1->foo/bq_2 on A, foo/->bar/ on B' ' |
| test_setup_3b R && |
| ( |
| cd 3b_R && |
| |
| git checkout B^0 && |
| |
| test_path_is_missing bar/bq && |
| GIT_MERGE_VERBOSITY=3 git -c merge.directoryRenames=true merge -s recursive A^0 >out 2>err && |
| |
| test_must_be_empty err && |
| |
| test_path_is_file bar/bq && |
| |
| git ls-files -s >index_files && |
| test_line_count = 2 index_files && |
| |
| git rev-parse >actual HEAD:bar/bq HEAD:bar/whatever && |
| git rev-parse >expect B:bq A:foo/whatever && |
| test_cmp expect actual && |
| |
| git hash-object bar/bq bar/whatever >actual && |
| git rev-parse B:bq A:foo/whatever >expect && |
| test_cmp expect actual && |
| |
| test_must_fail git rev-parse HEAD:bq HEAD:foo/bq && |
| test_path_is_missing bq && |
| test_path_is_missing foo/bq && |
| test_path_is_missing foo/whatever |
| ) |
| ' |
| |
| ########################################################################### |
| # SECTION 4: Cases involving dirty changes |
| ########################################################################### |
| |
| # Testcase 4a, Changed on A, subset of changes on B, locally modified |
| # Commit O: b_1 |
| # Commit A: b_2 |
| # Commit B: b_3 |
| # Working copy: b_4 |
| # Expected: b_2 for merge, b_4 in working copy |
| |
| test_setup_4a () { |
| git init 4a && |
| ( |
| cd 4a && |
| |
| test_write_lines 1 2 3 4 5 6 7 8 9 10 >b && |
| git add b && |
| test_tick && |
| git commit -m "O" && |
| |
| git branch O && |
| git branch A && |
| git branch B && |
| |
| git checkout A && |
| test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 10.5 >b && |
| git add b && |
| test_tick && |
| git commit -m "A" && |
| |
| git checkout B && |
| test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 >b && |
| git add b && |
| test_tick && |
| git commit -m "B" |
| ) |
| } |
| |
| # NOTE: For as long as we continue using unpack_trees() without index_only |
| # set to true, it will error out on a case like this claiming that the locally |
| # modified file would be overwritten by the merge. Getting this testcase |
| # correct requires doing the merge in-memory first, then realizing that no |
| # updates to the file are necessary, and thus that we can just leave the path |
| # alone. |
| test_expect_merge_algorithm failure success '4a: Change on A, change on B subset of A, dirty mods present' ' |
| test_setup_4a && |
| ( |
| cd 4a && |
| |
| git checkout A^0 && |
| echo "File rewritten" >b && |
| |
| test-tool chmtime --get -3600 b >old-mtime && |
| |
| GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err && |
| |
| test_must_be_empty err && |
| |
| # Make sure b was NOT updated |
| test-tool chmtime --get b >new-mtime && |
| test_cmp old-mtime new-mtime && |
| |
| git ls-files -s >index_files && |
| test_line_count = 1 index_files && |
| |
| git rev-parse >actual :0:b && |
| git rev-parse >expect A:b && |
| test_cmp expect actual && |
| |
| git hash-object b >actual && |
| echo "File rewritten" | git hash-object --stdin >expect && |
| test_cmp expect actual |
| ) |
| ' |
| |
| # Testcase 4b, Changed+renamed on A, subset of changes on B, locally modified |
| # Commit O: b_1 |
| # Commit A: c_2 |
| # Commit B: b_3 |
| # Working copy: c_4 |
| # Expected: c_2 |
| |
| test_setup_4b () { |
| git init 4b && |
| ( |
| cd 4b && |
| |
| test_write_lines 1 2 3 4 5 6 7 8 9 10 >b && |
| git add b && |
| test_tick && |
| git commit -m "O" && |
| |
| git branch O && |
| git branch A && |
| git branch B && |
| |
| git checkout A && |
| test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 10.5 >b && |
| git add b && |
| git mv b c && |
| test_tick && |
| git commit -m "A" && |
| |
| git checkout B && |
| test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 >b && |
| git add b && |
| test_tick && |
| git commit -m "B" |
| ) |
| } |
| |
| test_expect_success '4b: Rename+Mod(A)/Mod(B), change on B subset of A, dirty mods present' ' |
| test_setup_4b && |
| ( |
| cd 4b && |
| |
| git checkout A^0 && |
| echo "File rewritten" >c && |
| |
| test-tool chmtime --get -3600 c >old-mtime && |
| |
| GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err && |
| |
| test_must_be_empty err && |
| |
| # Make sure c was NOT updated |
| test-tool chmtime --get c >new-mtime && |
| test_cmp old-mtime new-mtime && |
| |
| git ls-files -s >index_files && |
| test_line_count = 1 index_files && |
| |
| git rev-parse >actual :0:c && |
| git rev-parse >expect A:c && |
| test_cmp expect actual && |
| |
| git hash-object c >actual && |
| echo "File rewritten" | git hash-object --stdin >expect && |
| test_cmp expect actual && |
| |
| test_must_fail git rev-parse HEAD:b && |
| test_path_is_missing b |
| ) |
| ' |
| |
| test_done |