git/t/t6422-merge-rename-corner-cases.sh

1468 lines
34 KiB
Bash
Raw Normal View History

#!/bin/sh
test_description="recursive merge corner cases w/ renames but not criss-crosses"
# t6036 has corner cases that involve both criss-cross merges and renames
GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
tests: mark tests relying on the current default for `init.defaultBranch` In addition to the manual adjustment to let the `linux-gcc` CI job run the test suite with `master` and then with `main`, this patch makes sure that GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME is set in all test scripts that currently rely on the initial branch name being `master by default. To determine which test scripts to mark up, the first step was to force-set the default branch name to `master` in - all test scripts that contain the keyword `master`, - t4211, which expects `t/t4211/history.export` with a hard-coded ref to initialize the default branch, - t5560 because it sources `t/t556x_common` which uses `master`, - t8002 and t8012 because both source `t/annotate-tests.sh` which also uses `master`) This trick was performed by this command: $ sed -i '/^ *\. \.\/\(test-lib\|lib-\(bash\|cvs\|git-svn\)\|gitweb-lib\)\.sh$/i\ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=master\ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\ ' $(git grep -l master t/t[0-9]*.sh) \ t/t4211*.sh t/t5560*.sh t/t8002*.sh t/t8012*.sh After that, careful, manual inspection revealed that some of the test scripts containing the needle `master` do not actually rely on a specific default branch name: either they mention `master` only in a comment, or they initialize that branch specificially, or they do not actually refer to the current default branch. Therefore, the aforementioned modification was undone in those test scripts thusly: $ git checkout HEAD -- \ t/t0027-auto-crlf.sh t/t0060-path-utils.sh \ t/t1011-read-tree-sparse-checkout.sh \ t/t1305-config-include.sh t/t1309-early-config.sh \ t/t1402-check-ref-format.sh t/t1450-fsck.sh \ t/t2024-checkout-dwim.sh \ t/t2106-update-index-assume-unchanged.sh \ t/t3040-subprojects-basic.sh t/t3301-notes.sh \ t/t3308-notes-merge.sh t/t3423-rebase-reword.sh \ t/t3436-rebase-more-options.sh \ t/t4015-diff-whitespace.sh t/t4257-am-interactive.sh \ t/t5323-pack-redundant.sh t/t5401-update-hooks.sh \ t/t5511-refspec.sh t/t5526-fetch-submodules.sh \ t/t5529-push-errors.sh t/t5530-upload-pack-error.sh \ t/t5548-push-porcelain.sh \ t/t5552-skipping-fetch-negotiator.sh \ t/t5572-pull-submodule.sh t/t5608-clone-2gb.sh \ t/t5614-clone-submodules-shallow.sh \ t/t7508-status.sh t/t7606-merge-custom.sh \ t/t9302-fast-import-unpack-limit.sh We excluded one set of test scripts in these commands, though: the range of `git p4` tests. The reason? `git p4` stores the (foreign) remote branch in the branch called `p4/master`, which is obviously not the default branch. Manual analysis revealed that only five of these tests actually require a specific default branch name to pass; They were modified thusly: $ sed -i '/^ *\. \.\/lib-git-p4\.sh$/i\ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=master\ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\ ' t/t980[0167]*.sh t/t9811*.sh Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-11-19 07:44:19 +08:00
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
. ./test-lib.sh
. "$TEST_DIRECTORY"/lib-merge.sh
test_setup_rename_delete_untracked () {
test_create_repo rename-delete-untracked &&
(
cd rename-delete-untracked &&
echo "A pretty inscription" >ring &&
git add ring &&
test_tick &&
git commit -m beginning &&
git branch people &&
git checkout -b rename-the-ring &&
git mv ring one-ring-to-rule-them-all &&
test_tick &&
git commit -m fullname &&
git checkout people &&
git rm ring &&
echo gollum >owner &&
git add owner &&
test_tick &&
git commit -m track-people-instead-of-objects &&
echo "Myyy PRECIOUSSS" >ring
)
}
merge-recursive: Fix deletion of untracked file in rename/delete conflicts In the recursive case (o->call_depth > 0), we do not modify the working directory. However, when o->call_depth==0, file renames can mean we need to delete the old filename from the working copy. Since there have been lots of changes and mistakes here, let's go through the details. Let's start with a simple explanation of what we are trying to achieve: Original goal: If a file is renamed on the side of history being merged into head, the filename serving as the source of that rename needs to be removed from the working directory. The path to getting the above statement implemented in merge-recursive took several steps. The relevant bits of code may be instructive to keep in mind for the explanation, especially since an English-only description involves double negatives that are hard to follow. These bits of code are: int remove_file(..., const char *path, int no_wd) { ... int update_working_directory = !o->call_depth && !no_wd; and remove_file(o, 1, ren1_src, <expression>); Where the choice for <expression> has morphed over time: 65ac6e9 (merge-recursive: adjust to loosened "working file clobbered" check 2006-10-27), introduced the "no_wd" parameter to remove_file() and used "1" for <expression>. This meant ren1_src was never deleted, leaving it around in the working copy. In 8371234 (Remove uncontested renamed files during merge. 2006-12-13), <expression> was changed to "index_only" (where index_only == !!o->call_depth; see b7fa51da). This was equivalent to using "0" for <expression> (due to the early logic in remove_file), and is orthogonal to the condition we actually want to check at this point; it resulted in the source file being removed except when index_only was false. This was problematic because the file could have been renamed on the side of history including head, in which case ren1_src could correspond to an untracked file that should not be deleted. In 183d797 (Keep untracked files not involved in a merge. 2007-02-04), <expression> was changed to "index_only || stage == 3". While this gives correct behavior, the "index_only ||" portion of <expression> is unnecessary and makes the code slightly harder to follow. There were also two further changes to this expression, though without any change in behavior. First in b7fa51d (merge-recursive: get rid of the index_only global variable 2008-09-02), it was changed to "o->call_depth || stage == 3". (index_only == !!o->call_depth). Later, in 41d70bd6 (merge-recursive: Small code clarification -- variable name and comments), this was changed to "o->call_depth || renamed_stage == 2" (where stage was renamed to other_stage and renamed_stage == other_stage ^ 1). So we ended with <expression> being "o->call_depth || renamed_stage == 2". But the "o->call_depth ||" piece was unnecessary. We can remove it, leaving us with <expression> being "renamed_stage == 2". This doesn't change behavior at all, but it makes the code clearer. Which is good, because it's about to get uglier. Corrected goal: If a file is renamed on the side of history being merged into head, the filename serving as the source of that rename needs to be removed from the working directory *IF* that file is tracked in head AND the file tracked in head is related to the original file. Note that the only difference between the original goal and the corrected goal is the two extra conditions added at the end. The first condition is relevant in a rename/delete conflict. If the file was deleted on the HEAD side of the merge and an untracked file of the same name was added to the working copy, then without that extra condition the untracked file will be erroneously deleted. This changes <expression> to "renamed_stage == 2 || !was_tracked(ren1_src)". The second additional condition is relevant in two cases. The first case the second condition can occur is when a file is deleted and a completely different file is added with the same name. To my knowledge, merge-recursive has no mechanism for detecting deleted-and- replaced-by-different-file cases, so I am simply punting on this possibility. The second case for the second condition to occur is when there is a rename/rename/add-source conflict. That is, when the original file was renamed on both sides of history AND the original filename is being re-used by some unrelated (but tracked) content. This case also presents some additional difficulties for us since we cannot currently detect these rename/rename/add-source conflicts; as long as the rename detection logic "optimizes" by ignoring filenames that are present at both ends of the diff, these conflicts will go unnoticed. However, rename/rename conflicts are handled by an entirely separate codepath not being discussed here, so this case is not relevant for the line of code under consideration. In summary: Change <expression> from "o->call_depth || renamed_stage == 2" to "renamed_stage == 2 || !was_tracked(ren1_src)", in order to remove unnecessary code and avoid deleting untracked files. 96 lines of explanation in the changelog to describe a one-line fix... Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2011-08-12 13:20:03 +08:00
test_expect_success "Does git preserve Gollum's precious artifact?" '
test_setup_rename_delete_untracked &&
(
cd rename-delete-untracked &&
test_must_fail git merge -s recursive rename-the-ring &&
# Make sure git did not delete an untracked file
test_path_is_file ring
)
'
# Testcase setup for rename/modify/add-source:
# Commit A: new file: a
# Commit B: modify a slightly
# Commit C: rename a->b, add completely different a
#
# We should be able to merge B & C cleanly
test_setup_rename_modify_add_source () {
test_create_repo rename-modify-add-source &&
(
cd rename-modify-add-source &&
printf "1\n2\n3\n4\n5\n6\n7\n" >a &&
git add a &&
git commit -m A &&
git tag A &&
git checkout -b B A &&
echo 8 >>a &&
git add a &&
git commit -m B &&
git checkout -b C A &&
git mv a b &&
echo something completely different >a &&
git add a &&
git commit -m C
)
}
test_expect_failure 'rename/modify/add-source conflict resolvable' '
test_setup_rename_modify_add_source &&
(
cd rename-modify-add-source &&
git checkout B^0 &&
git merge -s recursive C^0 &&
git rev-parse >expect \
B:a C:a &&
git rev-parse >actual \
b c &&
test_cmp expect actual
)
'
test_setup_break_detection_1 () {
test_create_repo break-detection-1 &&
(
cd break-detection-1 &&
printf "1\n2\n3\n4\n5\n" >a &&
echo foo >b &&
git add a b &&
git commit -m A &&
git tag A &&
git checkout -b B A &&
git mv a c &&
echo "Completely different content" >a &&
git add a &&
git commit -m B &&
git checkout -b C A &&
echo 6 >>a &&
git add a &&
git commit -m C
)
}
test_expect_failure 'conflict caused if rename not detected' '
test_setup_break_detection_1 &&
(
cd break-detection-1 &&
git checkout -q C^0 &&
git merge -s recursive B^0 &&
git ls-files -s >out &&
test_line_count = 3 out &&
git ls-files -u >out &&
test_line_count = 0 out &&
git ls-files -o >out &&
test_line_count = 1 out &&
test_line_count = 6 c &&
git rev-parse >expect \
B:a A:b &&
git rev-parse >actual \
:0:a :0:b &&
test_cmp expect actual
)
'
test_setup_break_detection_2 () {
test_create_repo break-detection-2 &&
(
cd break-detection-2 &&
printf "1\n2\n3\n4\n5\n" >a &&
echo foo >b &&
git add a b &&
git commit -m A &&
git tag A &&
git checkout -b D A &&
echo 7 >>a &&
git add a &&
git mv a c &&
echo "Completely different content" >a &&
git add a &&
git commit -m D &&
git checkout -b E A &&
git rm a &&
echo "Completely different content" >>a &&
git add a &&
git commit -m E
)
}
test_expect_failure 'missed conflict if rename not detected' '
test_setup_break_detection_2 &&
(
cd break-detection-2 &&
git checkout -q E^0 &&
test_must_fail git merge -s recursive D^0
)
'
# Tests for undetected rename/add-source causing a file to erroneously be
# deleted (and for mishandled rename/rename(1to1) causing the same issue).
#
# This test uses a rename/rename(1to1)+add-source conflict (1to1 means the
# same file is renamed on both sides to the same thing; it should trigger
# the 1to2 logic, which it would do if the add-source didn't cause issues
# for git's rename detection):
# Commit A: new file: a
# Commit B: rename a->b
# Commit C: rename a->b, add unrelated a
test_setup_break_detection_3 () {
test_create_repo break-detection-3 &&
(
cd break-detection-3 &&
printf "1\n2\n3\n4\n5\n" >a &&
git add a &&
git commit -m A &&
git tag A &&
git checkout -b B A &&
git mv a b &&
git commit -m B &&
git checkout -b C A &&
git mv a b &&
echo foobar >a &&
git add a &&
git commit -m C
)
}
test_expect_failure 'detect rename/add-source and preserve all data' '
test_setup_break_detection_3 &&
(
cd break-detection-3 &&
git checkout B^0 &&
git merge -s recursive C^0 &&
git ls-files -s >out &&
test_line_count = 2 out &&
git ls-files -u >out &&
test_line_count = 2 out &&
git ls-files -o >out &&
test_line_count = 1 out &&
test_path_is_file a &&
test_path_is_file b &&
git rev-parse >expect \
A:a C:a &&
git rev-parse >actual \
:0:b :0:a &&
test_cmp expect actual
)
'
test_expect_failure 'detect rename/add-source and preserve all data, merge other way' '
test_setup_break_detection_3 &&
(
cd break-detection-3 &&
git checkout C^0 &&
git merge -s recursive B^0 &&
git ls-files -s >out &&
test_line_count = 2 out &&
git ls-files -u >out &&
test_line_count = 2 out &&
git ls-files -o >out &&
test_line_count = 1 out &&
test_path_is_file a &&
test_path_is_file b &&
git rev-parse >expect \
A:a C:a &&
git rev-parse >actual \
:0:b :0:a &&
test_cmp expect actual
)
'
test_setup_rename_directory () {
test_create_repo rename-directory-$1 &&
(
cd rename-directory-$1 &&
printf "1\n2\n3\n4\n5\n6\n" >file &&
git add file &&
test_tick &&
git commit -m base &&
git tag base &&
git checkout -b right &&
echo 7 >>file &&
mkdir newfile &&
echo junk >newfile/realfile &&
git add file newfile/realfile &&
test_tick &&
git commit -m right &&
git checkout -b left-conflict base &&
echo 8 >>file &&
git add file &&
git mv file newfile &&
test_tick &&
git commit -m left &&
git checkout -b left-clean base &&
echo 0 >newfile &&
cat file >>newfile &&
git add newfile &&
git rm file &&
test_tick &&
git commit -m left
)
}
t6042: Add tests for content issues with modify/rename/directory conflicts Add testcases that cover a variety of merge issues with files being renamed and modified on different sides of history, when there are directories possibly conflicting with the rename location. Case 1: On one side of history, a file is modified and a new directory is added. On the other side of history, the file is modified in a non-conflicting way but is renamed to the location of the new directory. Case 2: [Same as case 1, but there is also a content conflict. In detail:] On one side of history, a file is modified and a new directory is added. On the other side of history, the file is modified in a conflicting way and it is renamed to the location of the new directory. Case 3: [Similar to case 1, but the "conflicting" directory is the directory where the file original resided. In detail:] On one side of history, a file is modified. On the other side of history, the file is modified in a non-conflicting way, but the directory it was under is removed and the file is renamed to the location of the directory it used to reside in (i.e. 'sub/file' gets renamed to 'sub'). This is flagged as a directory/rename conflict, but should be able to be resolved since the directory can be cleanly removed by the merge. One branch renames a file and makes a file where the directory the renamed file used to be in, and the other branch updates the file in place. Merging them should resolve it cleanly as long as the content level change on the branches do not overlap and rename is detected, or should leave conflict without losing information. Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2011-08-12 13:19:38 +08:00
test_expect_success 'rename/directory conflict + clean content merge' '
test_setup_rename_directory 1a &&
(
cd rename-directory-1a &&
t6042: Add tests for content issues with modify/rename/directory conflicts Add testcases that cover a variety of merge issues with files being renamed and modified on different sides of history, when there are directories possibly conflicting with the rename location. Case 1: On one side of history, a file is modified and a new directory is added. On the other side of history, the file is modified in a non-conflicting way but is renamed to the location of the new directory. Case 2: [Same as case 1, but there is also a content conflict. In detail:] On one side of history, a file is modified and a new directory is added. On the other side of history, the file is modified in a conflicting way and it is renamed to the location of the new directory. Case 3: [Similar to case 1, but the "conflicting" directory is the directory where the file original resided. In detail:] On one side of history, a file is modified. On the other side of history, the file is modified in a non-conflicting way, but the directory it was under is removed and the file is renamed to the location of the directory it used to reside in (i.e. 'sub/file' gets renamed to 'sub'). This is flagged as a directory/rename conflict, but should be able to be resolved since the directory can be cleanly removed by the merge. One branch renames a file and makes a file where the directory the renamed file used to be in, and the other branch updates the file in place. Merging them should resolve it cleanly as long as the content level change on the branches do not overlap and rename is detected, or should leave conflict without losing information. Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2011-08-12 13:19:38 +08:00
git checkout left-clean^0 &&
t6042: Add tests for content issues with modify/rename/directory conflicts Add testcases that cover a variety of merge issues with files being renamed and modified on different sides of history, when there are directories possibly conflicting with the rename location. Case 1: On one side of history, a file is modified and a new directory is added. On the other side of history, the file is modified in a non-conflicting way but is renamed to the location of the new directory. Case 2: [Same as case 1, but there is also a content conflict. In detail:] On one side of history, a file is modified and a new directory is added. On the other side of history, the file is modified in a conflicting way and it is renamed to the location of the new directory. Case 3: [Similar to case 1, but the "conflicting" directory is the directory where the file original resided. In detail:] On one side of history, a file is modified. On the other side of history, the file is modified in a non-conflicting way, but the directory it was under is removed and the file is renamed to the location of the directory it used to reside in (i.e. 'sub/file' gets renamed to 'sub'). This is flagged as a directory/rename conflict, but should be able to be resolved since the directory can be cleanly removed by the merge. One branch renames a file and makes a file where the directory the renamed file used to be in, and the other branch updates the file in place. Merging them should resolve it cleanly as long as the content level change on the branches do not overlap and rename is detected, or should leave conflict without losing information. Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2011-08-12 13:19:38 +08:00
test_must_fail git merge -s recursive right^0 &&
t6042: Add tests for content issues with modify/rename/directory conflicts Add testcases that cover a variety of merge issues with files being renamed and modified on different sides of history, when there are directories possibly conflicting with the rename location. Case 1: On one side of history, a file is modified and a new directory is added. On the other side of history, the file is modified in a non-conflicting way but is renamed to the location of the new directory. Case 2: [Same as case 1, but there is also a content conflict. In detail:] On one side of history, a file is modified and a new directory is added. On the other side of history, the file is modified in a conflicting way and it is renamed to the location of the new directory. Case 3: [Similar to case 1, but the "conflicting" directory is the directory where the file original resided. In detail:] On one side of history, a file is modified. On the other side of history, the file is modified in a non-conflicting way, but the directory it was under is removed and the file is renamed to the location of the directory it used to reside in (i.e. 'sub/file' gets renamed to 'sub'). This is flagged as a directory/rename conflict, but should be able to be resolved since the directory can be cleanly removed by the merge. One branch renames a file and makes a file where the directory the renamed file used to be in, and the other branch updates the file in place. Merging them should resolve it cleanly as long as the content level change on the branches do not overlap and rename is detected, or should leave conflict without losing information. Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2011-08-12 13:19:38 +08:00
git ls-files -s >out &&
test_line_count = 2 out &&
git ls-files -u >out &&
test_line_count = 1 out &&
git ls-files -o >out &&
merge tests: expect improved directory/file conflict handling in ort merge-recursive.c is built on the idea of running unpack_trees() and then "doing minor touch-ups" to get the result. Unfortunately, unpack_trees() was run in an update-as-it-goes mode, leading merge-recursive.c to follow suit and end up with an immediate evaluation and fix-it-up-as-you-go design. Some things like directory/file conflicts are not well representable in the index data structure, and required special extra code to handle. But then when it was discovered that rename/delete conflicts could also be involved in directory/file conflicts, the special directory/file conflict handling code had to be copied to the rename/delete codepath. ...and then it had to be copied for modify/delete, and for rename/rename(1to2) conflicts, ...and yet it still missed some. Further, when it was discovered that there were also file/submodule conflicts and submodule/directory conflicts, we needed to copy the special submodule handling code to all the special cases throughout the codebase. And then it was discovered that our handling of directory/file conflicts was suboptimal because it would create untracked files to store the contents of the conflicting file, which would not be cleaned up if someone were to run a 'git merge --abort' or 'git rebase --abort'. It was also difficult or scary to try to add or remove the index entries corresponding to these files given the directory/file conflict in the index. But changing merge-recursive.c to handle these correctly was a royal pain because there were so many sites in the code with similar but not identical code for handling directory/file/submodule conflicts that would all need to be updated. I have worked hard to push all directory/file/submodule conflict handling in merge-ort through a single codepath, and avoid creating untracked files for storing tracked content (it does record things at alternate paths, but makes sure they have higher-order stages in the index). Since updating merge-recursive is too much work and we don't want to destabilize it, instead update the testsuite to have different expectations for relevant directory/file/submodule conflict tests. Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-10-27 01:01:37 +08:00
if test "$GIT_TEST_MERGE_ALGORITHM" = ort
then
test_line_count = 1 out
else
test_line_count = 2 out
fi &&
t6042: Add tests for content issues with modify/rename/directory conflicts Add testcases that cover a variety of merge issues with files being renamed and modified on different sides of history, when there are directories possibly conflicting with the rename location. Case 1: On one side of history, a file is modified and a new directory is added. On the other side of history, the file is modified in a non-conflicting way but is renamed to the location of the new directory. Case 2: [Same as case 1, but there is also a content conflict. In detail:] On one side of history, a file is modified and a new directory is added. On the other side of history, the file is modified in a conflicting way and it is renamed to the location of the new directory. Case 3: [Similar to case 1, but the "conflicting" directory is the directory where the file original resided. In detail:] On one side of history, a file is modified. On the other side of history, the file is modified in a non-conflicting way, but the directory it was under is removed and the file is renamed to the location of the directory it used to reside in (i.e. 'sub/file' gets renamed to 'sub'). This is flagged as a directory/rename conflict, but should be able to be resolved since the directory can be cleanly removed by the merge. One branch renames a file and makes a file where the directory the renamed file used to be in, and the other branch updates the file in place. Merging them should resolve it cleanly as long as the content level change on the branches do not overlap and rename is detected, or should leave conflict without losing information. Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2011-08-12 13:19:38 +08:00
echo 0 >expect &&
git cat-file -p base:file >>expect &&
echo 7 >>expect &&
test_cmp expect newfile~HEAD &&
t6042: Add tests for content issues with modify/rename/directory conflicts Add testcases that cover a variety of merge issues with files being renamed and modified on different sides of history, when there are directories possibly conflicting with the rename location. Case 1: On one side of history, a file is modified and a new directory is added. On the other side of history, the file is modified in a non-conflicting way but is renamed to the location of the new directory. Case 2: [Same as case 1, but there is also a content conflict. In detail:] On one side of history, a file is modified and a new directory is added. On the other side of history, the file is modified in a conflicting way and it is renamed to the location of the new directory. Case 3: [Similar to case 1, but the "conflicting" directory is the directory where the file original resided. In detail:] On one side of history, a file is modified. On the other side of history, the file is modified in a non-conflicting way, but the directory it was under is removed and the file is renamed to the location of the directory it used to reside in (i.e. 'sub/file' gets renamed to 'sub'). This is flagged as a directory/rename conflict, but should be able to be resolved since the directory can be cleanly removed by the merge. One branch renames a file and makes a file where the directory the renamed file used to be in, and the other branch updates the file in place. Merging them should resolve it cleanly as long as the content level change on the branches do not overlap and rename is detected, or should leave conflict without losing information. Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2011-08-12 13:19:38 +08:00
test_path_is_file newfile/realfile &&
test_path_is_file newfile~HEAD
)
t6042: Add tests for content issues with modify/rename/directory conflicts Add testcases that cover a variety of merge issues with files being renamed and modified on different sides of history, when there are directories possibly conflicting with the rename location. Case 1: On one side of history, a file is modified and a new directory is added. On the other side of history, the file is modified in a non-conflicting way but is renamed to the location of the new directory. Case 2: [Same as case 1, but there is also a content conflict. In detail:] On one side of history, a file is modified and a new directory is added. On the other side of history, the file is modified in a conflicting way and it is renamed to the location of the new directory. Case 3: [Similar to case 1, but the "conflicting" directory is the directory where the file original resided. In detail:] On one side of history, a file is modified. On the other side of history, the file is modified in a non-conflicting way, but the directory it was under is removed and the file is renamed to the location of the directory it used to reside in (i.e. 'sub/file' gets renamed to 'sub'). This is flagged as a directory/rename conflict, but should be able to be resolved since the directory can be cleanly removed by the merge. One branch renames a file and makes a file where the directory the renamed file used to be in, and the other branch updates the file in place. Merging them should resolve it cleanly as long as the content level change on the branches do not overlap and rename is detected, or should leave conflict without losing information. Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2011-08-12 13:19:38 +08:00
'
test_expect_success 'rename/directory conflict + content merge conflict' '
test_setup_rename_directory 1b &&
(
cd rename-directory-1b &&
git reset --hard &&
git clean -fdqx &&
git checkout left-conflict^0 &&
test_must_fail git merge -s recursive right^0 &&
git ls-files -s >out &&
test_line_count = 4 out &&
git ls-files -u >out &&
test_line_count = 3 out &&
git ls-files -o >out &&
merge tests: expect improved directory/file conflict handling in ort merge-recursive.c is built on the idea of running unpack_trees() and then "doing minor touch-ups" to get the result. Unfortunately, unpack_trees() was run in an update-as-it-goes mode, leading merge-recursive.c to follow suit and end up with an immediate evaluation and fix-it-up-as-you-go design. Some things like directory/file conflicts are not well representable in the index data structure, and required special extra code to handle. But then when it was discovered that rename/delete conflicts could also be involved in directory/file conflicts, the special directory/file conflict handling code had to be copied to the rename/delete codepath. ...and then it had to be copied for modify/delete, and for rename/rename(1to2) conflicts, ...and yet it still missed some. Further, when it was discovered that there were also file/submodule conflicts and submodule/directory conflicts, we needed to copy the special submodule handling code to all the special cases throughout the codebase. And then it was discovered that our handling of directory/file conflicts was suboptimal because it would create untracked files to store the contents of the conflicting file, which would not be cleaned up if someone were to run a 'git merge --abort' or 'git rebase --abort'. It was also difficult or scary to try to add or remove the index entries corresponding to these files given the directory/file conflict in the index. But changing merge-recursive.c to handle these correctly was a royal pain because there were so many sites in the code with similar but not identical code for handling directory/file/submodule conflicts that would all need to be updated. I have worked hard to push all directory/file/submodule conflict handling in merge-ort through a single codepath, and avoid creating untracked files for storing tracked content (it does record things at alternate paths, but makes sure they have higher-order stages in the index). Since updating merge-recursive is too much work and we don't want to destabilize it, instead update the testsuite to have different expectations for relevant directory/file/submodule conflict tests. Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-10-27 01:01:37 +08:00
if test "$GIT_TEST_MERGE_ALGORITHM" = ort
then
test_line_count = 1 out
else
test_line_count = 2 out
fi &&
git cat-file -p left-conflict:newfile >left &&
git cat-file -p base:file >base &&
git cat-file -p right:file >right &&
test_must_fail git merge-file \
-L "HEAD:newfile" \
-L "" \
-L "right^0:file" \
left base right &&
test_cmp left newfile~HEAD &&
merge tests: expect improved directory/file conflict handling in ort merge-recursive.c is built on the idea of running unpack_trees() and then "doing minor touch-ups" to get the result. Unfortunately, unpack_trees() was run in an update-as-it-goes mode, leading merge-recursive.c to follow suit and end up with an immediate evaluation and fix-it-up-as-you-go design. Some things like directory/file conflicts are not well representable in the index data structure, and required special extra code to handle. But then when it was discovered that rename/delete conflicts could also be involved in directory/file conflicts, the special directory/file conflict handling code had to be copied to the rename/delete codepath. ...and then it had to be copied for modify/delete, and for rename/rename(1to2) conflicts, ...and yet it still missed some. Further, when it was discovered that there were also file/submodule conflicts and submodule/directory conflicts, we needed to copy the special submodule handling code to all the special cases throughout the codebase. And then it was discovered that our handling of directory/file conflicts was suboptimal because it would create untracked files to store the contents of the conflicting file, which would not be cleaned up if someone were to run a 'git merge --abort' or 'git rebase --abort'. It was also difficult or scary to try to add or remove the index entries corresponding to these files given the directory/file conflict in the index. But changing merge-recursive.c to handle these correctly was a royal pain because there were so many sites in the code with similar but not identical code for handling directory/file/submodule conflicts that would all need to be updated. I have worked hard to push all directory/file/submodule conflict handling in merge-ort through a single codepath, and avoid creating untracked files for storing tracked content (it does record things at alternate paths, but makes sure they have higher-order stages in the index). Since updating merge-recursive is too much work and we don't want to destabilize it, instead update the testsuite to have different expectations for relevant directory/file/submodule conflict tests. Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-10-27 01:01:37 +08:00
git rev-parse >expect \
base:file left-conflict:newfile right:file &&
if test "$GIT_TEST_MERGE_ALGORITHM" = ort
then
git rev-parse >actual \
:1:newfile~HEAD :2:newfile~HEAD :3:newfile~HEAD
else
git rev-parse >actual \
:1:newfile :2:newfile :3:newfile
fi &&
test_cmp expect actual &&
test_path_is_file newfile/realfile &&
test_path_is_file newfile~HEAD
)
t6042: Add tests for content issues with modify/rename/directory conflicts Add testcases that cover a variety of merge issues with files being renamed and modified on different sides of history, when there are directories possibly conflicting with the rename location. Case 1: On one side of history, a file is modified and a new directory is added. On the other side of history, the file is modified in a non-conflicting way but is renamed to the location of the new directory. Case 2: [Same as case 1, but there is also a content conflict. In detail:] On one side of history, a file is modified and a new directory is added. On the other side of history, the file is modified in a conflicting way and it is renamed to the location of the new directory. Case 3: [Similar to case 1, but the "conflicting" directory is the directory where the file original resided. In detail:] On one side of history, a file is modified. On the other side of history, the file is modified in a non-conflicting way, but the directory it was under is removed and the file is renamed to the location of the directory it used to reside in (i.e. 'sub/file' gets renamed to 'sub'). This is flagged as a directory/rename conflict, but should be able to be resolved since the directory can be cleanly removed by the merge. One branch renames a file and makes a file where the directory the renamed file used to be in, and the other branch updates the file in place. Merging them should resolve it cleanly as long as the content level change on the branches do not overlap and rename is detected, or should leave conflict without losing information. Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2011-08-12 13:19:38 +08:00
'
test_setup_rename_directory_2 () {
test_create_repo rename-directory-2 &&
(
cd rename-directory-2 &&
mkdir sub &&
printf "1\n2\n3\n4\n5\n6\n" >sub/file &&
git add sub/file &&
test_tick &&
git commit -m base &&
git tag base &&
git checkout -b right &&
echo 7 >>sub/file &&
git add sub/file &&
test_tick &&
git commit -m right &&
git checkout -b left base &&
echo 0 >newfile &&
cat sub/file >>newfile &&
git rm sub/file &&
mv newfile sub &&
git add sub &&
test_tick &&
git commit -m left
)
}
t6042: Add tests for content issues with modify/rename/directory conflicts Add testcases that cover a variety of merge issues with files being renamed and modified on different sides of history, when there are directories possibly conflicting with the rename location. Case 1: On one side of history, a file is modified and a new directory is added. On the other side of history, the file is modified in a non-conflicting way but is renamed to the location of the new directory. Case 2: [Same as case 1, but there is also a content conflict. In detail:] On one side of history, a file is modified and a new directory is added. On the other side of history, the file is modified in a conflicting way and it is renamed to the location of the new directory. Case 3: [Similar to case 1, but the "conflicting" directory is the directory where the file original resided. In detail:] On one side of history, a file is modified. On the other side of history, the file is modified in a non-conflicting way, but the directory it was under is removed and the file is renamed to the location of the directory it used to reside in (i.e. 'sub/file' gets renamed to 'sub'). This is flagged as a directory/rename conflict, but should be able to be resolved since the directory can be cleanly removed by the merge. One branch renames a file and makes a file where the directory the renamed file used to be in, and the other branch updates the file in place. Merging them should resolve it cleanly as long as the content level change on the branches do not overlap and rename is detected, or should leave conflict without losing information. Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2011-08-12 13:19:38 +08:00
test_expect_success 'disappearing dir in rename/directory conflict handled' '
test_setup_rename_directory_2 &&
(
cd rename-directory-2 &&
t6042: Add tests for content issues with modify/rename/directory conflicts Add testcases that cover a variety of merge issues with files being renamed and modified on different sides of history, when there are directories possibly conflicting with the rename location. Case 1: On one side of history, a file is modified and a new directory is added. On the other side of history, the file is modified in a non-conflicting way but is renamed to the location of the new directory. Case 2: [Same as case 1, but there is also a content conflict. In detail:] On one side of history, a file is modified and a new directory is added. On the other side of history, the file is modified in a conflicting way and it is renamed to the location of the new directory. Case 3: [Similar to case 1, but the "conflicting" directory is the directory where the file original resided. In detail:] On one side of history, a file is modified. On the other side of history, the file is modified in a non-conflicting way, but the directory it was under is removed and the file is renamed to the location of the directory it used to reside in (i.e. 'sub/file' gets renamed to 'sub'). This is flagged as a directory/rename conflict, but should be able to be resolved since the directory can be cleanly removed by the merge. One branch renames a file and makes a file where the directory the renamed file used to be in, and the other branch updates the file in place. Merging them should resolve it cleanly as long as the content level change on the branches do not overlap and rename is detected, or should leave conflict without losing information. Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2011-08-12 13:19:38 +08:00
git checkout left^0 &&
t6042: Add tests for content issues with modify/rename/directory conflicts Add testcases that cover a variety of merge issues with files being renamed and modified on different sides of history, when there are directories possibly conflicting with the rename location. Case 1: On one side of history, a file is modified and a new directory is added. On the other side of history, the file is modified in a non-conflicting way but is renamed to the location of the new directory. Case 2: [Same as case 1, but there is also a content conflict. In detail:] On one side of history, a file is modified and a new directory is added. On the other side of history, the file is modified in a conflicting way and it is renamed to the location of the new directory. Case 3: [Similar to case 1, but the "conflicting" directory is the directory where the file original resided. In detail:] On one side of history, a file is modified. On the other side of history, the file is modified in a non-conflicting way, but the directory it was under is removed and the file is renamed to the location of the directory it used to reside in (i.e. 'sub/file' gets renamed to 'sub'). This is flagged as a directory/rename conflict, but should be able to be resolved since the directory can be cleanly removed by the merge. One branch renames a file and makes a file where the directory the renamed file used to be in, and the other branch updates the file in place. Merging them should resolve it cleanly as long as the content level change on the branches do not overlap and rename is detected, or should leave conflict without losing information. Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2011-08-12 13:19:38 +08:00
git merge -s recursive right^0 &&
t6042: Add tests for content issues with modify/rename/directory conflicts Add testcases that cover a variety of merge issues with files being renamed and modified on different sides of history, when there are directories possibly conflicting with the rename location. Case 1: On one side of history, a file is modified and a new directory is added. On the other side of history, the file is modified in a non-conflicting way but is renamed to the location of the new directory. Case 2: [Same as case 1, but there is also a content conflict. In detail:] On one side of history, a file is modified and a new directory is added. On the other side of history, the file is modified in a conflicting way and it is renamed to the location of the new directory. Case 3: [Similar to case 1, but the "conflicting" directory is the directory where the file original resided. In detail:] On one side of history, a file is modified. On the other side of history, the file is modified in a non-conflicting way, but the directory it was under is removed and the file is renamed to the location of the directory it used to reside in (i.e. 'sub/file' gets renamed to 'sub'). This is flagged as a directory/rename conflict, but should be able to be resolved since the directory can be cleanly removed by the merge. One branch renames a file and makes a file where the directory the renamed file used to be in, and the other branch updates the file in place. Merging them should resolve it cleanly as long as the content level change on the branches do not overlap and rename is detected, or should leave conflict without losing information. Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2011-08-12 13:19:38 +08:00
git ls-files -s >out &&
test_line_count = 1 out &&
git ls-files -u >out &&
test_line_count = 0 out &&
git ls-files -o >out &&
test_line_count = 1 out &&
t6042: Add tests for content issues with modify/rename/directory conflicts Add testcases that cover a variety of merge issues with files being renamed and modified on different sides of history, when there are directories possibly conflicting with the rename location. Case 1: On one side of history, a file is modified and a new directory is added. On the other side of history, the file is modified in a non-conflicting way but is renamed to the location of the new directory. Case 2: [Same as case 1, but there is also a content conflict. In detail:] On one side of history, a file is modified and a new directory is added. On the other side of history, the file is modified in a conflicting way and it is renamed to the location of the new directory. Case 3: [Similar to case 1, but the "conflicting" directory is the directory where the file original resided. In detail:] On one side of history, a file is modified. On the other side of history, the file is modified in a non-conflicting way, but the directory it was under is removed and the file is renamed to the location of the directory it used to reside in (i.e. 'sub/file' gets renamed to 'sub'). This is flagged as a directory/rename conflict, but should be able to be resolved since the directory can be cleanly removed by the merge. One branch renames a file and makes a file where the directory the renamed file used to be in, and the other branch updates the file in place. Merging them should resolve it cleanly as long as the content level change on the branches do not overlap and rename is detected, or should leave conflict without losing information. Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2011-08-12 13:19:38 +08:00
echo 0 >expect &&
git cat-file -p base:sub/file >>expect &&
echo 7 >>expect &&
test_cmp expect sub &&
t6042: Add tests for content issues with modify/rename/directory conflicts Add testcases that cover a variety of merge issues with files being renamed and modified on different sides of history, when there are directories possibly conflicting with the rename location. Case 1: On one side of history, a file is modified and a new directory is added. On the other side of history, the file is modified in a non-conflicting way but is renamed to the location of the new directory. Case 2: [Same as case 1, but there is also a content conflict. In detail:] On one side of history, a file is modified and a new directory is added. On the other side of history, the file is modified in a conflicting way and it is renamed to the location of the new directory. Case 3: [Similar to case 1, but the "conflicting" directory is the directory where the file original resided. In detail:] On one side of history, a file is modified. On the other side of history, the file is modified in a non-conflicting way, but the directory it was under is removed and the file is renamed to the location of the directory it used to reside in (i.e. 'sub/file' gets renamed to 'sub'). This is flagged as a directory/rename conflict, but should be able to be resolved since the directory can be cleanly removed by the merge. One branch renames a file and makes a file where the directory the renamed file used to be in, and the other branch updates the file in place. Merging them should resolve it cleanly as long as the content level change on the branches do not overlap and rename is detected, or should leave conflict without losing information. Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2011-08-12 13:19:38 +08:00
test_path_is_file sub
)
t6042: Add tests for content issues with modify/rename/directory conflicts Add testcases that cover a variety of merge issues with files being renamed and modified on different sides of history, when there are directories possibly conflicting with the rename location. Case 1: On one side of history, a file is modified and a new directory is added. On the other side of history, the file is modified in a non-conflicting way but is renamed to the location of the new directory. Case 2: [Same as case 1, but there is also a content conflict. In detail:] On one side of history, a file is modified and a new directory is added. On the other side of history, the file is modified in a conflicting way and it is renamed to the location of the new directory. Case 3: [Similar to case 1, but the "conflicting" directory is the directory where the file original resided. In detail:] On one side of history, a file is modified. On the other side of history, the file is modified in a non-conflicting way, but the directory it was under is removed and the file is renamed to the location of the directory it used to reside in (i.e. 'sub/file' gets renamed to 'sub'). This is flagged as a directory/rename conflict, but should be able to be resolved since the directory can be cleanly removed by the merge. One branch renames a file and makes a file where the directory the renamed file used to be in, and the other branch updates the file in place. Merging them should resolve it cleanly as long as the content level change on the branches do not overlap and rename is detected, or should leave conflict without losing information. Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2011-08-12 13:19:38 +08:00
'
# Test for basic rename/add-dest conflict, with rename needing content merge:
# Commit O: a
# Commit A: rename a->b, modifying b too
# Commit B: modify a, add different b
test_setup_rename_with_content_merge_and_add () {
test_create_repo rename-with-content-merge-and-add-$1 &&
(
cd rename-with-content-merge-and-add-$1 &&
test_seq 1 5 >a &&
git add a &&
git commit -m O &&
git tag O &&
git checkout -b A O &&
git mv a b &&
test_seq 0 5 >b &&
git add b &&
git commit -m A &&
git checkout -b B O &&
echo 6 >>a &&
echo hello world >b &&
git add a b &&
git commit -m B
)
}
test_expect_success 'handle rename-with-content-merge vs. add' '
test_setup_rename_with_content_merge_and_add AB &&
(
cd rename-with-content-merge-and-add-AB &&
git checkout A^0 &&
test_must_fail git merge -s recursive B^0 >out &&
test_i18ngrep "CONFLICT (.*/add)" out &&
git ls-files -s >out &&
test_line_count = 2 out &&
git ls-files -u >out &&
test_line_count = 2 out &&
# Also, make sure both unmerged entries are for "b"
git ls-files -u b >out &&
test_line_count = 2 out &&
git ls-files -o >out &&
test_line_count = 1 out &&
test_path_is_missing a &&
test_path_is_file b &&
test_seq 0 6 >tmp &&
git hash-object tmp >expect &&
git rev-parse B:b >>expect &&
git rev-parse >actual \
:2:b :3:b &&
test_cmp expect actual &&
# Test that the two-way merge in b is as expected
git cat-file -p :2:b >>ours &&
git cat-file -p :3:b >>theirs &&
>empty &&
test_must_fail git merge-file \
-L "HEAD" \
-L "" \
-L "B^0" \
ours empty theirs &&
test_cmp ours b
)
'
test_expect_success 'handle rename-with-content-merge vs. add, merge other way' '
test_setup_rename_with_content_merge_and_add BA &&
(
cd rename-with-content-merge-and-add-BA &&
git reset --hard &&
git clean -fdx &&
git checkout B^0 &&
test_must_fail git merge -s recursive A^0 >out &&
test_i18ngrep "CONFLICT (.*/add)" out &&
git ls-files -s >out &&
test_line_count = 2 out &&
git ls-files -u >out &&
test_line_count = 2 out &&
# Also, make sure both unmerged entries are for "b"
git ls-files -u b >out &&
test_line_count = 2 out &&
git ls-files -o >out &&
test_line_count = 1 out &&
test_path_is_missing a &&
test_path_is_file b &&
test_seq 0 6 >tmp &&
git rev-parse B:b >expect &&
git hash-object tmp >>expect &&
git rev-parse >actual \
:2:b :3:b &&
test_cmp expect actual &&
# Test that the two-way merge in b is as expected
git cat-file -p :2:b >>ours &&
git cat-file -p :3:b >>theirs &&
>empty &&
test_must_fail git merge-file \
-L "HEAD" \
-L "" \
-L "A^0" \
ours empty theirs &&
test_cmp ours b
)
'
# Test for all kinds of things that can go wrong with rename/rename (2to1):
# Commit A: new files: a & b
# Commit B: rename a->c, modify b
# Commit C: rename b->c, modify a
#
# Merging of B & C should NOT be clean. Questions:
# * Both a & b should be removed by the merge; are they?
# * The two c's should contain modifications to a & b; do they?
# * The index should contain two files, both for c; does it?
# * The working copy should have two files, both of form c~<unique>; does it?
# * Nothing else should be present. Is anything?
test_setup_rename_rename_2to1 () {
test_create_repo rename-rename-2to1 &&
(
cd rename-rename-2to1 &&
printf "1\n2\n3\n4\n5\n" >a &&
printf "5\n4\n3\n2\n1\n" >b &&
git add a b &&
git commit -m A &&
git tag A &&
git checkout -b B A &&
git mv a c &&
echo 0 >>b &&
git add b &&
git commit -m B &&
git checkout -b C A &&
git mv b c &&
echo 6 >>a &&
git add a &&
git commit -m C
)
}
merge-recursive: Consider modifications in rename/rename(2to1) conflicts Our previous conflict resolution for renaming two different files to the same name ignored the fact that each of those files may have modifications from both sides of history to consider. We need to do a three-way merge for each of those files, and then handle the conflict of both sets of merged contents trying to be recorded with the same name. It is important to note that this changes our strategy in the recursive case. After doing a three-way content merge of each of the files involved, we still are faced with the fact that we are trying to put both of the results (including conflict markers) into the same path. We could do another two-way merge, but I think that becomes confusing. Also, taking a hint from the modify/delete and rename/delete cases we handled earlier, a more useful "common ground" would be to keep the three-way content merge but record it with the original filename. The renames can still be detected, we just allow it to be done in the o->call_depth=0 case. This seems to result in simpler & easier to understand merge conflicts as well, as evidenced by some of the changes needed in our testsuite in t6036. (However, it should be noted that this change will cause problems those renames also occur along with a file being added whose name matches the source of the rename. Since git currently cannot detect rename/add-source situations, though, this codepath is not currently used for those cases anyway. Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2011-08-12 13:20:18 +08:00
test_expect_success 'handle rename/rename (2to1) conflict correctly' '
test_setup_rename_rename_2to1 &&
(
cd rename-rename-2to1 &&
git checkout B^0 &&
test_must_fail git merge -s recursive C^0 >out &&
test_i18ngrep "CONFLICT (\(.*\)/\1)" out &&
git ls-files -s >out &&
test_line_count = 2 out &&
git ls-files -u >out &&
test_line_count = 2 out &&
git ls-files -u c >out &&
test_line_count = 2 out &&
git ls-files -o >out &&
test_line_count = 1 out &&
test_path_is_missing a &&
test_path_is_missing b &&
git rev-parse >expect \
C:a B:b &&
git rev-parse >actual \
:2:c :3:c &&
test_cmp expect actual &&
# Test that the two-way merge in new_a is as expected
git cat-file -p :2:c >>ours &&
git cat-file -p :3:c >>theirs &&
>empty &&
test_must_fail git merge-file \
-L "HEAD" \
-L "" \
-L "C^0" \
ours empty theirs &&
git hash-object c >actual &&
git hash-object ours >expect &&
test_cmp expect actual
)
'
# Testcase setup for simple rename/rename (1to2) conflict:
# Commit A: new file: a
# Commit B: rename a->b
# Commit C: rename a->c
test_setup_rename_rename_1to2 () {
test_create_repo rename-rename-1to2 &&
(
cd rename-rename-1to2 &&
echo stuff >a &&
git add a &&
test_tick &&
git commit -m A &&
git tag A &&
git checkout -b B A &&
git mv a b &&
test_tick &&
git commit -m B &&
git checkout -b C A &&
git mv a c &&
test_tick &&
git commit -m C
)
}
test_expect_success 'merge has correct working tree contents' '
test_setup_rename_rename_1to2 &&
(
cd rename-rename-1to2 &&
git checkout C^0 &&
test_must_fail git merge -s recursive B^0 &&
git ls-files -s >out &&
test_line_count = 3 out &&
git ls-files -u >out &&
test_line_count = 3 out &&
git ls-files -o >out &&
test_line_count = 1 out &&
test_path_is_missing a &&
git rev-parse >expect \
A:a A:a A:a \
A:a A:a &&
git rev-parse >actual \
:1:a :3:b :2:c &&
git hash-object >>actual \
b c &&
test_cmp expect actual
)
'
# Testcase setup for rename/rename(1to2)/add-source conflict:
# Commit A: new file: a
# Commit B: rename a->b
# Commit C: rename a->c, add completely different a
#
# Merging of B & C should NOT be clean; there's a rename/rename conflict
test_setup_rename_rename_1to2_add_source_1 () {
test_create_repo rename-rename-1to2-add-source-1 &&
(
cd rename-rename-1to2-add-source-1 &&
printf "1\n2\n3\n4\n5\n6\n7\n" >a &&
git add a &&
git commit -m A &&
git tag A &&
git checkout -b B A &&
git mv a b &&
git commit -m B &&
git checkout -b C A &&
git mv a c &&
echo something completely different >a &&
git add a &&
git commit -m C
)
}
test_expect_failure 'detect conflict with rename/rename(1to2)/add-source merge' '
test_setup_rename_rename_1to2_add_source_1 &&
(
cd rename-rename-1to2-add-source-1 &&
git checkout B^0 &&
test_must_fail git merge -s recursive C^0 &&
git ls-files -s >out &&
test_line_count = 4 out &&
git ls-files -o >out &&
test_line_count = 1 out &&
git rev-parse >expect \
C:a A:a B:b C:C &&
git rev-parse >actual \
:3:a :1:a :2:b :3:c &&
test_cmp expect actual &&
test_path_is_file a &&
test_path_is_file b &&
test_path_is_file c
)
'
test_setup_rename_rename_1to2_add_source_2 () {
test_create_repo rename-rename-1to2-add-source-2 &&
(
cd rename-rename-1to2-add-source-2 &&
>a &&
git add a &&
test_tick &&
git commit -m base &&
git tag A &&
git checkout -b B A &&
git mv a b &&
test_tick &&
git commit -m one &&
git checkout -b C A &&
git mv a b &&
echo important-info >a &&
git add a &&
test_tick &&
git commit -m two
)
}
test_expect_failure 'rename/rename/add-source still tracks new a file' '
test_setup_rename_rename_1to2_add_source_2 &&
(
cd rename-rename-1to2-add-source-2 &&
git checkout C^0 &&
git merge -s recursive B^0 &&
git ls-files -s >out &&
test_line_count = 2 out &&
git ls-files -o >out &&
test_line_count = 1 out &&
git rev-parse >expect \
C:a A:a &&
git rev-parse >actual \
:0:a :0:b &&
test_cmp expect actual
)
'
test_setup_rename_rename_1to2_add_dest () {
test_create_repo rename-rename-1to2-add-dest &&
(
cd rename-rename-1to2-add-dest &&
echo stuff >a &&
git add a &&
test_tick &&
git commit -m base &&
git tag A &&
git checkout -b B A &&
git mv a b &&
echo precious-data >c &&
git add c &&
test_tick &&
git commit -m one &&
git checkout -b C A &&
git mv a c &&
echo important-info >b &&
git add b &&
test_tick &&
git commit -m two
)
}
test_expect_success 'rename/rename/add-dest merge still knows about conflicting file versions' '
test_setup_rename_rename_1to2_add_dest &&
(
cd rename-rename-1to2-add-dest &&
git checkout C^0 &&
test_must_fail git merge -s recursive B^0 &&
git ls-files -s >out &&
test_line_count = 5 out &&
git ls-files -u b >out &&
test_line_count = 2 out &&
git ls-files -u c >out &&
test_line_count = 2 out &&
git ls-files -o >out &&
test_line_count = 1 out &&
git rev-parse >expect \
A:a C:b B:b C:c B:c &&
git rev-parse >actual \
:1:a :2:b :3:b :2:c :3:c &&
test_cmp expect actual &&
# Record some contents for re-doing merges
git cat-file -p A:a >stuff &&
git cat-file -p C:b >important_info &&
git cat-file -p B:c >precious_data &&
>empty &&
# Test the merge in b
test_must_fail git merge-file \
-L "HEAD" \
-L "" \
-L "B^0" \
important_info empty stuff &&
test_cmp important_info b &&
# Test the merge in c
test_must_fail git merge-file \
-L "HEAD" \
-L "" \
-L "B^0" \
stuff empty precious_data &&
test_cmp stuff c
)
'
# Testcase rad, rename/add/delete
# Commit O: foo
# Commit A: rm foo, add different bar
# Commit B: rename foo->bar
# Expected: CONFLICT (rename/add/delete), two-way merged bar
test_setup_rad () {
test_create_repo rad &&
(
cd rad &&
echo "original file" >foo &&
git add foo &&
git commit -m "original" &&
git branch O &&
git branch A &&
git branch B &&
git checkout A &&
git rm foo &&
echo "different file" >bar &&
git add bar &&
git commit -m "Remove foo, add bar" &&
git checkout B &&
git mv foo bar &&
git commit -m "rename foo to bar"
)
}
test_expect_merge_algorithm failure success 'rad-check: rename/add/delete conflict' '
test_setup_rad &&
(
cd rad &&
git checkout B^0 &&
test_must_fail git merge -s recursive A^0 >out 2>err &&
# Instead of requiring the output to contain one combined line
# CONFLICT (rename/add/delete)
# or perhaps two lines:
# CONFLICT (rename/add): new file collides with rename target
# CONFLICT (rename/delete): rename source removed on other side
# and instead of requiring "rename/add" instead of "add/add",
# be flexible in the type of console output message(s) reported
# for this particular case; we will be more stringent about the
# contents of the index and working directory.
test_i18ngrep "CONFLICT (.*/add)" out &&
test_i18ngrep "CONFLICT (rename.*/delete)" out &&
test_must_be_empty err &&
git ls-files -s >file_count &&
test_line_count = 2 file_count &&
git ls-files -u >file_count &&
test_line_count = 2 file_count &&
git ls-files -o >file_count &&
test_line_count = 3 file_count &&
git rev-parse >actual \
:2:bar :3:bar &&
git rev-parse >expect \
B:bar A:bar &&
test_path_is_missing foo &&
# bar should have two-way merged contents of the different
# versions of bar; check that content from both sides is
# present.
grep original bar &&
grep different bar
)
'
# Testcase rrdd, rename/rename(2to1)/delete/delete
# Commit O: foo, bar
# Commit A: rename foo->baz, rm bar
# Commit B: rename bar->baz, rm foo
# Expected: CONFLICT (rename/rename/delete/delete), two-way merged baz
test_setup_rrdd () {
test_create_repo rrdd &&
(
cd rrdd &&
echo foo >foo &&
echo bar >bar &&
git add foo bar &&
git commit -m O &&
git branch O &&
git branch A &&
git branch B &&
git checkout A &&
git mv foo baz &&
git rm bar &&
git commit -m "Rename foo, remove bar" &&
git checkout B &&
git mv bar baz &&
git rm foo &&
git commit -m "Rename bar, remove foo"
)
}
test_expect_merge_algorithm failure success 'rrdd-check: rename/rename(2to1)/delete/delete conflict' '
test_setup_rrdd &&
(
cd rrdd &&
git checkout A^0 &&
test_must_fail git merge -s recursive B^0 >out 2>err &&
# Instead of requiring the output to contain one combined line
# CONFLICT (rename/rename/delete/delete)
# or perhaps two lines:
# CONFLICT (rename/rename): ...
# CONFLICT (rename/delete): info about pair 1
# CONFLICT (rename/delete): info about pair 2
# and instead of requiring "rename/rename" instead of "add/add",
# be flexible in the type of console output message(s) reported
# for this particular case; we will be more stringent about the
# contents of the index and working directory.
test_i18ngrep "CONFLICT (\(.*\)/\1)" out &&
test_i18ngrep "CONFLICT (rename.*delete)" out &&
test_must_be_empty err &&
git ls-files -s >file_count &&
test_line_count = 2 file_count &&
git ls-files -u >file_count &&
test_line_count = 2 file_count &&
git ls-files -o >file_count &&
test_line_count = 3 file_count &&
git rev-parse >actual \
:2:baz :3:baz &&
git rev-parse >expect \
O:foo O:bar &&
test_path_is_missing foo &&
test_path_is_missing bar &&
# baz should have two-way merged contents of the original
# contents of foo and bar; check that content from both sides
# is present.
grep foo baz &&
grep bar baz
)
'
# Testcase mod6, chains of rename/rename(1to2) and rename/rename(2to1)
# Commit O: one, three, five
# Commit A: one->two, three->four, five->six
# Commit B: one->six, three->two, five->four
# Expected: six CONFLICT(rename/rename) messages, each path in two of the
# multi-way merged contents found in two, four, six
test_setup_mod6 () {
test_create_repo mod6 &&
(
cd mod6 &&
test_seq 11 19 >one &&
test_seq 31 39 >three &&
test_seq 51 59 >five &&
git add . &&
test_tick &&
git commit -m "O" &&
git branch O &&
git branch A &&
git branch B &&
git checkout A &&
test_seq 10 19 >one &&
echo 40 >>three &&
git add one three &&
git mv one two &&
git mv three four &&
git mv five six &&
test_tick &&
git commit -m "A" &&
git checkout B &&
echo 20 >>one &&
echo forty >>three &&
echo 60 >>five &&
git add one three five &&
git mv one six &&
git mv three two &&
git mv five four &&
test_tick &&
git commit -m "B"
)
}
test_expect_merge_algorithm failure success 'mod6-check: chains of rename/rename(1to2) and rename/rename(2to1)' '
test_setup_mod6 &&
(
cd mod6 &&
git checkout A^0 &&
test_must_fail git merge -s recursive B^0 >out 2>err &&
test_i18ngrep "CONFLICT (rename/rename)" out &&
test_must_be_empty err &&
git ls-files -s >file_count &&
test_line_count = 9 file_count &&
git ls-files -u >file_count &&
test_line_count = 9 file_count &&
git ls-files -o >file_count &&
test_line_count = 3 file_count &&
test_seq 10 20 >merged-one &&
test_seq 51 60 >merged-five &&
# Determine what the merge of three would give us.
test_seq 31 39 >three-base &&
test_seq 31 40 >three-side-A &&
test_seq 31 39 >three-side-B &&
echo forty >>three-side-B &&
test_must_fail git merge-file \
-L "HEAD:four" \
-L "" \
-L "B^0:two" \
three-side-A three-base three-side-B &&
sed -e "s/^\([<=>]\)/\1\1/" three-side-A >merged-three &&
# Verify the index is as expected
git rev-parse >actual \
:2:two :3:two \
:2:four :3:four \
:2:six :3:six &&
git hash-object >expect \
merged-one merged-three \
merged-three merged-five \
merged-five merged-one &&
test_cmp expect actual &&
git cat-file -p :2:two >expect &&
git cat-file -p :3:two >other &&
>empty &&
test_must_fail git merge-file \
-L "HEAD" -L "" -L "B^0" \
expect empty other &&
test_cmp expect two &&
git cat-file -p :2:four >expect &&
git cat-file -p :3:four >other &&
test_must_fail git merge-file \
-L "HEAD" -L "" -L "B^0" \
expect empty other &&
test_cmp expect four &&
git cat-file -p :2:six >expect &&
git cat-file -p :3:six >other &&
test_must_fail git merge-file \
-L "HEAD" -L "" -L "B^0" \
expect empty other &&
test_cmp expect six
)
'
test_conflicts_with_adds_and_renames() {
sideL=$1
sideR=$2
# Setup:
# L
# / \
# main ?
# \ /
# R
#
# Where:
# Both L and R have files named 'three' which collide. Each of
# the colliding files could have been involved in a rename, in
# which case there was a file named 'one' or 'two' that was
# modified on the opposite side of history and renamed into the
# collision on this side of history.
#
# Questions:
# 1) The index should contain both a stage 2 and stage 3 entry
# for the colliding file. Does it?
# 2) When renames are involved, the content merges are clean, so
# the index should reflect the content merges, not merely the
# version of the colliding file from the prior commit. Does
# it?
# 3) There should be a file in the worktree named 'three'
# containing the two-way merged contents of the content-merged
# versions of 'three' from each of the two colliding
# files. Is it present?
# 4) There should not be any three~* files in the working
# tree
test_setup_collision_conflict () {
#test_expect_success "setup simple $sideL/$sideR conflict" '
test_create_repo simple_${sideL}_${sideR} &&
(
cd simple_${sideL}_${sideR} &&
# Create some related files now
for i in $(test_seq 1 10)
do
echo Random base content line $i
done >file_v1 &&
cp file_v1 file_v2 &&
echo modification >>file_v2 &&
cp file_v1 file_v3 &&
echo more stuff >>file_v3 &&
cp file_v3 file_v4 &&
echo yet more stuff >>file_v4 &&
# Use a tag to record both these files for simple
# access, and clean out these untracked files
git tag file_v1 $(git hash-object -w file_v1) &&
git tag file_v2 $(git hash-object -w file_v2) &&
git tag file_v3 $(git hash-object -w file_v3) &&
git tag file_v4 $(git hash-object -w file_v4) &&
git clean -f &&
# Setup original commit (or merge-base), consisting of
# files named "one" and "two" if renames were involved.
touch irrelevant_file &&
git add irrelevant_file &&
if [ $sideL = "rename" ]
then
git show file_v1 >one &&
git add one
fi &&
if [ $sideR = "rename" ]
then
git show file_v3 >two &&
git add two
fi &&
test_tick && git commit -m initial &&
git branch L &&
git branch R &&
# Handle the left side
git checkout L &&
if [ $sideL = "rename" ]
then
git mv one three
else
git show file_v2 >three &&
git add three
fi &&
if [ $sideR = "rename" ]
then
git show file_v4 >two &&
git add two
fi &&
test_tick && git commit -m L &&
# Handle the right side
git checkout R &&
if [ $sideL = "rename" ]
then
git show file_v2 >one &&
git add one
fi &&
if [ $sideR = "rename" ]
then
git mv two three
else
git show file_v4 >three &&
git add three
fi &&
test_tick && git commit -m R
)
#'
}
test_expect_success "check simple $sideL/$sideR conflict" '
test_setup_collision_conflict &&
(
cd simple_${sideL}_${sideR} &&
git checkout L^0 &&
# Merge must fail; there is a conflict
test_must_fail git merge -s recursive R^0 &&
# Make sure the index has the right number of entries
git ls-files -s >out &&
test_line_count = 3 out &&
git ls-files -u >out &&
test_line_count = 2 out &&
# Ensure we have the correct number of untracked files
git ls-files -o >out &&
test_line_count = 1 out &&
# Nothing should have touched irrelevant_file
git rev-parse >actual \
:0:irrelevant_file \
:2:three \
:3:three &&
git rev-parse >expected \
main:irrelevant_file \
file_v2 \
file_v4 &&
test_cmp expected actual &&
# Make sure we have the correct merged contents for
# three
git show file_v1 >expected &&
cat <<-\EOF >>expected &&
<<<<<<< HEAD
modification
=======
more stuff
yet more stuff
>>>>>>> R^0
EOF
test_cmp expected three
)
'
}
test_conflicts_with_adds_and_renames rename rename
test_conflicts_with_adds_and_renames rename add
test_conflicts_with_adds_and_renames add rename
test_conflicts_with_adds_and_renames add add
# Setup:
# L
# / \
# main ?
# \ /
# R
#
# Where:
# main has two files, named 'one' and 'two'.
# branches L and R both modify 'one', in conflicting ways.
# branches L and R both modify 'two', in conflicting ways.
# branch L also renames 'one' to 'three'.
# branch R also renames 'two' to 'three'.
#
# So, we have four different conflicting files that all end up at path
# 'three'.
test_setup_nested_conflicts_from_rename_rename () {
test_create_repo nested_conflicts_from_rename_rename &&
(
cd nested_conflicts_from_rename_rename &&
# Create some related files now
for i in $(test_seq 1 10)
do
echo Random base content line $i
done >file_v1 &&
cp file_v1 file_v2 &&
cp file_v1 file_v3 &&
cp file_v1 file_v4 &&
cp file_v1 file_v5 &&
cp file_v1 file_v6 &&
echo one >>file_v1 &&
echo uno >>file_v2 &&
echo eins >>file_v3 &&
echo two >>file_v4 &&
echo dos >>file_v5 &&
echo zwei >>file_v6 &&
# Setup original commit (or merge-base), consisting of
# files named "one" and "two".
mv file_v1 one &&
mv file_v4 two &&
git add one two &&
test_tick && git commit -m english &&
git branch L &&
git branch R &&
# Handle the left side
git checkout L &&
t6042: work around speed optimization on Windows When Git determines whether a file has changed, it looks at the mtime, at the file size, and to detect changes even if the mtime is the same (on Windows, the mtime granularity is 100ns, read: if two files are written within the same 100ns time slot, they have the same mtime) and even if the file size is the same, Git also looks at the inode/device numbers. This design obviously comes from a Linux background, where `lstat()` calls were designed to be cheap. On Windows, there is no `lstat()`. It has to be emulated. And while obtaining the mtime and the file size is not all that expensive (you can get both with a single `GetFileAttributesW()` call), obtaining the equivalent of the inode and device numbers is very expensive (it requires a call to `GetFileInformationByHandle()`, which in turn requires a file handle, which is *a lot* more expensive than one might imagine). As it is very uncommon for developers to modify files within 100ns time slots, Git for Windows chooses not to fill inode/device numbers properly, but simply sets them to 0. However, in t6042 the files file_v1 and file_v2 are typically written within the same 100ns time slot, and they do not differ in file size. So the minor modification is not picked up. Let's work around this issue by avoiding the `git mv` calls in the 'mod6-setup: chains of rename/rename(1to2) and rename/rename(2to1)' test case. The target files are overwritten anyway, so it is not like we really rename those files. This fixes the issue because `git add` will now add the files as new files (as opposed to existing, just renamed files). Functionally, we do not change anything because we replace two `git mv <old> <new>` calls (where `<new>` is completely overwritten and `git add`ed later anyway) by `git rm <old>` calls (removing other files, too, that are also completely overwritten and `git add`ed later). Reviewed-by: Elijah Newren <newren@gmail.com> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-01-17 16:29:21 +08:00
git rm one two &&
mv -f file_v2 three &&
mv -f file_v5 two &&
git add two three &&
test_tick && git commit -m spanish &&
# Handle the right side
git checkout R &&
t6042: work around speed optimization on Windows When Git determines whether a file has changed, it looks at the mtime, at the file size, and to detect changes even if the mtime is the same (on Windows, the mtime granularity is 100ns, read: if two files are written within the same 100ns time slot, they have the same mtime) and even if the file size is the same, Git also looks at the inode/device numbers. This design obviously comes from a Linux background, where `lstat()` calls were designed to be cheap. On Windows, there is no `lstat()`. It has to be emulated. And while obtaining the mtime and the file size is not all that expensive (you can get both with a single `GetFileAttributesW()` call), obtaining the equivalent of the inode and device numbers is very expensive (it requires a call to `GetFileInformationByHandle()`, which in turn requires a file handle, which is *a lot* more expensive than one might imagine). As it is very uncommon for developers to modify files within 100ns time slots, Git for Windows chooses not to fill inode/device numbers properly, but simply sets them to 0. However, in t6042 the files file_v1 and file_v2 are typically written within the same 100ns time slot, and they do not differ in file size. So the minor modification is not picked up. Let's work around this issue by avoiding the `git mv` calls in the 'mod6-setup: chains of rename/rename(1to2) and rename/rename(2to1)' test case. The target files are overwritten anyway, so it is not like we really rename those files. This fixes the issue because `git add` will now add the files as new files (as opposed to existing, just renamed files). Functionally, we do not change anything because we replace two `git mv <old> <new>` calls (where `<new>` is completely overwritten and `git add`ed later anyway) by `git rm <old>` calls (removing other files, too, that are also completely overwritten and `git add`ed later). Reviewed-by: Elijah Newren <newren@gmail.com> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-01-17 16:29:21 +08:00
git rm one two &&
mv -f file_v3 one &&
mv -f file_v6 three &&
git add one three &&
test_tick && git commit -m german
)
}
test_expect_success 'check nested conflicts from rename/rename(2to1)' '
test_setup_nested_conflicts_from_rename_rename &&
(
cd nested_conflicts_from_rename_rename &&
git checkout L^0 &&
# Merge must fail; there is a conflict
test_must_fail git merge -s recursive R^0 &&
# Make sure the index has the right number of entries
git ls-files -s >out &&
test_line_count = 2 out &&
git ls-files -u >out &&
test_line_count = 2 out &&
# Ensure we have the correct number of untracked files
git ls-files -o >out &&
test_line_count = 1 out &&
# Compare :2:three to expected values
git cat-file -p main:one >base &&
git cat-file -p L:three >ours &&
git cat-file -p R:one >theirs &&
test_must_fail git merge-file \
-L "HEAD:three" -L "" -L "R^0:one" \
ours base theirs &&
sed -e "s/^\([<=>]\)/\1\1/" ours >L-three &&
git cat-file -p :2:three >expect &&
test_cmp expect L-three &&
# Compare :2:three to expected values
git cat-file -p main:two >base &&
git cat-file -p L:two >ours &&
git cat-file -p R:three >theirs &&
test_must_fail git merge-file \
-L "HEAD:two" -L "" -L "R^0:three" \
ours base theirs &&
sed -e "s/^\([<=>]\)/\1\1/" ours >R-three &&
git cat-file -p :3:three >expect &&
test_cmp expect R-three &&
# Compare three to expected contents
>empty &&
test_must_fail git merge-file \
-L "HEAD" -L "" -L "R^0" \
L-three empty R-three &&
test_cmp three L-three
)
'
# Testcase rename/rename(1to2) of a binary file
# Commit O: orig
# Commit A: orig-A
# Commit B: orig-B
# Expected: CONFLICT(rename/rename) message, three unstaged entries in the
# index, and contents of orig-[AB] at path orig-[AB]
test_setup_rename_rename_1_to_2_binary () {
test_create_repo rename_rename_1_to_2_binary &&
(
cd rename_rename_1_to_2_binary &&
echo '* binary' >.gitattributes &&
git add .gitattributes &&
test_seq 1 10 >orig &&
git add orig &&
git commit -m orig &&
git branch A &&
git branch B &&
git checkout A &&
git mv orig orig-A &&
test_seq 1 11 >orig-A &&
git add orig-A &&
git commit -m orig-A &&
git checkout B &&
git mv orig orig-B &&
test_seq 0 10 >orig-B &&
git add orig-B &&
git commit -m orig-B
)
}
test_expect_success 'rename/rename(1to2) with a binary file' '
test_setup_rename_rename_1_to_2_binary &&
(
cd rename_rename_1_to_2_binary &&
git checkout A^0 &&
test_must_fail git merge -s recursive B^0 &&
# Make sure the index has the right number of entries
git ls-files -s >actual &&
test_line_count = 4 actual &&
git rev-parse A:orig-A B:orig-B >expect &&
git hash-object orig-A orig-B >actual &&
test_cmp expect actual
)
'
test_done