diff options
Diffstat (limited to 't/t6426-merge-skip-unneeded-updates.sh')
-rwxr-xr-x | t/t6426-merge-skip-unneeded-updates.sh | 779 |
1 files changed, 779 insertions, 0 deletions
diff --git a/t/t6426-merge-skip-unneeded-updates.sh b/t/t6426-merge-skip-unneeded-updates.sh new file mode 100755 index 0000000..2bb8e7f --- /dev/null +++ b/t/t6426-merge-skip-unneeded-updates.sh @@ -0,0 +1,779 @@ +#!/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_i18ngrep "CONFLICT (.*/add):" out && + test_must_be_empty err && + + # Make sure c WAS updated + test-tool chmtime --get c >new-mtime && + test $(cat old-mtime) -lt $(cat new-mtime) + + # FIXME: rename/add conflicts are horribly broken right now; + # when I get back to my patch series fixing it and + # rename/rename(2to1) conflicts to bring them in line with + # how add/add conflicts behave, then checks like the below + # could be added. But that patch series is waiting until + # the rename-directory-detection series lands, which this + # is part of. And in the mean time, I do not want to further + # enforce broken behavior. So for now, the main test is the + # one above that err is an empty file. + + #git ls-files -s >index_files && + #test_line_count = 2 index_files && + + #git rev-parse >actual :2:c :3:c && + #git rev-parse >expect A:b A:c && + #test_cmp expect actual && + + #git cat-file -p A:b >>merged && + #git cat-file -p A:c >>merge-me && + #>empty && + #test_must_fail git merge-file \ + # -L "Temporary merge branch 1" \ + # -L "" \ + # -L "Temporary merge branch 2" \ + # merged empty merge-me && + #sed -e "s/^\([<=>]\)/\1\1\1/" merged >merged-internal && + + #git hash-object c >actual && + #git hash-object merged-internal >expect && + #test_cmp expect actual && + + #test_path_is_missing b + ) +' + + +########################################################################### +# 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 |