| /* |
| * 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.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_AddNew() throws Exception { |
| assertGitLinkValue( |
| testGitLink(null, null, LINK_ID3, newResolveMerger(), true), |
| LINK_ID3); |
| } |
| |
| @Test |
| public void testGitLinkMerging_Delete() throws Exception { |
| assertGitLinkDoesntExist(testGitLink(LINK_ID1, LINK_ID1, null, |
| newResolveMerger(), true)); |
| } |
| |
| @Test |
| public void testGitLinkMerging_UpdateDelete() throws Exception { |
| testGitLink(LINK_ID1, LINK_ID2, null, newResolveMerger(), false); |
| } |
| |
| @Test |
| public void testGitLinkMerging_DeleteUpdate() throws Exception { |
| testGitLink(LINK_ID1, null, LINK_ID3, newResolveMerger(), false); |
| } |
| |
| @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 |
| public void testGitLinkMerging_Delete_ignoreConflicts() throws Exception { |
| assertGitLinkDoesntExist(testGitLink(LINK_ID1, LINK_ID1, null, |
| newIgnoreConflictMerger(), true)); |
| } |
| |
| @Test |
| public void testGitLinkMerging_UpdateDelete_ignoreConflicts() |
| throws Exception { |
| assertGitLinkValue(testGitLink(LINK_ID1, LINK_ID2, null, |
| newIgnoreConflictMerger(), true), LINK_ID2); |
| } |
| |
| @Test |
| public void testGitLinkMerging_DeleteUpdate_ignoreConflicts() |
| throws Exception { |
| assertGitLinkDoesntExist(testGitLink(LINK_ID1, null, LINK_ID3, |
| newIgnoreConflictMerger(), true)); |
| } |
| |
| @Test |
| public void testGitLinkMerging_UpdateUpdate_ignoreConflicts() |
| throws Exception { |
| assertGitLinkValue(testGitLink(LINK_ID1, LINK_ID2, LINK_ID3, |
| newIgnoreConflictMerger(), true), LINK_ID2); |
| } |
| |
| @Test |
| public void testGitLinkMerging_bothAddedSameLink_ignoreConflicts() |
| throws Exception { |
| assertGitLinkValue(testGitLink(null, LINK_ID2, LINK_ID2, |
| newIgnoreConflictMerger(), true), LINK_ID2); |
| } |
| |
| @Test |
| 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(Boolean.valueOf(shouldMerge), Boolean.valueOf(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 |
| 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); |
| } |
| |
| @Test |
| public void testGitLinkMerging_linkBlobDeleted() throws Exception { |
| // We changed a link to a blob, others has deleted this link. |
| 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")); |
| |
| 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 void assertGitLinkDoesntExist(Merger resolveMerger) |
| throws Exception { |
| try (TreeWalk tw = new TreeWalk(db)) { |
| tw.setRecursive(true); |
| tw.reset(resolveMerger.getResultTreeId()); |
| |
| 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; |
| } |
| } |