reftable: support threshold based compaction

Transactions may wish to merge several tables together as part of an
operation.  Setting a byte limit allows the transaction to consider
only some recent tables, bounding the cost of the compaction.

Change-Id: If037f2cbdc174ff1a215d5917178b33cde4ddaba
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableCompactor.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableCompactor.java
index a91cdc8..4f92267 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableCompactor.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableCompactor.java
@@ -71,6 +71,8 @@
 	private final ReftableWriter writer = new ReftableWriter();
 	private final ArrayDeque<Reftable> tables = new ArrayDeque<>();
 
+	private long compactBytesLimit;
+	private long bytesToCompact;
 	private boolean includeDeletes;
 	private long minUpdateIndex;
 	private long maxUpdateIndex;
@@ -88,6 +90,16 @@
 	}
 
 	/**
+	 * @param bytes
+	 *            limit on number of bytes from source tables to compact.
+	 * @return {@code this}
+	 */
+	public ReftableCompactor setCompactBytesLimit(long bytes) {
+		compactBytesLimit = bytes;
+		return this;
+	}
+
+	/**
 	 * @param deletes
 	 *            {@code true} to include deletions in the output, which may be
 	 *            necessary for partial compaction.
@@ -139,6 +151,9 @@
 
 	/**
 	 * Add all of the tables, in the specified order.
+	 * <p>
+	 * Unconditionally adds all tables, ignoring the
+	 * {@link #setCompactBytesLimit(long)}.
 	 *
 	 * @param readers
 	 *            tables to compact. Tables should be ordered oldest first/most
@@ -150,6 +165,32 @@
 	}
 
 	/**
+	 * Try to add this reader at the bottom of the stack.
+	 * <p>
+	 * A reader may be rejected by returning {@code false} if the compactor is
+	 * already rewriting its {@link #setCompactBytesLimit(long)}. When this
+	 * happens the caller should stop trying to add tables, and execute the
+	 * compaction.
+	 *
+	 * @param reader
+	 *            the reader to insert at the bottom of the stack. Caller is
+	 *            responsible for closing the reader.
+	 * @return {@code true} if the compactor accepted this table; {@code false}
+	 *         if the compactor has reached its limit.
+	 * @throws IOException
+	 *             if size of {@code reader} cannot be read.
+	 */
+	public boolean tryAddFirst(ReftableReader reader) throws IOException {
+		long sz = reader.size();
+		if (compactBytesLimit > 0 && bytesToCompact + sz > compactBytesLimit) {
+			return false;
+		}
+		bytesToCompact += sz;
+		tables.addFirst(reader);
+		return true;
+	}
+
+	/**
 	 * Write a compaction to {@code out}.
 	 *
 	 * @param out
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReader.java
index 3d505e3..be1eb40 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/reftable/ReftableReader.java
@@ -433,6 +433,17 @@
 		return end % blockSize == 0 ? blocks : (blocks + 1);
 	}
 
+	/**
+	 * Get size of the reftable, in bytes.
+	 *
+	 * @return size of the reftable, in bytes.
+	 * @throws IOException
+	 *             size cannot be obtained.
+	 */
+	public long size() throws IOException {
+		return src.size();
+	}
+
 	@Override
 	public void close() throws IOException {
 		src.close();