From e60ea7324f37bd0a1086e27fad52ecc5232e9c06 Mon Sep 17 00:00:00 2001 From: Demetr Starshov Date: Tue, 11 Aug 2020 17:57:10 -0700 Subject: [PATCH] ResolveMerger: Adding test cases for GITLINK merge Add test cases which cover content-merge resolve logic. Git clients try to agressively merge blobs by content, but GITLINK types of entries can't be merged with each other or with blobs. This change ensures all possible permutations which can trigger blob and GITLINK content merge are covered. Signed-off-by: Demetr Starshov Change-Id: I7e83a28a14d4d2f9e0ba2b1cffbf3224fb7f3fef --- .../jgit/junit/RepositoryTestCase.java | 16 + .../eclipse/jgit/merge/GitlinkMergeTest.java | 291 ++++++++++++++++++ 2 files changed, 307 insertions(+) create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/GitlinkMergeTest.java diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java index de11e2c00..cc84f197a 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java @@ -34,6 +34,7 @@ import org.eclipse.jgit.dircache.DirCacheCheckout; import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.internal.storage.file.FileRepository; +import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; @@ -512,6 +513,21 @@ protected DirCacheEntry createEntry(final String path, final FileMode mode, return entry; } + /** + * Create DirCacheEntry + * + * @param path + * @param objectId + * @return the DirCacheEntry + */ + protected DirCacheEntry createGitLink(String path, AnyObjectId objectId) { + final DirCacheEntry entry = new DirCacheEntry(path, + DirCacheEntry.STAGE_0); + entry.setFileMode(FileMode.GITLINK); + entry.setObjectId(objectId); + return entry; + } + /** * Assert files are equal * diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/GitlinkMergeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/GitlinkMergeTest.java new file mode 100644 index 000000000..4165812e2 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/GitlinkMergeTest.java @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2020, Google LLC and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +package org.eclipse.jgit.merge; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; + +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.dircache.DirCache; +import org.eclipse.jgit.dircache.DirCacheBuilder; +import org.eclipse.jgit.dircache.DirCacheEntry; +import org.eclipse.jgit.lib.CommitBuilder; +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.junit.Ignore; +import org.junit.Test; + +public class GitlinkMergeTest extends SampleDataRepositoryTestCase { + private static final String LINK_ID1 = "DEADBEEFDEADBEEFBABEDEADBEEFDEADBEEFBABE"; + private static final String LINK_ID2 = "DEADDEADDEADDEADDEADDEADDEADDEADDEADDEAD"; + private static final String LINK_ID3 = "BEEFBEEFBEEFBEEFBEEFBEEFBEEFBEEFBEEFBEEF"; + + private static final String SUBMODULE_PATH = "submodule.link"; + + @Test + public void testGitLinkMerging_UpdateUpdate() throws Exception { + testGitLink(LINK_ID1, LINK_ID2, LINK_ID3, newResolveMerger(), false); + } + + @Test + public void testGitLinkMerging_bothAddedSameLink() throws Exception { + assertGitLinkValue( + testGitLink(null, LINK_ID2, LINK_ID2, newResolveMerger(), true), + LINK_ID2); + } + + @Test + public void testGitLinkMerging_bothAddedDifferentLink() throws Exception { + testGitLink(null, LINK_ID2, LINK_ID3, newResolveMerger(), false); + } + + @Test + public void testGitLinkMerging_AddNew_ignoreConflicts() throws Exception { + assertGitLinkValue( + testGitLink(null, null, LINK_ID3, newIgnoreConflictMerger(), + true), + LINK_ID3); + } + + @Test + @Ignore("broken - doesn't ignore conflicts") + public void testGitLinkMerging_UpdateUpdate_ignoreConflicts() + throws Exception { + assertGitLinkValue(testGitLink(LINK_ID1, LINK_ID2, LINK_ID3, + newIgnoreConflictMerger(), true), LINK_ID2); + } + + @Test + @Ignore("broken - doesn't ignore conflicts") + public void testGitLinkMerging_bothAddedSameLink_ignoreConflicts() + throws Exception { + assertGitLinkValue(testGitLink(null, LINK_ID2, LINK_ID2, + newIgnoreConflictMerger(), true), LINK_ID2); + } + + @Test + @Ignore("broken - doesn't ignore conflicts") + public void testGitLinkMerging_bothAddedDifferentLink_ignoreConflicts() + throws Exception { + assertGitLinkValue(testGitLink(null, LINK_ID2, LINK_ID3, + newIgnoreConflictMerger(), true), LINK_ID2); + } + + protected Merger testGitLink(@Nullable String baseLink, + @Nullable String oursLink, @Nullable String theirsLink, + Merger merger, boolean shouldMerge) + throws Exception { + DirCache treeB = db.readDirCache(); + DirCache treeO = db.readDirCache(); + DirCache treeT = db.readDirCache(); + + DirCacheBuilder bTreeBuilder = treeB.builder(); + DirCacheBuilder oTreeBuilder = treeO.builder(); + DirCacheBuilder tTreeBuilder = treeT.builder(); + + maybeAddLink(bTreeBuilder, baseLink); + maybeAddLink(oTreeBuilder, oursLink); + maybeAddLink(tTreeBuilder, theirsLink); + + bTreeBuilder.finish(); + oTreeBuilder.finish(); + tTreeBuilder.finish(); + + ObjectInserter ow = db.newObjectInserter(); + ObjectId b = commit(ow, treeB, new ObjectId[] {}); + ObjectId o = commit(ow, treeO, new ObjectId[] { b }); + ObjectId t = commit(ow, treeT, new ObjectId[] { b }); + + boolean merge = merger.merge(new ObjectId[] { o, t }); + assertEquals(shouldMerge, merge); + + return merger; + } + + private Merger newResolveMerger() { + return MergeStrategy.RESOLVE.newMerger(db, true); + } + + private Merger newIgnoreConflictMerger() { + return new ResolveMerger(db, true) { + @Override + protected boolean mergeImpl() throws IOException { + // emulate call with ignore conflicts. + return mergeTrees(mergeBase(), sourceTrees[0], sourceTrees[1], + true); + } + }; + } + + @Test + public void testGitLinkMerging_blobWithLink() throws Exception { + DirCache treeB = db.readDirCache(); + DirCache treeO = db.readDirCache(); + DirCache treeT = db.readDirCache(); + + DirCacheBuilder bTreeBuilder = treeB.builder(); + DirCacheBuilder oTreeBuilder = treeO.builder(); + DirCacheBuilder tTreeBuilder = treeT.builder(); + + bTreeBuilder.add( + createEntry(SUBMODULE_PATH, FileMode.REGULAR_FILE, "blob")); + oTreeBuilder.add( + createEntry(SUBMODULE_PATH, FileMode.REGULAR_FILE, "blob 2")); + + maybeAddLink(tTreeBuilder, LINK_ID3); + + bTreeBuilder.finish(); + oTreeBuilder.finish(); + tTreeBuilder.finish(); + + ObjectInserter ow = db.newObjectInserter(); + ObjectId b = commit(ow, treeB, new ObjectId[] {}); + ObjectId o = commit(ow, treeO, new ObjectId[] { b }); + ObjectId t = commit(ow, treeT, new ObjectId[] { b }); + + Merger resolveMerger = MergeStrategy.RESOLVE.newMerger(db); + boolean merge = resolveMerger.merge(new ObjectId[] { o, t }); + assertFalse(merge); + } + + @Test + public void testGitLinkMerging_linkWithBlob() throws Exception { + DirCache treeB = db.readDirCache(); + DirCache treeO = db.readDirCache(); + DirCache treeT = db.readDirCache(); + + DirCacheBuilder bTreeBuilder = treeB.builder(); + DirCacheBuilder oTreeBuilder = treeO.builder(); + DirCacheBuilder tTreeBuilder = treeT.builder(); + + maybeAddLink(bTreeBuilder, LINK_ID1); + maybeAddLink(oTreeBuilder, LINK_ID2); + tTreeBuilder.add( + createEntry(SUBMODULE_PATH, FileMode.REGULAR_FILE, "blob 3")); + + bTreeBuilder.finish(); + oTreeBuilder.finish(); + tTreeBuilder.finish(); + + ObjectInserter ow = db.newObjectInserter(); + ObjectId b = commit(ow, treeB, new ObjectId[] {}); + ObjectId o = commit(ow, treeO, new ObjectId[] { b }); + ObjectId t = commit(ow, treeT, new ObjectId[] { b }); + + Merger resolveMerger = MergeStrategy.RESOLVE.newMerger(db); + boolean merge = resolveMerger.merge(new ObjectId[] { o, t }); + assertFalse(merge); + } + + @Test + public void testGitLinkMerging_linkWithLink() throws Exception { + DirCache treeB = db.readDirCache(); + DirCache treeO = db.readDirCache(); + DirCache treeT = db.readDirCache(); + + DirCacheBuilder bTreeBuilder = treeB.builder(); + DirCacheBuilder oTreeBuilder = treeO.builder(); + DirCacheBuilder tTreeBuilder = treeT.builder(); + + bTreeBuilder.add( + createEntry(SUBMODULE_PATH, FileMode.REGULAR_FILE, "blob")); + maybeAddLink(oTreeBuilder, LINK_ID2); + maybeAddLink(tTreeBuilder, LINK_ID3); + + bTreeBuilder.finish(); + oTreeBuilder.finish(); + tTreeBuilder.finish(); + + ObjectInserter ow = db.newObjectInserter(); + ObjectId b = commit(ow, treeB, new ObjectId[] {}); + ObjectId o = commit(ow, treeO, new ObjectId[] { b }); + ObjectId t = commit(ow, treeT, new ObjectId[] { b }); + + Merger resolveMerger = MergeStrategy.RESOLVE.newMerger(db); + boolean merge = resolveMerger.merge(new ObjectId[] { o, t }); + assertFalse(merge); + } + + @Test + @Ignore("broken - try to do content-merge with GITLINK") + public void testGitLinkMerging_blobWithBlobFromLink() throws Exception { + DirCache treeB = db.readDirCache(); + DirCache treeO = db.readDirCache(); + DirCache treeT = db.readDirCache(); + + DirCacheBuilder bTreeBuilder = treeB.builder(); + DirCacheBuilder oTreeBuilder = treeO.builder(); + DirCacheBuilder tTreeBuilder = treeT.builder(); + + maybeAddLink(bTreeBuilder, LINK_ID1); + oTreeBuilder.add( + createEntry(SUBMODULE_PATH, FileMode.REGULAR_FILE, "blob 2")); + tTreeBuilder.add( + createEntry(SUBMODULE_PATH, FileMode.REGULAR_FILE, "blob 3")); + + bTreeBuilder.finish(); + oTreeBuilder.finish(); + tTreeBuilder.finish(); + + ObjectInserter ow = db.newObjectInserter(); + ObjectId b = commit(ow, treeB, new ObjectId[] {}); + ObjectId o = commit(ow, treeO, new ObjectId[] { b }); + ObjectId t = commit(ow, treeT, new ObjectId[] { b }); + + Merger resolveMerger = MergeStrategy.RESOLVE.newMerger(db); + boolean merge = resolveMerger.merge(new ObjectId[] { o, t }); + assertFalse(merge); + } + + private void maybeAddLink(DirCacheBuilder builder, + @Nullable String linkId) { + if (linkId == null) { + return; + } + DirCacheEntry newLink = createGitLink(SUBMODULE_PATH, + ObjectId.fromString(linkId)); + builder.add(newLink); + } + + private void assertGitLinkValue(Merger resolveMerger, String expectedValue) + throws Exception { + try (TreeWalk tw = new TreeWalk(db)) { + tw.setRecursive(true); + tw.reset(resolveMerger.getResultTreeId()); + + assertTrue(tw.next()); + assertEquals(SUBMODULE_PATH, tw.getPathString()); + assertEquals(ObjectId.fromString(expectedValue), tw.getObjectId(0)); + + assertFalse(tw.next()); + } + } + + private static ObjectId commit(ObjectInserter odi, DirCache treeB, + ObjectId[] parentIds) throws Exception { + CommitBuilder c = new CommitBuilder(); + c.setTreeId(treeB.writeTree(odi)); + c.setAuthor(new PersonIdent("A U Thor", "a.u.thor", 1L, 0)); + c.setCommitter(c.getAuthor()); + c.setParentIds(parentIds); + c.setMessage("Tree " + c.getTreeId().name()); + ObjectId id = odi.insert(c); + odi.flush(); + return id; + } +}