Merge "RecursiveMerger should not fail on content-merge conflicts of parents"
This commit is contained in:
commit
5171a1843a
|
@ -343,6 +343,89 @@ else if (worktreeState == WorktreeState.DifferentFromHeadAndOther
|
|||
}
|
||||
}
|
||||
|
||||
@Theory
|
||||
/**
|
||||
* Merging m2,s2 from the following topology. m1 and s1 are not mergeable
|
||||
* without conflicts. The same file is modified in both branches. The
|
||||
* modifications should be mergeable but only if the merge result of
|
||||
* merging m1 and s1 is choosen as parent (including the conflict markers).
|
||||
*
|
||||
* <pre>
|
||||
* m0--m1--m2
|
||||
* \ \/
|
||||
* \ /\
|
||||
* s1--s2
|
||||
* </pre>
|
||||
*/
|
||||
public void crissCrossMerge_ParentsNotMergeable(MergeStrategy strategy,
|
||||
IndexState indexState, WorktreeState worktreeState)
|
||||
throws Exception {
|
||||
if (!validateStates(indexState, worktreeState))
|
||||
return;
|
||||
|
||||
BranchBuilder master = db_t.branch("master");
|
||||
RevCommit m0 = master.commit().add("f", "1\n2\n3\n").message("m0")
|
||||
.create();
|
||||
RevCommit m1 = master.commit().add("f", "1\nx(master)\n2\n3\n")
|
||||
.message("m1").create();
|
||||
db_t.getRevWalk().parseCommit(m1);
|
||||
|
||||
BranchBuilder side = db_t.branch("side");
|
||||
RevCommit s1 = side.commit().parent(m0)
|
||||
.add("f", "1\nx(side)\n2\n3\ny(side)\n")
|
||||
.message("s1").create();
|
||||
RevCommit s2 = side.commit().parent(m1)
|
||||
.add("f", "1\nx(side)\n2\n3\ny(side-again)\n")
|
||||
.message("s2(merge)")
|
||||
.create();
|
||||
RevCommit m2 = master.commit().parent(s1)
|
||||
.add("f", "1\nx(side)\n2\n3\ny(side)\n").message("m2(merge)")
|
||||
.create();
|
||||
|
||||
Git git = Git.wrap(db);
|
||||
git.checkout().setName("master").call();
|
||||
modifyWorktree(worktreeState, "f", "side");
|
||||
modifyIndex(indexState, "f", "side");
|
||||
|
||||
ResolveMerger merger = (ResolveMerger) strategy.newMerger(db,
|
||||
worktreeState == WorktreeState.Bare);
|
||||
if (worktreeState != WorktreeState.Bare)
|
||||
merger.setWorkingTreeIterator(new FileTreeIterator(db));
|
||||
try {
|
||||
boolean expectSuccess = true;
|
||||
if (!(indexState == IndexState.Bare
|
||||
|| indexState == IndexState.Missing || indexState == IndexState.SameAsHead))
|
||||
// index is dirty
|
||||
expectSuccess = false;
|
||||
else if (worktreeState == WorktreeState.DifferentFromHeadAndOther
|
||||
|| worktreeState == WorktreeState.SameAsOther)
|
||||
expectSuccess = false;
|
||||
assertEquals("Merge didn't return as expected: strategy:"
|
||||
+ strategy.getName() + ", indexState:" + indexState
|
||||
+ ", worktreeState:" + worktreeState + " . ",
|
||||
Boolean.valueOf(expectSuccess),
|
||||
Boolean.valueOf(merger.merge(new RevCommit[] { m2, s2 })));
|
||||
assertEquals(MergeStrategy.RECURSIVE, strategy);
|
||||
if (!expectSuccess)
|
||||
// if the merge was not successful skip testing the state of
|
||||
// index and workingtree
|
||||
return;
|
||||
assertEquals("1\nx(side)\n2\n3\ny(side-again)",
|
||||
contentAsString(db, merger.getResultTreeId(), "f"));
|
||||
if (indexState != IndexState.Bare)
|
||||
assertEquals(
|
||||
"[f, mode:100644, content:1\nx(side)\n2\n3\ny(side-again)\n]",
|
||||
indexState(RepositoryTestCase.CONTENT));
|
||||
if (worktreeState != WorktreeState.Bare
|
||||
&& worktreeState != WorktreeState.Missing)
|
||||
assertEquals("1\nx(side)\n2\n3\ny(side-again)\n", read("f"));
|
||||
} catch (NoMergeBaseException e) {
|
||||
assertEquals(MergeStrategy.RESOLVE, strategy);
|
||||
assertEquals(e.getReason(),
|
||||
MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED);
|
||||
}
|
||||
}
|
||||
|
||||
@Theory
|
||||
/**
|
||||
* Merging m2,s2 from the following topology. The same file is modified
|
||||
|
|
|
@ -161,4 +161,16 @@ public void remove() {
|
|||
public boolean containsConflicts() {
|
||||
return containsConflicts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets explicitly whether this merge should be seen as containing a
|
||||
* conflict or not. Needed because during RecursiveMerger we want to do
|
||||
* content-merges and take the resulting content (even with conflict
|
||||
* markers!) as new conflict-free content
|
||||
*
|
||||
* @param containsConflicts
|
||||
*/
|
||||
protected void setContainsConflicts(boolean containsConflicts) {
|
||||
this.containsConflicts = containsConflicts;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -196,8 +196,7 @@ protected RevCommit getBaseCommit(RevCommit a, RevCommit b, int callDepth)
|
|||
if (mergeTrees(
|
||||
openTree(getBaseCommit(currentBase, nextBase,
|
||||
callDepth + 1).getTree()),
|
||||
currentBase.getTree(),
|
||||
nextBase.getTree()))
|
||||
currentBase.getTree(), nextBase.getTree(), true))
|
||||
currentBase = createCommitForTree(resultTree, parents);
|
||||
else
|
||||
throw new NoMergeBaseException(
|
||||
|
|
|
@ -297,7 +297,8 @@ protected boolean mergeImpl() throws IOException {
|
|||
dircache = getRepository().lockDirCache();
|
||||
|
||||
try {
|
||||
return mergeTrees(mergeBase(), sourceTrees[0], sourceTrees[1]);
|
||||
return mergeTrees(mergeBase(), sourceTrees[0], sourceTrees[1],
|
||||
false);
|
||||
} finally {
|
||||
if (implicitDirCache)
|
||||
dircache.unlock();
|
||||
|
@ -457,6 +458,9 @@ private DirCacheEntry keep(DirCacheEntry e) {
|
|||
* the index entry
|
||||
* @param work
|
||||
* the file in the working tree
|
||||
* @param ignoreConflicts
|
||||
* see
|
||||
* {@link ResolveMerger#mergeTrees(AbstractTreeIterator, RevTree, RevTree, boolean)}
|
||||
* @return <code>false</code> if the merge will fail because the index entry
|
||||
* didn't match ours or the working-dir file was dirty and a
|
||||
* conflict occurred
|
||||
|
@ -468,7 +472,8 @@ private DirCacheEntry keep(DirCacheEntry e) {
|
|||
*/
|
||||
protected boolean processEntry(CanonicalTreeParser base,
|
||||
CanonicalTreeParser ours, CanonicalTreeParser theirs,
|
||||
DirCacheBuildIterator index, WorkingTreeIterator work)
|
||||
DirCacheBuildIterator index, WorkingTreeIterator work,
|
||||
boolean ignoreConflicts)
|
||||
throws MissingObjectException, IncorrectObjectTypeException,
|
||||
CorruptObjectException, IOException {
|
||||
enterSubtree = true;
|
||||
|
@ -627,9 +632,11 @@ protected boolean processEntry(CanonicalTreeParser base,
|
|||
}
|
||||
|
||||
MergeResult<RawText> result = contentMerge(base, ours, theirs);
|
||||
if (ignoreConflicts)
|
||||
result.setContainsConflicts(false);
|
||||
File of = writeMergedFile(result);
|
||||
updateIndex(base, ours, theirs, result, of);
|
||||
if (result.containsConflicts())
|
||||
if (result.containsConflicts() && !ignoreConflicts)
|
||||
unmergedPaths.add(tw.getPathString());
|
||||
modifiedFiles.add(tw.getPathString());
|
||||
} else if (modeO != modeT) {
|
||||
|
@ -993,12 +1000,32 @@ public void setWorkingTreeIterator(WorkingTreeIterator workingTreeIterator) {
|
|||
* @param baseTree
|
||||
* @param headTree
|
||||
* @param mergeTree
|
||||
* @param ignoreConflicts
|
||||
* Controls what to do in case a content-merge is done and a
|
||||
* conflict is detected. The default setting for this should be
|
||||
* <code>false</code>. In this case the working tree file is
|
||||
* filled with new content (containing conflict markers) and the
|
||||
* index is filled with multiple stages containing BASE, OURS and
|
||||
* THEIRS content. Having such non-0 stages is the sign to git
|
||||
* tools that there are still conflicts for that path.
|
||||
* <p>
|
||||
* If <code>true</code> is specified the behavior is different.
|
||||
* In case a conflict is detected the working tree file is again
|
||||
* filled with new content (containing conflict markers). But
|
||||
* also stage 0 of the index is filled with that content. No
|
||||
* other stages are filled. Means: there is no conflict on that
|
||||
* path but the new content (including conflict markers) is
|
||||
* stored as successful merge result. This is needed in the
|
||||
* context of {@link RecursiveMerger} where when determining
|
||||
* merge bases we don't want to deal with content-merge
|
||||
* conflicts.
|
||||
* @return whether the trees merged cleanly
|
||||
* @throws IOException
|
||||
* @since 3.0
|
||||
*/
|
||||
protected boolean mergeTrees(AbstractTreeIterator baseTree,
|
||||
RevTree headTree, RevTree mergeTree) throws IOException {
|
||||
RevTree headTree, RevTree mergeTree, boolean ignoreConflicts)
|
||||
throws IOException {
|
||||
|
||||
builder = dircache.builder();
|
||||
DirCacheBuildIterator buildIt = new DirCacheBuildIterator(builder);
|
||||
|
@ -1011,7 +1038,7 @@ protected boolean mergeTrees(AbstractTreeIterator baseTree,
|
|||
if (workingTreeIterator != null)
|
||||
tw.addTree(workingTreeIterator);
|
||||
|
||||
if (!mergeTreeWalk(tw)) {
|
||||
if (!mergeTreeWalk(tw, ignoreConflicts)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1050,11 +1077,15 @@ protected boolean mergeTrees(AbstractTreeIterator baseTree,
|
|||
*
|
||||
* @param treeWalk
|
||||
* The walk to iterate over.
|
||||
* @param ignoreConflicts
|
||||
* see
|
||||
* {@link ResolveMerger#mergeTrees(AbstractTreeIterator, RevTree, RevTree, boolean)}
|
||||
* @return Whether the trees merged cleanly.
|
||||
* @throws IOException
|
||||
* @since 3.4
|
||||
*/
|
||||
protected boolean mergeTreeWalk(TreeWalk treeWalk) throws IOException {
|
||||
protected boolean mergeTreeWalk(TreeWalk treeWalk, boolean ignoreConflicts)
|
||||
throws IOException {
|
||||
boolean hasWorkingTreeIterator = tw.getTreeCount() > T_FILE;
|
||||
while (treeWalk.next()) {
|
||||
if (!processEntry(
|
||||
|
@ -1063,7 +1094,7 @@ protected boolean mergeTreeWalk(TreeWalk treeWalk) throws IOException {
|
|||
treeWalk.getTree(T_THEIRS, CanonicalTreeParser.class),
|
||||
treeWalk.getTree(T_INDEX, DirCacheBuildIterator.class),
|
||||
hasWorkingTreeIterator ? treeWalk.getTree(T_FILE,
|
||||
WorkingTreeIterator.class) : null)) {
|
||||
WorkingTreeIterator.class) : null, ignoreConflicts)) {
|
||||
cleanUp();
|
||||
return false;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue