DfsPackFileMidx: Move implementation to subclass
We expect to have midxs covering a single pack (GC) in the midx
chain. With the current implementation, this would load twice the same
information, in the midx and in the pack indexes.
As a first step, move the DfsPackFileMidx implementation to a
subclass, so later we can add the single-pack implementation.
We keep DfsPackFileMidx as the superclass with a factory method, as it
reads better for callers; they invoke "DfsPackFileMidx.create" and get the
right instance.
Change-Id: I2ee77a28db35f152a6124c19af91c0906a6a6964
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsPackFileMidxTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsPackFileMidxNPacksTest.java
similarity index 97%
rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsPackFileMidxTest.java
rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsPackFileMidxNPacksTest.java
index 48611da..758ac1f 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsPackFileMidxTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsPackFileMidxNPacksTest.java
@@ -36,7 +36,9 @@
import java.util.zip.Deflater;
import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.internal.storage.dfs.DfsPackFileMidx.DfsPackOffset;
import org.eclipse.jgit.internal.storage.dfs.DfsPackFileMidx.VOffsetCalculator;
+import org.eclipse.jgit.internal.storage.dfs.DfsPackFileMidxNPacks.VOffsetCalculatorNPacks;
import org.eclipse.jgit.internal.storage.file.PackBitmapIndex;
import org.eclipse.jgit.internal.storage.file.PackIndex;
import org.eclipse.jgit.internal.storage.midx.MultiPackIndex.PackOffset;
@@ -61,7 +63,7 @@
import org.junit.Before;
import org.junit.Test;
-public class DfsPackFileMidxTest {
+public class DfsPackFileMidxNPacksTest {
private static final ObjectId NOT_IN_PACK = ObjectId
.fromString("3f306cb3fcd5116919fecad615524bd6e6ea4ba7");
@@ -315,7 +317,7 @@ public void midx_findOffset() throws IOException {
try (DfsReader ctx = db.getObjectDatabase().newReader()) {
long posOne = midx.findOffset(ctx, o1);
- DfsPackFileMidx.DfsPackOffset po = midx.getOffsetCalculator()
+ DfsPackOffset po = midx.getOffsetCalculator()
.decode(posOne);
assertEquals(12, po.getPackOffset());
assertEquals(packThreeSize + packTwoSize, po.getPackStart());
@@ -980,7 +982,7 @@ public void voffsetcalculator_encode() {
DfsPackFile two = createDfsPackFile(1200);
DfsPackFile three = createDfsPackFile(900);
- VOffsetCalculator calc = VOffsetCalculator
+ VOffsetCalculatorNPacks calc = VOffsetCalculatorNPacks
.fromPacks(new DfsPackFile[] { one, two, three }, null);
PackOffset po = PackOffset.create(0, 12);
@@ -997,11 +999,11 @@ public void voffsetcalculator_decode() {
DfsPackFile two = createDfsPackFile(1200);
DfsPackFile three = createDfsPackFile(900);
- VOffsetCalculator calc = VOffsetCalculator
+ VOffsetCalculator calc = VOffsetCalculatorNPacks
.fromPacks(new DfsPackFile[] { one, two, three }, null);
// In first pack
- DfsPackFileMidx.DfsPackOffset decoded = calc.decode(130);
+ DfsPackOffset decoded = calc.decode(130);
assertEquals(one.getPackDescription(),
decoded.getPack().getPackDescription());
assertEquals(130, decoded.getPackOffset());
@@ -1028,7 +1030,7 @@ public void voffsetcalculator_notFound() {
DfsPackFile two = createDfsPackFile(1200);
DfsPackFile three = createDfsPackFile(900);
- VOffsetCalculator calc = VOffsetCalculator
+ VOffsetCalculatorNPacks calc = VOffsetCalculatorNPacks
.fromPacks(new DfsPackFile[] { one, two, three }, null);
assertEquals(-1, calc.encode(null));
@@ -1045,7 +1047,7 @@ public void voffsetcalculator_maxOffset() {
+ two.getPackDescription().getFileSize(PACK)
+ three.getPackDescription().getFileSize(PACK);
- VOffsetCalculator calc = VOffsetCalculator
+ VOffsetCalculator calc = VOffsetCalculatorNPacks
.fromPacks(new DfsPackFile[] { one, two, three }, null);
assertEquals(totalSize, calc.getMaxOffset());
@@ -1057,14 +1059,14 @@ public void voffsetcalculator_withBase_encode() {
DfsPackFile two = createDfsPackFile(1200);
DfsPackFile three = createDfsPackFile(900);
- VOffsetCalculator baseCalc = VOffsetCalculator
+ VOffsetCalculator baseCalc = VOffsetCalculatorNPacks
.fromPacks(new DfsPackFile[] { one, two, three }, null);
DfsPackFile four = createDfsPackFile(900);
DfsPackFile five = createDfsPackFile(1300);
DfsPackFile six = createDfsPackFile(1000);
- VOffsetCalculator calc = VOffsetCalculator
+ VOffsetCalculatorNPacks calc = VOffsetCalculatorNPacks
.fromPacks(new DfsPackFile[] { four, five, six }, baseCalc);
// These packIds are now from the second top midx
@@ -1082,18 +1084,18 @@ public void voffsetcalculator_withBase_decode() {
DfsPackFile one = createDfsPackFile(800);
DfsPackFile two = createDfsPackFile(1200);
DfsPackFile three = createDfsPackFile(900);
- VOffsetCalculator baseCalc = VOffsetCalculator
+ VOffsetCalculator baseCalc = VOffsetCalculatorNPacks
.fromPacks(new DfsPackFile[] { one, two, three }, null);
DfsPackFile four = createDfsPackFile(900);
DfsPackFile five = createDfsPackFile(1300);
DfsPackFile six = createDfsPackFile(1000);
- VOffsetCalculator calc = VOffsetCalculator
+ VOffsetCalculator calc = VOffsetCalculatorNPacks
.fromPacks(new DfsPackFile[] { four, five, six }, baseCalc);
// In pack 1
- DfsPackFileMidx.DfsPackOffset decoded = calc.decode(130);
+ DfsPackOffset decoded = calc.decode(130);
assertEquals(one.getPackDescription(),
decoded.getPack().getPackDescription());
assertEquals(130, decoded.getPackOffset());
@@ -1128,14 +1130,14 @@ public void voffsetcalculator_withBase_maxOffset() {
DfsPackFile two = createDfsPackFile(1200);
DfsPackFile three = createDfsPackFile(900);
- VOffsetCalculator baseCalc = VOffsetCalculator
+ VOffsetCalculator baseCalc = VOffsetCalculatorNPacks
.fromPacks(new DfsPackFile[] { one, two, three }, null);
DfsPackFile four = createDfsPackFile(900);
DfsPackFile five = createDfsPackFile(1300);
DfsPackFile six = createDfsPackFile(1000);
- VOffsetCalculator calc = VOffsetCalculator
+ VOffsetCalculator calc = VOffsetCalculatorNPacks
.fromPacks(new DfsPackFile[] { four, five, six }, baseCalc);
int expectedMaxOffset = 800 + 1200 + 900 + 900 + 1300 + 1000;
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 742fe6c..8755073 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
@@ -9,202 +9,94 @@
*/
package org.eclipse.jgit.internal.storage.dfs;
-import static org.eclipse.jgit.internal.storage.pack.PackExt.MULTI_PACK_INDEX;
-import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
-
import java.io.IOException;
-import java.nio.channels.Channels;
import java.util.ArrayList;
import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.Function;
-import java.util.stream.Collectors;
import java.util.zip.DataFormatException;
import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException;
-import org.eclipse.jgit.internal.storage.commitgraph.CommitGraph;
-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.MultiPackIndex;
-import org.eclipse.jgit.internal.storage.midx.MultiPackIndex.PackOffset;
-import org.eclipse.jgit.internal.storage.midx.MultiPackIndexLoader;
-import org.eclipse.jgit.internal.storage.pack.ObjectToPack;
import org.eclipse.jgit.internal.storage.pack.PackOutputStream;
-import org.eclipse.jgit.lib.AbbreviatedObjectId;
-import org.eclipse.jgit.lib.AnyObjectId;
-import org.eclipse.jgit.lib.BitmapIndex;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectIdSet;
import org.eclipse.jgit.lib.ObjectLoader;
-import org.eclipse.jgit.util.BlockList;
/**
- * Implementation of a DfsPackfile that tries to solve the queries in a
- * multipack index before resorting to the real packs.
+ * DfsPackFile with the extra methods to support midx.
* <p>
- * It uses the position in the multipack index of the objects as their "offset".
+ * This is an abstract class to keep the inheritance from DfsPackFile and allow
+ * a dummy implementation for single packs.
+ * <p>
+ * We implement at this level methods that just translate midx to pack offsets
+ * and forward to the pack.
*/
-public final class DfsPackFileMidx extends DfsPackFile {
+public abstract sealed class DfsPackFileMidx extends DfsPackFile
+ permits DfsPackFileMidxNPacks {
- 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;
-
- private MultiPackIndex midx;
-
- private final DfsPackFileMidx base;
-
- private final VOffsetCalculator offsetCalculator;
-
- static DfsPackFileMidx create(DfsBlockCache cache, DfsPackDescription desc,
- List<DfsPackFile> reqPacks, DfsPackFileMidx base) {
- return new DfsPackFileMidx(cache, desc, reqPacks, base);
+ /**
+ * Create a midx pack
+ *
+ * @param cache
+ * dfs block cache
+ * @param desc
+ * description of the pack, covering at least one other pack
+ * @param requiredPacks
+ * DfsPackFile instances of the covered packs
+ * @param base
+ * midx acting a base of this
+ * @return a midx pack
+ */
+ public static DfsPackFileMidx create(DfsBlockCache cache,
+ DfsPackDescription desc, List<DfsPackFile> requiredPacks,
+ @Nullable DfsPackFileMidx base) {
+ return new DfsPackFileMidxNPacks(cache, desc, requiredPacks, base);
}
- private DfsPackFileMidx(DfsBlockCache cache, DfsPackDescription desc,
- List<DfsPackFile> requiredPacks, @Nullable DfsPackFileMidx base) {
+ /**
+ * Default constructor
+ *
+ * @param cache
+ * dfs block cache
+ * @param desc
+ * midx pack description
+ */
+ protected DfsPackFileMidx(DfsBlockCache cache, DfsPackDescription desc) {
super(cache, desc);
- this.base = base;
- this.packs = requiredPacks;
- String[] coveredPackNames = desc.getCoveredPacks().stream()
- .map(DfsPackDescription::getPackName).toArray(String[]::new);
- packsInIdOrder = getPacksInMidxIdOrder(coveredPackNames);
- offsetCalculator = VOffsetCalculator.fromPacks(packsInIdOrder,
- base != null ? base.getOffsetCalculator() : null);
- this.length = offsetCalculator.getMaxOffset();
}
- private MultiPackIndex midx(DfsReader ctx) throws IOException {
- if (midx != null) {
- return midx;
+ /**
+ * Base of this multipack index
+ * <p>
+ * If this midx is part of a chain, this is its parent
+ *
+ * @return the base of this multipack index
+ */
+ public abstract DfsPackFileMidx getMultipackIndexBase();
+
+ /**
+ * Packs indexed by this multipack index (base NOT included)
+ *
+ * @return packs indexed by this multipack index
+ */
+ public abstract List<DfsPackFile> getCoveredPacks();
+
+ /**
+ * All packs indexed by this multipack index and its chain
+ * <p>
+ * This does not include the inner multipack indexes themselves, only their
+ * covered packs.
+ *
+ * @return packs indexed by this multipack index and its parents.
+ */
+ public List<DfsPackFile> getAllCoveredPacks() {
+ List<DfsPackFile> coveredPacks = new ArrayList<>(getCoveredPacks());
+ DfsPackFileMidx base = getMultipackIndexBase();
+ while (base != null) {
+ coveredPacks.addAll(base.getCoveredPacks());
+ base = base.getMultipackIndexBase();
}
- DfsStreamKey revKey = desc.getStreamKey(MULTI_PACK_INDEX);
- // Keep the value parsed in the loader, in case the Ref<> is
- // nullified in ClockBlockCacheTable#reserveSpace
- // before we read its value.
- AtomicReference<MultiPackIndex> loadedRef = new AtomicReference<>(null);
- DfsBlockCache.Ref<MultiPackIndex> cachedRef = cache.getOrLoadRef(revKey,
- REF_POSITION, () -> {
- RefWithSize midx1 = loadMultiPackIndex(ctx, desc);
- loadedRef.set(midx1.idx);
- return new DfsBlockCache.Ref<>(revKey, REF_POSITION,
- midx1.size, midx1.idx);
- });
- // if (loadedRef.get() == null) {
- // ctx.stats.ridxCacheHit;
- // }
- midx = cachedRef.get() != null ? cachedRef.get() : loadedRef.get();
- return midx;
- }
-
- private static RefWithSize loadMultiPackIndex(DfsReader ctx,
- DfsPackDescription desc) throws IOException {
- try (ReadableChannel rc = ctx.db.openFile(desc, MULTI_PACK_INDEX)) {
- MultiPackIndex midx = MultiPackIndexLoader
- .read(Channels.newInputStream(rc));
- // ctx.stats.readIdxBytes += rc.position();
- return new RefWithSize(midx, midx.getMemorySize());
- }
- }
-
- private record RefWithSize(MultiPackIndex idx, long size) {
- }
-
- private DfsPackFile[] getPacksInMidxIdOrder(String[] packNames) {
- Map<String, DfsPackFile> byName = packs.stream()
- .collect(Collectors.toUnmodifiableMap(
- p -> p.getPackDescription().getPackName(),
- Function.identity()));
- DfsPackFile[] result = new DfsPackFile[desc.getCoveredPacks().size()];
- for (int i = 0; i < packNames.length; i++) {
- DfsPackFile pack = byName.get(packNames[i]);
- if (pack == null) {
- // This should have been checked in the object db
- // when the pack description was loaded
- throw new IllegalStateException("Required pack missing"); //$NON-NLS-1$
- }
- result[i] = pack;
- }
- return result;
- }
-
- // Visible for testing
- VOffsetCalculator getOffsetCalculator() {
- return offsetCalculator;
- }
-
- @Override
- public PackIndex getPackIndex(DfsReader ctx) {
- throw new IllegalStateException(
- "Shouldn't use multipack index if the primary index is needed"); //$NON-NLS-1$
- }
-
- @Override
- public ObjectIdSet asObjectIdSet(DfsReader ctx) throws IOException {
- MultiPackIndex multiPackIndex = midx(ctx);
- return objectId -> multiPackIndex.hasObject(objectId);
- }
-
- @Override
- public PackReverseIndex getReverseIdx(DfsReader ctx) {
- throw new IllegalStateException(
- "Shouldn't use multipack index if the reverse index is needed"); //$NON-NLS-1$
- }
-
- @Override
- public PackBitmapIndex getBitmapIndex(DfsReader ctx) throws IOException {
- // TODO(ifrade): at some point we will have bitmaps over the multipack
- // index
- // At the moment bitmap is in GC, at the end of the chain
- if (base != null) {
- return base.getBitmapIndex(ctx);
- }
-
- for (DfsPackFile pack : packsInIdOrder) {
- PackBitmapIndex bitmapIndex = pack.getBitmapIndex(ctx);
- if (bitmapIndex != null) {
- return bitmapIndex;
- }
- }
- return null;
- }
-
- @Override
- List<DfsPackFile> fullyIncludedIn(DfsReader ctx,
- BitmapIndex.BitmapBuilder need) throws IOException {
- List<DfsPackFile> fullyIncluded = new ArrayList<>();
- for (DfsPackFile pack : packs) {
- List<DfsPackFile> includedPacks = pack.fullyIncludedIn(ctx, need);
- if (!includedPacks.isEmpty()) {
- fullyIncluded.addAll(includedPacks);
- }
- }
-
- if (base != null) {
- fullyIncluded.addAll(base.fullyIncludedIn(ctx, need));
- }
-
- return fullyIncluded;
- }
-
- @Override
- public CommitGraph getCommitGraph(DfsReader ctx) throws IOException {
- for (DfsPackFile pack : packs) {
- CommitGraph cg = pack.getCommitGraph(ctx);
- if (cg != null) {
- return cg;
- }
- }
- return null;
+ return coveredPacks;
}
/**
@@ -217,96 +109,25 @@ public CommitGraph getCommitGraph(DfsReader ctx) throws IOException {
* @throws IOException
* an error reading a midx in the chain
*/
- private int getObjectCount(DfsReader ctx) throws IOException {
- int baseObjectCount = base == null ? 0 : base.getObjectCount(ctx);
- return midx(ctx).getObjectCount() + baseObjectCount;
- }
-
- /**
- * Packs indexed by this multipack index (base NOT included)
- *
- * @return packs indexed by this multipack index
- */
- public List<DfsPackFile> getCoveredPacks() {
- return packs;
- }
-
- /**
- * All packs indexed by this multipack index and its chain
- * <p>
- * This does not include the inner multipack indexes themselves, only their
- * covered packs.
- *
- * @return packs indexed by this multipack index and its parents.
- */
- public List<DfsPackFile> getAllCoveredPacks() {
- List<DfsPackFile> coveredPacks = new ArrayList<>(packs);
- DfsPackFileMidx base = getMultipackIndexBase();
- while (base != null) {
- coveredPacks.addAll(base.getCoveredPacks());
- base = base.getMultipackIndexBase();
- }
-
- return coveredPacks;
- }
-
- /**
- * Base of this multipack index
- * <p>
- * If this midx is part of a chain, this is its parent
- *
- * @return the base of this multipack index
- */
- public DfsPackFileMidx getMultipackIndexBase() {
- return base;
+ protected int getObjectCount(DfsReader ctx) throws IOException {
+ return (int) getPackDescription().getObjectCount();
}
@Override
- public int findIdxPosition(DfsReader ctx, AnyObjectId id)
- throws IOException {
- int p = midx(ctx).findPosition(id);
- if (p >= 0) {
- int baseObjects = base == null ? 0 : base.getObjectCount(ctx);
- return p + baseObjects;
- }
-
- if (base == null) {
- return -1;
- }
-
- return base.findIdxPosition(ctx, id);
+ public PackIndex getPackIndex(DfsReader ctx) {
+ throw new IllegalStateException(
+ "Shouldn't use multipack index if the primary index is needed"); //$NON-NLS-1$
}
@Override
- public boolean hasObject(DfsReader ctx, AnyObjectId id) throws IOException {
- if (midx(ctx).hasObject(id)) {
- return true;
- }
-
- if (base == null) {
- return false;
- }
-
- return base.hasObject(ctx, id);
- }
-
- @Override
- ObjectLoader get(DfsReader ctx, AnyObjectId id) throws IOException {
- PackOffset location = midx(ctx).find(id);
- if (location != null) {
- return packsInIdOrder[location.getPackId()].get(ctx, id);
- }
-
- if (base == null) {
- return null;
- }
-
- return base.get(ctx, id);
+ public PackReverseIndex getReverseIdx(DfsReader ctx) {
+ throw new IllegalStateException(
+ "Shouldn't use multipack index if the reverse index is needed"); //$NON-NLS-1$
}
@Override
ObjectLoader load(DfsReader ctx, long midxOffset) throws IOException {
- DfsPackOffset location = offsetCalculator.decode(midxOffset);
+ DfsPackOffset location = getOffsetCalculator().decode(midxOffset);
if (location == null) {
return null;
}
@@ -314,41 +135,6 @@ ObjectLoader load(DfsReader ctx, long midxOffset) throws IOException {
}
@Override
- long findOffset(DfsReader ctx, AnyObjectId id) throws IOException {
- PackOffset location = midx(ctx).find(id);
- if (location != null) {
- return offsetCalculator.encode(location);
- }
-
- if (base == null) {
- return -1;
- }
-
- return base.findOffset(ctx, id);
- }
-
- @Override
- void resolve(DfsReader ctx, Set<ObjectId> matches, AbbreviatedObjectId id,
- int matchLimit) throws IOException {
- midx(ctx).resolve(matches, id, matchLimit);
- if (matches.size() < matchLimit && base != null) {
- base.resolve(ctx, matches, id, matchLimit);
- }
- }
-
- @Override
- void copyPackAsIs(PackOutputStream out, DfsReader ctx) throws IOException {
- // Assumming the order of the packs does not really matter
- for (DfsPackFile pack : packs) {
- pack.copyPackAsIs(out, ctx);
- }
-
- if (base != null) {
- base.copyPackAsIs(out, ctx);
- }
- }
-
- @Override
void copyAsIs(PackOutputStream out, DfsObjectToPack src, boolean validate,
DfsReader ctx) throws IOException,
StoredObjectRepresentationNotAvailableException {
@@ -357,7 +143,7 @@ void copyAsIs(PackOutputStream out, DfsObjectToPack src, boolean validate,
"pack mismatch in object description"); //$NON-NLS-1$
}
- DfsPackOffset location = offsetCalculator.decode(src.offset);
+ DfsPackOffset location = getOffsetCalculator().decode(src.offset);
// The real pack requires the real offset
src.offset = location.getPackOffset();
location.getPack().copyAsIs(out, src, validate, ctx);
@@ -366,90 +152,31 @@ void copyAsIs(PackOutputStream out, DfsObjectToPack src, boolean validate,
}
@Override
- byte[] getDeltaHeader(DfsReader ctx, long pos)
+ final byte[] getDeltaHeader(DfsReader ctx, long pos)
throws IOException, DataFormatException {
- DfsPackOffset location = offsetCalculator.decode(pos);
+ DfsPackOffset location = getOffsetCalculator().decode(pos);
return location.getPack().getDeltaHeader(ctx, location.getPackOffset());
}
@Override
- int getObjectType(DfsReader ctx, long pos) throws IOException {
- DfsPackOffset location = offsetCalculator.decode(pos);
+ final int getObjectType(DfsReader ctx, long pos) throws IOException {
+ DfsPackOffset location = getOffsetCalculator().decode(pos);
return location.getPack().getObjectType(ctx, location.getPackOffset());
}
@Override
- long getObjectSize(DfsReader ctx, AnyObjectId id) throws IOException {
- PackOffset local = midx(ctx).find(id);
- if (local != null) {
- return packsInIdOrder[local.getPackId()].getObjectSize(ctx, id);
- }
-
- if (base == null) {
- return -1;
- }
-
- return base.getObjectSize(ctx, id);
- }
-
- @Override
- long getObjectSize(DfsReader ctx, long pos) throws IOException {
+ final long getObjectSize(DfsReader ctx, long pos) throws IOException {
if (pos < 0) {
return -1;
}
- DfsPackOffset location = offsetCalculator.decode(pos);
+ DfsPackOffset location = getOffsetCalculator().decode(pos);
return location.getPack().getObjectSize(ctx, location.getPackOffset());
}
@Override
- boolean hasObjectSizeIndex(DfsReader ctx) {
- return false;
- }
-
- @Override
- int getObjectSizeIndexThreshold(DfsReader ctx) {
- return Integer.MAX_VALUE;
- }
-
- @Override
- long getIndexedObjectSize(DfsReader ctx, int idxPosition) {
- // TODO(ifrade): if we forward to the pack, it reads its primary index
- return -1;
- }
-
- @Override
- List<DfsObjectToPack> findAllFromPack(DfsReader ctx,
- Iterable<ObjectToPack> objects, boolean skipFound)
- throws IOException {
- List<DfsObjectToPack> tmp = new BlockList<>();
- List<ObjectToPack> notFoundHere = new BlockList<>();
- for (ObjectToPack obj : objects) {
- DfsObjectToPack otp = (DfsObjectToPack) obj;
- if (skipFound && otp.isFound()) {
- continue;
- }
- long p = offsetCalculator.encode(midx(ctx).find(otp));
- if (p < 0) {
- notFoundHere.add(otp);
- continue;
- }
- otp.setOffset(p);
- tmp.add(otp);
- }
-
- if (base != null && !notFoundHere.isEmpty()) {
- List<DfsObjectToPack> inChain = base.findAllFromPack(ctx,
- notFoundHere, skipFound);
- tmp.addAll(inChain);
- }
- tmp.sort(OFFSET_SORT);
- return tmp;
- }
-
- @Override
- void fillRepresentation(DfsObjectRepresentation r, long offset,
+ final void fillRepresentation(DfsObjectRepresentation r, long offset,
DfsReader ctx) throws IOException {
- DfsPackOffset location = offsetCalculator.decode(offset);
+ DfsPackOffset location = getOffsetCalculator().decode(offset);
if (location == null) {
throw new IllegalArgumentException("Invalid offset in midx"); //$NON-NLS-1$
}
@@ -462,16 +189,16 @@ void fillRepresentation(DfsObjectRepresentation r, long offset,
}
@Override
- void fillRepresentation(DfsObjectRepresentation r, long offset,
+ final void fillRepresentation(DfsObjectRepresentation r, long offset,
DfsReader ctx, PackReverseIndex rev) {
// This method shouldn't be called on the midx pack
throw new UnsupportedOperationException();
}
@Override
- boolean isCorrupt(long offset) {
+ final boolean isCorrupt(long offset) {
// The index must have been loaded before to have this offset
- DfsPackOffset location = offsetCalculator.decode(offset);
+ DfsPackOffset location = getOffsetCalculator().decode(offset);
if (location == null) {
throw new IllegalArgumentException("Invalid offset in midx"); //$NON-NLS-1$
}
@@ -479,105 +206,102 @@ boolean isCorrupt(long offset) {
}
@Override
- DfsBlock readOneBlock(long pos, DfsReader ctx, ReadableChannel rc)
+ final DfsBlock readOneBlock(long pos, DfsReader ctx, ReadableChannel rc)
throws IOException {
// The index must have been loaded before to have this offset
- DfsPackOffset location = offsetCalculator.decode(pos);
+ DfsPackOffset location = getOffsetCalculator().decode(pos);
return new DfsBlockMidx(location.getPack().readOneBlock(
location.getPackOffset(), ctx, rc), location.getPackStart());
}
@Override
- DfsBlock getOrLoadBlock(long pos, DfsReader ctx) throws IOException {
+ final DfsBlock getOrLoadBlock(long pos, DfsReader ctx) throws IOException {
// The index must have been loaded before to have this offset
- DfsPackOffset location = offsetCalculator.decode(pos);
+ DfsPackOffset location = getOffsetCalculator().decode(pos);
return new DfsBlockMidx(location.getPack().getOrLoadBlock(
location.getPackOffset(), ctx), location.getPackStart());
}
- // Visible for testing
- static class VOffsetCalculator {
- private final DfsPackFile[] packs;
+ /**
+ * Get the object calculator of this midx
+ *
+ * @return an offset calculator for this midx (including its chain)
+ */
+ protected abstract VOffsetCalculator getOffsetCalculator();
- private final long[] accSizes;
+ /**
+ * Translates from midx-offset (considering all packs concatenated in midx
+ * order) to (pack, offset) pair. This covers the whole midx chain.
+ *
+ * @implNote implementations take care of the encoding and chaining offset
+ * calculators.
+ */
+ protected interface VOffsetCalculator {
+ /**
+ * Return the pair of pack and offset from a midx offset
+ *
+ * @param voffset
+ * an offset in the midx chain
+ * @return the corresponding pack and offset pair
+ */
+ DfsPackOffset decode(long voffset);
- private final long baseMaxOffset;
-
- private final VOffsetCalculator baseOffsetCalculator;
-
- private final DfsPackOffset poBuffer = new DfsPackOffset();
-
- static VOffsetCalculator fromPacks(DfsPackFile[] packsInIdOrder,
- VOffsetCalculator baseOffsetCalculator) {
- long[] accSizes = new long[packsInIdOrder.length + 1];
- accSizes[0] = 0;
- for (int i = 0; i < packsInIdOrder.length; i++) {
- accSizes[i + 1] = accSizes[i] + packsInIdOrder[i]
- .getPackDescription().getFileSize(PACK);
- }
- return new VOffsetCalculator(packsInIdOrder, accSizes,
- baseOffsetCalculator);
- }
-
- VOffsetCalculator(DfsPackFile[] packs, long[] packSizes,
- VOffsetCalculator baseOffsetCalculator) {
- this.packs = packs;
- this.baseOffsetCalculator = baseOffsetCalculator;
- this.baseMaxOffset = baseOffsetCalculator != null
- ? baseOffsetCalculator.getMaxOffset()
- : 0;
- accSizes = packSizes;
- }
-
- long encode(PackOffset location) {
- if (location == null) {
- return -1;
- }
- return location.getOffset() + accSizes[location.getPackId()]
- + baseMaxOffset;
- }
-
- DfsPackOffset decode(long voffset) {
- if (voffset == -1) {
- return null;
- }
-
- if (voffset < baseMaxOffset) {
- return baseOffsetCalculator.decode(voffset);
- }
-
- long localOffset = voffset - baseMaxOffset;
- for (int i = 0; i < accSizes.length; i++) {
- if (localOffset <= accSizes[i]) {
- return poBuffer.setValues(packs[i - 1],
- accSizes[i - 1] + baseMaxOffset, voffset);
- }
- }
- throw new IllegalArgumentException("Asking offset beyond limits"); //$NON-NLS-1$
- }
-
- long getMaxOffset() {
- return accSizes[accSizes.length - 1] + baseMaxOffset;
- }
+ /**
+ * Max offset for this DfsPackFileMidx
+ *
+ * @return max offset for this pack (including its parents)
+ */
+ long getMaxOffset();
}
- static class DfsPackOffset {
+ /**
+ * Data object that keeps a location readable as midx-offset or as
+ * (pack/offset).
+ * <p>
+ * midx-offset is the offset considering the concatenation of all covered
+ * packs in midx order. Only in the first pack of the base of the midx
+ * chain, the pack offsets match the midx offsets.
+ */
+ protected static final class DfsPackOffset {
private DfsPackFile pack;
private long packStart;
- private long offset;
+ private long midxOffset;
- private DfsPackOffset setValues(DfsPackFile pack, long packStart,
- long globalOffset) {
+ /**
+ * Set a location in this instance
+ *
+ * @param pack
+ * the pack that contains the object
+ * @param packStart
+ * midx-offset where the pack starts
+ * @param midxOffset
+ * midx-offset
+ * @return an instance with this data
+ */
+ DfsPackOffset setValues(DfsPackFile pack, long packStart,
+ long midxOffset) {
this.pack = pack;
this.packStart = packStart;
- this.offset = globalOffset;
+ this.midxOffset = midxOffset;
return this;
}
/**
- * The pack
+ * Set only the midx-offset
+ *
+ * @param midxOffset
+ * offset in the midx
+ * @return and updated DfsPackOffset instance
+ */
+ DfsPackOffset setMidxOffset(long midxOffset) {
+ this.midxOffset = midxOffset;
+ return this;
+ }
+
+ /**
+ * The pack containing the object
*
* @return the pack
*/
@@ -601,10 +325,10 @@ long getPackStart() {
/**
* Offset inside the pack (regular offset)
*
- * @return offset inside this pack
+ * @return offset inside the pack
*/
long getPackOffset() {
- return offset - packStart;
+ return midxOffset - packStart;
}
}
}
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
new file mode 100644
index 0000000..edcfafa
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFileMidxNPacks.java
@@ -0,0 +1,434 @@
+/*
+ * 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.dfs;
+
+import static org.eclipse.jgit.internal.storage.pack.PackExt.MULTI_PACK_INDEX;
+import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
+
+import java.io.IOException;
+import java.nio.channels.Channels;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.internal.storage.commitgraph.CommitGraph;
+import org.eclipse.jgit.internal.storage.file.PackBitmapIndex;
+import org.eclipse.jgit.internal.storage.midx.MultiPackIndex;
+import org.eclipse.jgit.internal.storage.midx.MultiPackIndex.PackOffset;
+import org.eclipse.jgit.internal.storage.midx.MultiPackIndexLoader;
+import org.eclipse.jgit.internal.storage.pack.ObjectToPack;
+import org.eclipse.jgit.internal.storage.pack.PackOutputStream;
+import org.eclipse.jgit.lib.AbbreviatedObjectId;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.BitmapIndex;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdSet;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.util.BlockList;
+
+/**
+ * Implementation of a DfsPackfile that tries to solve the queries in a
+ * multipack index before resorting to the real packs.
+ * <p>
+ * It uses the position in the multipack index of the objects as their "offset".
+ */
+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;
+
+ private MultiPackIndex midx;
+
+ private final DfsPackFileMidx base;
+
+ private final VOffsetCalculatorNPacks offsetCalculator;
+
+ DfsPackFileMidxNPacks(DfsBlockCache cache, DfsPackDescription desc,
+ List<DfsPackFile> requiredPacks, @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);
+ offsetCalculator = VOffsetCalculatorNPacks.fromPacks(packsInIdOrder,
+ base != null ? base.getOffsetCalculator() : null);
+ this.length = offsetCalculator.getMaxOffset();
+ }
+
+ private MultiPackIndex midx(DfsReader ctx) throws IOException {
+ if (midx != null) {
+ return midx;
+ }
+
+ DfsStreamKey revKey = desc.getStreamKey(MULTI_PACK_INDEX);
+ // Keep the value parsed in the loader, in case the Ref<> is
+ // nullified in ClockBlockCacheTable#reserveSpace
+ // before we read its value.
+ AtomicReference<MultiPackIndex> loadedRef = new AtomicReference<>(null);
+ DfsBlockCache.Ref<MultiPackIndex> cachedRef = cache.getOrLoadRef(revKey,
+ REF_POSITION, () -> {
+ RefWithSize midx1 = loadMultiPackIndex(ctx, desc);
+ loadedRef.set(midx1.idx);
+ return new DfsBlockCache.Ref<>(revKey, REF_POSITION,
+ midx1.size, midx1.idx);
+ });
+ // if (loadedRef.get() == null) {
+ // ctx.stats.ridxCacheHit;
+ // }
+ midx = cachedRef.get() != null ? cachedRef.get() : loadedRef.get();
+ return midx;
+ }
+
+ private static RefWithSize loadMultiPackIndex(DfsReader ctx,
+ DfsPackDescription desc) throws IOException {
+ try (ReadableChannel rc = ctx.db.openFile(desc, MULTI_PACK_INDEX)) {
+ MultiPackIndex midx = MultiPackIndexLoader
+ .read(Channels.newInputStream(rc));
+ // ctx.stats.readIdxBytes += rc.position();
+ return new RefWithSize(midx, midx.getMemorySize());
+ }
+ }
+
+ private record RefWithSize(MultiPackIndex idx, long size) {
+ }
+
+ private DfsPackFile[] getPacksInMidxIdOrder(String[] packNames) {
+ Map<String, DfsPackFile> byName = packs.stream()
+ .collect(Collectors.toUnmodifiableMap(
+ p -> p.getPackDescription().getPackName(),
+ Function.identity()));
+ DfsPackFile[] result = new DfsPackFile[desc.getCoveredPacks().size()];
+ for (int i = 0; i < packNames.length; i++) {
+ DfsPackFile pack = byName.get(packNames[i]);
+ if (pack == null) {
+ // This should have been checked in the object db
+ // when the pack description was loaded
+ throw new IllegalStateException("Required pack missing"); //$NON-NLS-1$
+ }
+ result[i] = pack;
+ }
+ return result;
+ }
+
+ // Visible for testing
+ @Override
+ protected VOffsetCalculator getOffsetCalculator() {
+ return offsetCalculator;
+ }
+
+ @Override
+ public ObjectIdSet asObjectIdSet(DfsReader ctx) throws IOException {
+ MultiPackIndex multiPackIndex = midx(ctx);
+ return multiPackIndex::hasObject;
+ }
+
+
+ @Override
+ public PackBitmapIndex getBitmapIndex(DfsReader ctx) throws IOException {
+ // TODO(ifrade): at some point we will have bitmaps over the multipack
+ // index
+ // At the moment bitmap is in GC, at the end of the chain
+ if (base != null) {
+ return base.getBitmapIndex(ctx);
+ }
+
+ for (DfsPackFile pack : packsInIdOrder) {
+ PackBitmapIndex bitmapIndex = pack.getBitmapIndex(ctx);
+ if (bitmapIndex != null) {
+ return bitmapIndex;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ List<DfsPackFile> fullyIncludedIn(DfsReader ctx,
+ BitmapIndex.BitmapBuilder need) throws IOException {
+ List<DfsPackFile> fullyIncluded = new ArrayList<>();
+ for (DfsPackFile pack : packs) {
+ List<DfsPackFile> includedPacks = pack.fullyIncludedIn(ctx, need);
+ if (!includedPacks.isEmpty()) {
+ fullyIncluded.addAll(includedPacks);
+ }
+ }
+
+ if (base != null) {
+ fullyIncluded.addAll(base.fullyIncludedIn(ctx, need));
+ }
+
+ return fullyIncluded;
+ }
+
+ @Override
+ public CommitGraph getCommitGraph(DfsReader ctx) throws IOException {
+ for (DfsPackFile pack : packs) {
+ CommitGraph cg = pack.getCommitGraph(ctx);
+ if (cg != null) {
+ return cg;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Count of objects in this <b>pack</> (i.e. including, recursively, its
+ * base)
+ *
+ * @param ctx
+ * a reader
+ * @return count of objects in this pack, including its bases
+ * @throws IOException
+ * an error reading a midx in the chain
+ */
+ @Override
+ protected int getObjectCount(DfsReader ctx) throws IOException {
+ int baseObjectCount = base == null ? 0 : base.getObjectCount(ctx);
+ return midx(ctx).getObjectCount() + baseObjectCount;
+ }
+
+ /**
+ * Packs indexed by this multipack index (base NOT included)
+ *
+ * @return packs indexed by this multipack index
+ */
+ @Override
+ public List<DfsPackFile> getCoveredPacks() {
+ return packs;
+ }
+
+ /**
+ * Base of this multipack index
+ * <p>
+ * If this midx is part of a chain, this is its parent
+ *
+ * @return the base of this multipack index
+ */
+ @Override
+ public DfsPackFileMidx getMultipackIndexBase() {
+ return base;
+ }
+
+ @Override
+ public int findIdxPosition(DfsReader ctx, AnyObjectId id)
+ throws IOException {
+ int p = midx(ctx).findPosition(id);
+ if (p >= 0) {
+ int baseObjects = base == null ? 0 : base.getObjectCount(ctx);
+ return p + baseObjects;
+ }
+
+ if (base == null) {
+ return -1;
+ }
+
+ return base.findIdxPosition(ctx, id);
+ }
+
+ @Override
+ public boolean hasObject(DfsReader ctx, AnyObjectId id) throws IOException {
+ if (midx(ctx).hasObject(id)) {
+ return true;
+ }
+
+ if (base == null) {
+ return false;
+ }
+
+ return base.hasObject(ctx, id);
+ }
+
+ @Override
+ ObjectLoader get(DfsReader ctx, AnyObjectId id) throws IOException {
+ PackOffset location = midx(ctx).find(id);
+ if (location != null) {
+ return packsInIdOrder[location.getPackId()].get(ctx, id);
+ }
+
+ if (base == null) {
+ return null;
+ }
+
+ return base.get(ctx, id);
+ }
+
+ @Override
+ long findOffset(DfsReader ctx, AnyObjectId id) throws IOException {
+ PackOffset location = midx(ctx).find(id);
+ if (location != null) {
+ return offsetCalculator.encode(location);
+ }
+
+ if (base == null) {
+ return -1;
+ }
+
+ return base.findOffset(ctx, id);
+ }
+
+ @Override
+ void resolve(DfsReader ctx, Set<ObjectId> matches, AbbreviatedObjectId id,
+ int matchLimit) throws IOException {
+ midx(ctx).resolve(matches, id, matchLimit);
+ if (matches.size() < matchLimit && base != null) {
+ base.resolve(ctx, matches, id, matchLimit);
+ }
+ }
+
+ @Override
+ void copyPackAsIs(PackOutputStream out, DfsReader ctx) throws IOException {
+ // Assumming the order of the packs does not really matter
+ for (DfsPackFile pack : packs) {
+ pack.copyPackAsIs(out, ctx);
+ }
+
+ if (base != null) {
+ base.copyPackAsIs(out, ctx);
+ }
+ }
+
+ @Override
+ long getObjectSize(DfsReader ctx, AnyObjectId id) throws IOException {
+ PackOffset local = midx(ctx).find(id);
+ if (local != null) {
+ return packsInIdOrder[local.getPackId()].getObjectSize(ctx, id);
+ }
+
+ if (base == null) {
+ return -1;
+ }
+
+ return base.getObjectSize(ctx, id);
+ }
+
+ @Override
+ boolean hasObjectSizeIndex(DfsReader ctx) {
+ return false;
+ }
+
+ @Override
+ int getObjectSizeIndexThreshold(DfsReader ctx) {
+ return Integer.MAX_VALUE;
+ }
+
+ @Override
+ long getIndexedObjectSize(DfsReader ctx, int idxPosition) {
+ // TODO(ifrade): if we forward to the pack, it reads its primary index
+ return -1;
+ }
+
+ @Override
+ List<DfsObjectToPack> findAllFromPack(DfsReader ctx,
+ Iterable<ObjectToPack> objects, boolean skipFound)
+ throws IOException {
+ List<DfsObjectToPack> tmp = new BlockList<>();
+ List<ObjectToPack> notFoundHere = new BlockList<>();
+ for (ObjectToPack obj : objects) {
+ DfsObjectToPack otp = (DfsObjectToPack) obj;
+ if (skipFound && otp.isFound()) {
+ continue;
+ }
+ long p = offsetCalculator.encode(midx(ctx).find(otp));
+ if (p < 0) {
+ notFoundHere.add(otp);
+ continue;
+ }
+ otp.setOffset(p);
+ tmp.add(otp);
+ }
+
+ if (base != null && !notFoundHere.isEmpty()) {
+ List<DfsObjectToPack> inChain = base.findAllFromPack(ctx,
+ notFoundHere, skipFound);
+ tmp.addAll(inChain);
+ }
+ tmp.sort(OFFSET_SORT);
+ return tmp;
+ }
+
+
+ // Visible for testing
+ static class VOffsetCalculatorNPacks implements VOffsetCalculator {
+ private final DfsPackFile[] packs;
+
+ private final long[] accSizes;
+
+ private final long baseMaxOffset;
+
+ private final VOffsetCalculator baseOffsetCalculator;
+
+ private final DfsPackOffset poBuffer = new DfsPackOffset();
+
+ static VOffsetCalculatorNPacks fromPacks(DfsPackFile[] packsInIdOrder,
+ VOffsetCalculator baseOffsetCalculator) {
+ long[] accSizes = new long[packsInIdOrder.length + 1];
+ accSizes[0] = 0;
+ for (int i = 0; i < packsInIdOrder.length; i++) {
+ accSizes[i + 1] = accSizes[i] + packsInIdOrder[i]
+ .getPackDescription().getFileSize(PACK);
+ }
+ return new VOffsetCalculatorNPacks(packsInIdOrder, accSizes,
+ baseOffsetCalculator);
+ }
+
+ VOffsetCalculatorNPacks(DfsPackFile[] packs, long[] packSizes,
+ VOffsetCalculator baseOffsetCalculator) {
+ this.packs = packs;
+ this.baseOffsetCalculator = baseOffsetCalculator;
+ this.baseMaxOffset = baseOffsetCalculator != null
+ ? baseOffsetCalculator.getMaxOffset()
+ : 0;
+ accSizes = packSizes;
+ }
+
+ long encode(MultiPackIndex.PackOffset location) {
+ if (location == null) {
+ return -1;
+ }
+ return location.getOffset() + accSizes[location.getPackId()]
+ + baseMaxOffset;
+ }
+
+ @Override
+ public DfsPackOffset decode(long voffset) {
+ if (voffset == -1) {
+ return null;
+ }
+
+ if (voffset < baseMaxOffset) {
+ return baseOffsetCalculator.decode(voffset);
+ }
+
+ long localOffset = voffset - baseMaxOffset;
+ for (int i = 0; i < accSizes.length; i++) {
+ if (localOffset <= accSizes[i]) {
+ return poBuffer.setValues(packs[i - 1],
+ accSizes[i - 1] + baseMaxOffset, voffset);
+ }
+ }
+ throw new IllegalArgumentException("Asking offset beyond limits"); //$NON-NLS-1$
+ }
+
+ @Override
+ public long getMaxOffset() {
+ return accSizes[accSizes.length - 1] + baseMaxOffset;
+ }
+ }
+}