Skip to content

Commit

Permalink
Fix merges involving clean subtrees with StrategySimpleTwoWayInCore
Browse files Browse the repository at this point in the history
If a both sides modify files in the same subtree, but do so in a
non-conflicting way, we should still be able to merge them by an
automated merge strategy.  Recursing into the subtree permits us
to do this merge.

Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>
  • Loading branch information
spearce authored and robinrosenberg committed Mar 7, 2009
1 parent aa91718 commit 093920a
Show file tree
Hide file tree
Showing 2 changed files with 225 additions and 11 deletions.
204 changes: 204 additions & 0 deletions org.spearce.jgit.test/tst/org/spearce/jgit/merge/SimpleMergeTest.java
Expand Up @@ -146,6 +146,210 @@ public void testTrivialTwoWay_validSubtreeSort() throws Exception {
assertFalse(tw.next()); assertFalse(tw.next());
} }


public void testTrivialTwoWay_concurrentSubtreeChange() throws Exception {
final DirCache treeB = DirCache.read(db);
final DirCache treeO = DirCache.read(db);
final DirCache treeT = DirCache.read(db);
{
final DirCacheBuilder b = treeB.builder();
final DirCacheBuilder o = treeO.builder();
final DirCacheBuilder t = treeT.builder();

b.add(makeEntry("d/o", FileMode.REGULAR_FILE));
b.add(makeEntry("d/t", FileMode.REGULAR_FILE));

o.add(makeEntry("d/o", FileMode.REGULAR_FILE, "o !"));
o.add(makeEntry("d/t", FileMode.REGULAR_FILE));

t.add(makeEntry("d/o", FileMode.REGULAR_FILE));
t.add(makeEntry("d/t", FileMode.REGULAR_FILE, "t !"));

b.finish();
o.finish();
t.finish();
}

final ObjectWriter ow = new ObjectWriter(db);
final ObjectId b = commit(ow, treeB, new ObjectId[] {});
final ObjectId o = commit(ow, treeO, new ObjectId[] { b });
final ObjectId t = commit(ow, treeT, new ObjectId[] { b });

Merger ourMerger = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(db);
boolean merge = ourMerger.merge(new ObjectId[] { o, t });
assertTrue(merge);

final TreeWalk tw = new TreeWalk(db);
tw.setRecursive(true);
tw.reset(ourMerger.getResultTreeId());

assertTrue(tw.next());
assertEquals("d/o", tw.getPathString());
assertCorrectId(treeO, tw);

assertTrue(tw.next());
assertEquals("d/t", tw.getPathString());
assertCorrectId(treeT, tw);

assertFalse(tw.next());
}

public void testTrivialTwoWay_conflictSubtreeChange() throws Exception {
final DirCache treeB = DirCache.read(db);
final DirCache treeO = DirCache.read(db);
final DirCache treeT = DirCache.read(db);
{
final DirCacheBuilder b = treeB.builder();
final DirCacheBuilder o = treeO.builder();
final DirCacheBuilder t = treeT.builder();

b.add(makeEntry("d/o", FileMode.REGULAR_FILE));
b.add(makeEntry("d/t", FileMode.REGULAR_FILE));

o.add(makeEntry("d/o", FileMode.REGULAR_FILE));
o.add(makeEntry("d/t", FileMode.REGULAR_FILE, "o !"));

t.add(makeEntry("d/o", FileMode.REGULAR_FILE, "t !"));
t.add(makeEntry("d/t", FileMode.REGULAR_FILE, "t !"));

b.finish();
o.finish();
t.finish();
}

final ObjectWriter ow = new ObjectWriter(db);
final ObjectId b = commit(ow, treeB, new ObjectId[] {});
final ObjectId o = commit(ow, treeO, new ObjectId[] { b });
final ObjectId t = commit(ow, treeT, new ObjectId[] { b });

Merger ourMerger = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(db);
boolean merge = ourMerger.merge(new ObjectId[] { o, t });
assertFalse(merge);
}

public void testTrivialTwoWay_leftDFconflict1() throws Exception {
final DirCache treeB = DirCache.read(db);
final DirCache treeO = DirCache.read(db);
final DirCache treeT = DirCache.read(db);
{
final DirCacheBuilder b = treeB.builder();
final DirCacheBuilder o = treeO.builder();
final DirCacheBuilder t = treeT.builder();

b.add(makeEntry("d/o", FileMode.REGULAR_FILE));
b.add(makeEntry("d/t", FileMode.REGULAR_FILE));

o.add(makeEntry("d", FileMode.REGULAR_FILE));

t.add(makeEntry("d/o", FileMode.REGULAR_FILE));
t.add(makeEntry("d/t", FileMode.REGULAR_FILE, "t !"));

b.finish();
o.finish();
t.finish();
}

final ObjectWriter ow = new ObjectWriter(db);
final ObjectId b = commit(ow, treeB, new ObjectId[] {});
final ObjectId o = commit(ow, treeO, new ObjectId[] { b });
final ObjectId t = commit(ow, treeT, new ObjectId[] { b });

Merger ourMerger = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(db);
boolean merge = ourMerger.merge(new ObjectId[] { o, t });
assertFalse(merge);
}

