Merge "Do not log packfiles moved away or pruned"
diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/MultiPackIndex.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/MultiPackIndex.java
index a7df4fa..d91f713 100644
--- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/MultiPackIndex.java
+++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/MultiPackIndex.java
@@ -16,14 +16,13 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.util.LinkedHashMap;
 
 import org.eclipse.jgit.internal.storage.file.ObjectDirectory;
 import org.eclipse.jgit.internal.storage.file.Pack;
 import org.eclipse.jgit.internal.storage.file.PackFile;
-import org.eclipse.jgit.internal.storage.file.PackIndex;
 import org.eclipse.jgit.internal.storage.midx.MultiPackIndexPrettyPrinter;
 import org.eclipse.jgit.internal.storage.midx.MultiPackIndexWriter;
+import org.eclipse.jgit.internal.storage.midx.PackIndexMerger;
 import org.eclipse.jgit.internal.storage.pack.PackExt;
 import org.eclipse.jgit.lib.NullProgressMonitor;
 import org.kohsuke.args4j.Argument;
@@ -86,11 +85,11 @@ private void writeMultiPackIndex() throws IOException {
 
 		ObjectDirectory odb = (ObjectDirectory) db.getObjectDatabase();
 
-		LinkedHashMap<String, PackIndex> indexes = new LinkedHashMap<>();
+		PackIndexMerger.Builder builder = PackIndexMerger.builder();
 		for (Pack pack : odb.getPacks()) {
 			PackFile packFile = pack.getPackFile().create(PackExt.INDEX);
 			try {
-				indexes.put(packFile.getName(), pack.getIndex());
+				builder.addPack(packFile.getName(), pack.getIndex());
 			} catch (IOException e) {
 				throw die("Cannot open index in pack", e);
 			}
@@ -98,7 +97,7 @@ private void writeMultiPackIndex() throws IOException {
 
 		MultiPackIndexWriter writer = new MultiPackIndexWriter();
 		try (FileOutputStream out = new FileOutputStream(midxPath)) {
-			writer.write(NullProgressMonitor.INSTANCE, out, indexes);
+			writer.write(NullProgressMonitor.INSTANCE, out, builder.build());
 		} catch (IOException e) {
 			throw die("Cannot write midx " + midxPath, e);
 		}
diff --git a/org.eclipse.jgit.test/exttst/org/eclipse/jgit/internal/storage/midx/CgitMidxCompatibilityTest.java b/org.eclipse.jgit.test/exttst/org/eclipse/jgit/internal/storage/midx/CgitMidxCompatibilityTest.java
index 334e52b..3aa6d69 100644
--- a/org.eclipse.jgit.test/exttst/org/eclipse/jgit/internal/storage/midx/CgitMidxCompatibilityTest.java
+++ b/org.eclipse.jgit.test/exttst/org/eclipse/jgit/internal/storage/midx/CgitMidxCompatibilityTest.java
@@ -25,13 +25,10 @@
 import java.nio.file.Files;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
 import org.eclipse.jgit.internal.storage.file.Pack;
 import org.eclipse.jgit.internal.storage.file.PackFile;
-import org.eclipse.jgit.internal.storage.file.PackIndex;
 import org.eclipse.jgit.internal.storage.pack.PackExt;
 import org.eclipse.jgit.lib.NullProgressMonitor;
 import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
@@ -93,15 +90,15 @@ public void jgit_loadsCgitMidx()
 	}
 
 	private byte[] generateJGitMidx() throws IOException {
-		Map<String, PackIndex> indexes = new HashMap<>();
+		PackIndexMerger.Builder builder = PackIndexMerger.builder();
 		for (Pack pack : db.getObjectDatabase().getPacks()) {
 			PackFile packFile = pack.getPackFile().create(PackExt.INDEX);
-			indexes.put(packFile.getName(), pack.getIndex());
+			builder.addPack(packFile.getName(), pack.getIndex());
 		}
 
 		MultiPackIndexWriter writer = new MultiPackIndexWriter();
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
-		writer.write(NullProgressMonitor.INSTANCE, out, indexes);
+		writer.write(NullProgressMonitor.INSTANCE, out, builder.build());
 		return out.toByteArray();
 	}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsMidxWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsMidxWriterBitmapsTest.java
similarity index 98%
rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsMidxWriterTest.java
rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsMidxWriterBitmapsTest.java
index ac89baa..0872dd4 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsMidxWriterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsMidxWriterBitmapsTest.java
@@ -37,7 +37,7 @@
 import com.googlecode.javaewah.EWAHCompressedBitmap;
 
 @RunWith(Parameterized.class)
-public class DfsMidxWriterTest {
+public class DfsMidxWriterBitmapsTest {
 
 	@Parameterized.Parameters(name = "{0}")
 	public static Iterable<TestInput> data() throws Exception {
@@ -55,7 +55,7 @@ public String toString() {
 
 	private TestInput ti;
 
-	public DfsMidxWriterTest(TestInput ti) {
+	public DfsMidxWriterBitmapsTest(TestInput ti) {
 		this.ti = ti;
 	}
 
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/MidxIteratorsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/MidxIteratorsTest.java
new file mode 100644
index 0000000..3105423
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/MidxIteratorsTest.java
@@ -0,0 +1,434 @@
+/*
+ * Copyright (C) 2026, Google LLC.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.internal.storage.midx;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.IntStream;
+
+import org.eclipse.jgit.internal.storage.file.PackIndex;
+import org.eclipse.jgit.internal.storage.midx.MultiPackIndex.MidxIterator;
+import org.eclipse.jgit.junit.FakeIndexFactory;
+import org.eclipse.jgit.lib.ObjectId;
+import org.junit.Test;
+
+public class MidxIteratorsTest {
+	private final static String OID_PREFIX = "0000000000000000000000000000000000";
+
+	@Test
+	public void fromPackIndexIterator_basicIteration() {
+		PackIndex index1 = indexOf(object("000001", 500),
+				object("000003", 3000), object("000005", 1500));
+
+		MidxIterator it = MidxIterators.fromPackIndexIterator("index1", index1);
+		assertNextEntry(it, "000001", 0, 500);
+		assertNextEntry(it, "000003", 0, 3000);
+		assertNextEntry(it, "000005", 0, 1500);
+		assertFalse(it.hasNext());
+	}
+
+	@Test
+	public void fromPackIndexIterator_peek() {
+		PackIndex index1 = indexOf(object("000001", 500),
+				object("000003", 3000), object("000005", 1500));
+
+		MidxIterator it = MidxIterators.fromPackIndexIterator("index1", index1);
+		assertPeekEntry(it, "000001", 0, 500);
+		assertPeekEntry(it, "000001", 0, 500);
+		assertNextEntry(it, "000001", 0, 500);
+
+		assertPeekEntry(it, "000003", 0, 3000);
+		assertPeekEntry(it, "000003", 0, 3000);
+		assertNextEntry(it, "000003", 0, 3000);
+
+		assertPeekEntry(it, "000005", 0, 1500);
+		assertPeekEntry(it, "000005", 0, 1500);
+		assertNextEntry(it, "000005", 0, 1500);
+		assertFalse(it.hasNext());
+	}
+
+	@Test
+	public void fromPackIndexIterator_reset() {
+		PackIndex index1 = indexOf(object("000001", 500),
+				object("000003", 3000), object("000005", 1500));
+
+		MidxIterator it = MidxIterators.fromPackIndexIterator("index1", index1);
+		while (it.hasNext()) {
+			it.next();
+		}
+		it.reset();
+		assertNextEntry(it, "000001", 0, 500);
+		assertNextEntry(it, "000003", 0, 3000);
+
+		it.reset();
+		assertPeekEntry(it, "000001", 0, 500);
+	}
+
+	@Test
+	public void fromPackIndexIterator_getPackNames() {
+		PackIndex index1 = indexOf(object("000001", 500),
+				object("000003", 1500), object("000005", 3000));
+		MidxIterator it = MidxIterators.fromPackIndexIterator("index1", index1);
+		assertEquals(List.of("index1"), it.getPackNames());
+	}
+
+	@Test
+	public void fromPackIndexIterator_empty() {
+		MidxIterator it = MidxIterators.fromPackIndexIterator("index1",
+				indexOf());
+		assertFalse(it.hasNext());
+	}
+
+	@Test
+	public void join_basicIteration() {
+		FakeMidxIterator itOne = FakeMidxIterator.from("itOne", 2,
+				List.of(new IndexEntry("000001", 0, 500),
+						new IndexEntry("000003", 1, 1500)));
+
+		FakeMidxIterator itTwo = FakeMidxIterator.from("itTwo", 2,
+				List.of(new IndexEntry("000002", 0, 500),
+						new IndexEntry("000004", 1, 1500)));
+
+		MidxIterator it = MidxIterators.join(List.of(itOne, itTwo));
+		assertNextEntry(it, "000001", 0, 500);
+		assertNextEntry(it, "000002", 2, 500);
+		assertNextEntry(it, "000003", 1, 1500);
+		assertNextEntry(it, "000004", 3, 1500);
+		assertFalse(it.hasNext());
+	}
+
+	@Test
+	public void join_basicIteration_packIndexIterators() {
+		PackIndex idxOne = indexOf(object("000001", 500),
+				object("000003", 1500), object("000005", 3000));
+
+		PackIndex idxTwo = indexOf(object("000002", 500),
+				object("000003", 1500), object("000004", 3000));
+
+		List<MidxIterator> packIts = List.of(
+				MidxIterators.fromPackIndexIterator("index1", idxOne),
+				MidxIterators.fromPackIndexIterator("index2", idxTwo));
+		MidxIterator it = MidxIterators.join(packIts);
+		assertNextEntry(it, "000001", 0, 500);
+		assertNextEntry(it, "000002", 1, 500);
+		assertNextEntry(it, "000003", 0, 1500);
+		assertNextEntry(it, "000003", 1, 1500);
+		assertNextEntry(it, "000004", 1, 3000);
+		assertNextEntry(it, "000005", 0, 3000);
+		assertFalse(it.hasNext());
+	}
+
+	@Test
+	public void join_duplicates_inPackIdOrder() {
+		FakeMidxIterator itOne = FakeMidxIterator.from("itOne", 2,
+				List.of(new IndexEntry("000001", 0, 501),
+						new IndexEntry("000003", 1, 1501)));
+
+		FakeMidxIterator itTwo = FakeMidxIterator.from("itTwo", 2,
+				List.of(new IndexEntry("000001", 0, 500),
+						new IndexEntry("000003", 1, 1500)));
+
+		MidxIterator it = MidxIterators.join(List.of(itOne, itTwo));
+		assertNextEntry(it, "000001", 0, 501);
+		assertNextEntry(it, "000001", 2, 500);
+		assertNextEntry(it, "000003", 1, 1501);
+		assertNextEntry(it, "000003", 3, 1500);
+		assertFalse(it.hasNext());
+	}
+
+	@Test
+	public void join_duplicates_inPackIdOrder_shift() {
+		FakeMidxIterator itOne = FakeMidxIterator.from("itOne", 5,
+				List.of(new IndexEntry("000001", 4, 501),
+						new IndexEntry("000003", 2, 1501)));
+
+		FakeMidxIterator itTwo = FakeMidxIterator.from("itTwo", 2,
+				List.of(new IndexEntry("000001", 0, 500),
+						new IndexEntry("000003", 1, 1500)));
+
+		MidxIterator it = MidxIterators.join(List.of(itOne, itTwo));
+		assertNextEntry(it, "000001", 4, 501);
+		assertNextEntry(it, "000001", 5, 500);
+		assertNextEntry(it, "000003", 2, 1501);
+		assertNextEntry(it, "000003", 6, 1500);
+		assertFalse(it.hasNext());
+	}
+
+	@Test
+	public void join_peek() {
+		FakeMidxIterator itOne = FakeMidxIterator.from("itOne", 2,
+				List.of(new IndexEntry("000001", 0, 500),
+						new IndexEntry("000003", 1, 1500)));
+
+		FakeMidxIterator itTwo = FakeMidxIterator.from("itTwo", 2,
+				List.of(new IndexEntry("000002", 0, 500),
+						new IndexEntry("000004", 1, 1500)));
+
+		MidxIterator it = MidxIterators.join(List.of(itOne, itTwo));
+		assertPeekEntry(it, "000001", 0, 500);
+		assertPeekEntry(it, "000001", 0, 500);
+		assertNextEntry(it, "000001", 0, 500);
+
+		assertPeekEntry(it, "000002", 2, 500);
+		assertPeekEntry(it, "000002", 2, 500);
+		assertNextEntry(it, "000002", 2, 500);
+
+		assertPeekEntry(it, "000003", 1, 1500);
+		assertPeekEntry(it, "000003", 1, 1500);
+		assertNextEntry(it, "000003", 1, 1500);
+
+		assertPeekEntry(it, "000004", 3, 1500);
+		assertPeekEntry(it, "000004", 3, 1500);
+		assertNextEntry(it, "000004", 3, 1500);
+		assertFalse(it.hasNext());
+	}
+
+	@Test
+	public void join_reset() {
+		FakeMidxIterator itOne = FakeMidxIterator.from("itOne", 2,
+				List.of(new IndexEntry("000001", 0, 500),
+						new IndexEntry("000003", 1, 1500)));
+
+		FakeMidxIterator itTwo = FakeMidxIterator.from("itTwo", 2,
+				List.of(new IndexEntry("000002", 0, 500),
+						new IndexEntry("000004", 1, 1500)));
+
+		MidxIterator it = MidxIterators.join(List.of(itOne, itTwo));
+		while (it.hasNext()) {
+			it.next();
+		}
+
+		it.reset();
+		assertNextEntry(it, "000001", 0, 500);
+		assertNextEntry(it, "000002", 2, 500);
+
+		it.reset();
+		assertPeekEntry(it, "000001", 0, 500);
+	}
+
+	@Test
+	public void join_getPackNames() {
+		FakeMidxIterator itOne = FakeMidxIterator.from("itOne", 2,
+				List.of(new IndexEntry("000001", 0, 500),
+						new IndexEntry("000003", 1, 1500)));
+
+		FakeMidxIterator itTwo = FakeMidxIterator.from("itTwo", 2,
+				List.of(new IndexEntry("000002", 0, 500),
+						new IndexEntry("000004", 1, 1500)));
+
+		MidxIterator it = MidxIterators.join(List.of(itOne, itTwo));
+		assertEquals(List.of("itOne0", "itOne1", "itTwo0", "itTwo1"),
+				it.getPackNames());
+	}
+
+	@Test
+	public void join_empty_totallyEmpty() {
+		FakeMidxIterator itOne = FakeMidxIterator.from("itOne", 2, List.of());
+		FakeMidxIterator itTwo = FakeMidxIterator.from("itTwo", 2, List.of());
+
+		MidxIterator it = MidxIterators.join(List.of(itOne, itTwo));
+		assertFalse(it.hasNext());
+	}
+
+	@Test
+	public void join_empty_oneSideEmpty() {
+		FakeMidxIterator itOne = FakeMidxIterator.from("itOne", 2, List.of());
+		FakeMidxIterator itTwo = FakeMidxIterator.from("itTwo", 2,
+				List.of(new IndexEntry("000002", 0, 500),
+						new IndexEntry("000004", 1, 1500)));
+
+		MidxIterator it = MidxIterators.join(List.of(itOne, itTwo));
+		// Even when empty, the first iterator occupies the packIds
+		assertNextEntry(it, "000002", 2, 500);
+		assertNextEntry(it, "000004", 3, 1500);
+		assertFalse(it.hasNext());
+	}
+
+	@Test
+	public void dedup_basicIteration() {
+		FakeMidxIterator itOne = FakeMidxIterator.from("itOne", 2,
+				List.of(new IndexEntry("000001", 0, 500),
+						new IndexEntry("000001", 1, 600),
+						new IndexEntry("000003", 0, 1500),
+						new IndexEntry("000003", 1, 1501)));
+		MidxIterator dedup = MidxIterators.dedup(itOne);
+		assertNextEntry(dedup, "000001", 0, 500);
+		assertNextEntry(dedup, "000003", 0, 1500);
+		assertFalse(dedup.hasNext());
+	}
+
+	@Test
+	public void dedup_peek() {
+		FakeMidxIterator itOne = FakeMidxIterator.from("itOne", 2,
+				List.of(new IndexEntry("000001", 0, 500),
+						new IndexEntry("000001", 1, 600),
+						new IndexEntry("000003", 0, 1500),
+						new IndexEntry("000003", 1, 1501)));
+
+		MidxIterator it = MidxIterators.dedup(itOne);
+		assertPeekEntry(it, "000001", 0, 500);
+		assertPeekEntry(it, "000001", 0, 500);
+		assertNextEntry(it, "000001", 0, 500);
+
+		assertPeekEntry(it, "000003", 0, 1500);
+		assertPeekEntry(it, "000003", 0, 1500);
+		assertNextEntry(it, "000003", 0, 1500);
+
+		assertFalse(it.hasNext());
+	}
+
+	@Test
+	public void dedup_reset() {
+		FakeMidxIterator itOne = FakeMidxIterator.from("itOne", 2,
+				List.of(new IndexEntry("000001", 0, 500),
+						new IndexEntry("000001", 1, 600),
+						new IndexEntry("000003", 0, 1500),
+						new IndexEntry("000003", 1, 1501),
+						new IndexEntry("000005", 0, 200),
+						new IndexEntry("000005", 1, 201)));
+
+		MidxIterator it = MidxIterators.dedup(itOne);
+		while (it.hasNext()) {
+			it.next();
+		}
+		it.reset();
+		assertNextEntry(it, "000001", 0, 500);
+		assertNextEntry(it, "000003", 0, 1500);
+
+		it.reset();
+		assertPeekEntry(it, "000001", 0, 500);
+	}
+
+	@Test
+	public void dedup_reset_sameElement() {
+		FakeMidxIterator itOne = FakeMidxIterator.from("itOne", 2,
+				List.of(new IndexEntry("000001", 0, 500),
+						new IndexEntry("000001", 1, 600),
+						new IndexEntry("000001", 2, 1500),
+						new IndexEntry("000001", 3, 1501),
+						new IndexEntry("000001", 4, 200),
+						new IndexEntry("000001", 5, 201)));
+
+		MidxIterator it = MidxIterators.dedup(itOne);
+		while (it.hasNext()) {
+			it.next();
+		}
+		it.reset();
+		assertNextEntry(it, "000001", 0, 500);
+
+		it.reset();
+		assertPeekEntry(it, "000001", 0, 500);
+	}
+
+	@Test
+	public void dedup_getPackNames() {
+		FakeMidxIterator itOne = FakeMidxIterator.from("itOne", 4,
+				List.of(new IndexEntry("000001", 0, 500),
+						new IndexEntry("000001", 1, 600),
+						new IndexEntry("000003", 2, 1500),
+						new IndexEntry("000003", 3, 1501)));
+		MidxIterator dedup = MidxIterators.dedup(itOne);
+		assertEquals(List.of("itOne0", "itOne1", "itOne2", "itOne3"),
+				dedup.getPackNames());
+	}
+
+	@Test
+	public void dedup_getEmpty() {
+		FakeMidxIterator itOne = FakeMidxIterator.from("itOne", 4, List.of());
+		MidxIterator dedup = MidxIterators.dedup(itOne);
+		assertFalse(dedup.hasNext());
+	}
+
+	private static void assertNextEntry(MidxIterator it, String shortOid,
+			int packId, int offset) {
+		assertTrue("expected to have more items", it.hasNext());
+		MultiPackIndex.MutableEntry e = it.next();
+		assertEquals(OID_PREFIX + shortOid, e.getObjectId().name());
+		assertEquals(packId, e.getPackId());
+		assertEquals(offset, e.getOffset());
+	}
+
+	private static void assertPeekEntry(MidxIterator it, String shortOid,
+			int packId, int offset) {
+		assertTrue(it.hasNext());
+		MultiPackIndex.MutableEntry e = it.peek();
+		assertEquals(OID_PREFIX + shortOid, e.getObjectId().name());
+		assertEquals(packId, e.getPackId());
+		assertEquals(offset, e.getOffset());
+	}
+
+	private static PackIndex indexOf(FakeIndexFactory.IndexObject... objs) {
+		return FakeIndexFactory.indexOf(Arrays.asList(objs));
+	}
+
+	private static FakeIndexFactory.IndexObject object(String name,
+			long offset) {
+		return new FakeIndexFactory.IndexObject(OID_PREFIX + name, offset);
+	}
+
+	private static class FakeMidxIterator implements MidxIterator {
+		private final List<String> packNames;
+
+		private final List<IndexEntry> entries;
+
+		private int position;
+
+		static FakeMidxIterator from(String packNameBase, int packCount,
+				List<IndexEntry> entries) {
+			List<String> packNames = IntStream.range(0, packCount)
+					.mapToObj(i -> packNameBase + i).toList();
+			return new FakeMidxIterator(packNames, entries);
+		}
+
+		private FakeMidxIterator(List<String> packNames,
+				List<IndexEntry> entries) {
+			this.entries = entries;
+			this.packNames = packNames;
+		}
+
+		@Override
+		public MultiPackIndex.MutableEntry peek() {
+			return entries.get(position).asMutableEntry();
+		}
+
+		@Override
+		public List<String> getPackNames() {
+			return packNames;
+		}
+
+		@Override
+		public boolean hasNext() {
+			return position < entries.size();
+		}
+
+		@Override
+		public MultiPackIndex.MutableEntry next() {
+			return entries.get(position++).asMutableEntry();
+		}
+
+		@Override
+		public void reset() {
+			position = 0;
+		}
+	}
+
+	record IndexEntry(String shortOid, int packId, int offset) {
+		MultiPackIndex.MutableEntry asMutableEntry() {
+			MultiPackIndex.MutableEntry entry = new MultiPackIndex.MutableEntry();
+			entry.oid.fromObjectId(ObjectId.fromString(OID_PREFIX + shortOid));
+			entry.packOffset.setValues(packId, offset);
+			return entry;
+		}
+	}
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/MultiPackIndexLoaderTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/MultiPackIndexLoaderTest.java
index 3c7e27d..a59e874 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/MultiPackIndexLoaderTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/MultiPackIndexLoaderTest.java
@@ -17,7 +17,6 @@
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
-import java.util.LinkedHashMap;
 import java.util.List;
 
 import org.eclipse.jgit.internal.storage.file.PackIndex;
@@ -64,13 +63,11 @@ public void load_validFile_basic_jgit() throws Exception {
 				new FakeIndexFactory.IndexObject(
 						"0000000000000000000000000000000000000012", 1502)));
 
-		LinkedHashMap<String, PackIndex> packs = new LinkedHashMap<>(3);
-		packs.put("p1", idxOne);
-		packs.put("p2", idxTwo);
-		packs.put("p3", idxThree);
+		PackIndexMerger data = PackIndexMerger.builder().addPack("p1", idxOne)
+				.addPack("p2", idxTwo).addPack("p3", idxThree).build();
 		MultiPackIndexWriter writer = new MultiPackIndexWriter();
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
-		writer.write(NullProgressMonitor.INSTANCE, out, packs);
+		writer.write(NullProgressMonitor.INSTANCE, out, data);
 
 		MultiPackIndex midx = MultiPackIndexLoader
 				.read(new ByteArrayInputStream(out.toByteArray()));
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/MultiPackIndexTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/MultiPackIndexTest.java
index a2c86f3..75a5a95 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/MultiPackIndexTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/MultiPackIndexTest.java
@@ -22,7 +22,6 @@
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.Iterator;
-import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Set;
 
@@ -114,7 +113,7 @@ public void jgit_largeOffsetChunk() throws IOException {
 						"0000000000000000000000000000000000000002", (1L << 35)),
 				new FakeIndexFactory.IndexObject(
 						"0000000000000000000000000000000000000003", 13)));
-		LinkedHashMap<String, PackIndex> packs = orderedMapOf("p1", idxOne,
+		PackIndexMerger packs = midxDataFor("p1", idxOne,
 				"p2", idxTwo);
 		MultiPackIndexWriter writer = new MultiPackIndexWriter();
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
@@ -145,7 +144,7 @@ public void jgit_largeOffset_noChunk() throws IOException {
 						"0000000000000000000000000000000000000002", 501),
 				new FakeIndexFactory.IndexObject(
 						"0000000000000000000000000000000000000003", 13)));
-		LinkedHashMap<String, PackIndex> packs = orderedMapOf("p1", idxOne,
+		PackIndexMerger packs = midxDataFor("p1", idxOne,
 				"p2", idxTwo);
 		MultiPackIndexWriter writer = new MultiPackIndexWriter();
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
@@ -182,7 +181,7 @@ public void jgit_resolve() throws IOException {
 				// Match
 				"32fe829a1c000000000000000000000000000010");
 
-		LinkedHashMap<String, PackIndex> packs = orderedMapOf("p1", idxOne,
+		PackIndexMerger packs = midxDataFor("p1", idxOne,
 				"p2", idxTwo);
 		MultiPackIndexWriter writer = new MultiPackIndexWriter();
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
@@ -226,7 +225,7 @@ public void jgit_resolve_matchLimit() throws IOException {
 				// Match
 				"32fe829a1c000000000000000000000000000010");
 
-		LinkedHashMap<String, PackIndex> packs = orderedMapOf("r1", idxOne,
+		PackIndexMerger packs = midxDataFor("r1", idxOne,
 				"r2", idxTwo);
 		MultiPackIndexWriter writer = new MultiPackIndexWriter();
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
@@ -261,7 +260,7 @@ public void jgit_resolve_noMatches() throws IOException {
 				"bbbbbb0000000000000000000000000000000003",
 				"32fe829a1c000000000000000000000000000010");
 
-		LinkedHashMap<String, PackIndex> packs = orderedMapOf("p1", idxOne,
+		PackIndexMerger packs = midxDataFor("p1", idxOne,
 				"p2", idxTwo);
 		MultiPackIndexWriter writer = new MultiPackIndexWriter();
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
@@ -292,7 +291,7 @@ public void jgit_resolve_noMatches_last() throws IOException {
 				"bbbbbb0000000000000000000000000000000003",
 				"32fe829a1c000000000000000000000000000010");
 
-		LinkedHashMap<String, PackIndex> packs = orderedMapOf("p1", idxOne,
+		PackIndexMerger packs = midxDataFor("p1", idxOne,
 				"p2", idxTwo);
 		MultiPackIndexWriter writer = new MultiPackIndexWriter();
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
@@ -314,11 +313,11 @@ public void jgit_resolve_empty() throws IOException {
 		PackIndex idxOne = FakeIndexFactory.indexOf(List.of());
 		PackIndex idxTwo = FakeIndexFactory.indexOf(List.of());
 
-		LinkedHashMap<String, PackIndex> packs = orderedMapOf("p1", idxOne,
+		PackIndexMerger data = midxDataFor("p1", idxOne,
 				"p2", idxTwo);
 		MultiPackIndexWriter writer = new MultiPackIndexWriter();
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
-		writer.write(NullProgressMonitor.INSTANCE, out, packs);
+		writer.write(NullProgressMonitor.INSTANCE, out, data);
 		MultiPackIndex midx = MultiPackIndexLoader
 				.read(new ByteArrayInputStream(out.toByteArray()));
 
@@ -377,11 +376,11 @@ private static MultiPackIndex createMultiPackIndex() throws IOException {
 				new FakeIndexFactory.IndexObject(
 						"0000000000000000000000000000000000000012", 1502)));
 
-		LinkedHashMap<String, PackIndex> packs = orderedMapOf("p1", idxOne,
+		PackIndexMerger data = midxDataFor("p1", idxOne,
 				"p2", idxTwo, "p3", idxThree);
 		MultiPackIndexWriter writer = new MultiPackIndexWriter();
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
-		writer.write(NullProgressMonitor.INSTANCE, out, packs);
+		writer.write(NullProgressMonitor.INSTANCE, out, data);
 
 		return MultiPackIndexLoader
 				.read(new ByteArrayInputStream(out.toByteArray()));
@@ -398,11 +397,11 @@ public void jgit_getObjectCount_emtpy() throws IOException {
 		PackIndex idxOne = FakeIndexFactory.indexOf(List.of());
 		PackIndex idxTwo = FakeIndexFactory.indexOf(List.of());
 
-		LinkedHashMap<String, PackIndex> packs = orderedMapOf("p1", idxOne,
+		PackIndexMerger data = midxDataFor("p1", idxOne,
 				"p2", idxTwo);
 		MultiPackIndexWriter writer = new MultiPackIndexWriter();
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
-		writer.write(NullProgressMonitor.INSTANCE, out, packs);
+		writer.write(NullProgressMonitor.INSTANCE, out, data);
 		MultiPackIndex midx = MultiPackIndexLoader
 				.read(new ByteArrayInputStream(out.toByteArray()));
 
@@ -433,11 +432,11 @@ public void jgit_findBitmapPosition() throws IOException {
 				new FakeIndexFactory.IndexObject(
 						"0000000000000000000000000000000000000012", 1502)));
 
-		LinkedHashMap<String, PackIndex> packs = orderedMapOf("p1", idxOne,
+		PackIndexMerger data = midxDataFor("p1", idxOne,
 				"p2", idxTwo, "p3", idxThree);
 		MultiPackIndexWriter writer = new MultiPackIndexWriter();
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
-		writer.write(NullProgressMonitor.INSTANCE, out, packs);
+		writer.write(NullProgressMonitor.INSTANCE, out, data);
 
 		MultiPackIndex midx = MultiPackIndexLoader
 				.read(new ByteArrayInputStream(out.toByteArray()));
@@ -495,11 +494,11 @@ public void jgit_getObjectAtBitmapPosition() throws IOException {
 				new FakeIndexFactory.IndexObject(
 						"0000000000000000000000000000000000000012", 1502)));
 
-		LinkedHashMap<String, PackIndex> packs = orderedMapOf("p1", idxOne,
+		PackIndexMerger data = midxDataFor("p1", idxOne,
 				"p2", idxTwo, "p3", idxThree);
 		MultiPackIndexWriter writer = new MultiPackIndexWriter();
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
-		writer.write(NullProgressMonitor.INSTANCE, out, packs);
+		writer.write(NullProgressMonitor.INSTANCE, out, data);
 
 		MultiPackIndex midx = MultiPackIndexLoader
 				.read(new ByteArrayInputStream(out.toByteArray()));
@@ -563,11 +562,11 @@ public void jgit_iterator_emtpy() throws IOException {
 		PackIndex idxOne = FakeIndexFactory.indexOf(List.of());
 		PackIndex idxTwo = FakeIndexFactory.indexOf(List.of());
 
-		LinkedHashMap<String, PackIndex> packs = orderedMapOf("p1", idxOne,
+		PackIndexMerger data = midxDataFor("p1", idxOne,
 				"p2", idxTwo);
 		MultiPackIndexWriter writer = new MultiPackIndexWriter();
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
-		writer.write(NullProgressMonitor.INSTANCE, out, packs);
+		writer.write(NullProgressMonitor.INSTANCE, out, data);
 		MultiPackIndex midx = MultiPackIndexLoader
 				.read(new ByteArrayInputStream(out.toByteArray()));
 
@@ -591,11 +590,11 @@ public void jgit_iterator_peek() throws IOException {
 				new FakeIndexFactory.IndexObject(
 						"0000000000000000000000000000000000000015", 1501)));
 
-		LinkedHashMap<String, PackIndex> packs = orderedMapOf("p1", idxOne,
+		PackIndexMerger data = midxDataFor("p1", idxOne,
 				"p2", idxTwo);
 		MultiPackIndexWriter writer = new MultiPackIndexWriter();
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
-		writer.write(NullProgressMonitor.INSTANCE, out, packs);
+		writer.write(NullProgressMonitor.INSTANCE, out, data);
 
 		MultiPackIndex midx = MultiPackIndexLoader
 				.read(new ByteArrayInputStream(out.toByteArray()));
@@ -662,21 +661,16 @@ private static void assertEntry(MultiPackIndex.MutableEntry e, String oid,
 		assertEquals(expectedOffset, e.packOffset.getOffset());
 	}
 
-	private static LinkedHashMap<String, PackIndex> orderedMapOf(String s1,
-			PackIndex pi1, String s2, PackIndex pi2) {
-		LinkedHashMap<String, PackIndex> map = new LinkedHashMap<>(2);
-		map.put(s1, pi1);
-		map.put(s2, pi2);
-		return map;
+	private static PackIndexMerger midxDataFor(String s1, PackIndex pi1,
+			String s2, PackIndex pi2) {
+		return PackIndexMerger.builder().addPack(s1, pi1).addPack(s2, pi2)
+				.build();
 	}
 
-	private static LinkedHashMap<String, PackIndex> orderedMapOf(String s1,
+	private static PackIndexMerger midxDataFor(String s1,
 			PackIndex pi1, String s2, PackIndex pi2, String s3, PackIndex pi3) {
-		LinkedHashMap<String, PackIndex> map = new LinkedHashMap<>(3);
-		map.put(s1, pi1);
-		map.put(s2, pi2);
-		map.put(s3, pi3);
-		return map;
+		return PackIndexMerger.builder().addPack(s1, pi1).addPack(s2, pi2)
+				.addPack(s3, pi3).build();
 	}
 
 	private static ObjectId oid(String last3chars) {
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/MultiPackIndexWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/MultiPackIndexWriterTest.java
index 5971dceb..1ca8aaf 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/MultiPackIndexWriterTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/MultiPackIndexWriterTest.java
@@ -23,7 +23,6 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.LinkedHashMap;
 import java.util.List;
 
 import org.eclipse.jgit.internal.storage.file.PackIndex;
@@ -47,9 +46,9 @@ public void write_allSmallOffsets() throws IOException {
 				object("0000000000000000000000000000000000000004", 1500),
 				object("0000000000000000000000000000000000000006", 3000));
 
-		LinkedHashMap<String, PackIndex> data = new LinkedHashMap<>();
-		data.put("packname1", index1);
-		data.put("packname2", index2);
+		PackIndexMerger data = PackIndexMerger.builder()
+				.addPack("packname1", index1).addPack("packname2", index2)
+				.build();
 
 		MultiPackIndexWriter writer = new MultiPackIndexWriter();
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
@@ -85,9 +84,9 @@ public void write_smallOffset_limit() throws IOException {
 				object("0000000000000000000000000000000000000002", 500),
 				object("0000000000000000000000000000000000000004", 1500),
 				object("0000000000000000000000000000000000000006", 3000));
-		LinkedHashMap<String, PackIndex> data = new LinkedHashMap<>(2);
-		data.put("packname1", index1);
-		data.put("packname2", index2);
+		PackIndexMerger data = PackIndexMerger.builder()
+				.addPack("packname1", index1).addPack("packname2", index2)
+				.build();
 
 		MultiPackIndexWriter writer = new MultiPackIndexWriter();
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
@@ -122,9 +121,9 @@ public void write_largeOffset() throws IOException {
 				object("0000000000000000000000000000000000000002", 500),
 				object("0000000000000000000000000000000000000004", 1500),
 				object("0000000000000000000000000000000000000006", 3000));
-		LinkedHashMap<String, PackIndex> data = new LinkedHashMap<>(2);
-		data.put("bbbbbbbbb", index1);
-		data.put("aaaaaaaaa", index2);
+		PackIndexMerger data = PackIndexMerger.builder()
+				.addPack("bbbbbbbbb", index1).addPack("aaaaaaaaa", index2)
+				.build();
 
 		MultiPackIndexWriter writer = new MultiPackIndexWriter();
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
@@ -159,9 +158,8 @@ public void write_largeOffset() throws IOException {
 	public void jgit_emptyMidx() throws IOException {
 		PackIndex idxOne = FakeIndexFactory.indexOf(List.of());
 		PackIndex idxTwo = FakeIndexFactory.indexOf(List.of());
-		LinkedHashMap<String, PackIndex> packs = new LinkedHashMap<>(2);
-		packs.put("p1", idxOne);
-		packs.put("p2", idxTwo);
+		PackIndexMerger packs = PackIndexMerger.builder().addPack("p1", idxOne)
+				.addPack("p2", idxTwo).build();
 		MultiPackIndexWriter writer = new MultiPackIndexWriter();
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
 		writer.write(NullProgressMonitor.INSTANCE, out, packs);
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/PackIndexMergerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/PackIndexMergerTest.java
index a43992d..971087e 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/PackIndexMergerTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/PackIndexMergerTest.java
@@ -14,19 +14,23 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.util.Arrays;
 import java.util.Iterator;
-import java.util.LinkedHashMap;
 
 import org.eclipse.jgit.internal.storage.file.PackIndex;
+import org.eclipse.jgit.internal.storage.midx.MultiPackIndex.MutableEntry;
 import org.eclipse.jgit.junit.FakeIndexFactory;
 import org.eclipse.jgit.junit.FakeIndexFactory.IndexObject;
+import org.eclipse.jgit.lib.NullProgressMonitor;
 import org.junit.Test;
 
 public class PackIndexMergerTest {
 
 	@Test
-	public void rawIterator_noDuplicates() {
+	public void bySha1Iterator_noDuplicates() {
 		PackIndex idxOne = indexOf(
 				oidOffset("0000000000000000000000000000000000000001", 500),
 				oidOffset("0000000000000000000000000000000000000005", 12),
@@ -39,12 +43,12 @@ public void rawIterator_noDuplicates() {
 				oidOffset("0000000000000000000000000000000000000004", 502),
 				oidOffset("0000000000000000000000000000000000000007", 14),
 				oidOffset("0000000000000000000000000000000000000012", 1502));
-		PackIndexMerger merger = new PackIndexMerger(
-				orderedMapOf("p1", idxOne, "p2", idxTwo, "p3", idxThree));
+		PackIndexMerger merger = createMergerFor("p1", idxOne, "p2", idxTwo,
+				"p3", idxThree);
 		assertEquals(9, merger.getUniqueObjectCount());
 		assertEquals(3, merger.getPackCount());
 		assertFalse(merger.needsLargeOffsetsChunk());
-		Iterator<PackIndexMerger.MidxMutableEntry> it = merger.rawIterator();
+		Iterator<MutableEntry> it = merger.bySha1Iterator();
 		assertNextEntry(it, "0000000000000000000000000000000000000001", 0, 500);
 		assertNextEntry(it, "0000000000000000000000000000000000000002", 1, 501);
 		assertNextEntry(it, "0000000000000000000000000000000000000003", 1, 13);
@@ -61,92 +65,31 @@ public void rawIterator_noDuplicates() {
 	}
 
 	@Test
-	public void rawIterator_noDuplicates_honorPackOrder() {
+	public void bySha1Iterator_withDuplicates() {
 		PackIndex idxOne = indexOf(
 				oidOffset("0000000000000000000000000000000000000001", 500),
-				oidOffset("0000000000000000000000000000000000000005", 12),
 				oidOffset("0000000000000000000000000000000000000010", 1500));
 		PackIndex idxTwo = indexOf(
 				oidOffset("0000000000000000000000000000000000000002", 501),
 				oidOffset("0000000000000000000000000000000000000003", 13),
+				oidOffset("0000000000000000000000000000000000000005", 800),
 				oidOffset("0000000000000000000000000000000000000015", 1501));
 		PackIndex idxThree = indexOf(
 				oidOffset("0000000000000000000000000000000000000004", 502),
+				oidOffset("0000000000000000000000000000000000000005", 12),
 				oidOffset("0000000000000000000000000000000000000007", 14),
 				oidOffset("0000000000000000000000000000000000000012", 1502));
-        PackIndexMerger merger = new PackIndexMerger(
-				orderedMapOf("p3", idxThree, "p2", idxTwo, "p1", idxOne));
+		PackIndexMerger merger = createMergerFor("p1", idxOne, "p2", idxTwo,
+				"p3", idxThree);
 		assertEquals(9, merger.getUniqueObjectCount());
 		assertEquals(3, merger.getPackCount());
 		assertFalse(merger.needsLargeOffsetsChunk());
-		Iterator<PackIndexMerger.MidxMutableEntry> it = merger.rawIterator();
-		assertNextEntry(it, "0000000000000000000000000000000000000001", 2, 500);
-		assertNextEntry(it, "0000000000000000000000000000000000000002", 1, 501);
-		assertNextEntry(it, "0000000000000000000000000000000000000003", 1, 13);
-		assertNextEntry(it, "0000000000000000000000000000000000000004", 0, 502);
-		assertNextEntry(it, "0000000000000000000000000000000000000005", 2, 12);
-		assertNextEntry(it, "0000000000000000000000000000000000000007", 0, 14);
-		assertNextEntry(it, "0000000000000000000000000000000000000010", 2,
-				1500);
-		assertNextEntry(it, "0000000000000000000000000000000000000012", 0,
-				1502);
-		assertNextEntry(it, "0000000000000000000000000000000000000015", 1,
-				1501);
-		assertFalse(it.hasNext());
-	}
-
-	@Test
-	public void rawIterator_allDuplicates() {
-		PackIndex idxOne = indexOf(
-				oidOffset("0000000000000000000000000000000000000001", 500),
-				oidOffset("0000000000000000000000000000000000000005", 12),
-				oidOffset("0000000000000000000000000000000000000010", 1500));
-		PackIndexMerger merger = new PackIndexMerger(
-				orderedMapOf("p1", idxOne, "p2", idxOne, "p3", idxOne));
-		assertEquals(3, merger.getUniqueObjectCount());
-		assertEquals(3, merger.getPackCount());
-		assertFalse(merger.needsLargeOffsetsChunk());
-		Iterator<PackIndexMerger.MidxMutableEntry> it = merger.rawIterator();
-		assertNextEntry(it, "0000000000000000000000000000000000000001", 0, 500);
-		assertNextEntry(it, "0000000000000000000000000000000000000001", 1, 500);
-		assertNextEntry(it, "0000000000000000000000000000000000000001", 2, 500);
-		assertNextEntry(it, "0000000000000000000000000000000000000005", 0, 12);
-		assertNextEntry(it, "0000000000000000000000000000000000000005", 1, 12);
-		assertNextEntry(it, "0000000000000000000000000000000000000005", 2, 12);
-		assertNextEntry(it, "0000000000000000000000000000000000000010", 0,
-				1500);
-		assertNextEntry(it, "0000000000000000000000000000000000000010", 1,
-				1500);
-		assertNextEntry(it, "0000000000000000000000000000000000000010", 2,
-				1500);
-		assertFalse(it.hasNext());
-	}
-
-	@Test
-	public void bySha1Iterator_noDuplicates() {
-		PackIndex idxOne = indexOf(
-				oidOffset("0000000000000000000000000000000000000001", 500),
-				oidOffset("0000000000000000000000000000000000000005", 12),
-				oidOffset("0000000000000000000000000000000000000010", 1500));
-		PackIndex idxTwo = indexOf(
-				oidOffset("0000000000000000000000000000000000000002", 501),
-				oidOffset("0000000000000000000000000000000000000003", 13),
-				oidOffset("0000000000000000000000000000000000000015", 1501));
-		PackIndex idxThree = indexOf(
-				oidOffset("0000000000000000000000000000000000000004", 502),
-				oidOffset("0000000000000000000000000000000000000007", 14),
-				oidOffset("0000000000000000000000000000000000000012", 1502));
-		PackIndexMerger merger = new PackIndexMerger(
-				orderedMapOf("p1", idxOne, "p2", idxTwo, "p3", idxThree));
-		assertEquals(9, merger.getUniqueObjectCount());
-		assertEquals(3, merger.getPackCount());
-		assertFalse(merger.needsLargeOffsetsChunk());
-		Iterator<PackIndexMerger.MidxMutableEntry> it = merger.bySha1Iterator();
+		Iterator<MutableEntry> it = merger.bySha1Iterator();
 		assertNextEntry(it, "0000000000000000000000000000000000000001", 0, 500);
 		assertNextEntry(it, "0000000000000000000000000000000000000002", 1, 501);
 		assertNextEntry(it, "0000000000000000000000000000000000000003", 1, 13);
 		assertNextEntry(it, "0000000000000000000000000000000000000004", 2, 502);
-		assertNextEntry(it, "0000000000000000000000000000000000000005", 0, 12);
+		assertNextEntry(it, "0000000000000000000000000000000000000005", 1, 800);
 		assertNextEntry(it, "0000000000000000000000000000000000000007", 2, 14);
 		assertNextEntry(it, "0000000000000000000000000000000000000010", 0,
 				1500);
@@ -163,12 +106,12 @@ public void bySha1Iterator_allDuplicates() {
 				oidOffset("0000000000000000000000000000000000000001", 500),
 				oidOffset("0000000000000000000000000000000000000005", 12),
 				oidOffset("0000000000000000000000000000000000000010", 1500));
-		PackIndexMerger merger = new PackIndexMerger(
-				orderedMapOf("p1", idxOne, "p2", idxOne, "p3", idxOne));
+		PackIndexMerger merger = createMergerFor("p1", idxOne, "p2", idxOne,
+				"p3", idxOne);
 		assertEquals(3, merger.getUniqueObjectCount());
 		assertEquals(3, merger.getPackCount());
 		assertFalse(merger.needsLargeOffsetsChunk());
-		Iterator<PackIndexMerger.MidxMutableEntry> it = merger.bySha1Iterator();
+		Iterator<MutableEntry> it = merger.bySha1Iterator();
 		assertNextEntry(it, "0000000000000000000000000000000000000001", 0, 500);
 		assertNextEntry(it, "0000000000000000000000000000000000000005", 0, 12);
 		assertNextEntry(it, "0000000000000000000000000000000000000010", 0,
@@ -187,12 +130,12 @@ public void bySha1Iterator_differentIndexSizes() {
 				oidOffset("0000000000000000000000000000000000000004", 500),
 				oidOffset("0000000000000000000000000000000000000007", 12),
 				oidOffset("0000000000000000000000000000000000000012", 1500));
-		PackIndexMerger merger = new PackIndexMerger(
-				orderedMapOf("p1", idxOne, "p2", idxTwo, "p3", idxThree));
+		PackIndexMerger merger = createMergerFor("p1", idxOne, "p2", idxTwo,
+				"p3", idxThree);
 		assertEquals(6, merger.getUniqueObjectCount());
 		assertEquals(3, merger.getPackCount());
 		assertFalse(merger.needsLargeOffsetsChunk());
-		Iterator<PackIndexMerger.MidxMutableEntry> it = merger.bySha1Iterator();
+		Iterator<MutableEntry> it = merger.bySha1Iterator();
 		assertNextEntry(it, "0000000000000000000000000000000000000002", 1, 500);
 		assertNextEntry(it, "0000000000000000000000000000000000000003", 1, 12);
 		assertNextEntry(it, "0000000000000000000000000000000000000004", 2, 500);
@@ -205,8 +148,46 @@ public void bySha1Iterator_differentIndexSizes() {
 	}
 
 	@Test
+	public void bySha1Iterator_withAnotherMidx() throws IOException {
+		PackIndex idxOne = indexOf(
+				oidOffset("0000000000000000000000000000000000000010", 1500));
+		PackIndex idxTwo = indexOf(
+				oidOffset("0000000000000000000000000000000000000002", 500),
+				oidOffset("0000000000000000000000000000000000000003", 12));
+		PackIndex idxThree = indexOf(
+				oidOffset("0000000000000000000000000000000000000004", 500),
+				oidOffset("0000000000000000000000000000000000000007", 12),
+				oidOffset("0000000000000000000000000000000000000012", 1500));
+		MultiPackIndex midx = midxOf("one", idxOne, "two", idxTwo, "three",
+				idxThree);
+
+		PackIndex idxFour = indexOf(
+				oidOffset("0000000000000000000000000000000000000001", 12),
+				oidOffset("0000000000000000000000000000000000000007", 600),
+				oidOffset("0000000000000000000000000000000000000015", 300));
+
+		PackIndexMerger merger = PackIndexMerger.builder()
+				.addMidx(midx.iterator()).addPack("four", idxFour).build();
+		assertEquals(8, merger.getUniqueObjectCount());
+		assertEquals(4, merger.getPackCount());
+		assertFalse(merger.needsLargeOffsetsChunk());
+		Iterator<MutableEntry> it = merger.bySha1Iterator();
+		assertNextEntry(it, "0000000000000000000000000000000000000001", 3, 12);
+		assertNextEntry(it, "0000000000000000000000000000000000000002", 1, 500);
+		assertNextEntry(it, "0000000000000000000000000000000000000003", 1, 12);
+		assertNextEntry(it, "0000000000000000000000000000000000000004", 2, 500);
+		assertNextEntry(it, "0000000000000000000000000000000000000007", 2, 12);
+		assertNextEntry(it, "0000000000000000000000000000000000000010", 0,
+				1500);
+		assertNextEntry(it, "0000000000000000000000000000000000000012", 2,
+				1500);
+		assertNextEntry(it, "0000000000000000000000000000000000000015", 3, 300);
+		assertFalse(it.hasNext());
+	}
+
+	@Test
 	public void merger_noIndexes() {
-        PackIndexMerger merger = new PackIndexMerger(new LinkedHashMap<>());
+		PackIndexMerger merger = PackIndexMerger.builder().build();
 		assertEquals(0, merger.getUniqueObjectCount());
 		assertFalse(merger.needsLargeOffsetsChunk());
 		assertTrue(merger.getPackNames().isEmpty());
@@ -216,8 +197,8 @@ public void merger_noIndexes() {
 
 	@Test
 	public void merger_emptyIndexes() {
-		PackIndexMerger merger = new PackIndexMerger(
-                orderedMapOf("p1", indexOf(), "p2", indexOf()));
+		PackIndexMerger merger = createMergerFor("p1", indexOf(), "p2",
+				indexOf());
 		assertEquals(0, merger.getUniqueObjectCount());
 		assertFalse(merger.needsLargeOffsetsChunk());
 		assertEquals(2, merger.getPackNames().size());
@@ -232,8 +213,7 @@ public void bySha1Iterator_largeOffsets_needsChunk() {
 				oidOffset("0000000000000000000000000000000000000004", 12));
 		PackIndex idx2 = indexOf(oidOffset(
 				"0000000000000000000000000000000000000003", (1L << 31) + 10));
-		PackIndexMerger merger = new PackIndexMerger(
-                orderedMapOf("p1", idx1, "p2", idx2));
+		PackIndexMerger merger = createMergerFor("p1", idx1, "p2", idx2);
 		assertTrue(merger.needsLargeOffsetsChunk());
 		assertEquals(2, merger.getOffsetsOver31BitsCount());
 		assertEquals(3, merger.getUniqueObjectCount());
@@ -248,8 +228,7 @@ public void bySha1Iterator_largeOffsets_noChunk() {
 				oidOffset("0000000000000000000000000000000000000004", 12));
 		PackIndex idx2 = indexOf(oidOffset(
 				"0000000000000000000000000000000000000003", (1L << 31) + 10));
-		PackIndexMerger merger = new PackIndexMerger(
-                orderedMapOf("p1", idx1, "p2", idx2));
+		PackIndexMerger merger = createMergerFor("p1", idx1, "p2", idx2);
 		assertFalse(merger.needsLargeOffsetsChunk());
 		assertEquals(2, merger.getOffsetsOver31BitsCount());
 		assertEquals(3, merger.getUniqueObjectCount());
@@ -269,8 +248,8 @@ public void getObjectsPerPack_noDuplicates() {
 				oidOffset("0000000000000000000000000000000000000004", 502),
 				oidOffset("0000000000000000000000000000000000000007", 14),
 				oidOffset("0000000000000000000000000000000000000012", 1502));
-		PackIndexMerger merger = new PackIndexMerger(
-				orderedMapOf("p1", idxOne, "p2", idxTwo, "p3", idxThree));
+		PackIndexMerger merger = createMergerFor("p1", idxOne, "p2", idxTwo,
+				"p3", idxThree);
 		assertArrayEquals(new int[] { 3, 3, 3 }, merger.getObjectsPerPack());
 	}
 
@@ -285,8 +264,8 @@ public void getObjectsPerPack_differentIndexSizes() {
 				oidOffset("0000000000000000000000000000000000000004", 500),
 				oidOffset("0000000000000000000000000000000000000007", 12),
 				oidOffset("0000000000000000000000000000000000000012", 1500));
-		PackIndexMerger merger = new PackIndexMerger(
-				orderedMapOf("p1", idxOne, "p2", idxTwo, "p3", idxThree));
+		PackIndexMerger merger = createMergerFor("p1", idxOne, "p2", idxTwo,
+				"p3", idxThree);
 		assertArrayEquals(new int[] { 1, 2, 3 }, merger.getObjectsPerPack());
 	}
 
@@ -296,29 +275,28 @@ public void getObjectsPerPack_allDuplicates() {
 				oidOffset("0000000000000000000000000000000000000001", 500),
 				oidOffset("0000000000000000000000000000000000000005", 12),
 				oidOffset("0000000000000000000000000000000000000010", 1500));
-		PackIndexMerger merger = new PackIndexMerger(
-				orderedMapOf("p1", idxOne, "p2", idxOne, "p3", idxOne));
+		PackIndexMerger merger = createMergerFor("p1", idxOne, "p2", idxOne,
+				"p3", idxOne);
 		assertArrayEquals(new int[] { 3, 0, 0 }, merger.getObjectsPerPack());
 	}
 
 	@Test
 	public void getObjectsPerPack_noIndexes() {
-		PackIndexMerger merger = new PackIndexMerger(new LinkedHashMap<>());
+		PackIndexMerger merger = PackIndexMerger.builder().build();
 		assertArrayEquals(new int[] {}, merger.getObjectsPerPack());
 	}
 
 	@Test
 	public void getObjectsPerPack_emptyIndexes() {
-		PackIndexMerger merger = new PackIndexMerger(
-				orderedMapOf("p1", indexOf(), "p2", indexOf()));
+		PackIndexMerger merger = createMergerFor("p1", indexOf(), "p2",
+				indexOf());
 		assertArrayEquals(new int[] { 0, 0 }, merger.getObjectsPerPack());
 	}
 
-	private static void assertNextEntry(
-			Iterator<PackIndexMerger.MidxMutableEntry> it, String oid,
+	private static void assertNextEntry(Iterator<MutableEntry> it, String oid,
 			int packId, long offset) {
 		assertTrue(it.hasNext());
-		PackIndexMerger.MidxMutableEntry e = it.next();
+		MutableEntry e = it.next();
 		assertEquals(oid, e.getObjectId().name());
 		assertEquals(packId, e.getPackId());
 		assertEquals(offset, e.getOffset());
@@ -332,21 +310,28 @@ private static PackIndex indexOf(IndexObject... objs) {
 		return FakeIndexFactory.indexOf(Arrays.asList(objs));
 	}
 
-    private static LinkedHashMap<String, PackIndex> orderedMapOf(String s1,
-                                                                 PackIndex pi1, String s2, PackIndex pi2) {
-        LinkedHashMap map = new LinkedHashMap(3);
-        map.put(s1, pi1);
-        map.put(s2, pi2);
-        return map;
-    }
+	private static MultiPackIndex midxOf(String s1, PackIndex idx1, String s2,
+			PackIndex idx2, String s3, PackIndex idx3) throws IOException {
+		PackIndexMerger merger = createMergerFor(s1, idx1, s2, idx2, s3, idx3);
+		MultiPackIndexWriter w = new MultiPackIndexWriter();
 
-	private static LinkedHashMap<String, PackIndex> orderedMapOf(String s1,
-                                                                 PackIndex pi1, String s2, PackIndex pi2, String s3, PackIndex pi3) {
-        LinkedHashMap map = new LinkedHashMap(3);
-        map.put(s1, pi1);
-        map.put(s2, pi2);
-        map.put(s3, pi3);
-        return map;
-    }
+		ByteArrayOutputStream out = new ByteArrayOutputStream();
+		w.write(NullProgressMonitor.INSTANCE, out, merger);
+
+		ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
+		return MultiPackIndexLoader.read(in);
+	}
+
+	private static PackIndexMerger createMergerFor(String s1, PackIndex pi1,
+			String s2, PackIndex pi2) {
+		return PackIndexMerger.builder().addPack(s1, pi1).addPack(s2, pi2)
+				.build();
+	}
+
+	private static PackIndexMerger createMergerFor(String s1, PackIndex pi1,
+			String s2, PackIndex pi2, String s3, PackIndex pi3) {
+		return PackIndexMerger.builder().addPack(s1, pi1).addPack(s2, pi2)
+				.addPack(s3, pi3).build();
+	}
 
 }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/PackIndexPeekIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/PackIndexPeekIteratorTest.java
deleted file mode 100644
index 0b3ccac..0000000
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/midx/PackIndexPeekIteratorTest.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2025, Google LLC
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Distribution License v. 1.0 which is available at
- * https://www.eclipse.org/org/documents/edl-v10.php.
- *
- * SPDX-License-Identifier: BSD-3-Clause
- */
-package org.eclipse.jgit.internal.storage.midx;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-
-import java.util.Arrays;
-
-import org.eclipse.jgit.internal.storage.file.PackIndex;
-import org.eclipse.jgit.junit.FakeIndexFactory;
-import org.junit.Test;
-
-public class PackIndexPeekIteratorTest {
-    @Test
-    public void next() {
-        PackIndex index1 = indexOf(
-                object("0000000000000000000000000000000000000001", 500),
-                object("0000000000000000000000000000000000000003", 1500),
-                object("0000000000000000000000000000000000000005", 3000));
-        PackIndexMerger.PackIndexPeekIterator it = new PackIndexMerger.PackIndexPeekIterator(0, index1);
-        assertEquals("0000000000000000000000000000000000000001", it.next().name());
-        assertEquals("0000000000000000000000000000000000000003", it.next().name());
-        assertEquals("0000000000000000000000000000000000000005", it.next().name());
-        assertNull(it.next());
-    }
-
-    @Test
-    public void peek_doesNotAdvance() {
-        PackIndex index1 = indexOf(
-                object("0000000000000000000000000000000000000001", 500),
-                object("0000000000000000000000000000000000000003", 1500),
-                object("0000000000000000000000000000000000000005", 3000));
-        PackIndexMerger.PackIndexPeekIterator it = new PackIndexMerger.PackIndexPeekIterator(0, index1);
-        it.next();
-        assertEquals("0000000000000000000000000000000000000001", it.peek().name());
-        assertEquals("0000000000000000000000000000000000000001", it.peek().name());
-        it.next();
-        assertEquals("0000000000000000000000000000000000000003", it.peek().name());
-        assertEquals("0000000000000000000000000000000000000003", it.peek().name());
-        it.next();
-        assertEquals("0000000000000000000000000000000000000005", it.peek().name());
-        assertEquals("0000000000000000000000000000000000000005", it.peek().name());
-        it.next();
-        assertNull(it.peek());
-        assertNull(it.peek());
-    }
-
-    @Test
-    public void empty() {
-        PackIndex index1 = indexOf();
-        PackIndexMerger.PackIndexPeekIterator it = new PackIndexMerger.PackIndexPeekIterator(0, index1);
-        assertNull(it.next());
-        assertNull(it.peek());
-    }
-
-    private static PackIndex indexOf(FakeIndexFactory.IndexObject... objs) {
-        return FakeIndexFactory.indexOf(Arrays.asList(objs));
-    }
-
-    private static FakeIndexFactory.IndexObject object(String name, long offset) {
-        return new FakeIndexFactory.IndexObject(name, offset);
-    }
-}
\ No newline at end of file
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsMidxWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsMidxWriter.java
index 654bc30..0cdb304 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsMidxWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsMidxWriter.java
@@ -17,7 +17,6 @@
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashSet;
-import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -26,8 +25,8 @@
 
 import org.eclipse.jgit.annotations.Nullable;
 import org.eclipse.jgit.internal.storage.file.PackBitmapIndexBuilder;
-import org.eclipse.jgit.internal.storage.file.PackIndex;
 import org.eclipse.jgit.internal.storage.midx.MultiPackIndexWriter;
+import org.eclipse.jgit.internal.storage.midx.PackIndexMerger;
 import org.eclipse.jgit.internal.storage.pack.PackBitmapCalculator;
 import org.eclipse.jgit.internal.storage.pack.PackBitmapIndexWriter;
 import org.eclipse.jgit.lib.Constants;
@@ -92,11 +91,10 @@ public static DfsPackDescription writeMidx(ProgressMonitor pm,
 			DfsObjDatabase objdb, List<DfsPackFile> packs,
 			@Nullable DfsPackDescription base, PackConfig packConfig)
 			throws IOException {
-		LinkedHashMap<String, PackIndex> inputs = new LinkedHashMap<>(
-				packs.size());
+		PackIndexMerger.Builder dataBuilder = PackIndexMerger.builder();
 		try (DfsReader ctx = objdb.newReader()) {
 			for (DfsPackFile pack : packs) {
-				inputs.put(pack.getPackDescription().getPackName(),
+				dataBuilder.addPack(pack.getPackDescription().getPackName(),
 						pack.getPackIndex(ctx));
 			}
 		}
@@ -105,7 +103,8 @@ public static DfsPackDescription writeMidx(ProgressMonitor pm,
 		try (DfsOutputStream out = objdb.writeFile(midxPackDesc,
 				MULTI_PACK_INDEX)) {
 			MultiPackIndexWriter w = new MultiPackIndexWriter();
-			MultiPackIndexWriter.Result result = w.write(pm, out, inputs);
+			MultiPackIndexWriter.Result result = w.write(pm, out,
+					dataBuilder.build());
 			midxPackDesc.addFileExt(MULTI_PACK_INDEX);
 			midxPackDesc.setFileSize(MULTI_PACK_INDEX, result.bytesWritten());
 			midxPackDesc.setObjectCount(result.objectCount());
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 0582737..3c63db2 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
@@ -20,6 +20,7 @@
 import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException;
 import org.eclipse.jgit.internal.storage.file.PackIndex;
 import org.eclipse.jgit.internal.storage.file.PackReverseIndex;
+import org.eclipse.jgit.internal.storage.midx.MultiPackIndex;
 import org.eclipse.jgit.internal.storage.pack.ObjectToPack;
 import org.eclipse.jgit.internal.storage.pack.PackOutputStream;
 import org.eclipse.jgit.lib.AbbreviatedObjectId;
@@ -152,6 +153,18 @@ protected int getObjectCount(DfsReader ctx) throws IOException {
 	 */
 	protected abstract byte[] getChecksum(DfsReader ctx) throws IOException;
 
+	/**
+	 * Get a midx iterator over the contents of *this* midx, without the base.
+	 *
+	 * @param ctx
+	 *            a ready
+	 * @return an iterator over the objects in this midx in sha1 order
+	 * @throws IOException
+	 *             an error loading the underlying data
+	 */
+	protected abstract MultiPackIndex.MidxIterator localIterator(DfsReader ctx)
+			throws IOException;
+
 	@Override
 	public final PackIndex getPackIndex(DfsReader ctx) {
 		return new MidxPackIndex(this, ctx);
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 bed7a53..f042f2e 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
@@ -52,8 +52,6 @@ public final class DfsPackFileMidxNPacks extends DfsPackFileMidx {
 
 	private static final int REF_POSITION = 0;
 
-	private final List<DfsPackFile> packs;
-
 	// The required packs, in the order specified in the multipack index
 	// Initialized lazily, when the midx is loaded
 	private final DfsPackFile[] packsInIdOrder;
@@ -64,14 +62,27 @@ public final class DfsPackFileMidxNPacks extends DfsPackFileMidx {
 
 	private final VOffsetCalculatorNPacks offsetCalculator;
 
+	/**
+	 * Create the DfsPackFileMidx instance for this midx with n packs
+	 *
+	 * @param cache
+	 *            dfs block cache
+	 * @param desc
+	 *            description of the midx
+	 * @param knownPacks
+	 *            known packs, to translate the pack names in coveredPacks into
+	 *            DfsPackFile instances. It must contain at least all packs
+	 *            covered by this midx.
+	 * @param base
+	 *            base used by this midx.
+	 */
 	DfsPackFileMidxNPacks(DfsBlockCache cache, DfsPackDescription desc,
-			List<DfsPackFile> requiredPacks, @Nullable DfsPackFileMidx base) {
+			List<DfsPackFile> knownPacks, @Nullable DfsPackFileMidx base) {
 		super(cache, desc);
 		this.base = base;
-		this.packs = requiredPacks;
 		String[] coveredPackNames = desc.getCoveredPacks().stream()
 				.map(DfsPackDescription::getPackName).toArray(String[]::new);
-		packsInIdOrder = getPacksInMidxIdOrder(coveredPackNames);
+		packsInIdOrder = getPacksInMidxIdOrder(knownPacks, coveredPackNames);
 		offsetCalculator = VOffsetCalculatorNPacks.fromPacks(packsInIdOrder,
 				base != null ? base.getOffsetCalculator() : null);
 		this.length = offsetCalculator.getMaxOffset();
@@ -114,8 +125,9 @@ private static RefWithSize loadMultiPackIndex(DfsReader ctx,
 	private record RefWithSize(MultiPackIndex idx, long size) {
 	}
 
-	private DfsPackFile[] getPacksInMidxIdOrder(String[] packNames) {
-		Map<String, DfsPackFile> byName = packs.stream()
+	private DfsPackFile[] getPacksInMidxIdOrder(List<DfsPackFile> knownPacks,
+			String[] packNames) {
+		Map<String, DfsPackFile> byName = knownPacks.stream()
 				.collect(Collectors.toUnmodifiableMap(
 						p -> p.getPackDescription().getPackName(),
 						Function.identity()));
@@ -170,7 +182,7 @@ && getPackDescription().hasFileExt(BITMAP_INDEX)) {
 	List<DfsPackFile> fullyIncludedIn(DfsReader ctx,
 			BitmapIndex.BitmapBuilder need) throws IOException {
 		List<DfsPackFile> fullyIncluded = new ArrayList<>();
-		for (DfsPackFile pack : packs) {
+		for (DfsPackFile pack : packsInIdOrder) {
 			List<DfsPackFile> includedPacks = pack.fullyIncludedIn(ctx, need);
 			if (!includedPacks.isEmpty()) {
 				fullyIncluded.addAll(includedPacks);
@@ -186,7 +198,7 @@ List<DfsPackFile> fullyIncludedIn(DfsReader ctx,
 
 	@Override
 	public CommitGraph getCommitGraph(DfsReader ctx) throws IOException {
-		for (DfsPackFile pack : packs) {
+		for (DfsPackFile pack : packsInIdOrder) {
 			CommitGraph cg = pack.getCommitGraph(ctx);
 			if (cg != null) {
 				return cg;
@@ -242,6 +254,12 @@ protected int getObjectCount(DfsReader ctx) throws IOException {
 		return midx(ctx).getChecksum();
 	}
 
+	@Override
+	protected MultiPackIndex.MidxIterator localIterator(DfsReader ctx)
+			throws IOException {
+		return midx(ctx).iterator();
+	}
+
 	/**
 	 * Packs indexed by this multipack index (base NOT included)
 	 *
@@ -249,7 +267,7 @@ protected int getObjectCount(DfsReader ctx) throws IOException {
 	 */
 	@Override
 	public List<DfsPackFile> getCoveredPacks() {
-		return packs;
+		return List.of(packsInIdOrder);
 	}
 
 	/**
@@ -344,7 +362,7 @@ void resolve(DfsReader ctx, Set<ObjectId> matches, AbbreviatedObjectId id,
 	@Override
 	void copyPackAsIs(PackOutputStream out, DfsReader ctx) throws IOException {
 		// Assumming the order of the packs does not really matter
-		for (DfsPackFile pack : packs) {
+		for (DfsPackFile pack : packsInIdOrder) {
 			pack.copyPackAsIs(out, ctx);
 		}
 
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 71ff884..89dbf9d 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
@@ -25,6 +25,8 @@
 import org.eclipse.jgit.internal.storage.file.PackBitmapIndex;
 import org.eclipse.jgit.internal.storage.file.PackIndex;
 import org.eclipse.jgit.internal.storage.file.PackReverseIndex;
+import org.eclipse.jgit.internal.storage.midx.MidxIterators;
+import org.eclipse.jgit.internal.storage.midx.MultiPackIndex;
 import org.eclipse.jgit.internal.storage.midx.MultiPackIndex.PackOffset;
 import org.eclipse.jgit.internal.storage.pack.ObjectToPack;
 import org.eclipse.jgit.internal.storage.pack.PackExt;
@@ -149,6 +151,14 @@ protected int getObjectCount(DfsReader ctx) throws IOException {
 		return checksum;
 	}
 
+	@Override
+	protected MultiPackIndex.MidxIterator localIterator(DfsReader ctx)
+			throws IOException {
+		String packName = pack.getPackDescription().getPackName();
+		PackIndex packIndex = pack.getPackIndex(ctx);
+		return MidxIterators.fromPackIndexIterator(packName, packIndex);
+	}
+
 	/**
 	 * Packs indexed by this multipack index (base NOT included)
 	 *
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MidxIterators.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MidxIterators.java
new file mode 100644
index 0000000..c30b9b8
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MidxIterators.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2026, Google LLC.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.internal.storage.midx;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+import org.eclipse.jgit.internal.storage.file.PackIndex;
+import org.eclipse.jgit.internal.storage.midx.MultiPackIndex.MidxIterator;
+import org.eclipse.jgit.internal.storage.midx.MultiPackIndex.MutableEntry;
+import org.eclipse.jgit.lib.MutableObjectId;
+
+/**
+ * Helpers for midx iterators
+ */
+public final class MidxIterators {
+
+	/**
+	 * Wrap a PackIndex iterator so it looks like a midx iterator with a fixed
+	 * packId
+	 *
+	 * @param packName
+	 *            pack name this iterator is going over
+	 * @param idx
+	 *            a PackIndex
+	 * @return a midx iterator that returns the objects of the pack index in the
+	 *         original iterator order
+	 */
+	public static MidxIterator fromPackIndexIterator(String packName,
+			PackIndex idx) {
+		return new MidxIteratorOverPackIndex(packName, idx);
+	}
+
+	/**
+	 * Merge results from multiple midx iterators.
+	 * <p>
+	 * This iterator can return duplicates (in ascending packId order). Wrap
+	 * with {@link #dedup(MidxIterator)} to remove duplicates.
+	 * <p>
+	 * This iterator shifts the pack ids in iterator order. If the first
+	 * iterator covers 3 packs, packIds of the second iterator are increased by
+	 * 3.
+	 *
+	 * @param iterators
+	 *            midx iterators to combine
+	 * @return a unique iterator that returns the union of the iterators in sha1
+	 *         order. The packIds of the entries are shi
+	 */
+	public static MidxIterator join(List<MidxIterator> iterators) {
+		return new JoinMidxIterator(iterators);
+	}
+
+	/**
+	 * Dedup consecutive duplicates from a midx iterator
+	 *
+	 * @param source
+	 *            a midx iterator emitting entries in sha1 order. In case of
+	 *            duplicates, the first one is returned and others skipped.
+	 * @return iterator without duplicates.
+	 */
+	public static MidxIterator dedup(MidxIterator source) {
+		return new DedupMidxIterator(source);
+	}
+
+	private MidxIterators() {
+	}
+
+	/**
+	 * Convert a PackIndex iterator into a MidxIterator
+	 */
+	private static class MidxIteratorOverPackIndex implements MidxIterator {
+
+		private final List<String> packNames;
+
+		private final PackIndex idx;
+
+		private Iterator<PackIndex.MutableEntry> idxIt;
+
+		private boolean peeked;
+
+		private final MutableEntry entry = new MutableEntry();
+
+		MidxIteratorOverPackIndex(String packName,
+				PackIndex idx) {
+			this.packNames = List.of(packName);
+			this.idx = idx;
+			this.idxIt = idx.iterator();
+		}
+
+		@Override
+		public MutableEntry peek() {
+			if (peeked) {
+				return entry;
+			}
+
+			peeked = true;
+			readNext();
+			return entry;
+		}
+
+		@Override
+		public List<String> getPackNames() {
+			return packNames;
+		}
+
+		@Override
+		public boolean hasNext() {
+			if (peeked) {
+				return true;
+			}
+			return idxIt.hasNext();
+		}
+
+		@Override
+		public MutableEntry next() {
+			if (peeked) {
+				peeked = false;
+				return entry;
+			}
+			readNext();
+			return entry;
+		}
+
+		private void readNext() {
+			PackIndex.MutableEntry idx = idxIt.next();
+			idx.copyOidTo(entry.oid);
+			entry.packOffset.setValues(0, idx.getOffset());
+		}
+
+		@Override
+		public void reset() {
+			this.idxIt = idx.iterator();
+			this.entry.clear();
+			peeked = false;
+		}
+	}
+
+	private static class JoinMidxIterator implements MidxIterator {
+
+		private final List<String> packNames;
+
+		private final List<MidxIterator> indexIterators;
+
+		private final int[] packCountAgg;
+
+		private final MutableEntry local = new MutableEntry();
+
+		public JoinMidxIterator(List<MidxIterator> indexIterators) {
+			this.indexIterators = indexIterators;
+			packCountAgg = new int[indexIterators.size()];
+			for (int i = 1; i < indexIterators.size(); i++) {
+				packCountAgg[i] = indexIterators.get(i - 1).getPackNames()
+						.size() + packCountAgg[i - 1];
+			}
+			packNames = indexIterators.stream().map(MidxIterator::getPackNames)
+					.flatMap(List::stream).toList();
+		}
+
+		@Override
+		public MutableEntry peek() {
+			int p = best();
+			MidxIterator it = indexIterators.get(p);
+			return shiftPackId(it.peek(), packCountAgg[p]);
+		}
+
+		@Override
+		public List<String> getPackNames() {
+			return packNames;
+		}
+
+		@Override
+		public boolean hasNext() {
+			return indexIterators.stream().anyMatch(Iterator::hasNext);
+		}
+
+		@Override
+		public MutableEntry next() {
+			int p = best();
+			MidxIterator it = indexIterators.get(p);
+			return shiftPackId(it.next(), packCountAgg[p]);
+		}
+
+		private MutableEntry shiftPackId(MutableEntry entry, int shift) {
+			local.fill(entry, shift);
+			return local;
+		}
+
+		private int best() {
+			int winnerPos = -1;
+			int winnerPackShift = 0;
+			MidxIterator winner = null;
+			for (int index = 0; index < indexIterators.size(); index++) {
+				MidxIterator current = indexIterators.get(index);
+				if (!current.hasNext()) {
+					continue;
+				}
+				if (winner == null
+						|| compareEntries(current.peek(), packCountAgg[index],
+								winner.peek(), winnerPackShift) < 0) {
+					winner = current;
+					winnerPos = index;
+					winnerPackShift = packCountAgg[winnerPos];
+				}
+			}
+
+			if (winner == null) {
+				throw new NoSuchElementException();
+			}
+
+			return winnerPos;
+		}
+
+		private static int compareEntries(MutableEntry a, int aPackShift,
+				MutableEntry b, int bPackShift) {
+			int cmp = a.oid.compareTo(b.oid);
+			if (cmp != 0) {
+				return cmp;
+			}
+
+			return Integer.compare(a.getPackId() + aPackShift,
+					b.getPackId() + bPackShift);
+		}
+
+		@Override
+		public void reset() {
+			indexIterators.stream().forEach(MidxIterator::reset);
+			local.clear();
+		}
+	}
+
+	private static class DedupMidxIterator implements MidxIterator {
+		private final MidxIterator src;
+
+		private final MutableObjectId lastOid = new MutableObjectId();
+
+		private MutableEntry next;
+
+		private final MutableEntry copy = new MutableEntry();
+
+		/**
+		 * Iterator over sorted by sha1 entries that removes duplicates choosing
+		 * the first one found.
+		 *
+		 * @param src
+		 *            iterator in sha1 order
+		 */
+		DedupMidxIterator(MidxIterator src) {
+			this.src = src;
+			readNext();
+		}
+
+		@Override
+		public MutableEntry peek() {
+			return next;
+		}
+
+		@Override
+		public List<String> getPackNames() {
+			return src.getPackNames();
+		}
+
+		@Override
+		public boolean hasNext() {
+			return next != null;
+		}
+
+		@Override
+		public MutableEntry next() {
+			copy.fill(next, 0);
+			readNext();
+			return copy;
+		}
+
+		private void readNext() {
+			while (true) {
+				if (!src.hasNext()) {
+					next = null;
+					return;
+				}
+
+				next = src.next();
+				if (!lastOid.equals(next.oid)) {
+					lastOid.fromObjectId(next.oid);
+					return;
+				}
+			}
+		}
+
+		@Override
+		public void reset() {
+			lastOid.clear();
+			src.reset();
+			readNext();
+		}
+	}
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndex.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndex.java
index 1721cb6..b209aa9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndex.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndex.java
@@ -162,6 +162,11 @@ interface MidxIterator extends Iterator<MutableEntry> {
 		 * @return pack names
 		 */
 		List<String> getPackNames();
+
+		/**
+		 * Restart the iteration from the beginning
+		 */
+		void reset();
 	}
 
 	/**
@@ -235,21 +240,11 @@ public String toString() {
 	 * <p>
 	 * Mutable so the iterator can reuse the instance for performance.
 	 */
-	class MutableEntry implements Comparable<MutableEntry> {
+	class MutableEntry {
 		protected final MutableObjectId oid = new MutableObjectId();
 
 		protected final PackOffset packOffset = new PackOffset();
 
-		@Override
-		public int compareTo(MutableEntry mutableEntry) {
-			int cmp = oid.compareTo(mutableEntry.oid);
-			if (cmp != 0) {
-				return cmp;
-			}
-
-			return packOffset.getPackId() - mutableEntry.packOffset.getPackId();
-		}
-
 		/**
 		 * Copy data from other into this instance, adding the shift to the
 		 * packId
@@ -278,6 +273,11 @@ public long getOffset() {
 			return packOffset.getOffset();
 		}
 
+		public void clear() {
+			oid.clear();
+			packOffset.setValues(0, 0);
+		}
+
 		@Override
 		public String toString() {
 			return String.format("%s,%s", oid.name(), packOffset);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndexV1.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndexV1.java
index ac6f00c..cfa5c33 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndexV1.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndexV1.java
@@ -392,6 +392,11 @@ public MutableEntry peek() {
 		public List<String> getPackNames() {
 			return Arrays.asList(midx.getPackNames());
 		}
+
+		@Override
+		public void reset() {
+			position = 0;
+		}
 	}
 
 	private static class ReverseIndex {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndexWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndexWriter.java
index 4d2e580..7c69b78 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndexWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/MultiPackIndexWriter.java
@@ -35,9 +35,8 @@
 import java.util.Map;
 
 import org.eclipse.jgit.internal.JGitText;
-import org.eclipse.jgit.internal.storage.file.PackIndex;
 import org.eclipse.jgit.internal.storage.io.CancellableDigestOutputStream;
-import org.eclipse.jgit.internal.storage.midx.PackIndexMerger.MidxMutableEntry;
+import org.eclipse.jgit.internal.storage.midx.MultiPackIndex.MutableEntry;
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.util.NB;
 
@@ -78,17 +77,15 @@ public record Result(long bytesWritten, int objectCount,
 	 *            progress monitor
 	 * @param outputStream
 	 *            stream to write the multipack index file
-	 * @param inputs
-	 *            pairs of name and index for each pack to include in the
-	 *            multipack index.
+	 * @param data
+	 *            a pack index merger with the data sources (in order) for this
+	 *            midx
 	 * @return data about the write (e.g. bytes written)
 	 * @throws IOException
 	 *             Error writing to the stream
 	 */
 	public Result write(ProgressMonitor monitor, OutputStream outputStream,
-			Map<String, PackIndex> inputs) throws IOException {
-		PackIndexMerger data = new PackIndexMerger(inputs);
-
+			PackIndexMerger data) throws IOException {
 		// List of chunks in the order they need to be written
 		List<ChunkHeader> chunkHeaders = createChunkHeaders(data);
 		long expectedSize = calculateExpectedSize(chunkHeaders);
@@ -223,9 +220,9 @@ private void writeChunkLookup(CancellableDigestOutputStream out,
 	private void writeFanoutTable(WriteContext ctx) throws IOException {
 		byte[] tmp = new byte[4];
 		int[] fanout = new int[256];
-		Iterator<MidxMutableEntry> iterator = ctx.data.bySha1Iterator();
+		Iterator<MutableEntry> iterator = ctx.data.bySha1Iterator();
 		while (iterator.hasNext()) {
-			MidxMutableEntry e = iterator.next();
+			MutableEntry e = iterator.next();
 			fanout[e.getObjectId().getFirstByte() & 0xff]++;
 		}
 		for (int i = 1; i < fanout.length; i++) {
@@ -250,9 +247,9 @@ private void writeFanoutTable(WriteContext ctx) throws IOException {
 	private void writeOidLookUp(WriteContext ctx) throws IOException {
 		byte[] tmp = new byte[OBJECT_ID_LENGTH];
 
-		Iterator<MidxMutableEntry> iterator = ctx.data.bySha1Iterator();
+		Iterator<MutableEntry> iterator = ctx.data.bySha1Iterator();
 		while (iterator.hasNext()) {
-			MidxMutableEntry e = iterator.next();
+			MutableEntry e = iterator.next();
 			e.getObjectId().copyRawTo(tmp, 0);
 			ctx.out.write(tmp, 0, OBJECT_ID_LENGTH);
 		}
@@ -272,9 +269,9 @@ private void writeOidLookUp(WriteContext ctx) throws IOException {
 	 */
 	private void writeObjectOffsets(WriteContext ctx) throws IOException {
 		byte[] entry = new byte[8];
-		Iterator<MidxMutableEntry> iterator = ctx.data.bySha1Iterator();
+		Iterator<MutableEntry> iterator = ctx.data.bySha1Iterator();
 		while (iterator.hasNext()) {
-			MidxMutableEntry e = iterator.next();
+			MutableEntry e = iterator.next();
 			NB.encodeInt32(entry, 0, e.getPackId());
 			if (!ctx.data.needsLargeOffsetsChunk()
 					|| fitsIn31bits(e.getOffset())) {
@@ -305,10 +302,10 @@ private void writeRidx(WriteContext ctx) throws IOException {
 		// memory. We could also iterate reverse indexes looking up
 		// their position in the midx (and discarding if the pack doesn't
 		// match).
-		Iterator<MidxMutableEntry> iterator = ctx.data.bySha1Iterator();
+		Iterator<MutableEntry> iterator = ctx.data.bySha1Iterator();
 		int midxPosition = 0;
 		while (iterator.hasNext()) {
-			MidxMutableEntry e = iterator.next();
+			MutableEntry e = iterator.next();
 			OffsetPosition op = new OffsetPosition(e.getOffset(), midxPosition);
 			midxPosition++;
 			packOffsets.computeIfAbsent(e.getPackId(), k -> new ArrayList<>())
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/PackIndexMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/PackIndexMerger.java
index 4a296ee..3436446 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/PackIndexMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/midx/PackIndexMerger.java
@@ -10,13 +10,11 @@
 package org.eclipse.jgit.internal.storage.midx;
 
 import java.util.ArrayList;
-import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
-import java.util.NoSuchElementException;
 
 import org.eclipse.jgit.internal.storage.file.PackIndex;
-import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.internal.storage.midx.MultiPackIndex.MidxIterator;
+import org.eclipse.jgit.internal.storage.midx.MultiPackIndex.MutableEntry;
 import org.eclipse.jgit.lib.MutableObjectId;
 
 /**
@@ -32,59 +30,13 @@
  * entries. The stats of the combined index are calculated in an iteration at
  * construction time.
  */
-class PackIndexMerger {
+public class PackIndexMerger {
 
 	private static final int LIMIT_31_BITS = (1 << 31) - 1;
 
 	private static final long LIMIT_32_BITS = (1L << 32) - 1;
 
-	/**
-	 * Object returned by the iterator.
-	 * <p>
-	 * The iterator returns (on each next()) the same instance with different
-	 * values, to avoid allocating many short-lived objects. Callers should not
-	 * keep a reference to that returned value.
-	 */
-	static class MidxMutableEntry {
-		// The object id
-		private final MutableObjectId oid = new MutableObjectId();
-
-		// Position of the pack in the ordered list of pack in this merger
-		private int packId;
-
-		// Offset in its pack
-		private long offset;
-
-		public AnyObjectId getObjectId() {
-			return oid;
-		}
-
-		public int getPackId() {
-			return packId;
-		}
-
-		public long getOffset() {
-			return offset;
-		}
-
-		/**
-		 * Copy values from another mutable entry
-		 *
-		 * @param packId
-		 *            packId
-		 * @param other
-		 *            another mutable entry
-		 */
-		private void fill(int packId, PackIndex.MutableEntry other) {
-			other.copyOidTo(oid);
-			this.packId = packId;
-			this.offset = other.getOffset();
-		}
-	}
-
-	private final List<String> packNames;
-
-	private final List<PackIndex> indexes;
+	private final MidxIterator midxIterator;
 
 	private final boolean needsLargeOffsetsChunk;
 
@@ -94,43 +46,99 @@ private void fill(int packId, PackIndex.MutableEntry other) {
 
 	private final int[] objectsPerPack;
 
-	/**
-	 * Build a common view of these pack indexes
-	 * <p>
-	 * Order matters: in case of duplicates, the first pack with the object wins
-	 *
-	 * @param packs
-	 *            map of pack names to indexes, ordered.
-	 */
-	PackIndexMerger(Map<String, PackIndex> packs) {
-		this.packNames = packs.keySet().stream().toList();
-		this.indexes = packs.values().stream().toList();
+	private final List<String> packnames;
 
-		objectsPerPack = new int[packNames.size()];
-		// Iterate for duplicates
+	/**
+	 * Builder collecting the inputs for the merger.
+	 * <p>
+	 * Order matters. Packs will appear in the midx in the order they are added.
+	 */
+	public static class Builder {
+
+		private final List<MidxIterator> packIndexes = new ArrayList<>();
+
+		/**
+		 * Add a regular pack to the midx
+		 *
+		 * @param name
+		 *            name of the pack
+		 * @param idx
+		 *            primary index of the pack
+		 * @return this builder
+		 */
+		public Builder addPack(String name, PackIndex idx) {
+			packIndexes.add(MidxIterators.fromPackIndexIterator(name, idx));
+			return this;
+		}
+
+		/**
+		 * Add data from this midx iterator to the merge
+		 * <p>
+		 * Packs are kept in the order of the iterator.
+		 *
+		 * @param midx
+		 *            a midx iterator
+		 * @return this builder
+		 */
+		public Builder addMidx(MidxIterator midx) {
+			packIndexes.add(midx);
+			return this;
+		}
+
+		/**
+		 * Build the merger instance
+		 *
+		 * @return a merger instance
+		 */
+		public PackIndexMerger build() {
+			return new PackIndexMerger(
+					MidxIterators.dedup(MidxIterators.join(packIndexes)));
+		}
+	}
+
+	/**
+	 * Create a builder
+	 *
+	 * @return an empty builder
+	 */
+	public static Builder builder() {
+		return new Builder();
+	}
+
+	/**
+	 * A common view of the input pack indexes
+	 *
+	 * @param midxIterator
+	 *            MidxIterator built by deduping union of all pack indexes
+	 */
+	private PackIndexMerger(MidxIterator midxIterator) {
+		this.midxIterator = midxIterator;
+		this.packnames = midxIterator.getPackNames();
+
+		objectsPerPack = new int[packnames.size()];
+		// Iterate for duplicates and counts that we need to build the chunk
+		// headers.
 		int objectCount = 0;
 		boolean hasLargeOffsets = false;
 		int over31bits = 0;
 		MutableObjectId lastSeen = new MutableObjectId();
-		MultiIndexIterator it = new MultiIndexIterator(indexes);
-		while (it.hasNext()) {
-			MidxMutableEntry entry = it.next();
-			if (lastSeen.equals(entry.oid)) {
-				continue;
-			}
+		while (midxIterator.hasNext()) {
+			MutableEntry entry = midxIterator.next();
 			// If there is at least one offset value larger than 2^32-1, then
 			// the large offset chunk must exist, and offsets larger than
 			// 2^31-1 must be stored in it instead
-			if (entry.offset > LIMIT_32_BITS) {
+			if (entry.getOffset() > LIMIT_32_BITS) {
 				hasLargeOffsets = true;
 			}
-			if (entry.offset > LIMIT_31_BITS) {
+			if (entry.getOffset() > LIMIT_31_BITS) {
 				over31bits++;
 			}
 
 			lastSeen.fromObjectId(entry.oid);
 			objectCount++;
-			objectsPerPack[entry.packId]++;
+			// TODO(ifrade): we can calculate the fanout table already here.
+			// It saves an iteration over all objects for only 1Kb of memory
+			objectsPerPack[entry.getPackId()]++;
 		}
 		uniqueObjectCount = objectCount;
 		offsetsOver31BitsCount = over31bits;
@@ -168,7 +176,7 @@ int getOffsetsOver31BitsCount() {
 	}
 
 	/**
-	 * Number of objects selected for the midx per packid
+	 * Number of objects selected for the midx per pack id
 	 *
 	 * @return array where position n contains the amount of objects selected
 	 *         for pack id n
@@ -187,7 +195,7 @@ int getOffsetsOver31BitsCount() {
 	 * @return List of pack names, in the order used by the merge.
 	 */
 	List<String> getPackNames() {
-		return packNames;
+		return packnames;
 	}
 
 	/**
@@ -196,160 +204,22 @@ List<String> getPackNames() {
 	 * @return count of packs merged
 	 */
 	int getPackCount() {
-		return packNames.size();
+		return packnames.size();
 	}
 
 	/**
 	 * Iterator over the merged indexes in sha1 order without duplicates
 	 * <p>
+	 * This always returns the same iterator resetted. We don't support two
+	 * iterators over this merged data.
+	 * <p>
 	 * The returned entry in the iterator is mutable, callers should NOT keep a
 	 * reference to it.
 	 *
 	 * @return an iterator in sha1 order without duplicates.
 	 */
-	Iterator<MidxMutableEntry> bySha1Iterator() {
-		return new DedupMultiIndexIterator(new MultiIndexIterator(indexes),
-				getUniqueObjectCount());
-	}
-
-	/**
-	 * For testing. Iterate all entries, not skipping duplicates (stable order)
-	 *
-	 * @return an iterator of all objects in sha1 order, including duplicates.
-	 */
-	Iterator<MidxMutableEntry> rawIterator() {
-		return new MultiIndexIterator(indexes);
-	}
-
-	/**
-	 * Iterator over n-indexes in ObjectId order.
-	 * <p>
-	 * It returns duplicates if the same object id is in different indexes. Wrap
-	 * it with {@link DedupMultiIndexIterator (Iterator, int)} to avoid
-	 * duplicates.
-	 */
-	private static final class MultiIndexIterator
-			implements Iterator<MidxMutableEntry> {
-
-		private final List<PackIndexPeekIterator> indexIterators;
-
-		private final MidxMutableEntry mutableEntry = new MidxMutableEntry();
-
-		MultiIndexIterator(List<PackIndex> indexes) {
-			this.indexIterators = new ArrayList<>(indexes.size());
-			for (int i = 0; i < indexes.size(); i++) {
-				PackIndexPeekIterator it = new PackIndexPeekIterator(i,
-						indexes.get(i));
-				// Position in the first element
-				if (it.next() != null) {
-					indexIterators.add(it);
-				}
-			}
-		}
-
-		@Override
-		public boolean hasNext() {
-			return !indexIterators.isEmpty();
-		}
-
-		@Override
-		public MidxMutableEntry next() {
-			PackIndexPeekIterator winner = null;
-			for (int index = 0; index < indexIterators.size(); index++) {
-				PackIndexPeekIterator current = indexIterators.get(index);
-				if (winner == null
-						|| current.peek().compareBySha1To(winner.peek()) < 0) {
-					winner = current;
-				}
-			}
-
-			if (winner == null) {
-				throw new NoSuchElementException();
-			}
-
-			mutableEntry.fill(winner.getPackId(), winner.peek());
-			if (winner.next() == null) {
-				indexIterators.remove(winner);
-			}
-			return mutableEntry;
-		}
-	}
-
-	private static class DedupMultiIndexIterator
-			implements Iterator<MidxMutableEntry> {
-		private final MultiIndexIterator src;
-
-		private int remaining;
-
-		private final MutableObjectId lastOid = new MutableObjectId();
-
-		DedupMultiIndexIterator(MultiIndexIterator src, int totalCount) {
-			this.src = src;
-			this.remaining = totalCount;
-		}
-
-		@Override
-		public boolean hasNext() {
-			return remaining > 0;
-		}
-
-		@Override
-		public MidxMutableEntry next() {
-			MidxMutableEntry next = src.next();
-			while (next != null && lastOid.equals(next.oid)) {
-				next = src.next();
-			}
-
-			if (next == null) {
-				throw new NoSuchElementException();
-			}
-
-			lastOid.fromObjectId(next.oid);
-			remaining--;
-			return next;
-		}
-	}
-
-	/**
-	 * Convenience around the PackIndex iterator to read the current value
-	 * multiple times without consuming it.
-	 * <p>
-	 * This is used to merge indexes in the multipack index, where we need to
-	 * compare the current value between indexes multiple times to find the
-	 * next.
-	 * <p>
-	 * We could also implement this keeping the position (int) and
-	 * MutableEntry#getObjectId, but that would create an ObjectId per entry.
-	 * This implementation reuses the MutableEntry and avoid instantiations.
-	 */
-	// Visible for testing
-	static class PackIndexPeekIterator {
-		private final Iterator<PackIndex.MutableEntry> it;
-
-		private final int packId;
-
-		PackIndex.MutableEntry current;
-
-		PackIndexPeekIterator(int packId, PackIndex index) {
-			it = index.iterator();
-			this.packId = packId;
-		}
-
-		PackIndex.MutableEntry next() {
-			if (it.hasNext()) {
-				current = it.next();
-			} else {
-				current = null;
-			}
-			return current;
-		}
-
-		PackIndex.MutableEntry peek() {
-			return current;
-		}
-
-		int getPackId() {
-			return packId;
-		}
+	MidxIterator bySha1Iterator() {
+		midxIterator.reset();
+		return midxIterator;
 	}
 }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java
index ba7573a..8e604ba 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java
@@ -623,7 +623,7 @@ private Set<RevCommit> getCommitsMergedInto(RevCommit needle, Collection<RevComm
 				markStart(c);
 				boolean commitFound = false;
 				RevCommit next;
-				while ((next = next()) != null) {
+				while ((next = next()) != null && !monitor.isCancelled()) {
 					if (next.getGeneration() < cutoff) {
 						markUninteresting(next);
 						uninteresting.add(next);