Merge "DfsPackFileMidx: Add method to translate midx position to objectId"
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsPackFileMidxNPacksTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsPackFileMidxNPacksTest.java
index 758ac1f..ea333e6 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsPackFileMidxNPacksTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsPackFileMidxNPacksTest.java
@@ -11,6 +11,7 @@
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.GC;
+import static org.eclipse.jgit.internal.storage.pack.PackExt.MULTI_PACK_INDEX;
 import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
 import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
 import static org.eclipse.jgit.lib.Constants.OBJ_COMMIT;
@@ -120,6 +121,49 @@ public void midx_findIdxPosition_withBase() throws IOException {
 	}
 
 	@Test
+	public void midx_getObjectAt() throws IOException {
+		ObjectId o1 = writePackWithBlob("something".getBytes(UTF_8));
+		ObjectId o2 = writePackWithBlob("something else".getBytes(UTF_8));
+		ObjectId o3 = writePackWithBlob("and more".getBytes(UTF_8));
+		DfsPackFileMidx midx = writeMultipackIndex();
+
+		try (DfsReader ctx = db.getObjectDatabase().newReader()) {
+			assertEquals(o1, midx.getObjectAt(ctx, 2));
+			assertEquals(o2, midx.getObjectAt(ctx, 0));
+			assertEquals(o3, midx.getObjectAt(ctx, 1));
+		}
+	}
+
+	@Test
+	public void midx_getObjectAt_withBase() throws IOException {
+		ObjectId o1 = writePackWithBlob("o1".getBytes(UTF_8));
+		ObjectId o2 = writePackWithBlob("o2".getBytes(UTF_8));
+		ObjectId o3 = writePackWithBlob("o3".getBytes(UTF_8));
+		ObjectId o4 = writePackWithBlob("o4".getBytes(UTF_8));
+		ObjectId o5 = writePackWithBlob("o5".getBytes(UTF_8));
+		ObjectId o6 = writePackWithBlob("o6".getBytes(UTF_8));
+		DfsPackFile[] packs = db.getObjectDatabase().getPacks();
+
+		// Packs are in reverse insertion order
+		DfsPackFileMidx midxBase = writeMultipackIndex(
+				Arrays.copyOfRange(packs, 4, 6), null);
+		DfsPackFileMidx midxMid = writeMultipackIndex(
+				Arrays.copyOfRange(packs, 2, 4), midxBase);
+		DfsPackFileMidx midxTip = writeMultipackIndex(
+				Arrays.copyOfRange(packs, 0, 2), midxMid);
+
+		try (DfsReader ctx = db.getObjectDatabase().newReader()) {
+			assertEquals(o1, midxTip.getObjectAt(ctx, 0));
+			assertEquals(o2, midxTip.getObjectAt(ctx, 1));
+			assertEquals(o3, midxTip.getObjectAt(ctx, 2));
+			assertEquals(o4, midxTip.getObjectAt(ctx, 3));
+			// In sha1 order
+			assertEquals(o5, midxTip.getObjectAt(ctx, 5));
+			assertEquals(o6, midxTip.getObjectAt(ctx, 4));
+		}
+	}
+
+	@Test
 	public void midx_hasObject() throws IOException {
 		ObjectId o1 = writePackWithRandomBlob(100);
 		ObjectId o2 = writePackWithRandomBlob(200);
@@ -1184,7 +1228,9 @@ private DfsPackFileMidx writeMultipackIndex(DfsPackFile[] packs,
 					.write(NullProgressMonitor.INSTANCE, out, forMidx);
 			desc.setCoveredPacks(midxStats.packNames().stream()
 					.map(descByName::get).toList());
+			desc.setObjectCount(midxStats.objectCount());
 			desc.addFileExt(PackExt.MULTI_PACK_INDEX);
+			desc.setFileSize(MULTI_PACK_INDEX, midxStats.bytesWritten());
 		}
 		db.getObjectDatabase().commitPack(List.of(desc), null);
 		return DfsPackFileMidx.create(DfsBlockCache.getInstance(), desc,
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsPackFileMidxSingleTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsPackFileMidxSingleTest.java
index 9f698db..01271e5 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsPackFileMidxSingleTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsPackFileMidxSingleTest.java
@@ -117,6 +117,48 @@ public void findIdxPosition_withBase() throws IOException {
 	}
 
 	@Test
+	public void getObjectAt() throws IOException {
+		ObjectId[] oids = writePackWithBlobs("something", "something else",
+				"and more");
+		// oids = [a4..., 33..., 64...]
+		DfsPackFileMidx midx = writeSinglePackMidx();
+
+		try (DfsReader ctx = db.getObjectDatabase().newReader()) {
+			assertEquals(oids[0], midx.getObjectAt(ctx, 2));
+			assertEquals(oids[1], midx.getObjectAt(ctx, 0));
+			assertEquals(oids[2], midx.getObjectAt(ctx, 1));
+		}
+	}
+
+	@Test
+	public void getObjectAt_withBase() throws IOException {
+		ObjectId o1 = writePackWithBlob("o1".getBytes(UTF_8));
+		ObjectId o2 = writePackWithBlob("o2".getBytes(UTF_8));
+		ObjectId o3 = writePackWithBlob("o3".getBytes(UTF_8));
+		ObjectId o4 = writePackWithBlob("o4".getBytes(UTF_8));
+		ObjectId o5 = writePackWithBlob("o5".getBytes(UTF_8));
+		ObjectId o6 = writePackWithBlob("o6".getBytes(UTF_8));
+		DfsPackFile[] packs = db.getObjectDatabase().getPacks();
+
+		// Packs are in reverse insertion order
+		DfsPackFileMidx midxBase = writeMultipackIndex(
+				Arrays.copyOfRange(packs, 3, 6), null);
+		DfsPackFileMidx midxMid = writeSinglePackMidx(packs[2], midxBase);
+		DfsPackFileMidx midxTip = writeMultipackIndex(
+				Arrays.copyOfRange(packs, 0, 2), midxMid);
+
+		try (DfsReader ctx = db.getObjectDatabase().newReader()) {
+			assertEquals(o1, midxTip.getObjectAt(ctx, 0));
+			assertEquals(o3, midxTip.getObjectAt(ctx, 1));
+			assertEquals(o2, midxTip.getObjectAt(ctx, 2));
+			assertEquals(o4, midxTip.getObjectAt(ctx, 3));
+			// In sha1 order
+			assertEquals(o6, midxTip.getObjectAt(ctx, 4));
+			assertEquals(o5, midxTip.getObjectAt(ctx, 5));
+		}
+	}
+
+	@Test
 	public void hasObject() throws IOException {
 		ObjectId[] oids = writePackWithBlobs("aaaa", "bbbb", "cccc");
 		DfsPackFile midx = writeSinglePackMidx();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFileMidx.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFileMidx.java
index 0f96593..5f08444 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFileMidx.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFileMidx.java
@@ -19,6 +19,7 @@
 import org.eclipse.jgit.internal.storage.file.PackIndex;
 import org.eclipse.jgit.internal.storage.file.PackReverseIndex;
 import org.eclipse.jgit.internal.storage.pack.PackOutputStream;
+import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectLoader;
 
 /**
@@ -104,6 +105,24 @@ public List<DfsPackFile> getAllCoveredPacks() {
 	}
 
 	/**
+	 * Get the objectId at the corresponding position in the midx chain up to
+	 * this point
+	 * <p>
+	 * In a chain with midx-tip (100 objects) and midx-base (50 objects),
+	 * positions 0-49 belong to the base midx and 50-149 to the tip midx.
+	 *
+	 * @param ctx
+	 *            a reader for the midx data
+	 * @param nthPosition
+	 *            position in midx chain
+	 * @return the objectId
+	 * @throws IOException
+	 *             a problem reading midx bytes
+	 */
+	abstract ObjectId getObjectAt(DfsReader ctx, long nthPosition)
+			throws IOException;
+
+	/**
 	 * Count of objects in this <b>pack</b> (i.e. including, recursively, its
 	 * base)
 	 *
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFileMidxNPacks.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFileMidxNPacks.java
index 6ff078a..6d9ec1a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFileMidxNPacks.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFileMidxNPacks.java
@@ -140,7 +140,6 @@ public ObjectIdSet asObjectIdSet(DfsReader ctx) throws IOException {
 		return multiPackIndex::hasObject;
 	}
 
-
 	@Override
 	public PackBitmapIndex getBitmapIndex(DfsReader ctx) throws IOException {
 		// TODO(ifrade): at some point we will have bitmaps over the multipack
@@ -227,6 +226,17 @@ public DfsPackFileMidx getMultipackIndexBase() {
 	}
 
 	@Override
+	ObjectId getObjectAt(DfsReader ctx, long nthPosition) throws IOException {
+		int baseObjectCount = base == null ? 0 : base.getObjectCount(ctx);
+		if (nthPosition >= baseObjectCount) {
+			long localPosition = nthPosition - baseObjectCount;
+			return midx(ctx).getObjectAt((int) localPosition);
+		}
+
+		return base.getObjectAt(ctx, nthPosition);
+	}
+
+	@Override
 	public int findIdxPosition(DfsReader ctx, AnyObjectId id)
 			throws IOException {
 		int p = midx(ctx).findPosition(id);
@@ -363,7 +373,6 @@ List<DfsObjectToPack> findAllFromPack(DfsReader ctx,
 		return tmp;
 	}
 
-
 	// Visible for testing
 	static class VOffsetCalculatorNPacks implements VOffsetCalculator {
 		private final DfsPackFile[] packs;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFileMidxSingle.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFileMidxSingle.java
index 60fd7d5..54dce89 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFileMidxSingle.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFileMidxSingle.java
@@ -151,6 +151,17 @@ public int findIdxPosition(DfsReader ctx, AnyObjectId id)
 	}
 
 	@Override
+	ObjectId getObjectAt(DfsReader ctx, long nthPosition) throws IOException {
+		int baseObjects = base == null ? 0 : base.getObjectCount(ctx);
+		if (nthPosition >= baseObjects) {
+			long localPosition = nthPosition - baseObjects;
+			return pack.getPackIndex(ctx).getObjectId(localPosition);
+		}
+
+		return base.getObjectAt(ctx, nthPosition);
+	}
+
+	@Override
 	public boolean hasObject(DfsReader ctx, AnyObjectId id) throws IOException {
 		if (pack.hasObject(ctx, id)) {
 			return true;