public void testTrivialTwoWay_rightDFconflict1() throws Exception {
final DirCache treeB = DirCache.read(db);
final DirCache treeO = DirCache.read(db);
final DirCache treeT = DirCache.read(db);
{
final DirCacheBuilder b = treeB.builder();
final DirCacheBuilder o = treeO.builder();
final DirCacheBuilder t = treeT.builder();

b.add(makeEntry("d/o", FileMode.REGULAR_FILE));
b.add(makeEntry("d/t", FileMode.REGULAR_FILE));

o.add(makeEntry("d/o", FileMode.REGULAR_FILE));
o.add(makeEntry("d/t", FileMode.REGULAR_FILE, "o !"));

t.add(makeEntry("d", FileMode.REGULAR_FILE));

b.finish();
o.finish();
t.finish();
}

final ObjectWriter ow = new ObjectWriter(db);
final ObjectId b = commit(ow, treeB, new ObjectId[] {});
final ObjectId o = commit(ow, treeO, new ObjectId[] { b });
final ObjectId t = commit(ow, treeT, new ObjectId[] { b });

Merger ourMerger = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(db);
boolean merge = ourMerger.merge(new ObjectId[] { o, t });
assertFalse(merge);
}

public void testTrivialTwoWay_leftDFconflict2() throws Exception {
final DirCache treeB = DirCache.read(db);
final DirCache treeO = DirCache.read(db);
final DirCache treeT = DirCache.read(db);
{
final DirCacheBuilder b = treeB.builder();
final DirCacheBuilder o = treeO.builder();
final DirCacheBuilder t = treeT.builder();

b.add(makeEntry("d", FileMode.REGULAR_FILE));

o.add(makeEntry("d", FileMode.REGULAR_FILE, "o !"));

t.add(makeEntry("d/o", FileMode.REGULAR_FILE));

b.finish();
o.finish();
t.finish();
}

final ObjectWriter ow = new ObjectWriter(db);
final ObjectId b = commit(ow, treeB, new ObjectId[] {});
final ObjectId o = commit(ow, treeO, new ObjectId[] { b });
final ObjectId t = commit(ow, treeT, new ObjectId[] { b });

Merger ourMerger = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(db);
boolean merge = ourMerger.merge(new ObjectId[] { o, t });
assertFalse(merge);
}

public void testTrivialTwoWay_rightDFconflict2() throws Exception {
final DirCache treeB = DirCache.read(db);
final DirCache treeO = DirCache.read(db);
final DirCache treeT = DirCache.read(db);
{
final DirCacheBuilder b = treeB.builder();
final DirCacheBuilder o = treeO.builder();
final DirCacheBuilder t = treeT.builder();

b.add(makeEntry("d", FileMode.REGULAR_FILE));

o.add(makeEntry("d/o", FileMode.REGULAR_FILE));

t.add(makeEntry("d", FileMode.REGULAR_FILE, "t !"));

b.finish();
o.finish();
t.finish();
}

final ObjectWriter ow = new ObjectWriter(db);
final ObjectId b = commit(ow, treeB, new ObjectId[] {});
final ObjectId o = commit(ow, treeO, new ObjectId[] { b });
final ObjectId t = commit(ow, treeT, new ObjectId[] { b });

Merger ourMerger = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(db);
boolean merge = ourMerger.merge(new ObjectId[] { o, t });
assertFalse(merge);
}

private void assertCorrectId(final DirCache treeT, final TreeWalk tw) { private void assertCorrectId(final DirCache treeT, final TreeWalk tw) {
assertEquals(treeT.getEntry(tw.getPathString()).getObjectId(), tw assertEquals(treeT.getEntry(tw.getPathString()).getObjectId(), tw
.getObjectId(0)); .getObjectId(0));
Expand Down
Expand Up @@ -115,7 +115,7 @@ protected boolean mergeImpl() throws IOException {
final int modeO = tw.getRawMode(T_OURS); final int modeO = tw.getRawMode(T_OURS);
final int modeT = tw.getRawMode(T_THEIRS); final int modeT = tw.getRawMode(T_THEIRS);
if (modeO == modeT && tw.idEqual(T_OURS, T_THEIRS)) { if (modeO == modeT && tw.idEqual(T_OURS, T_THEIRS)) {
same(); add(T_OURS, DirCacheEntry.STAGE_0);
continue; continue;
} }


Expand All @@ -124,8 +124,24 @@ protected boolean mergeImpl() throws IOException {
add(T_THEIRS, DirCacheEntry.STAGE_0); add(T_THEIRS, DirCacheEntry.STAGE_0);
else if (modeB == modeT && tw.idEqual(T_BASE, T_THEIRS)) else if (modeB == modeT && tw.idEqual(T_BASE, T_THEIRS))
add(T_OURS, DirCacheEntry.STAGE_0); add(T_OURS, DirCacheEntry.STAGE_0);
else { else if (tw.isSubtree()) {
conflict(); if (nonTree(modeB)) {
add(T_BASE, DirCacheEntry.STAGE_1);
hasConflict = true;
}
if (nonTree(modeO)) {
add(T_OURS, DirCacheEntry.STAGE_2);
hasConflict = true;
}
if (nonTree(modeT)) {
add(T_THEIRS, DirCacheEntry.STAGE_3);
hasConflict = true;
}
tw.enterSubtree();
} else {
add(T_BASE, DirCacheEntry.STAGE_1);
add(T_OURS, DirCacheEntry.STAGE_2);
add(T_THEIRS, DirCacheEntry.STAGE_3);
hasConflict = true; hasConflict = true;
} }
} }
Expand All @@ -143,14 +159,8 @@ else if (modeB == modeT && tw.idEqual(T_BASE, T_THEIRS))
} }
} }


private void same() throws IOException { private static boolean nonTree(final int mode) {
add(T_OURS, DirCacheEntry.STAGE_0); return mode != 0 && !FileMode.TREE.equals(mode);
}

private void conflict() throws IOException {
add(T_BASE, DirCacheEntry.STAGE_1);
add(T_OURS, DirCacheEntry.STAGE_2);
add(T_THEIRS, DirCacheEntry.STAGE_3);
} }


private void add(final int tree, final int stage) throws IOException { private void add(final int tree, final int stage) throws IOException {
Expand Down

0 comments on commit 093920a

Please sign in to comment.