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.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSession.java b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSession.java index 7ea7bd1c9..dfd7cca1b 100644 --- a/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSession.java +++ b/org.eclipse.jgit.ssh.apache/src/org/eclipse/jgit/transport/sshd/SshdSession.java @@ -242,7 +242,7 @@ public int exitValue() { @Override public void destroy() { if (channel.isOpen()) { - channel.close(true); + channel.close(false); } } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsBundleWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsBundleWriterTest.java new file mode 100644 index 000000000..4238ee6bf --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsBundleWriterTest.java @@ -0,0 +1,85 @@ +/* + * 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.internal.storage.dfs; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Collections; +import java.util.Set; + +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.transport.FetchResult; +import org.eclipse.jgit.transport.RefSpec; +import org.eclipse.jgit.transport.TransportBundleStream; +import org.eclipse.jgit.transport.URIish; +import org.junit.Before; +import org.junit.Test; + +public class DfsBundleWriterTest { + private TestRepository git; + + private InMemoryRepository repo; + + @Before + public void setUp() throws IOException { + DfsRepositoryDescription desc = new DfsRepositoryDescription("test"); + git = new TestRepository<>(new InMemoryRepository(desc)); + repo = git.getRepository(); + } + + @Test + public void testRepo() throws Exception { + RevCommit commit0 = git.commit().message("0").create(); + RevCommit commit1 = git.commit().message("1").parent(commit0).create(); + git.update("master", commit1); + + RevCommit commit2 = git.commit().message("0").create(); + + byte[] bundle = makeBundle(); + try (Repository newRepo = new InMemoryRepository( + new DfsRepositoryDescription("copy"))) { + fetchFromBundle(newRepo, bundle); + Ref ref = newRepo.exactRef("refs/heads/master"); + assertNotNull(ref); + assertEquals(commit1.toObjectId(), ref.getObjectId()); + + // Unreferenced objects are included as well. + assertTrue(newRepo.getObjectDatabase().has(commit2)); + } + } + + private byte[] makeBundle() throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + DfsBundleWriter.writeEntireRepositoryAsBundle( + NullProgressMonitor.INSTANCE, out, repo); + return out.toByteArray(); + } + + private static FetchResult fetchFromBundle(Repository newRepo, + byte[] bundle) throws Exception { + URIish uri = new URIish("in-memory://"); + ByteArrayInputStream in = new ByteArrayInputStream(bundle); + RefSpec rs = new RefSpec("refs/heads/*:refs/heads/*"); + Set refs = Collections.singleton(rs); + try (TransportBundleStream transport = new TransportBundleStream( + newRepo, uri, in)) { + return transport.fetch(NullProgressMonitor.INSTANCE, refs); + } + } +} 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..c850b4d74 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/GitlinkMergeTest.java @@ -0,0 +1,368 @@ +/* + * 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(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 + 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; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBundleWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBundleWriter.java new file mode 100644 index 000000000..736f381d7 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBundleWriter.java @@ -0,0 +1,52 @@ +/* + * 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.internal.storage.dfs; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jgit.internal.storage.pack.CachedPack; +import org.eclipse.jgit.lib.ProgressMonitor; +import org.eclipse.jgit.transport.BundleWriter; + +/** Writes {@link DfsRepository} to a Git bundle. */ +public class DfsBundleWriter { + /** + * Writes the entire {@link DfsRepository} to a Git bundle. + *

+ * This method try to avoid traversing the pack files as much as possible + * and dumps all objects as-is to a Git bundle. + * + * @param pm + * progress monitor + * @param os + * Git bundle output + * @param db + * repository + * @throws IOException + * thrown if the output stream throws one. + */ + public static void writeEntireRepositoryAsBundle(ProgressMonitor pm, + OutputStream os, DfsRepository db) throws IOException { + BundleWriter bw = new BundleWriter(db); + db.getRefDatabase().getRefs().forEach(bw::include); + List packs = new ArrayList<>(); + for (DfsPackFile p : db.getObjectDatabase().getPacks()) { + packs.add(new DfsCachedPack(p)); + } + bw.addObjectsAsIs(packs); + bw.writeBundle(pm, os); + } + + private DfsBundleWriter() { + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java index 9e409490f..3e4b5df6a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java @@ -754,6 +754,19 @@ public void preparePack(@NonNull Iterator objectsSource) } } + /** + * Prepare the list of objects to be written to the pack stream. + * + *

+ * PackWriter will concat and write out the specified packs as-is. + * + * @param c + * cached packs to be written. + */ + public void preparePack(Collection c) { + cachedPacks.addAll(c); + } + /** * Prepare the list of objects to be written to the pack stream. *

diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java index 506d33312..6c217fdf2 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java @@ -588,7 +588,8 @@ protected boolean processEntry(CanonicalTreeParser base, final int modeO = tw.getRawMode(T_OURS); final int modeT = tw.getRawMode(T_THEIRS); final int modeB = tw.getRawMode(T_BASE); - + boolean gitLinkMerging = isGitLink(modeO) || isGitLink(modeT) + || isGitLink(modeB); if (modeO == 0 && modeT == 0 && modeB == 0) // File is either untracked or new, staged but uncommitted return true; @@ -737,31 +738,28 @@ protected boolean processEntry(CanonicalTreeParser base, return false; } - boolean gitlinkConflict = isGitLink(modeO) || isGitLink(modeT); - // Don't attempt to resolve submodule link conflicts - if (gitlinkConflict || !attributes.canBeContentMerged()) { + if (gitLinkMerging && ignoreConflicts) { + // Always select 'ours' in case of GITLINK merge failures so + // a caller can use virtual commit. + add(tw.getRawPath(), ours, DirCacheEntry.STAGE_0, EPOCH, 0); + return true; + } else if (gitLinkMerging) { + add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0); + add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0); + add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0); + MergeResult result = createGitLinksMergeResult( + base, ours, theirs); + result.setContainsConflicts(true); + mergeResults.put(tw.getPathString(), result); + unmergedPaths.add(tw.getPathString()); + return true; + } else if (!attributes.canBeContentMerged()) { add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0); add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0); add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0); - if (gitlinkConflict) { - MergeResult result = new MergeResult<>( - Arrays.asList( - new SubmoduleConflict(base == null ? null - : base.getEntryObjectId()), - new SubmoduleConflict(ours == null ? null - : ours.getEntryObjectId()), - new SubmoduleConflict(theirs == null ? null - : theirs.getEntryObjectId()))); - result.setContainsConflicts(true); - mergeResults.put(tw.getPathString(), result); - if (!ignoreConflicts) { - unmergedPaths.add(tw.getPathString()); - } - } else { - // attribute merge issues are conflicts but not failures - unmergedPaths.add(tw.getPathString()); - } + // attribute merge issues are conflicts but not failures + unmergedPaths.add(tw.getPathString()); return true; } @@ -786,45 +784,73 @@ protected boolean processEntry(CanonicalTreeParser base, // OURS or THEIRS has been deleted if (((modeO != 0 && !tw.idEqual(T_BASE, T_OURS)) || (modeT != 0 && !tw .idEqual(T_BASE, T_THEIRS)))) { - MergeResult result = contentMerge(base, ours, theirs, - attributes); - - if (ignoreConflicts) { - // 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. - result.setContainsConflicts(false); - updateIndex(base, ours, theirs, result, attributes); - } else { + if (gitLinkMerging && ignoreConflicts) { + add(tw.getRawPath(), ours, DirCacheEntry.STAGE_0, EPOCH, 0); + } else if (gitLinkMerging) { add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, 0); add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, 0); - DirCacheEntry e = add(tw.getRawPath(), theirs, - DirCacheEntry.STAGE_3, EPOCH, 0); + add(tw.getRawPath(), theirs, DirCacheEntry.STAGE_3, EPOCH, 0); + MergeResult result = createGitLinksMergeResult( + base, ours, theirs); + result.setContainsConflicts(true); + mergeResults.put(tw.getPathString(), result); + unmergedPaths.add(tw.getPathString()); + } else { + MergeResult result = contentMerge(base, ours, + theirs, attributes); - // OURS was deleted checkout THEIRS - if (modeO == 0) { - // Check worktree before checking out THEIRS - if (isWorktreeDirty(work, ourDce)) { - return false; - } - if (nonTree(modeT)) { - if (e != null) { - addToCheckout(tw.getPathString(), e, attributes); + if (ignoreConflicts) { + // 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. + result.setContainsConflicts(false); + updateIndex(base, ours, theirs, result, attributes); + } else { + add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH, + 0); + add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, EPOCH, + 0); + DirCacheEntry e = add(tw.getRawPath(), theirs, + DirCacheEntry.STAGE_3, EPOCH, 0); + + // OURS was deleted checkout THEIRS + if (modeO == 0) { + // Check worktree before checking out THEIRS + if (isWorktreeDirty(work, ourDce)) { + return false; + } + if (nonTree(modeT)) { + if (e != null) { + addToCheckout(tw.getPathString(), e, + attributes); + } } } + + unmergedPaths.add(tw.getPathString()); + + // generate a MergeResult for the deleted file + mergeResults.put(tw.getPathString(), result); } - - unmergedPaths.add(tw.getPathString()); - - // generate a MergeResult for the deleted file - mergeResults.put(tw.getPathString(), result); } } } return true; } + private static MergeResult createGitLinksMergeResult( + CanonicalTreeParser base, CanonicalTreeParser ours, + CanonicalTreeParser theirs) { + return new MergeResult<>(Arrays.asList( + new SubmoduleConflict( + base == null ? null : base.getEntryObjectId()), + new SubmoduleConflict( + ours == null ? null : ours.getEntryObjectId()), + new SubmoduleConflict( + theirs == null ? null : theirs.getEntryObjectId()))); + } + /** * Does the content merge. The three texts base, ours and theirs are * specified with {@link CanonicalTreeParser}. If any of the parsers is diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java index 57eed3ad2..e1aa9d72f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java @@ -17,12 +17,16 @@ import java.io.OutputStreamWriter; import java.io.Writer; import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collection; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.internal.storage.pack.CachedPack; import org.eclipse.jgit.internal.storage.pack.PackWriter; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; @@ -62,6 +66,8 @@ public class BundleWriter { private final Set tagTargets; + private final List cachedPacks = new ArrayList<>(); + private PackConfig packConfig; private ObjectCountCallback callback; @@ -149,6 +155,25 @@ else if (r.getObjectId() != null tagTargets.add(r.getObjectId()); } + /** + * Add objects to the bundle file. + * + *

+ * When this method is used, object traversal is disabled and specified pack + * files are directly saved to the Git bundle file. + * + *

+ * Unlike {@link #include}, this doesn't affect the refs. Even if the + * objects are not reachable from any ref, they will be included in the + * bundle file. + * + * @param c + * pack to include + */ + public void addObjectsAsIs(Collection c) { + cachedPacks.addAll(c); + } + /** * Assume a commit is available on the recipient's side. *

@@ -187,19 +212,24 @@ public void writeBundle(ProgressMonitor monitor, OutputStream os) try (PackWriter packWriter = newPackWriter()) { packWriter.setObjectCountCallback(callback); - final HashSet inc = new HashSet<>(); - final HashSet exc = new HashSet<>(); - inc.addAll(include.values()); - for (RevCommit r : assume) - exc.add(r.getId()); packWriter.setIndexDisabled(true); packWriter.setDeltaBaseAsOffset(true); - packWriter.setThin(!exc.isEmpty()); packWriter.setReuseValidatingObjects(false); - if (exc.isEmpty()) { - packWriter.setTagTargets(tagTargets); + if (cachedPacks.isEmpty()) { + HashSet inc = new HashSet<>(); + HashSet exc = new HashSet<>(); + inc.addAll(include.values()); + for (RevCommit r : assume) { + exc.add(r.getId()); + } + if (exc.isEmpty()) { + packWriter.setTagTargets(tagTargets); + } + packWriter.setThin(!exc.isEmpty()); + packWriter.preparePack(monitor, inc, exc); + } else { + packWriter.preparePack(cachedPacks); } - packWriter.preparePack(monitor, inc, exc); final Writer w = new OutputStreamWriter(os, UTF_8); w.write(TransportBundle.V2_BUNDLE_SIGNATURE);