PushCertificateStore: Add method to save in batch

Change-Id: I8bfaee1a52d368ffe2cd7e8af1754a5261569078
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushCertificateStoreTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushCertificateStoreTest.java
index d2faaa7..77a0708 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushCertificateStoreTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushCertificateStoreTest.java
@@ -60,7 +60,9 @@
 
 import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
 import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.lib.BatchRefUpdate;
 import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.NullProgressMonitor;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.revwalk.RevCommit;
@@ -271,6 +273,26 @@ public void lockFailure() throws Exception {
 		assertCerts(store2, "refs/heads/branch", addBranch);
 	}
 
+	@Test
+	public void saveInBatch() throws Exception {
+		BatchRefUpdate batch = repo.getRefDatabase().newBatchUpdate();
+		PushCertificate addMaster = newCert(
+				command(zeroId(), ID1, "refs/heads/master"));
+		store.put(addMaster, newIdent());
+		store.save(batch);
+
+		List<ReceiveCommand> commands = batch.getCommands();
+		assertEquals(1, commands.size());
+		ReceiveCommand cmd = commands.get(0);
+
+		try (RevWalk rw = new RevWalk(repo)) {
+			assertEquals("refs/meta/push-certs", cmd.getRefName());
+			assertEquals(ReceiveCommand.Result.NOT_ATTEMPTED, cmd.getResult());
+			batch.execute(rw, NullProgressMonitor.INSTANCE);
+			assertEquals(ReceiveCommand.Result.OK, cmd.getResult());
+		}
+	}
+
 	private PersonIdent newIdent() {
 		return new PersonIdent(
 				"A U. Thor", "author@example.com", ts.getAndIncrement(), 0);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java
index 94677f9..4334620 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java
@@ -67,6 +67,7 @@
 import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
 import org.eclipse.jgit.dircache.DirCacheEntry;
 import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.BatchRefUpdate;
 import org.eclipse.jgit.lib.CommitBuilder;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
@@ -326,9 +327,64 @@ public void put(PushCertificate cert, PersonIdent ident) {
 	 *             repository.
 	 */
 	public RefUpdate.Result save() throws IOException {
-		if (pending.isEmpty()) {
+		ObjectId newId = write();
+		if (newId == null) {
 			return RefUpdate.Result.NO_CHANGE;
 		}
+		try (ObjectInserter inserter = db.newObjectInserter()) {
+			RefUpdate.Result result = updateRef(newId);
+			switch (result) {
+				case FAST_FORWARD:
+				case NEW:
+				case NO_CHANGE:
+					pending.clear();
+					break;
+				default:
+					break;
+			}
+			return result;
+		} finally {
+			close();
+		}
+	}
+
+	/**
+	 * Save pending certificates to the store in an existing batch ref update.
+	 * <p>
+	 * One commit is created per certificate added with {@link
+	 * #put(PushCertificate, PersonIdent)}, in order of identity timestamps, all
+	 * commits are flushed, and a single command is added to the batch.
+	 * <p>
+	 * The pending list is <em>not</em> cleared. If the ref update succeeds, the
+	 * caller is responsible for calling {@link #clear()}.
+	 *
+	 * @param batch
+	 *            update to save to.
+	 * @throws IOException
+	 *             if there was an error reading from or writing to the
+	 *             repository.
+	 */
+	public void save(BatchRefUpdate batch) throws IOException {
+		ObjectId newId = write();
+		if (newId == null) {
+			return;
+		}
+		batch.addCommand(new ReceiveCommand(
+				commit != null ? commit : ObjectId.zeroId(), newId, REF_NAME));
+	}
+
+	/**
+	 * Clear pending certificates added with {@link #put(PushCertificate,
+	 * PersonIdent)}.
+	 */
+	public void clear() {
+		pending.clear();
+	}
+
+	private ObjectId write() throws IOException {
+		if (pending.isEmpty()) {
+			return null;
+		}
 		if (reader == null) {
 			load();
 		}
@@ -341,19 +397,7 @@ public RefUpdate.Result save() throws IOException {
 				curr = saveCert(inserter, dc, pc, curr);
 			}
 			inserter.flush();
-			RefUpdate.Result result = updateRef(curr);
-			switch (result) {
-				case FAST_FORWARD:
-				case NEW:
-				case NO_CHANGE:
-					pending.clear();
-					break;
-				default:
-					break;
-			}
-			return result;
-		} finally {
-			close();
+			return curr;
 		}
 	}