Merge branch 'en/merge-recursive-2'
* en/merge-recursive-2: (57 commits) merge-recursive: Don't re-sort a list whose order we depend upon merge-recursive: Fix virtual merge base for rename/rename(1to2)/add-dest t6036: criss-cross + rename/rename(1to2)/add-dest + simple modify merge-recursive: Avoid unnecessary file rewrites t6022: Additional tests checking for unnecessary updates of files merge-recursive: Fix spurious 'refusing to lose untracked file...' messages t6022: Add testcase for spurious "refusing to lose untracked" messages t3030: fix accidental success in symlink rename merge-recursive: Fix working copy handling for rename/rename/add/add merge-recursive: add handling for rename/rename/add-dest/add-dest merge-recursive: Have conflict_rename_delete reuse modify/delete code merge-recursive: Make modify/delete handling code reusable merge-recursive: Consider modifications in rename/rename(2to1) conflicts merge-recursive: Create function for merging with branchname:file markers merge-recursive: Record more data needed for merging with dual renames merge-recursive: Defer rename/rename(2to1) handling until process_entry merge-recursive: Small cleanups for conflict_rename_rename_1to2 merge-recursive: Fix rename/rename(1to2) resolution for virtual merge base merge-recursive: Introduce a merge_file convenience function merge-recursive: Fix modify/delete resolution in the recursive case ...
This commit is contained in:
commit
96b7c4deb8
@ -29,6 +29,9 @@ member (you need this if you add things later) and you should set the
|
||||
|
||||
. Can sort an unsorted list using `sort_string_list`.
|
||||
|
||||
. Can remove individual items of an unsorted list using
|
||||
`unsorted_string_list_delete_item`.
|
||||
|
||||
. Finally it should free the list using `string_list_clear`.
|
||||
|
||||
Example:
|
||||
@ -112,6 +115,13 @@ write `string_list_insert(...)->util = ...;`.
|
||||
The above two functions need to look through all items, as opposed to their
|
||||
counterpart for sorted lists, which performs a binary search.
|
||||
|
||||
`unsorted_string_list_delete_item`::
|
||||
|
||||
Remove an item from a string_list. The `string` pointer of the items
|
||||
will be freed in case the `strdup_strings` member of the string_list
|
||||
is set. The third parameter controls if the `util` pointer of the
|
||||
items should be freed or not.
|
||||
|
||||
Data structures
|
||||
---------------
|
||||
|
||||
|
1087
merge-recursive.c
1087
merge-recursive.c
File diff suppressed because it is too large
Load Diff
@ -26,6 +26,7 @@ struct merge_options {
|
||||
struct strbuf obuf;
|
||||
struct string_list current_file_set;
|
||||
struct string_list current_directory_set;
|
||||
struct string_list df_conflict_file_set;
|
||||
};
|
||||
|
||||
/* merge_trees() but with recursive ancestor consolidation */
|
||||
|
@ -185,3 +185,12 @@ int unsorted_string_list_has_string(struct string_list *list,
|
||||
return unsorted_string_list_lookup(list, string) != NULL;
|
||||
}
|
||||
|
||||
void unsorted_string_list_delete_item(struct string_list *list, int i, int free_util)
|
||||
{
|
||||
if (list->strdup_strings)
|
||||
free(list->items[i].string);
|
||||
if (free_util)
|
||||
free(list->items[i].util);
|
||||
list->items[i] = list->items[list->nr-1];
|
||||
list->nr--;
|
||||
}
|
||||
|
@ -44,4 +44,5 @@ void sort_string_list(struct string_list *list);
|
||||
int unsorted_string_list_has_string(struct string_list *list, const char *string);
|
||||
struct string_list_item *unsorted_string_list_lookup(struct string_list *list,
|
||||
const char *string);
|
||||
void unsorted_string_list_delete_item(struct string_list *list, int i, int free_util);
|
||||
#endif /* STRING_LIST_H */
|
||||
|
@ -267,7 +267,8 @@ test_expect_success 'setup 8' '
|
||||
ln -s e a &&
|
||||
git add a e &&
|
||||
test_tick &&
|
||||
git commit -m "rename a->e, symlink a->e"
|
||||
git commit -m "rename a->e, symlink a->e" &&
|
||||
oln=`printf e | git hash-object --stdin`
|
||||
fi
|
||||
'
|
||||
|
||||
@ -630,16 +631,18 @@ test_expect_success 'merge-recursive copy vs. rename' '
|
||||
|
||||
if test_have_prereq SYMLINKS
|
||||
then
|
||||
test_expect_success 'merge-recursive rename vs. rename/symlink' '
|
||||
test_expect_failure 'merge-recursive rename vs. rename/symlink' '
|
||||
|
||||
git checkout -f rename &&
|
||||
git merge rename-ln &&
|
||||
( git ls-tree -r HEAD ; git ls-files -s ) >actual &&
|
||||
(
|
||||
echo "120000 blob $oln a"
|
||||
echo "100644 blob $o0 b"
|
||||
echo "100644 blob $o0 c"
|
||||
echo "100644 blob $o0 d/e"
|
||||
echo "100644 blob $o0 e"
|
||||
echo "120000 $oln 0 a"
|
||||
echo "100644 $o0 0 b"
|
||||
echo "100644 $o0 0 c"
|
||||
echo "100644 $o0 0 d/e"
|
||||
|
@ -59,15 +59,19 @@ test_expect_success 'setup modify/delete + directory/file conflict' '
|
||||
git add letters &&
|
||||
git commit -m initial &&
|
||||
|
||||
# Throw in letters.txt for sorting order fun
|
||||
# ("letters.txt" sorts between "letters" and "letters/file")
|
||||
echo i >>letters &&
|
||||
git add letters &&
|
||||
echo "version 2" >letters.txt &&
|
||||
git add letters letters.txt &&
|
||||
git commit -m modified &&
|
||||
|
||||
git checkout -b delete HEAD^ &&
|
||||
git rm letters &&
|
||||
mkdir letters &&
|
||||
>letters/file &&
|
||||
git add letters &&
|
||||
echo "version 1" >letters.txt &&
|
||||
git add letters letters.txt &&
|
||||
git commit -m deleted
|
||||
'
|
||||
|
||||
@ -75,25 +79,31 @@ test_expect_success 'modify/delete + directory/file conflict' '
|
||||
git checkout delete^0 &&
|
||||
test_must_fail git merge modify &&
|
||||
|
||||
test 3 = $(git ls-files -s | wc -l) &&
|
||||
test 2 = $(git ls-files -u | wc -l) &&
|
||||
test 1 = $(git ls-files -o | wc -l) &&
|
||||
test 5 -eq $(git ls-files -s | wc -l) &&
|
||||
test 4 -eq $(git ls-files -u | wc -l) &&
|
||||
test 1 -eq $(git ls-files -o | wc -l) &&
|
||||
|
||||
test -f letters/file &&
|
||||
test -f letters.txt &&
|
||||
test -f letters~modify
|
||||
'
|
||||
|
||||
test_expect_success 'modify/delete + directory/file conflict; other way' '
|
||||
# Yes, we really need the double reset since "letters" appears as
|
||||
# both a file and a directory.
|
||||
git reset --hard &&
|
||||
git reset --hard &&
|
||||
git clean -f &&
|
||||
git checkout modify^0 &&
|
||||
|
||||
test_must_fail git merge delete &&
|
||||
|
||||
test 3 = $(git ls-files -s | wc -l) &&
|
||||
test 2 = $(git ls-files -u | wc -l) &&
|
||||
test 1 = $(git ls-files -o | wc -l) &&
|
||||
test 5 -eq $(git ls-files -s | wc -l) &&
|
||||
test 4 -eq $(git ls-files -u | wc -l) &&
|
||||
test 1 -eq $(git ls-files -o | wc -l) &&
|
||||
|
||||
test -f letters/file &&
|
||||
test -f letters.txt &&
|
||||
test -f letters~HEAD
|
||||
'
|
||||
|
||||
|
@ -252,6 +252,7 @@ test_expect_success 'setup for rename + d/f conflicts' '
|
||||
git reset --hard &&
|
||||
git checkout --orphan dir-in-way &&
|
||||
git rm -rf . &&
|
||||
git clean -fdqx &&
|
||||
|
||||
mkdir sub &&
|
||||
mkdir dir &&
|
||||
@ -302,11 +303,11 @@ test_expect_success 'Rename+D/F conflict; renamed file merges but dir in way' '
|
||||
git checkout -q renamed-file-has-no-conflicts^0 &&
|
||||
test_must_fail git merge --strategy=recursive dir-in-way >output &&
|
||||
|
||||
grep "CONFLICT (delete/modify): dir/file-in-the-way" output &&
|
||||
grep "CONFLICT (modify/delete): dir/file-in-the-way" output &&
|
||||
grep "Auto-merging dir" output &&
|
||||
grep "Adding as dir~HEAD instead" output &&
|
||||
|
||||
test 2 -eq "$(git ls-files -u | wc -l)" &&
|
||||
test 3 -eq "$(git ls-files -u | wc -l)" &&
|
||||
test 2 -eq "$(git ls-files -u dir/file-in-the-way | wc -l)" &&
|
||||
|
||||
test_must_fail git diff --quiet &&
|
||||
@ -324,11 +325,11 @@ test_expect_success 'Same as previous, but merged other way' '
|
||||
test_must_fail git merge --strategy=recursive renamed-file-has-no-conflicts >output 2>errors &&
|
||||
|
||||
! grep "error: refusing to lose untracked file at" errors &&
|
||||
grep "CONFLICT (delete/modify): dir/file-in-the-way" output &&
|
||||
grep "CONFLICT (modify/delete): dir/file-in-the-way" output &&
|
||||
grep "Auto-merging dir" output &&
|
||||
grep "Adding as dir~renamed-file-has-no-conflicts instead" output &&
|
||||
|
||||
test 2 -eq "$(git ls-files -u | wc -l)" &&
|
||||
test 3 -eq "$(git ls-files -u | wc -l)" &&
|
||||
test 2 -eq "$(git ls-files -u dir/file-in-the-way | wc -l)" &&
|
||||
|
||||
test_must_fail git diff --quiet &&
|
||||
@ -350,11 +351,11 @@ cat >expected <<\EOF &&
|
||||
8
|
||||
9
|
||||
10
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD:dir
|
||||
12
|
||||
=======
|
||||
11
|
||||
>>>>>>> dir-not-in-way
|
||||
>>>>>>> dir-not-in-way:sub/file
|
||||
EOF
|
||||
|
||||
test_expect_success 'Rename+D/F conflict; renamed file cannot merge, dir not in way' '
|
||||
@ -404,11 +405,11 @@ cat >expected <<\EOF &&
|
||||
8
|
||||
9
|
||||
10
|
||||
<<<<<<< HEAD
|
||||
<<<<<<< HEAD:sub/file
|
||||
11
|
||||
=======
|
||||
12
|
||||
>>>>>>> renamed-file-has-conflicts
|
||||
>>>>>>> renamed-file-has-conflicts:dir
|
||||
EOF
|
||||
|
||||
test_expect_success 'Same as previous, but merged other way' '
|
||||
@ -609,4 +610,278 @@ test_expect_success 'check handling of differently renamed file with D/F conflic
|
||||
! test -f original
|
||||
'
|
||||
|
||||
test_expect_success 'setup avoid unnecessary update, normal rename' '
|
||||
git reset --hard &&
|
||||
git checkout --orphan avoid-unnecessary-update-1 &&
|
||||
git rm -rf . &&
|
||||
git clean -fdqx &&
|
||||
|
||||
printf "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n" >original &&
|
||||
git add -A &&
|
||||
git commit -m "Common commmit" &&
|
||||
|
||||
git mv original rename &&
|
||||
echo 11 >>rename &&
|
||||
git add -u &&
|
||||
git commit -m "Renamed and modified" &&
|
||||
|
||||
git checkout -b merge-branch-1 HEAD~1 &&
|
||||
echo "random content" >random-file &&
|
||||
git add -A &&
|
||||
git commit -m "Random, unrelated changes"
|
||||
'
|
||||
|
||||
test_expect_success 'avoid unnecessary update, normal rename' '
|
||||
git checkout -q avoid-unnecessary-update-1^0 &&
|
||||
test-chmtime =1000000000 rename &&
|
||||
test-chmtime -v +0 rename >expect &&
|
||||
git merge merge-branch-1 &&
|
||||
test-chmtime -v +0 rename >actual &&
|
||||
test_cmp expect actual # "rename" should have stayed intact
|
||||
'
|
||||
|
||||
test_expect_success 'setup to test avoiding unnecessary update, with D/F conflict' '
|
||||
git reset --hard &&
|
||||
git checkout --orphan avoid-unnecessary-update-2 &&
|
||||
git rm -rf . &&
|
||||
git clean -fdqx &&
|
||||
|
||||
mkdir df &&
|
||||
printf "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n" >df/file &&
|
||||
git add -A &&
|
||||
git commit -m "Common commmit" &&
|
||||
|
||||
git mv df/file temp &&
|
||||
rm -rf df &&
|
||||
git mv temp df &&
|
||||
echo 11 >>df &&
|
||||
git add -u &&
|
||||
git commit -m "Renamed and modified" &&
|
||||
|
||||
git checkout -b merge-branch-2 HEAD~1 &&
|
||||
>unrelated-change &&
|
||||
git add unrelated-change &&
|
||||
git commit -m "Only unrelated changes"
|
||||
'
|
||||
|
||||
test_expect_success 'avoid unnecessary update, with D/F conflict' '
|
||||
git checkout -q avoid-unnecessary-update-2^0 &&
|
||||
test-chmtime =1000000000 df &&
|
||||
test-chmtime -v +0 df >expect &&
|
||||
git merge merge-branch-2 &&
|
||||
test-chmtime -v +0 df >actual &&
|
||||
test_cmp expect actual # "df" should have stayed intact
|
||||
'
|
||||
|
||||
test_expect_success 'setup avoid unnecessary update, dir->(file,nothing)' '
|
||||
git rm -rf . &&
|
||||
git clean -fdqx &&
|
||||
rm -rf .git &&
|
||||
git init &&
|
||||
|
||||
>irrelevant &&
|
||||
mkdir df &&
|
||||
>df/file &&
|
||||
git add -A &&
|
||||
git commit -mA &&
|
||||
|
||||
git checkout -b side
|
||||
git rm -rf df &&
|
||||
git commit -mB &&
|
||||
|
||||
git checkout master &&
|
||||
git rm -rf df &&
|
||||
echo bla >df &&
|
||||
git add -A &&
|
||||
git commit -m "Add a newfile"
|
||||
'
|
||||
|
||||
test_expect_success 'avoid unnecessary update, dir->(file,nothing)' '
|
||||
git checkout -q master^0 &&
|
||||
test-chmtime =1000000000 df &&
|
||||
test-chmtime -v +0 df >expect &&
|
||||
git merge side &&
|
||||
test-chmtime -v +0 df >actual &&
|
||||
test_cmp expect actual # "df" should have stayed intact
|
||||
'
|
||||
|
||||
test_expect_success 'setup avoid unnecessary update, modify/delete' '
|
||||
git rm -rf . &&
|
||||
git clean -fdqx &&
|
||||
rm -rf .git &&
|
||||
git init &&
|
||||
|
||||
>irrelevant &&
|
||||
>file &&
|
||||
git add -A &&
|
||||
git commit -mA &&
|
||||
|
||||
git checkout -b side
|
||||
git rm -f file &&
|
||||
git commit -m "Delete file" &&
|
||||
|
||||
git checkout master &&
|
||||
echo bla >file &&
|
||||
git add -A &&
|
||||
git commit -m "Modify file"
|
||||
'
|
||||
|
||||
test_expect_success 'avoid unnecessary update, modify/delete' '
|
||||
git checkout -q master^0 &&
|
||||
test-chmtime =1000000000 file &&
|
||||
test-chmtime -v +0 file >expect &&
|
||||
test_must_fail git merge side &&
|
||||
test-chmtime -v +0 file >actual &&
|
||||
test_cmp expect actual # "file" should have stayed intact
|
||||
'
|
||||
|
||||
test_expect_success 'setup avoid unnecessary update, rename/add-dest' '
|
||||
git rm -rf . &&
|
||||
git clean -fdqx &&
|
||||
rm -rf .git &&
|
||||
git init &&
|
||||
|
||||
printf "1\n2\n3\n4\n5\n6\n7\n8\n" >file &&
|
||||
git add -A &&
|
||||
git commit -mA &&
|
||||
|
||||
git checkout -b side
|
||||
cp file newfile &&
|
||||
git add -A &&
|
||||
git commit -m "Add file copy" &&
|
||||
|
||||
git checkout master &&
|
||||
git mv file newfile &&
|
||||
git commit -m "Rename file"
|
||||
'
|
||||
|
||||
test_expect_success 'avoid unnecessary update, rename/add-dest' '
|
||||
git checkout -q master^0 &&
|
||||
test-chmtime =1000000000 newfile &&
|
||||
test-chmtime -v +0 newfile >expect &&
|
||||
git merge side &&
|
||||
test-chmtime -v +0 newfile >actual &&
|
||||
test_cmp expect actual # "file" should have stayed intact
|
||||
'
|
||||
|
||||
test_expect_success 'setup merge of rename + small change' '
|
||||
git reset --hard &&
|
||||
git checkout --orphan rename-plus-small-change &&
|
||||
git rm -rf . &&
|
||||
git clean -fdqx &&
|
||||
|
||||
echo ORIGINAL >file &&
|
||||
git add file &&
|
||||
|
||||
test_tick &&
|
||||
git commit -m Initial &&
|
||||
git checkout -b rename_branch &&
|
||||
git mv file renamed_file &&
|
||||
git commit -m Rename &&
|
||||
git checkout rename-plus-small-change &&
|
||||
echo NEW-VERSION >file &&
|
||||
git commit -a -m Reformat
|
||||
'
|
||||
|
||||
test_expect_success 'merge rename + small change' '
|
||||
git merge rename_branch &&
|
||||
|
||||
test 1 -eq $(git ls-files -s | wc -l) &&
|
||||
test 0 -eq $(git ls-files -o | wc -l) &&
|
||||
test $(git rev-parse HEAD:renamed_file) = $(git rev-parse HEAD~1:file)
|
||||
'
|
||||
|
||||
test_expect_success 'setup for use of extended merge markers' '
|
||||
git rm -rf . &&
|
||||
git clean -fdqx &&
|
||||
rm -rf .git &&
|
||||
git init &&
|
||||
|
||||
printf "1\n2\n3\n4\n5\n6\n7\n8\n" >original_file &&
|
||||
git add original_file &&
|
||||
git commit -mA &&
|
||||
|
||||
git checkout -b rename &&
|
||||
echo 9 >>original_file &&
|
||||
git add original_file &&
|
||||
git mv original_file renamed_file &&
|
||||
git commit -mB &&
|
||||
|
||||
git checkout master &&
|
||||
echo 8.5 >>original_file &&
|
||||
git add original_file &&
|
||||
git commit -mC
|
||||
'
|
||||
|
||||
cat >expected <<\EOF &&
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
<<<<<<< HEAD:renamed_file
|
||||
9
|
||||
=======
|
||||
8.5
|
||||
>>>>>>> master^0:original_file
|
||||
EOF
|
||||
|
||||
test_expect_success 'merge master into rename has correct extended markers' '
|
||||
git checkout rename^0 &&
|
||||
test_must_fail git merge -s recursive master^0 &&
|
||||
test_cmp expected renamed_file
|
||||
'
|
||||
|
||||
cat >expected <<\EOF &&
|
||||
1
|
||||
2
|
||||
3
|
||||
4
|
||||
5
|
||||
6
|
||||
7
|
||||
8
|
||||
<<<<<<< HEAD:original_file
|
||||
8.5
|
||||
=======
|
||||
9
|
||||
>>>>>>> rename^0:renamed_file
|
||||
EOF
|
||||
|
||||
test_expect_success 'merge rename into master has correct extended markers' '
|
||||
git reset --hard &&
|
||||
git checkout master^0 &&
|
||||
test_must_fail git merge -s recursive rename^0 &&
|
||||
test_cmp expected renamed_file
|
||||
'
|
||||
|
||||
test_expect_success 'setup spurious "refusing to lose untracked" message' '
|
||||
git rm -rf . &&
|
||||
git clean -fdqx &&
|
||||
rm -rf .git &&
|
||||
git init &&
|
||||
|
||||
> irrelevant_file &&
|
||||
printf "1\n2\n3\n4\n5\n6\n7\n8\n" >original_file &&
|
||||
git add irrelevant_file original_file &&
|
||||
git commit -mA &&
|
||||
|
||||
git checkout -b rename &&
|
||||
git mv original_file renamed_file &&
|
||||
git commit -mB &&
|
||||
|
||||
git checkout master &&
|
||||
git rm original_file &&
|
||||
git commit -mC
|
||||
'
|
||||
|
||||
test_expect_success 'no spurious "refusing to lose untracked" message' '
|
||||
git checkout master^0 &&
|
||||
test_must_fail git merge rename^0 2>errors.txt &&
|
||||
! grep "refusing to lose untracked file" errors.txt
|
||||
'
|
||||
|
||||
test_done
|
||||
|
@ -1,9 +1,15 @@
|
||||
#!/bin/sh
|
||||
|
||||
test_description='recursive merge corner cases'
|
||||
test_description='recursive merge corner cases involving criss-cross merges'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
get_clean_checkout () {
|
||||
git reset --hard &&
|
||||
git clean -fdqx &&
|
||||
git checkout "$1"
|
||||
}
|
||||
|
||||
#
|
||||
# L1 L2
|
||||
# o---o
|
||||
@ -51,23 +57,15 @@ test_expect_success 'merge simple rename+criss-cross with no modifications' '
|
||||
|
||||
test_must_fail git merge -s recursive R2^0 &&
|
||||
|
||||
test 5 = $(git ls-files -s | wc -l) &&
|
||||
test 3 = $(git ls-files -u | wc -l) &&
|
||||
test 0 = $(git ls-files -o | wc -l) &&
|
||||
test 2 = $(git ls-files -s | wc -l) &&
|
||||
test 2 = $(git ls-files -u | wc -l) &&
|
||||
test 2 = $(git ls-files -o | wc -l) &&
|
||||
|
||||
test $(git rev-parse :0:one) = $(git rev-parse L2:one) &&
|
||||
test $(git rev-parse :0:two) = $(git rev-parse R2:two) &&
|
||||
test $(git rev-parse :2:three) = $(git rev-parse L2:three) &&
|
||||
test $(git rev-parse :3:three) = $(git rev-parse R2:three) &&
|
||||
|
||||
cp two merged &&
|
||||
>empty &&
|
||||
test_must_fail git merge-file \
|
||||
-L "Temporary merge branch 2" \
|
||||
-L "" \
|
||||
-L "Temporary merge branch 1" \
|
||||
merged empty one &&
|
||||
test $(git rev-parse :1:three) = $(git hash-object merged)
|
||||
test $(git rev-parse L2:three) = $(git hash-object three~HEAD) &&
|
||||
test $(git rev-parse R2:three) = $(git hash-object three~R2^0)
|
||||
'
|
||||
|
||||
#
|
||||
@ -126,24 +124,15 @@ test_expect_success 'merge criss-cross + rename merges with basic modification'
|
||||
|
||||
test_must_fail git merge -s recursive R2^0 &&
|
||||
|
||||
test 5 = $(git ls-files -s | wc -l) &&
|
||||
test 3 = $(git ls-files -u | wc -l) &&
|
||||
test 0 = $(git ls-files -o | wc -l) &&
|
||||
test 2 = $(git ls-files -s | wc -l) &&
|
||||
test 2 = $(git ls-files -u | wc -l) &&
|
||||
test 2 = $(git ls-files -o | wc -l) &&
|
||||
|
||||
test $(git rev-parse :0:one) = $(git rev-parse L2:one) &&
|
||||
test $(git rev-parse :0:two) = $(git rev-parse R2:two) &&
|
||||
test $(git rev-parse :2:three) = $(git rev-parse L2:three) &&
|
||||
test $(git rev-parse :3:three) = $(git rev-parse R2:three) &&
|
||||
|
||||
head -n 10 two >merged &&
|
||||
cp one merge-me &&
|
||||
>empty &&
|
||||
test_must_fail git merge-file \
|
||||
-L "Temporary merge branch 2" \
|
||||
-L "" \
|
||||
-L "Temporary merge branch 1" \
|
||||
merged empty merge-me &&
|
||||
test $(git rev-parse :1:three) = $(git hash-object merged)
|
||||
test $(git rev-parse L2:three) = $(git hash-object three~HEAD) &&
|
||||
test $(git rev-parse R2:three) = $(git hash-object three~R2^0)
|
||||
'
|
||||
|
||||
#
|
||||
@ -231,4 +220,557 @@ test_expect_success 'git detects differently handled merges conflict' '
|
||||
test $(git rev-parse :1:new_a) = $(git hash-object merged)
|
||||
'
|
||||
|
||||
#
|
||||
# criss-cross + modify/delete:
|
||||
#
|
||||
# B D
|
||||
# o---o
|
||||
# / \ / \
|
||||
# A o X ? F
|
||||
# \ / \ /
|
||||
# o---o
|
||||
# C E
|
||||
#
|
||||
# Commit A: file with contents 'A\n'
|
||||
# Commit B: file with contents 'B\n'
|
||||
# Commit C: file not present
|
||||
# Commit D: file with contents 'B\n'
|
||||
# Commit E: file not present
|
||||
#
|
||||
# Merging commits D & E should result in modify/delete conflict.
|
||||
|
||||
test_expect_success 'setup criss-cross + modify/delete resolved differently' '
|
||||
git rm -rf . &&
|
||||
git clean -fdqx &&
|
||||
rm -rf .git &&
|
||||
git init &&
|
||||
|
||||
echo A >file &&
|
||||
git add file &&
|
||||
test_tick &&
|
||||
git commit -m A &&
|
||||
|
||||
git branch B &&
|
||||
git checkout -b C &&
|
||||
git rm file &&
|
||||
test_tick &&
|
||||
git commit -m C &&
|
||||
|
||||
git checkout B &&
|
||||
echo B >file &&
|
||||
git add file &&
|
||||
test_tick &&
|
||||
git commit -m B &&
|
||||
|
||||
git checkout B^0 &&
|
||||
test_must_fail git merge C &&
|
||||
echo B >file &&
|
||||
git add file &&
|
||||
test_tick &&
|
||||
git commit -m D &&
|
||||
git tag D &&
|
||||
|
||||
git checkout C^0 &&
|
||||
test_must_fail git merge B &&
|
||||
git rm file &&
|
||||
test_tick &&
|
||||
git commit -m E &&
|
||||
git tag E
|
||||
'
|
||||
|
||||
test_expect_success 'git detects conflict merging criss-cross+modify/delete' '
|
||||
git checkout D^0 &&
|
||||
|
||||
test_must_fail git merge -s recursive E^0 &&
|
||||
|
||||
test 2 -eq $(git ls-files -s | wc -l) &&
|
||||
test 2 -eq $(git ls-files -u | wc -l) &&
|
||||
|
||||
test $(git rev-parse :1:file) = $(git rev-parse master:file) &&
|
||||
test $(git rev-parse :2:file) = $(git rev-parse B:file)
|
||||
'
|
||||
|
||||
test_expect_success 'git detects conflict merging criss-cross+modify/delete, reverse direction' '
|
||||
git reset --hard &&
|
||||
git checkout E^0 &&
|
||||
|
||||
test_must_fail git merge -s recursive D^0 &&
|
||||
|
||||
test 2 -eq $(git ls-files -s | wc -l) &&
|
||||
test 2 -eq $(git ls-files -u | wc -l) &&
|
||||
|
||||
test $(git rev-parse :1:file) = $(git rev-parse master:file) &&
|
||||
test $(git rev-parse :3:file) = $(git rev-parse B:file)
|
||||
'
|
||||
|
||||
#
|
||||
# criss-cross + modify/modify with very contrived file contents:
|
||||
#
|
||||
# B D
|
||||
# o---o
|
||||
# / \ / \
|
||||
# A o X ? F
|
||||
# \ / \ /
|
||||
# o---o
|
||||
# C E
|
||||
#
|
||||
# Commit A: file with contents 'A\n'
|
||||
# Commit B: file with contents 'B\n'
|
||||
# Commit C: file with contents 'C\n'
|
||||
# Commit D: file with contents 'D\n'
|
||||
# Commit E: file with contents:
|
||||
# <<<<<<< Temporary merge branch 1
|
||||
# C
|
||||
# =======
|
||||
# B
|
||||
# >>>>>>> Temporary merge branch 2
|
||||
#
|
||||
# Now, when we merge commits D & E, does git detect the conflict?
|
||||
|
||||
test_expect_success 'setup differently handled merges of content conflict' '
|
||||
git clean -fdqx &&
|
||||
rm -rf .git &&
|
||||
git init &&
|
||||
|
||||
echo A >file &&
|
||||
git add file &&
|
||||
test_tick &&
|
||||
git commit -m A &&
|
||||
|
||||
git branch B &&
|
||||
git checkout -b C &&
|
||||
echo C >file &&
|
||||
git add file &&
|
||||
test_tick &&
|
||||
git commit -m C &&
|
||||
|
||||
git checkout B &&
|
||||
echo B >file &&
|
||||
git add file &&
|
||||
test_tick &&
|
||||
git commit -m B &&
|
||||
|
||||
git checkout B^0 &&
|
||||
test_must_fail git merge C &&
|
||||
echo D >file &&
|
||||
git add file &&
|
||||
test_tick &&
|
||||
git commit -m D &&
|
||||
git tag D &&
|
||||
|
||||
git checkout C^0 &&
|
||||
test_must_fail git merge B &&
|
||||
cat <<EOF >file &&
|
||||
<<<<<<< Temporary merge branch 1
|
||||
C
|
||||
=======
|
||||
B
|
||||
>>>>>>> Temporary merge branch 2
|
||||
EOF
|
||||
git add file &&
|
||||
test_tick &&
|
||||
git commit -m E &&
|
||||
git tag E
|
||||
'
|
||||
|
||||
test_expect_failure 'git detects conflict w/ criss-cross+contrived resolution' '
|
||||
git checkout D^0 &&
|
||||
|
||||
test_must_fail git merge -s recursive E^0 &&
|
||||
|
||||
test 3 -eq $(git ls-files -s | wc -l) &&
|
||||
test 3 -eq $(git ls-files -u | wc -l) &&
|
||||
test 0 -eq $(git ls-files -o | wc -l) &&
|
||||
|
||||
test $(git rev-parse :2:file) = $(git rev-parse D:file) &&
|
||||
test $(git rev-parse :3:file) = $(git rev-parse E:file)
|
||||
'
|
||||
|
||||
#
|
||||
# criss-cross + d/f conflict via add/add:
|
||||
# Commit A: Neither file 'a' nor directory 'a/' exist.
|
||||
# Commit B: Introduce 'a'
|
||||
# Commit C: Introduce 'a/file'
|
||||
# Commit D: Merge B & C, keeping 'a' and deleting 'a/'
|
||||
#
|
||||
# Two different later cases:
|
||||
# Commit E1: Merge B & C, deleting 'a' but keeping 'a/file'
|
||||
# Commit E2: Merge B & C, deleting 'a' but keeping a slightly modified 'a/file'
|
||||
#
|
||||
# B D
|
||||
# o---o
|
||||
# / \ / \
|
||||
# A o X ? F
|
||||
# \ / \ /
|
||||
# o---o
|
||||
# C E1 or E2
|
||||
#
|
||||
# Merging D & E1 requires we first create a virtual merge base X from
|
||||
# merging A & B in memory. Now, if X could keep both 'a' and 'a/file' in
|
||||
# the index, then the merge of D & E1 could be resolved cleanly with both
|
||||
# 'a' and 'a/file' removed. Since git does not currently allow creating
|
||||
# such a tree, the best we can do is have X contain both 'a~<unique>' and
|
||||
# 'a/file' resulting in the merge of D and E1 having a rename/delete
|
||||
# conflict for 'a'. (Although this merge appears to be unsolvable with git
|
||||
# currently, git could do a lot better than it currently does with these
|
||||
# d/f conflicts, which is the purpose of this test.)
|
||||
#
|
||||
# Merge of D & E2 has similar issues for path 'a', but should always result
|
||||
# in a modify/delete conflict for path 'a/file'.
|
||||
#
|
||||
# We run each merge in both directions, to check for directional issues
|
||||
# with D/F conflict handling.
|
||||
#
|
||||
|
||||
test_expect_success 'setup differently handled merges of directory/file conflict' '
|
||||
git rm -rf . &&
|
||||
git clean -fdqx &&
|
||||
rm -rf .git &&
|
||||
git init &&
|
||||
|
||||
>ignore-me &&
|
||||
git add ignore-me &&
|
||||
test_tick &&
|
||||
git commit -m A &&
|
||||
git tag A &&
|
||||
|
||||
git branch B &&
|
||||
git checkout -b C &&
|
||||
mkdir a &&
|
||||
echo 10 >a/file &&
|
||||
git add a/file &&
|
||||
test_tick &&
|
||||
git commit -m C &&
|
||||
|
||||
git checkout B &&
|
||||
echo 5 >a &&
|
||||
git add a &&
|
||||
test_tick &&
|
||||
git commit -m B &&
|
||||
|
||||
git checkout B^0 &&
|
||||
test_must_fail git merge C &&
|
||||
git clean -f &&
|
||||
rm -rf a/ &&
|
||||
echo 5 >a &&
|
||||
git add a &&
|
||||
test_tick &&
|
||||
git commit -m D &&
|
||||
git tag D &&
|
||||
|
||||
git checkout C^0 &&
|
||||
test_must_fail git merge B &&
|
||||
git clean -f &&
|
||||
git rm --cached a &&
|
||||
echo 10 >a/file &&
|
||||
git add a/file &&
|
||||
test_tick &&
|
||||
git commit -m E1 &&
|
||||
git tag E1 &&
|
||||
|
||||
git checkout C^0 &&
|
||||
test_must_fail git merge B &&
|
||||
git clean -f &&
|
||||
git rm --cached a &&
|
||||
printf "10\n11\n" >a/file &&
|
||||
git add a/file &&
|
||||
test_tick &&
|
||||
git commit -m E2 &&
|
||||
git tag E2
|
||||
'
|
||||
|
||||
test_expect_success 'merge of D & E1 fails but has appropriate contents' '
|
||||
get_clean_checkout D^0 &&
|
||||
|
||||
test_must_fail git merge -s recursive E1^0 &&
|
||||
|
||||
test 2 -eq $(git ls-files -s | wc -l) &&
|
||||
test 1 -eq $(git ls-files -u | wc -l) &&
|
||||
test 0 -eq $(git ls-files -o | wc -l) &&
|
||||
|
||||
test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) &&
|
||||
test $(git rev-parse :2:a) = $(git rev-parse B:a)
|
||||
'
|
||||
|
||||
test_expect_success 'merge of E1 & D fails but has appropriate contents' '
|
||||
get_clean_checkout E1^0 &&
|
||||
|
||||
test_must_fail git merge -s recursive D^0 &&
|
||||
|
||||
test 2 -eq $(git ls-files -s | wc -l) &&
|
||||
test 1 -eq $(git ls-files -u | wc -l) &&
|
||||
test 0 -eq $(git ls-files -o | wc -l) &&
|
||||
|
||||
test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) &&
|
||||
test $(git rev-parse :3:a) = $(git rev-parse B:a)
|
||||
'
|
||||
|
||||
test_expect_success 'merge of D & E2 fails but has appropriate contents' '
|
||||
get_clean_checkout D^0 &&
|
||||
|
||||
test_must_fail git merge -s recursive E2^0 &&
|
||||
|
||||
test 4 -eq $(git ls-files -s | wc -l) &&
|
||||
test 3 -eq $(git ls-files -u | wc -l) &&
|
||||
test 1 -eq $(git ls-files -o | wc -l) &&
|
||||
|
||||
test $(git rev-parse :2:a) = $(git rev-parse B:a) &&
|
||||
test $(git rev-parse :3:a/file) = $(git rev-parse E2:a/file) &&
|
||||
test $(git rev-parse :1:a/file) = $(git rev-parse C:a/file) &&
|
||||
test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) &&
|
||||
|
||||
test -f a~HEAD
|
||||
'
|
||||
|
||||
test_expect_success 'merge of E2 & D fails but has appropriate contents' '
|
||||
get_clean_checkout E2^0 &&
|
||||
|
||||
test_must_fail git merge -s recursive D^0 &&
|
||||
|
||||
test 4 -eq $(git ls-files -s | wc -l) &&
|
||||
test 3 -eq $(git ls-files -u | wc -l) &&
|
||||
test 1 -eq $(git ls-files -o | wc -l) &&
|
||||
|
||||
test $(git rev-parse :3:a) = $(git rev-parse B:a) &&
|
||||
test $(git rev-parse :2:a/file) = $(git rev-parse E2:a/file) &&
|
||||
test $(git rev-parse :1:a/file) = $(git rev-parse C:a/file)
|
||||
test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) &&
|
||||
|
||||
test -f a~D^0
|
||||
'
|
||||
|
||||
#
|
||||
# criss-cross with rename/rename(1to2)/modify followed by
|
||||
# rename/rename(2to1)/modify:
|
||||
#
|
||||
# B D
|
||||
# o---o
|
||||
# / \ / \
|
||||
# A o X ? F
|
||||
# \ / \ /
|
||||
# o---o
|
||||
# C E
|
||||
#
|
||||
# Commit A: new file: a
|
||||
# Commit B: rename a->b, modifying by adding a line
|
||||
# Commit C: rename a->c
|
||||
# Commit D: merge B&C, resolving conflict by keeping contents in newname
|
||||
# Commit E: merge B&C, resolving conflict similar to D but adding another line
|
||||
#
|
||||
# There is a conflict merging B & C, but one of filename not of file
|
||||
# content. Whoever created D and E chose specific resolutions for that
|
||||
# conflict resolution. Now, since: (1) there is no content conflict
|
||||
# merging B & C, (2) D does not modify that merged content further, and (3)
|
||||
# both D & E resolve the name conflict in the same way, the modification to
|
||||
# newname in E should not cause any conflicts when it is merged with D.
|
||||
# (Note that this can be accomplished by having the virtual merge base have
|
||||
# the merged contents of b and c stored in a file named a, which seems like
|
||||
# the most logical choice anyway.)
|
||||
#
|
||||
# Comment from Junio: I do not necessarily agree with the choice "a", but
|
||||
# it feels sound to say "B and C do not agree what the final pathname
|
||||
# should be, but we know this content was derived from the common A:a so we
|
||||
# use one path whose name is arbitrary in the virtual merge base X between
|
||||
# D and E" and then further let the rename detection to notice that that
|
||||
# arbitrary path gets renamed between X-D to "newname" and X-E also to
|
||||
# "newname" to resolve it as both sides renaming it to the same new
|
||||
# name. It is akin to what we do at the content level, i.e. "B and C do not
|
||||
# agree what the final contents should be, so we leave the conflict marker
|
||||
# but that may cancel out at the final merge stage".
|
||||
|
||||
test_expect_success 'setup rename/rename(1to2)/modify followed by what looks like rename/rename(2to1)/modify' '
|
||||
git reset --hard &&
|
||||
git rm -rf . &&
|
||||
git clean -fdqx &&
|
||||
rm -rf .git &&
|
||||
git init &&
|
||||
|
||||
printf "1\n2\n3\n4\n5\n6\n" >a &&
|
||||
git add a &&
|
||||
git commit -m A &&
|
||||
git tag A &&
|
||||
|
||||
git checkout -b B A &&
|
||||
git mv a b &&
|
||||
echo 7 >>b &&
|
||||
git add -u &&
|
||||
git commit -m B &&
|
||||
|
||||
git checkout -b C A &&
|
||||
git mv a c &&
|
||||
git commit -m C &&
|
||||
|
||||
git checkout -q B^0 &&
|
||||
git merge --no-commit -s ours C^0 &&
|
||||
git mv b newname &&
|
||||
git commit -m "Merge commit C^0 into HEAD" &&
|
||||
git tag D &&
|
||||
|
||||
git checkout -q C^0 &&
|
||||
git merge --no-commit -s ours B^0 &&
|
||||
git mv c newname &&
|
||||
printf "7\n8\n" >>newname &&
|
||||
git add -u &&
|
||||
git commit -m "Merge commit B^0 into HEAD" &&
|
||||
git tag E
|
||||
'
|
||||
|
||||
test_expect_success 'handle rename/rename(1to2)/modify followed by what looks like rename/rename(2to1)/modify' '
|
||||
git checkout D^0 &&
|
||||
|
||||
git merge -s recursive E^0 &&
|
||||
|
||||
test 1 -eq $(git ls-files -s | wc -l) &&
|
||||
test 0 -eq $(git ls-files -u | wc -l) &&
|
||||
test 0 -eq $(git ls-files -o | wc -l) &&
|
||||
|
||||
test $(git rev-parse HEAD:newname) = $(git rev-parse E:newname)
|
||||
'
|
||||
|
||||
#
|
||||
# criss-cross with rename/rename(1to2)/add-source + resolvable modify/modify:
|
||||
#
|
||||
# B D
|
||||
# o---o
|
||||
# / \ / \
|
||||
# A o X ? F
|
||||
# \ / \ /
|
||||
# o---o
|
||||
# C E
|
||||
#
|
||||
# Commit A: new file: a
|
||||
# Commit B: rename a->b
|
||||
# Commit C: rename a->c, add different a
|
||||
# Commit D: merge B&C, keeping b&c and (new) a modified at beginning
|
||||
# Commit E: merge B&C, keeping b&c and (new) a modified at end
|
||||
#
|
||||
# Merging commits D & E should result in no conflict; doing so correctly
|
||||
# requires getting the virtual merge base (from merging B&C) right, handling
|
||||
# renaming carefully (both in the virtual merge base and later), and getting
|
||||
# content merge handled.
|
||||
|
||||
test_expect_success 'setup criss-cross + rename/rename/add + modify/modify' '
|
||||
git rm -rf . &&
|
||||
git clean -fdqx &&
|
||||
rm -rf .git &&
|
||||
git init &&
|
||||
|
||||
printf "lots\nof\nwords\nand\ncontent\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 &&
|
||||
printf "2\n3\n4\n5\n6\n7\n" >a &&
|
||||
git add a &&
|
||||
git commit -m C &&
|
||||
|
||||
git checkout B^0 &&
|
||||
git merge --no-commit -s ours C^0 &&
|
||||
git checkout C -- a c &&
|
||||
mv a old_a &&
|
||||
echo 1 >a &&
|
||||
cat old_a >>a &&
|
||||
rm old_a &&
|
||||
git add -u &&
|
||||
git commit -m "Merge commit C^0 into HEAD" &&
|
||||
git tag D &&
|
||||
|
||||
git checkout C^0 &&
|
||||
git merge --no-commit -s ours B^0 &&
|
||||
git checkout B -- b &&
|
||||
echo 8 >>a &&
|
||||
git add -u &&
|
||||
git commit -m "Merge commit B^0 into HEAD" &&
|
||||
git tag E
|
||||
'
|
||||
|
||||
test_expect_failure 'detect rename/rename/add-source for virtual merge-base' '
|
||||
git checkout D^0 &&
|
||||
|
||||
git merge -s recursive E^0 &&
|
||||
|
||||
test 3 -eq $(git ls-files -s | wc -l) &&
|
||||
test 0 -eq $(git ls-files -u | wc -l) &&
|
||||
test 0 -eq $(git ls-files -o | wc -l) &&
|
||||
|
||||
test $(git rev-parse HEAD:b) = $(git rev-parse A:a) &&
|
||||
test $(git rev-parse HEAD:c) = $(git rev-parse A:a) &&
|
||||
test "$(cat a)" = "$(printf "1\n2\n3\n4\n5\n6\n7\n8\n")"
|
||||
'
|
||||
|
||||
#
|
||||
# criss-cross with rename/rename(1to2)/add-dest + simple modify:
|
||||
#
|
||||
# B D
|
||||
# o---o
|
||||
# / \ / \
|
||||
# A o X ? F
|
||||
# \ / \ /
|
||||
# o---o
|
||||
# C E
|
||||
#
|
||||
# Commit A: new file: a
|
||||
# Commit B: rename a->b, add c
|
||||
# Commit C: rename a->c
|
||||
# Commit D: merge B&C, keeping A:a and B:c
|
||||
# Commit E: merge B&C, keeping A:a and slightly modified c from B
|
||||
#
|
||||
# Merging commits D & E should result in no conflict. The virtual merge
|
||||
# base of B & C needs to not delete B:c for that to work, though...
|
||||
|
||||
test_expect_success 'setup criss-cross+rename/rename/add-dest + simple modify' '
|
||||
git rm -rf . &&
|
||||
git clean -fdqx &&
|
||||
rm -rf .git &&
|
||||
git init &&
|
||||
|
||||
>a &&
|
||||
git add a &&
|
||||
git commit -m A &&
|
||||
git tag A &&
|
||||
|
||||
git checkout -b B A &&
|
||||
git mv a b &&
|
||||
printf "1\n2\n3\n4\n5\n6\n7\n" >c &&
|
||||
git add c &&
|
||||
git commit -m B &&
|
||||
|
||||
git checkout -b C A &&
|
||||
git mv a c &&
|
||||
git commit -m C &&
|
||||
|
||||
git checkout B^0 &&
|
||||
git merge --no-commit -s ours C^0 &&
|
||||
git mv b a &&
|
||||
git commit -m "D is like B but renames b back to a" &&
|
||||
git tag D &&
|
||||
|
||||
git checkout B^0 &&
|
||||
git merge --no-commit -s ours C^0 &&
|
||||
git mv b a &&
|
||||
echo 8 >>c &&
|
||||
git add c &&
|
||||
git commit -m "E like D but has mod in c" &&
|
||||
git tag E
|
||||
'
|
||||
|
||||
test_expect_success 'virtual merge base handles rename/rename(1to2)/add-dest' '
|
||||
git checkout D^0 &&
|
||||
|
||||
git merge -s recursive E^0 &&
|
||||
|
||||
test 2 -eq $(git ls-files -s | wc -l) &&
|
||||
test 0 -eq $(git ls-files -u | wc -l) &&
|
||||
test 0 -eq $(git ls-files -o | wc -l) &&
|
||||
|
||||
test $(git rev-parse HEAD:a) = $(git rev-parse A:a) &&
|
||||
test $(git rev-parse HEAD:c) = $(git rev-parse E:c)
|
||||
'
|
||||
|
||||
test_done
|
||||
|
578
t/t6042-merge-rename-corner-cases.sh
Executable file
578
t/t6042-merge-rename-corner-cases.sh
Executable file
@ -0,0 +1,578 @@
|
||||
#!/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
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
test_expect_success 'setup rename/delete + untracked file' '
|
||||
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
|
||||
'
|
||||
|
||||
test_expect_success "Does git preserve Gollum's precious artifact?" '
|
||||
test_must_fail git merge -s recursive rename-the-ring &&
|
||||
|
||||
# Make sure git did not delete an untracked file
|
||||
test -f 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_expect_success 'setup rename/modify/add-source conflict' '
|
||||
git rm -rf . &&
|
||||
git clean -fdqx &&
|
||||
rm -rf .git &&
|
||||
git init &&
|
||||
|
||||
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' '
|
||||
git checkout B^0 &&
|
||||
|
||||
git merge -s recursive C^0 &&
|
||||
|
||||
test $(git rev-parse B:a) = $(git rev-parse b) &&
|
||||
test $(git rev-parse C:a) = $(git rev-parse a)
|
||||
'
|
||||
|
||||
test_expect_success 'setup resolvable conflict missed if rename missed' '
|
||||
git rm -rf . &&
|
||||
git clean -fdqx &&
|
||||
rm -rf .git &&
|
||||
git init &&
|
||||
|
||||
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' '
|
||||
git checkout -q C^0 &&
|
||||
git merge -s recursive B^0 &&
|
||||
|
||||
test 3 -eq $(git ls-files -s | wc -l) &&
|
||||
test 0 -eq $(git ls-files -u | wc -l) &&
|
||||
test 0 -eq $(git ls-files -o | wc -l) &&
|
||||
|
||||
test 6 -eq $(wc -l < c) &&
|
||||
test $(git rev-parse HEAD:a) = $(git rev-parse B:a) &&
|
||||
test $(git rev-parse HEAD:b) = $(git rev-parse A:b)
|
||||
'
|
||||
|
||||
test_expect_success 'setup conflict resolved wrong if rename missed' '
|
||||
git reset --hard &&
|
||||
git clean -f &&
|
||||
|
||||
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' '
|
||||
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_expect_success 'setup undetected rename/add-source causes data loss' '
|
||||
git rm -rf . &&
|
||||
git clean -fdqx &&
|
||||
rm -rf .git &&
|
||||
git init &&
|
||||
|
||||
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' '
|
||||
git checkout B^0 &&
|
||||
|
||||
git merge -s recursive C^0 &&
|
||||
|
||||
test 2 -eq $(git ls-files -s | wc -l) &&
|
||||
test 2 -eq $(git ls-files -u | wc -l) &&
|
||||
test 0 -eq $(git ls-files -o | wc -l) &&
|
||||
|
||||
test -f a &&
|
||||
test -f b &&
|
||||
|
||||
test $(git rev-parse HEAD:b) = $(git rev-parse A:a) &&
|
||||
test $(git rev-parse HEAD:a) = $(git rev-parse C:a)
|
||||
'
|
||||
|
||||
test_expect_failure 'detect rename/add-source and preserve all data, merge other way' '
|
||||
git checkout C^0 &&
|
||||
|
||||
git merge -s recursive B^0 &&
|
||||
|
||||
test 2 -eq $(git ls-files -s | wc -l) &&
|
||||
test 2 -eq $(git ls-files -u | wc -l) &&
|
||||
test 0 -eq $(git ls-files -o | wc -l) &&
|
||||
|
||||
test -f a &&
|
||||
test -f b &&
|
||||
|
||||
test $(git rev-parse HEAD:b) = $(git rev-parse A:a) &&
|
||||
test $(git rev-parse HEAD:a) = $(git rev-parse C:a)
|
||||
'
|
||||
|
||||
test_expect_success 'setup content merge + rename/directory conflict' '
|
||||
git rm -rf . &&
|
||||
git clean -fdqx &&
|
||||
rm -rf .git &&
|
||||
git init &&
|
||||
|
||||
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
|
||||
'
|
||||
|
||||
test_expect_success 'rename/directory conflict + clean content merge' '
|
||||
git reset --hard &&
|
||||
git reset --hard &&
|
||||
git clean -fdqx &&
|
||||
|
||||
git checkout left-clean^0 &&
|
||||
|
||||
test_must_fail git merge -s recursive right^0 &&
|
||||
|
||||
test 2 -eq $(git ls-files -s | wc -l) &&
|
||||
test 1 -eq $(git ls-files -u | wc -l) &&
|
||||
test 1 -eq $(git ls-files -o | wc -l) &&
|
||||
|
||||
echo 0 >expect &&
|
||||
git cat-file -p base:file >>expect &&
|
||||
echo 7 >>expect &&
|
||||
test_cmp expect newfile~HEAD &&
|
||||
|
||||
test $(git rev-parse :2:newfile) = $(git hash-object expect) &&
|
||||
|
||||
test -f newfile/realfile &&
|
||||
test -f newfile~HEAD
|
||||
'
|
||||
|
||||
test_expect_success 'rename/directory conflict + content merge conflict' '
|
||||
git reset --hard &&
|
||||
git reset --hard &&
|
||||
git clean -fdqx &&
|
||||
|
||||
git checkout left-conflict^0 &&
|
||||
|
||||
test_must_fail git merge -s recursive right^0 &&
|
||||
|
||||
test 4 -eq $(git ls-files -s | wc -l) &&
|
||||
test 3 -eq $(git ls-files -u | wc -l) &&
|
||||
test 1 -eq $(git ls-files -o | wc -l) &&
|
||||
|
||||
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 &&
|
||||
|
||||
test $(git rev-parse :1:newfile) = $(git rev-parse base:file) &&
|
||||
test $(git rev-parse :2:newfile) = $(git rev-parse left-conflict:newfile) &&
|
||||
test $(git rev-parse :3:newfile) = $(git rev-parse right:file) &&
|
||||
|
||||
test -f newfile/realfile &&
|
||||
test -f newfile~HEAD
|
||||
'
|
||||
|
||||
test_expect_success 'setup content merge + rename/directory conflict w/ disappearing dir' '
|
||||
git reset --hard &&
|
||||
git rm -rf . &&
|
||||
git clean -fdqx &&
|
||||
rm -rf .git &&
|
||||
git init &&
|
||||
|
||||
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
|
||||
'
|
||||
|
||||
test_expect_success 'disappearing dir in rename/directory conflict handled' '
|
||||
git reset --hard &&
|
||||
git clean -fdqx &&
|
||||
|
||||
git checkout left^0 &&
|
||||
|
||||
git merge -s recursive right^0 &&
|
||||
|
||||
test 1 -eq $(git ls-files -s | wc -l) &&
|
||||
test 0 -eq $(git ls-files -u | wc -l) &&
|
||||
test 0 -eq $(git ls-files -o | wc -l) &&
|
||||
|
||||
echo 0 >expect &&
|
||||
git cat-file -p base:sub/file >>expect &&
|
||||
echo 7 >>expect &&
|
||||
test_cmp expect sub &&
|
||||
|
||||
test -f sub
|
||||
'
|
||||
|
||||
# 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_expect_success 'setup rename/rename (2to1) + modify/modify' '
|
||||
git rm -rf . &&
|
||||
git clean -fdqx &&
|
||||
rm -rf .git &&
|
||||
git init &&
|
||||
|
||||
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
|
||||
'
|
||||
|
||||
test_expect_success 'handle rename/rename (2to1) conflict correctly' '
|
||||
git checkout B^0 &&
|
||||
|
||||
test_must_fail git merge -s recursive C^0 >out &&
|
||||
grep "CONFLICT (rename/rename)" out &&
|
||||
|
||||
test 2 -eq $(git ls-files -s | wc -l) &&
|
||||
test 2 -eq $(git ls-files -u | wc -l) &&
|
||||
test 2 -eq $(git ls-files -u c | wc -l) &&
|
||||
test 3 -eq $(git ls-files -o | wc -l) &&
|
||||
|
||||
test ! -f a &&
|
||||
test ! -f b &&
|
||||
test -f c~HEAD &&
|
||||
test -f c~C^0 &&
|
||||
|
||||
test $(git hash-object c~HEAD) = $(git rev-parse C:a) &&
|
||||
test $(git hash-object c~C^0) = $(git rev-parse B:b)
|
||||
'
|
||||
|
||||
# Testcase setup for simple rename/rename (1to2) conflict:
|
||||
# Commit A: new file: a
|
||||
# Commit B: rename a->b
|
||||
# Commit C: rename a->c
|
||||
test_expect_success 'setup simple rename/rename (1to2) conflict' '
|
||||
git rm -rf . &&
|
||||
git clean -fdqx &&
|
||||
rm -rf .git &&
|
||||
git init &&
|
||||
|
||||
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' '
|
||||
git checkout C^0 &&
|
||||
|
||||
test_must_fail git merge -s recursive B^0 &&
|
||||
|
||||
test 3 -eq $(git ls-files -s | wc -l) &&
|
||||
test 3 -eq $(git ls-files -u | wc -l) &&
|
||||
test 0 -eq $(git ls-files -o | wc -l) &&
|
||||
|
||||
test $(git rev-parse :1:a) = $(git rev-parse A:a) &&
|
||||
test $(git rev-parse :3:b) = $(git rev-parse A:a) &&
|
||||
test $(git rev-parse :2:c) = $(git rev-parse A:a) &&
|
||||
|
||||
test ! -f a &&
|
||||
test $(git hash-object b) = $(git rev-parse A:a) &&
|
||||
test $(git hash-object c) = $(git rev-parse A:a)
|
||||
'
|
||||
|
||||
# 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_expect_success 'setup rename/rename(1to2)/add-source conflict' '
|
||||
git rm -rf . &&
|
||||
git clean -fdqx &&
|
||||
rm -rf .git &&
|
||||
git init &&
|
||||
|
||||
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' '
|
||||
git checkout B^0 &&
|
||||
|
||||
test_must_fail git merge -s recursive C^0 &&
|
||||
|
||||
test 4 -eq $(git ls-files -s | wc -l) &&
|
||||
test 0 -eq $(git ls-files -o | wc -l) &&
|
||||
|
||||
test $(git rev-parse 3:a) = $(git rev-parse C:a) &&
|
||||
test $(git rev-parse 1:a) = $(git rev-parse A:a) &&
|
||||
test $(git rev-parse 2:b) = $(git rev-parse B:b) &&
|
||||
test $(git rev-parse 3:c) = $(git rev-parse C:c) &&
|
||||
|
||||
test -f a &&
|
||||
test -f b &&
|
||||
test -f c
|
||||
'
|
||||
|
||||
test_expect_success 'setup rename/rename(1to2)/add-source resolvable conflict' '
|
||||
git rm -rf . &&
|
||||
git clean -fdqx &&
|
||||
rm -rf .git &&
|
||||
git init &&
|
||||
|
||||
>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' '
|
||||
git checkout C^0 &&
|
||||
git merge -s recursive B^0 &&
|
||||
|
||||
test 2 -eq $(git ls-files -s | wc -l) &&
|
||||
test 0 -eq $(git ls-files -o | wc -l) &&
|
||||
|
||||
test $(git rev-parse HEAD:a) = $(git rev-parse C:a) &&
|
||||
test $(git rev-parse HEAD:b) = $(git rev-parse A:a)
|
||||
'
|
||||
|
||||
test_expect_success 'setup rename/rename(1to2)/add-dest conflict' '
|
||||
git rm -rf . &&
|
||||
git clean -fdqx &&
|
||||
rm -rf .git &&
|
||||
git init &&
|
||||
|
||||
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' '
|
||||
git checkout C^0 &&
|
||||
test_must_fail git merge -s recursive B^0 &&
|
||||
|
||||
test 5 -eq $(git ls-files -s | wc -l) &&
|
||||
test 2 -eq $(git ls-files -u b | wc -l) &&
|
||||
test 2 -eq $(git ls-files -u c | wc -l) &&
|
||||
test 4 -eq $(git ls-files -o | wc -l) &&
|
||||
|
||||
test $(git rev-parse :1:a) = $(git rev-parse A:a) &&
|
||||
test $(git rev-parse :2:b) = $(git rev-parse C:b) &&
|
||||
test $(git rev-parse :3:b) = $(git rev-parse B:b) &&
|
||||
test $(git rev-parse :2:c) = $(git rev-parse C:c) &&
|
||||
test $(git rev-parse :3:c) = $(git rev-parse B:c) &&
|
||||
|
||||
test $(git hash-object c~HEAD) = $(git rev-parse C:c) &&
|
||||
test $(git hash-object c~B\^0) = $(git rev-parse B:c) &&
|
||||
test $(git hash-object b~HEAD) = $(git rev-parse C:b) &&
|
||||
test $(git hash-object b~B\^0) = $(git rev-parse B:b) &&
|
||||
|
||||
test ! -f b &&
|
||||
test ! -f c
|
||||
'
|
||||
|
||||
test_done
|
Loading…
Reference in New Issue
Block a user