BundleWriter: Allow constructing from only an ObjectReader

Change-Id: I01821d6a9fbed7a5fe4619884e42937fbd6909ce
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java
index a83a993..658b971 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/BundleWriterTest.java
@@ -45,7 +45,10 @@
 
 package org.eclipse.jgit.transport;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -59,10 +62,16 @@
 import java.util.Set;
 
 import org.eclipse.jgit.errors.MissingBundlePrerequisiteException;
+import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.errors.NotSupportedException;
 import org.eclipse.jgit.errors.TransportException;
+import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.NullProgressMonitor;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
@@ -161,6 +170,39 @@
 		assertTrue(caught);
 	}
 
+	@Test
+	public void testCustomObjectReader() throws Exception {
+		String refName = "refs/heads/blob";
+		String data = "unflushed data";
+		ObjectId id;
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		try (Repository repo = new InMemoryRepository(
+					new DfsRepositoryDescription("repo"));
+				ObjectInserter ins = repo.newObjectInserter();
+				ObjectReader or = ins.newReader()) {
+			id = ins.insert(OBJ_BLOB, Constants.encode(data));
+			BundleWriter bw = new BundleWriter(or);
+			bw.include(refName, id);
+			bw.writeBundle(NullProgressMonitor.INSTANCE, out);
+			assertNull(repo.exactRef(refName));
+			try {
+				repo.open(id, OBJ_BLOB);
+				fail("We should not be able to open the unflushed blob");
+			} catch (MissingObjectException e) {
+				// Expected.
+			}
+		}
+
+		try (Repository repo = new InMemoryRepository(
+					new DfsRepositoryDescription("copy"))) {
+			fetchFromBundle(repo, out.toByteArray());
+			Ref ref = repo.exactRef(refName);
+			assertNotNull(ref);
+			assertEquals(id, ref.getObjectId());
+			assertEquals(data, new String(repo.open(id, OBJ_BLOB).getBytes(), UTF_8));
+		}
+	}
+
 	private static FetchResult fetchFromBundle(final Repository newRepo,
 			final byte[] bundle) throws URISyntaxException,
 			NotSupportedException, TransportException {
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 37d70e3..0920f21 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java
@@ -58,6 +58,7 @@
 import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
@@ -84,6 +85,8 @@
 public class BundleWriter {
 	private final Repository db;
 
+	private final ObjectReader reader;
+
 	private final Map<String, ObjectId> include;
 
 	private final Set<RevCommit> assume;
@@ -100,8 +103,26 @@
 	 * @param repo
 	 *            repository where objects are stored.
 	 */
-	public BundleWriter(final Repository repo) {
+	public BundleWriter(Repository repo) {
 		db = repo;
+		reader = null;
+		include = new TreeMap<>();
+		assume = new HashSet<>();
+		tagTargets = new HashSet<>();
+	}
+
+	/**
+	 * Create a writer for a bundle.
+	 *
+	 * @param or
+	 *            reader for reading objects. Will be closed at the end of {@link
+	 *            #writeBundle(ProgressMonitor, OutputStream)}, but readers may be
+	 *            reused after closing.
+	 * @since 4.8
+	 */
+	public BundleWriter(ObjectReader or) {
+		db = null;
+		reader = or;
 		include = new TreeMap<>();
 		assume = new HashSet<>();
 		tagTargets = new HashSet<>();
@@ -112,7 +133,8 @@
 	 *
 	 * @param pc
 	 *            configuration controlling packing parameters. If null the
-	 *            source repository's settings will be used.
+	 *            source repository's settings will be used, or the default
+	 *            settings if constructed without a repo.
 	 */
 	public void setPackConfig(PackConfig pc) {
 		this.packConfig = pc;
@@ -196,10 +218,7 @@
 	 */
 	public void writeBundle(ProgressMonitor monitor, OutputStream os)
 			throws IOException {
-		PackConfig pc = packConfig;
-		if (pc == null)
-			pc = new PackConfig(db);
-		try (PackWriter packWriter = new PackWriter(pc, db.newObjectReader())) {
+		try (PackWriter packWriter = newPackWriter()) {
 			packWriter.setObjectCountCallback(callback);
 
 			final HashSet<ObjectId> inc = new HashSet<>();
@@ -242,6 +261,14 @@
 		}
 	}
 
+	private PackWriter newPackWriter() {
+		PackConfig pc = packConfig;
+		if (pc == null) {
+			pc = db != null ? new PackConfig(db) : new PackConfig();
+		}
+		return new PackWriter(pc, reader != null ? reader : db.newObjectReader());
+	}
+
 	/**
 	 * Set the {@link ObjectCountCallback}.
 	 * <p>