Merge "RecursiveMerger should not fail on content-merge conflicts of parents"

This commit is contained in:
Christian Halstrick 2014-07-29 03:46:52 -04:00 committed by Gerrit Code Review @ Eclipse.org
commit 5171a1843a
4 changed files with 134 additions and 9 deletions

View File

@ -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

View File

@ -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;
}
}

View File

@ -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(

View File

@ -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;
}