mirror of
https://github.com/git/git.git
synced 2026-01-11 13:23:12 +09:00
Merge branch 'en/ort-recursive-d-f-conflict-fix'
The ort merge machinery hit an assertion failure in a history with criss-cross merges renamed a directory and a non-directory, which has been corrected. * en/ort-recursive-d-f-conflict-fix: merge-ort: fix corner case recursive submodule/directory conflict handling
This commit is contained in:
commit
2db806d817
35
merge-ort.c
35
merge-ort.c
@ -1502,11 +1502,44 @@ static void resolve_trivial_directory_merge(struct conflict_info *ci, int side)
|
|||||||
VERIFY_CI(ci);
|
VERIFY_CI(ci);
|
||||||
assert((side == 1 && ci->match_mask == 5) ||
|
assert((side == 1 && ci->match_mask == 5) ||
|
||||||
(side == 2 && ci->match_mask == 3));
|
(side == 2 && ci->match_mask == 3));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Since ci->stages[0] matches ci->stages[3-side], resolve merge in
|
||||||
|
* favor of ci->stages[side].
|
||||||
|
*/
|
||||||
oidcpy(&ci->merged.result.oid, &ci->stages[side].oid);
|
oidcpy(&ci->merged.result.oid, &ci->stages[side].oid);
|
||||||
ci->merged.result.mode = ci->stages[side].mode;
|
ci->merged.result.mode = ci->stages[side].mode;
|
||||||
ci->merged.is_null = is_null_oid(&ci->stages[side].oid);
|
ci->merged.is_null = is_null_oid(&ci->stages[side].oid);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Because we resolved in favor of "side", we are no longer
|
||||||
|
* considering the paths which matched (i.e. had the same hash) any
|
||||||
|
* more. Strip the matching paths from both dirmask & filemask.
|
||||||
|
* Another consequence of merging in favor of side is that we can no
|
||||||
|
* longer have a directory/file conflict either..but there's a slight
|
||||||
|
* nuance we consider before clearing it.
|
||||||
|
*
|
||||||
|
* In most cases, resolving in favor of the other side means there's
|
||||||
|
* no conflict at all, but if we had a directory/file conflict to
|
||||||
|
* start, and the directory is resolved away, the remaining file could
|
||||||
|
* still be part of a rename. If the remaining file is part of a
|
||||||
|
* rename, then it may also be part of a rename conflict (e.g.
|
||||||
|
* rename/delete or rename/rename(1to2)), so we can't
|
||||||
|
* mark it as a clean merge if we started with a directory/file
|
||||||
|
* conflict and still have a file left.
|
||||||
|
*
|
||||||
|
* In contrast, if we started with a directory/file conflict and
|
||||||
|
* still have a directory left, no file under that directory can be
|
||||||
|
* part of a rename, otherwise we would have had to recurse into the
|
||||||
|
* directory and would have never ended up within
|
||||||
|
* resolve_trivial_directory_merge() for that directory.
|
||||||
|
*/
|
||||||
|
ci->dirmask &= (~ci->match_mask);
|
||||||
|
ci->filemask &= (~ci->match_mask);
|
||||||
|
assert(!ci->filemask || !ci->dirmask);
|
||||||
ci->match_mask = 0;
|
ci->match_mask = 0;
|
||||||
ci->merged.clean = 1; /* (ci->filemask == 0); */
|
ci->merged.clean = !ci->df_conflict || ci->dirmask;
|
||||||
|
ci->df_conflict = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int handle_deferred_entries(struct merge_options *opt,
|
static int handle_deferred_entries(struct merge_options *opt,
|
||||||
|
|||||||
@ -1439,4 +1439,90 @@ test_expect_success 'rename/rename(1to2) with a binary file' '
|
|||||||
)
|
)
|
||||||
'
|
'
|
||||||
|
|
||||||
|
# Testcase preliminary submodule/directory conflict and submodule rename
|
||||||
|
# Commit O: <empty, or additional irrelevant stuff>
|
||||||
|
# Commit A1: introduce "folder" (as a tree)
|
||||||
|
# Commit B1: introduce "folder" (as a submodule)
|
||||||
|
# Commit A2: merge B1 into A1, but keep folder as a tree
|
||||||
|
# Commit B2: merge A1 into B1, but keep folder as a submodule
|
||||||
|
# Merge A2 & B2
|
||||||
|
test_setup_submodule_directory_preliminary_conflict () {
|
||||||
|
git init submodule_directory_preliminary_conflict &&
|
||||||
|
(
|
||||||
|
cd submodule_directory_preliminary_conflict &&
|
||||||
|
|
||||||
|
# Trying to do the A2 and B2 merges above is slightly more
|
||||||
|
# challenging with a local submodule (because checking out
|
||||||
|
# another commit has the submodule in the way). Instead,
|
||||||
|
# first create the commits with the wrong parents but right
|
||||||
|
# trees, in the order A1, A2, B1, B2...
|
||||||
|
#
|
||||||
|
# Then go back and create new A2 & B2 with the correct
|
||||||
|
# parents and the same trees.
|
||||||
|
|
||||||
|
git commit --allow-empty -m orig &&
|
||||||
|
|
||||||
|
git branch A &&
|
||||||
|
git branch B &&
|
||||||
|
|
||||||
|
git checkout B &&
|
||||||
|
mkdir folder &&
|
||||||
|
echo A>folder/A &&
|
||||||
|
echo B>folder/B &&
|
||||||
|
echo C>folder/C &&
|
||||||
|
echo D>folder/D &&
|
||||||
|
echo E>folder/E &&
|
||||||
|
git add folder &&
|
||||||
|
git commit -m B1 &&
|
||||||
|
|
||||||
|
git commit --allow-empty -m B2 &&
|
||||||
|
|
||||||
|
git checkout A &&
|
||||||
|
git init folder &&
|
||||||
|
(
|
||||||
|
cd folder &&
|
||||||
|
>Z &&
|
||||||
|
>Y &&
|
||||||
|
git add Z Y &&
|
||||||
|
git commit -m "original submodule commit"
|
||||||
|
) &&
|
||||||
|
git add folder &&
|
||||||
|
git commit -m A1 &&
|
||||||
|
|
||||||
|
git commit --allow-empty -m A2 &&
|
||||||
|
|
||||||
|
NewA2=$(git commit-tree -p A^ -p B^ -m "Merge B into A" A^{tree}) &&
|
||||||
|
NewB2=$(git commit-tree -p B^ -p A^ -m "Merge A into B" B^{tree}) &&
|
||||||
|
git update-ref refs/heads/A $NewA2 &&
|
||||||
|
git update-ref refs/heads/B $NewB2
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
test_expect_success 'submodule/directory preliminary conflict' '
|
||||||
|
test_setup_submodule_directory_preliminary_conflict &&
|
||||||
|
(
|
||||||
|
cd submodule_directory_preliminary_conflict &&
|
||||||
|
|
||||||
|
git checkout A^0 &&
|
||||||
|
|
||||||
|
test_expect_code 1 git merge B^0 &&
|
||||||
|
|
||||||
|
# Make sure the index has the right number of entries
|
||||||
|
git ls-files -s >actual &&
|
||||||
|
test_line_count = 2 actual &&
|
||||||
|
|
||||||
|
# The "folder" as directory should have been resolved away
|
||||||
|
# as part of the merge. The "folder" as submodule got
|
||||||
|
# renamed to "folder~Temporary merge branch 2" in the
|
||||||
|
# virtual merge base, resulting in a
|
||||||
|
# "folder~Temporary merge branch 2" -> "folder"
|
||||||
|
# rename in the outermerge for the submodule, which then
|
||||||
|
# becomes part of a rename/delete conflict (because "folder"
|
||||||
|
# as a submodule was deleted in A2).
|
||||||
|
submod=$(git rev-parse A:folder) &&
|
||||||
|
printf "160000 $submod 1\tfolder\n160000 $submod 2\tfolder\n" >expect &&
|
||||||
|
test_cmp expect actual
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
test_done
|
test_done
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user