]> source.dussan.org Git - jgit.git/commitdiff
PackIndexWriter: create interface to write indexes 75/1202275/8
authorSam Delmerico <delmerico@google.com>
Mon, 7 Oct 2024 22:20:26 +0000 (15:20 -0700)
committerSam Delmerico <delmerico@google.com>
Tue, 22 Oct 2024 21:57:48 +0000 (14:57 -0700)
PackWriter assumes that the primary index goes to a file in a well-known
format. This cannot accomodate implementations in other storages or
formats (e.g. in a database).

Create an interface to write the index (PackIndexWriter). This interface
will be implemented by the existing pack index writer classes
(PackIndexWriterV1 etc.).

As the "PackIndexWriter" name was used by the previous superclass of the
file writers, we rename that class to "BasePackIndexWriter".

Change-Id: Ia7348395315e458fc7adc75a8db5dcb903e2a4a1

14 files changed:
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/AbbreviationTest.java
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BasePackWriterTest.java [new file with mode: 0644]
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java [deleted file]
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsInserter.java
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BasePackIndexWriter.java [new file with mode: 0644]
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryPackParser.java
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndex.java
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexWriter.java [deleted file]
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexWriterV1.java
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexWriterV2.java
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackInserter.java
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackIndexWriter.java [new file with mode: 0644]
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java
org.eclipse.jgit/src/org/eclipse/jgit/storage/pack/PackConfig.java

index bd36337f35be363eeca0054cb5c543c30aecf4b1..41a33df0e40e0649f8e4ed65c852a0b8fce6c2d4 100644 (file)
@@ -29,6 +29,7 @@ import java.util.List;
 
 import org.eclipse.jgit.errors.AmbiguousObjectException;
 import org.eclipse.jgit.internal.storage.pack.PackExt;
+import org.eclipse.jgit.internal.storage.pack.PackIndexWriter;
 import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
 import org.eclipse.jgit.junit.TestRepository;
 import org.eclipse.jgit.lib.AbbreviatedObjectId;
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BasePackWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/BasePackWriterTest.java
new file mode 100644 (file)
index 0000000..92d7465
--- /dev/null
@@ -0,0 +1,1079 @@
+/*
+ * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com> and others
+ *
+ * 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.file;
+
+import static org.eclipse.jgit.internal.storage.pack.PackWriter.NONE;
+import static org.eclipse.jgit.lib.Constants.INFO_ALTERNATES;
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry;
+import org.eclipse.jgit.internal.storage.pack.PackExt;
+import org.eclipse.jgit.internal.storage.pack.PackWriter;
+import org.eclipse.jgit.junit.JGitTestUtil;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.junit.TestRepository.BranchBuilder;
+import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdSet;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.Sets;
+import org.eclipse.jgit.revwalk.DepthWalk;
+import org.eclipse.jgit.revwalk.ObjectWalk;
+import org.eclipse.jgit.revwalk.RevBlob;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.storage.pack.PackConfig;
+import org.eclipse.jgit.storage.pack.PackStatistics;
+import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
+import org.eclipse.jgit.transport.PackParser;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class BasePackWriterTest extends SampleDataRepositoryTestCase {
+
+       private static final List<RevObject> EMPTY_LIST_REVS = Collections
+                       .<RevObject> emptyList();
+
+       private static final Set<ObjectIdSet> EMPTY_ID_SET = Collections
+                       .<ObjectIdSet> emptySet();
+
+       private PackConfig config;
+
+       private PackWriter writer;
+
+       private ByteArrayOutputStream os;
+
+       private Pack pack;
+
+       private ObjectInserter inserter;
+
+       private FileRepository dst;
+
+       private RevBlob contentA;
+
+       private RevBlob contentB;
+
+       private RevBlob contentC;
+
+       private RevBlob contentD;
+
+       private RevBlob contentE;
+
+       private RevCommit c1;
+
+       private RevCommit c2;
+
+       private RevCommit c3;
+
+       private RevCommit c4;
+
+       private RevCommit c5;
+
+       @Override
+       @Before
+       public void setUp() throws Exception {
+               super.setUp();
+               os = new ByteArrayOutputStream();
+               config = new PackConfig(db);
+
+               dst = createBareRepository();
+               File alt = new File(dst.getObjectDatabase().getDirectory(), INFO_ALTERNATES);
+               alt.getParentFile().mkdirs();
+               write(alt, db.getObjectDatabase().getDirectory().getAbsolutePath() + "\n");
+       }
+
+       @Override
+       @After
+       public void tearDown() throws Exception {
+               if (writer != null) {
+                       writer.close();
+                       writer = null;
+               }
+               if (inserter != null) {
+                       inserter.close();
+                       inserter = null;
+               }
+               super.tearDown();
+       }
+
+       /**
+        * Test constructor for exceptions, default settings, initialization.
+        *
+        * @throws IOException
+        */
+       @Test
+       public void testContructor() throws IOException {
+               writer = new PackWriter(config, db.newObjectReader());
+               assertFalse(writer.isDeltaBaseAsOffset());
+               assertTrue(config.isReuseDeltas());
+               assertTrue(config.isReuseObjects());
+               assertEquals(0, writer.getObjectCount());
+       }
+
+       /**
+        * Change default settings and verify them.
+        */
+       @Test
+       public void testModifySettings() {
+               config.setReuseDeltas(false);
+               config.setReuseObjects(false);
+               config.setDeltaBaseAsOffset(false);
+               assertFalse(config.isReuseDeltas());
+               assertFalse(config.isReuseObjects());
+               assertFalse(config.isDeltaBaseAsOffset());
+
+               writer = new PackWriter(config, db.newObjectReader());
+               writer.setDeltaBaseAsOffset(true);
+               assertTrue(writer.isDeltaBaseAsOffset());
+               assertFalse(config.isDeltaBaseAsOffset());
+       }
+
+       /**
+        * Write empty pack by providing empty sets of interesting/uninteresting
+        * objects and check for correct format.
+        *
+        * @throws IOException
+        */
+       @Test
+       public void testWriteEmptyPack1() throws IOException {
+               createVerifyOpenPack(NONE, NONE, false, false);
+
+               assertEquals(0, writer.getObjectCount());
+               assertEquals(0, pack.getObjectCount());
+               assertEquals("da39a3ee5e6b4b0d3255bfef95601890afd80709", writer
+                               .computeName().name());
+       }
+
+       /**
+        * Write empty pack by providing empty iterator of objects to write and
+        * check for correct format.
+        *
+        * @throws IOException
+        */
+       @Test
+       public void testWriteEmptyPack2() throws IOException {
+               createVerifyOpenPack(EMPTY_LIST_REVS);
+
+               assertEquals(0, writer.getObjectCount());
+               assertEquals(0, pack.getObjectCount());
+       }
+
+       /**
+        * Try to pass non-existing object as uninteresting, with non-ignoring
+        * setting.
+        *
+        * @throws IOException
+        */
+       @Test
+       public void testNotIgnoreNonExistingObjects() throws IOException {
+               final ObjectId nonExisting = ObjectId
+                               .fromString("0000000000000000000000000000000000000001");
+               try {
+                       createVerifyOpenPack(NONE, haves(nonExisting), false, false);
+                       fail("Should have thrown MissingObjectException");
+               } catch (MissingObjectException x) {
+                       // expected
+               }
+       }
+
+       /**
+        * Try to pass non-existing object as uninteresting, with ignoring setting.
+        *
+        * @throws IOException
+        */
+       @Test
+       public void testIgnoreNonExistingObjects() throws IOException {
+               final ObjectId nonExisting = ObjectId
+                               .fromString("0000000000000000000000000000000000000001");
+               createVerifyOpenPack(NONE, haves(nonExisting), false, true);
+               // shouldn't throw anything
+       }
+
+       /**
+        * Try to pass non-existing object as uninteresting, with ignoring setting.
+        * Use a repo with bitmap indexes because then PackWriter will use
+        * PackWriterBitmapWalker which had problems with this situation.
+        *
+        * @throws Exception
+        */
+       @Test
+       public void testIgnoreNonExistingObjectsWithBitmaps() throws Exception {
+               final ObjectId nonExisting = ObjectId
+                               .fromString("0000000000000000000000000000000000000001");
+               new GC(db).gc().get();
+               createVerifyOpenPack(NONE, haves(nonExisting), false, true, true);
+               // shouldn't throw anything
+       }
+
+       /**
+        * Create pack basing on only interesting objects, then precisely verify
+        * content. No delta reuse here.
+        *
+        * @throws IOException
+        */
+       @Test
+       public void testWritePack1() throws IOException {
+               config.setReuseDeltas(false);
+               writeVerifyPack1();
+       }
+
+       /**
+        * Test writing pack without object reuse. Pack content/preparation as in
+        * {@link #testWritePack1()}.
+        *
+        * @throws IOException
+        */
+       @Test
+       public void testWritePack1NoObjectReuse() throws IOException {
+               config.setReuseDeltas(false);
+               config.setReuseObjects(false);
+               writeVerifyPack1();
+       }
+
+       /**
+        * Create pack basing on both interesting and uninteresting objects, then
+        * precisely verify content. No delta reuse here.
+        *
+        * @throws IOException
+        */
+       @Test
+       public void testWritePack2() throws IOException {
+               writeVerifyPack2(false);
+       }
+
+       /**
+        * Test pack writing with deltas reuse, delta-base first rule. Pack
+        * content/preparation as in {@link #testWritePack2()}.
+        *
+        * @throws IOException
+        */
+       @Test
+       public void testWritePack2DeltasReuseRefs() throws IOException {
+               writeVerifyPack2(true);
+       }
+
+       /**
+        * Test pack writing with delta reuse. Delta bases referred as offsets. Pack
+        * configuration as in {@link #testWritePack2DeltasReuseRefs()}.
+        *
+        * @throws IOException
+        */
+       @Test
+       public void testWritePack2DeltasReuseOffsets() throws IOException {
+               config.setDeltaBaseAsOffset(true);
+               writeVerifyPack2(true);
+       }
+
+       /**
+        * Test pack writing with delta reuse. Raw-data copy (reuse) is made on a
+        * pack with CRC32 index. Pack configuration as in
+        * {@link #testWritePack2DeltasReuseRefs()}.
+        *
+        * @throws IOException
+        */
+       @Test
+       public void testWritePack2DeltasCRC32Copy() throws IOException {
+               final File packDir = db.getObjectDatabase().getPackDirectory();
+               final PackFile crc32Pack = new PackFile(packDir,
+                               "pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f.pack");
+               final PackFile crc32Idx = new PackFile(packDir,
+                               "pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f.idx");
+               copyFile(JGitTestUtil.getTestResourceFile(
+                               "pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f.idxV2"),
+                               crc32Idx);
+               db.openPack(crc32Pack);
+
+               writeVerifyPack2(true);
+       }
+
+       /**
+        * Create pack basing on fixed objects list, then precisely verify content.
+        * No delta reuse here.
+        *
+        * @throws IOException
+        * @throws MissingObjectException
+        *
+        */
+       @Test
+       public void testWritePack3() throws MissingObjectException, IOException {
+               config.setReuseDeltas(false);
+               final ObjectId forcedOrder[] = new ObjectId[] {
+                               ObjectId.fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"),
+                               ObjectId.fromString("c59759f143fb1fe21c197981df75a7ee00290799"),
+                               ObjectId.fromString("aabf2ffaec9b497f0950352b3e582d73035c2035"),
+                               ObjectId.fromString("902d5476fa249b7abc9d84c611577a81381f0327"),
+                               ObjectId.fromString("6ff87c4664981e4397625791c8ea3bbb5f2279a3") ,
+                               ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259") };
+               try (RevWalk parser = new RevWalk(db)) {
+                       final RevObject forcedOrderRevs[] = new RevObject[forcedOrder.length];
+                       for (int i = 0; i < forcedOrder.length; i++)
+                               forcedOrderRevs[i] = parser.parseAny(forcedOrder[i]);
+
+                       createVerifyOpenPack(Arrays.asList(forcedOrderRevs));
+               }
+
+               assertEquals(forcedOrder.length, writer.getObjectCount());
+               verifyObjectsOrder(forcedOrder);
+               assertEquals("ed3f96b8327c7c66b0f8f70056129f0769323d86", writer
+                               .computeName().name());
+       }
+
+       /**
+        * Another pack creation: basing on both interesting and uninteresting
+        * objects. No delta reuse possible here, as this is a specific case when we
+        * write only 1 commit, associated with 1 tree, 1 blob.
+        *
+        * @throws IOException
+        */
+       @Test
+       public void testWritePack4() throws IOException {
+               writeVerifyPack4(false);
+       }
+
+       /**
+        * Test thin pack writing: 1 blob delta base is on objects edge. Pack
+        * configuration as in {@link #testWritePack4()}.
+        *
+        * @throws IOException
+        */
+       @Test
+       public void testWritePack4ThinPack() throws IOException {
+               writeVerifyPack4(true);
+       }
+
+       /**
+        * Compare sizes of packs created using {@link #testWritePack2()} and
+        * {@link #testWritePack2DeltasReuseRefs()}. The pack using deltas should
+        * be smaller.
+        *
+        * @throws Exception
+        */
+       @Test
+       public void testWritePack2SizeDeltasVsNoDeltas() throws Exception {
+               config.setReuseDeltas(false);
+               config.setDeltaCompress(false);
+               testWritePack2();
+               final long sizePack2NoDeltas = os.size();
+               tearDown();
+               setUp();
+               testWritePack2DeltasReuseRefs();
+               final long sizePack2DeltasRefs = os.size();
+
+               assertTrue(sizePack2NoDeltas > sizePack2DeltasRefs);
+       }
+
+       /**
+        * Compare sizes of packs created using
+        * {@link #testWritePack2DeltasReuseRefs()} and
+        * {@link #testWritePack2DeltasReuseOffsets()}. The pack with delta bases
+        * written as offsets should be smaller.
+        *
+        * @throws Exception
+        */
+       @Test
+       public void testWritePack2SizeOffsetsVsRefs() throws Exception {
+               testWritePack2DeltasReuseRefs();
+               final long sizePack2DeltasRefs = os.size();
+               tearDown();
+               setUp();
+               testWritePack2DeltasReuseOffsets();
+               final long sizePack2DeltasOffsets = os.size();
+
+               assertTrue(sizePack2DeltasRefs > sizePack2DeltasOffsets);
+       }
+
+       /**
+        * Compare sizes of packs created using {@link #testWritePack4()} and
+        * {@link #testWritePack4ThinPack()}. Obviously, the thin pack should be
+        * smaller.
+        *
+        * @throws Exception
+        */
+       @Test
+       public void testWritePack4SizeThinVsNoThin() throws Exception {
+               testWritePack4();
+               final long sizePack4 = os.size();
+               tearDown();
+               setUp();
+               testWritePack4ThinPack();
+               final long sizePack4Thin = os.size();
+
+               assertTrue(sizePack4 > sizePack4Thin);
+       }
+
+       @Test
+       public void testDeltaStatistics() throws Exception {
+               config.setDeltaCompress(true);
+               // TestRepository will close repo
+               FileRepository repo = createBareRepository();
+               ArrayList<RevObject> blobs = new ArrayList<>();
+               try (TestRepository<FileRepository> testRepo = new TestRepository<>(
+                               repo)) {
+                       blobs.add(testRepo.blob(genDeltableData(1000)));
+                       blobs.add(testRepo.blob(genDeltableData(1005)));
+                       try (PackWriter pw = new PackWriter(repo)) {
+                               NullProgressMonitor m = NullProgressMonitor.INSTANCE;
+                               pw.preparePack(blobs.iterator());
+                               pw.writePack(m, m, os);
+                               PackStatistics stats = pw.getStatistics();
+                               assertEquals(1, stats.getTotalDeltas());
+                               assertTrue("Delta bytes not set.",
+                                               stats.byObjectType(OBJ_BLOB).getDeltaBytes() > 0);
+                       }
+               }
+       }
+
+       // Generate consistent junk data for building files that delta well
+       private String genDeltableData(int length) {
+               assertTrue("Generated data must have a length > 0", length > 0);
+               char[] data = {'a', 'b', 'c', '\n'};
+               StringBuilder builder = new StringBuilder(length);
+               for (int i = 0; i < length; i++) {
+                       builder.append(data[i % 4]);
+               }
+               return builder.toString();
+       }
+
+
+       @Test
+       public void testWriteIndex() throws Exception {
+               config.setIndexVersion(2);
+               writeVerifyPack4(false);
+
+               PackFile packFile = pack.getPackFile();
+               PackFile indexFile = packFile.create(PackExt.INDEX);
+
+               // Validate that IndexPack came up with the right CRC32 value.
+               final PackIndex idx1 = PackIndex.open(indexFile);
+               assertTrue(idx1 instanceof PackIndexV2);
+               assertEquals(0x4743F1E4L, idx1.findCRC32(ObjectId
+                               .fromString("82c6b885ff600be425b4ea96dee75dca255b69e7")));
+
+               // Validate that an index written by PackWriter is the same.
+               final File idx2File = new File(indexFile.getAbsolutePath() + ".2");
+               try (FileOutputStream is = new FileOutputStream(idx2File)) {
+                       writer.writeIndex(is);
+               }
+               final PackIndex idx2 = PackIndex.open(idx2File);
+               assertTrue(idx2 instanceof PackIndexV2);
+               assertEquals(idx1.getObjectCount(), idx2.getObjectCount());
+               assertEquals(idx1.getOffset64Count(), idx2.getOffset64Count());
+
+               for (int i = 0; i < idx1.getObjectCount(); i++) {
+                       final ObjectId id = idx1.getObjectId(i);
+                       assertEquals(id, idx2.getObjectId(i));
+                       assertEquals(idx1.findOffset(id), idx2.findOffset(id));
+                       assertEquals(idx1.findCRC32(id), idx2.findCRC32(id));
+               }
+       }
+
+       @Test
+       public void testWriteObjectSizeIndex_noDeltas() throws Exception {
+               config.setMinBytesForObjSizeIndex(0);
+               HashSet<ObjectId> interesting = new HashSet<>();
+               interesting.add(ObjectId
+                               .fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"));
+
+               NullProgressMonitor m1 = NullProgressMonitor.INSTANCE;
+               writer = new PackWriter(config, db.newObjectReader());
+               writer.setUseBitmaps(false);
+               writer.setThin(false);
+               writer.setIgnoreMissingUninteresting(false);
+               writer.preparePack(m1, interesting, NONE);
+               writer.writePack(m1, m1, os);
+
+               PackIndex idx;
+               try (ByteArrayOutputStream is = new ByteArrayOutputStream()) {
+                       writer.writeIndex(is);
+                       idx = PackIndex.read(new ByteArrayInputStream(is.toByteArray()));
+               }
+
+               PackObjectSizeIndex objSizeIdx;
+               try (ByteArrayOutputStream objSizeStream = new ByteArrayOutputStream()) {
+                       writer.writeObjectSizeIndex(objSizeStream);
+                       objSizeIdx = PackObjectSizeIndexLoader.load(
+                                       new ByteArrayInputStream(objSizeStream.toByteArray()));
+               }
+               writer.close();
+
+               ObjectId knownBlob1 = ObjectId
+                               .fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259");
+               ObjectId knownBlob2 = ObjectId
+                               .fromString("6ff87c4664981e4397625791c8ea3bbb5f2279a3");
+               assertEquals(18009, objSizeIdx.getSize(idx.findPosition(knownBlob1)));
+               assertEquals(18787, objSizeIdx.getSize(idx.findPosition(knownBlob2)));
+       }
+
+       @Test
+       public void testWriteReverseIndexConfig() {
+               assertFalse(config.isWriteReverseIndex());
+               config.setWriteReverseIndex(true);
+               assertTrue(config.isWriteReverseIndex());
+       }
+
+       @Test
+       public void testWriteReverseIndexOff() throws Exception {
+               config.setWriteReverseIndex(false);
+               writer = new PackWriter(config, db.newObjectReader());
+               ByteArrayOutputStream reverseIndexOutput = new ByteArrayOutputStream();
+
+               writer.writeReverseIndex(reverseIndexOutput);
+
+               assertEquals(0, reverseIndexOutput.size());
+       }
+
+       @Test
+       public void testWriteReverseIndexOn() throws Exception {
+               config.setWriteReverseIndex(true);
+               writeVerifyPack4(false);
+               ByteArrayOutputStream reverseIndexOutput = new ByteArrayOutputStream();
+               int headerBytes = 12;
+               int bodyBytes = 12;
+               int footerBytes = 40;
+
+               writer.writeReverseIndex(reverseIndexOutput);
+
+               assertTrue(reverseIndexOutput.size() == headerBytes + bodyBytes
+                               + footerBytes);
+       }
+
+       @Test
+       public void testExclude() throws Exception {
+               // TestRepository closes repo
+               FileRepository repo = createBareRepository();
+
+               try (TestRepository<FileRepository> testRepo = new TestRepository<>(
+                               repo)) {
+                       BranchBuilder bb = testRepo.branch("refs/heads/master");
+                       contentA = testRepo.blob("A");
+                       c1 = bb.commit().add("f", contentA).create();
+                       testRepo.getRevWalk().parseHeaders(c1);
+                       PackIndex pf1 = writePack(repo, wants(c1), EMPTY_ID_SET);
+                       assertContent(pf1, Arrays.asList(c1.getId(), c1.getTree().getId(),
+                                       contentA.getId()));
+                       contentB = testRepo.blob("B");
+                       c2 = bb.commit().add("f", contentB).create();
+                       testRepo.getRevWalk().parseHeaders(c2);
+                       PackIndex pf2 = writePack(repo, wants(c2),
+                                       Sets.of((ObjectIdSet) pf1));
+                       assertContent(pf2, Arrays.asList(c2.getId(), c2.getTree().getId(),
+                                       contentB.getId()));
+               }
+       }
+
+       private static void assertContent(PackIndex pi, List<ObjectId> expected) {
+               assertEquals("Pack index has wrong size.", expected.size(),
+                               pi.getObjectCount());
+               for (int i = 0; i < pi.getObjectCount(); i++)
+                       assertTrue(
+                                       "Pack index didn't contain the expected id "
+                                                       + pi.getObjectId(i),
+                                       expected.contains(pi.getObjectId(i)));
+       }
+
+       @Test
+       public void testShallowIsMinimalDepth1() throws Exception {
+               try (FileRepository repo = setupRepoForShallowFetch()) {
+                       PackIndex idx = writeShallowPack(repo, 1, wants(c2), NONE, NONE);
+                       assertContent(idx, Arrays.asList(c2.getId(), c2.getTree().getId(),
+                                       contentA.getId(), contentB.getId()));
+
+                       // Client already has blobs A and B, verify those are not packed.
+                       idx = writeShallowPack(repo, 1, wants(c5), haves(c2), shallows(c2));
+                       assertContent(idx, Arrays.asList(c5.getId(), c5.getTree().getId(),
+                                       contentC.getId(), contentD.getId(), contentE.getId()));
+               }
+       }
+
+       @Test
+       public void testShallowIsMinimalDepth2() throws Exception {
+               try (FileRepository repo = setupRepoForShallowFetch()) {
+                       PackIndex idx = writeShallowPack(repo, 2, wants(c2), NONE, NONE);
+                       assertContent(idx,
+                                       Arrays.asList(c1.getId(), c2.getId(), c1.getTree().getId(),
+                                                       c2.getTree().getId(), contentA.getId(),
+                                                       contentB.getId()));
+
+                       // Client already has blobs A and B, verify those are not packed.
+                       idx = writeShallowPack(repo, 2, wants(c5), haves(c1, c2),
+                                       shallows(c1));
+                       assertContent(idx,
+                                       Arrays.asList(c4.getId(), c5.getId(), c4.getTree().getId(),
+                                                       c5.getTree().getId(), contentC.getId(),
+                                                       contentD.getId(), contentE.getId()));
+               }
+       }
+
+       @Test
+       public void testShallowFetchShallowParentDepth1() throws Exception {
+               try (FileRepository repo = setupRepoForShallowFetch()) {
+                       PackIndex idx = writeShallowPack(repo, 1, wants(c5), NONE, NONE);
+                       assertContent(idx, Arrays.asList(c5.getId(), c5.getTree().getId(),
+                                       contentA.getId(), contentB.getId(), contentC.getId(),
+                                       contentD.getId(), contentE.getId()));
+
+                       idx = writeShallowPack(repo, 1, wants(c4), haves(c5), shallows(c5));
+                       assertContent(idx, Arrays.asList(c4.getId(), c4.getTree().getId()));
+               }
+       }
+
+       @Test
+       public void testShallowFetchShallowParentDepth2() throws Exception {
+               try (FileRepository repo = setupRepoForShallowFetch()) {
+                       PackIndex idx = writeShallowPack(repo, 2, wants(c5), NONE, NONE);
+                       assertContent(idx,
+                                       Arrays.asList(c4.getId(), c5.getId(), c4.getTree().getId(),
+                                                       c5.getTree().getId(), contentA.getId(),
+                                                       contentB.getId(), contentC.getId(),
+                                                       contentD.getId(), contentE.getId()));
+
+                       idx = writeShallowPack(repo, 2, wants(c3), haves(c4, c5),
+                                       shallows(c4));
+                       assertContent(idx, Arrays.asList(c2.getId(), c3.getId(),
+                                       c2.getTree().getId(), c3.getTree().getId()));
+               }
+       }
+
+       @Test
+       public void testShallowFetchShallowAncestorDepth1() throws Exception {
+               try (FileRepository repo = setupRepoForShallowFetch()) {
+                       PackIndex idx = writeShallowPack(repo, 1, wants(c5), NONE, NONE);
+                       assertContent(idx, Arrays.asList(c5.getId(), c5.getTree().getId(),
+                                       contentA.getId(), contentB.getId(), contentC.getId(),
+                                       contentD.getId(), contentE.getId()));
+
+                       idx = writeShallowPack(repo, 1, wants(c3), haves(c5), shallows(c5));
+                       assertContent(idx, Arrays.asList(c3.getId(), c3.getTree().getId()));
+               }
+       }
+
+       @Test
+       public void testShallowFetchShallowAncestorDepth2() throws Exception {
+               try (FileRepository repo = setupRepoForShallowFetch()) {
+                       PackIndex idx = writeShallowPack(repo, 2, wants(c5), NONE, NONE);
+                       assertContent(idx,
+                                       Arrays.asList(c4.getId(), c5.getId(), c4.getTree().getId(),
+                                                       c5.getTree().getId(), contentA.getId(),
+                                                       contentB.getId(), contentC.getId(),
+                                                       contentD.getId(), contentE.getId()));
+
+                       idx = writeShallowPack(repo, 2, wants(c2), haves(c4, c5),
+                                       shallows(c4));
+                       assertContent(idx, Arrays.asList(c1.getId(), c2.getId(),
+                                       c1.getTree().getId(), c2.getTree().getId()));
+               }
+       }
+
+       @Test
+       public void testTotalPackFilesScanWhenSearchForReuseTimeoutNotSet()
+                       throws Exception {
+               FileRepository fileRepository = setUpRepoWithMultiplePackfiles();
+               PackWriter mockedPackWriter = Mockito
+                               .spy(new PackWriter(config, fileRepository.newObjectReader()));
+
+               doNothing().when(mockedPackWriter).select(any(), any());
+
+               try (FileOutputStream packOS = new FileOutputStream(
+                               getPackFileToWrite(fileRepository, mockedPackWriter))) {
+                       mockedPackWriter.writePack(NullProgressMonitor.INSTANCE,
+                                       NullProgressMonitor.INSTANCE, packOS);
+               }
+
+               long numberOfPackFiles = new GC(fileRepository)
+                               .getStatistics().numberOfPackFiles;
+               int expectedSelectCalls =
+                               // Objects contained in multiple packfiles * number of packfiles
+                               2 * (int) numberOfPackFiles +
+                               // Objects in single packfile
+                                               1;
+               verify(mockedPackWriter, times(expectedSelectCalls)).select(any(),
+                               any());
+       }
+
+       @Test
+       public void testTotalPackFilesScanWhenSkippingSearchForReuseTimeoutCheck()
+                       throws Exception {
+               FileRepository fileRepository = setUpRepoWithMultiplePackfiles();
+               PackConfig packConfig = new PackConfig();
+               packConfig.setSearchForReuseTimeout(Duration.ofSeconds(-1));
+               PackWriter mockedPackWriter = Mockito.spy(
+                               new PackWriter(packConfig, fileRepository.newObjectReader()));
+
+               doNothing().when(mockedPackWriter).select(any(), any());
+
+               try (FileOutputStream packOS = new FileOutputStream(
+                               getPackFileToWrite(fileRepository, mockedPackWriter))) {
+                       mockedPackWriter.writePack(NullProgressMonitor.INSTANCE,
+                                       NullProgressMonitor.INSTANCE, packOS);
+               }
+
+               long numberOfPackFiles = new GC(fileRepository)
+                               .getStatistics().numberOfPackFiles;
+               int expectedSelectCalls =
+                               // Objects contained in multiple packfiles * number of packfiles
+                               2 * (int) numberOfPackFiles +
+                               // Objects contained in single packfile
+                                               1;
+               verify(mockedPackWriter, times(expectedSelectCalls)).select(any(),
+                               any());
+       }
+
+       @Test
+       public void testPartialPackFilesScanWhenDoingSearchForReuseTimeoutCheck()
+                       throws Exception {
+               FileRepository fileRepository = setUpRepoWithMultiplePackfiles();
+               PackConfig packConfig = new PackConfig();
+               packConfig.setSearchForReuseTimeout(Duration.ofSeconds(-1));
+               PackWriter mockedPackWriter = Mockito.spy(
+                               new PackWriter(packConfig, fileRepository.newObjectReader()));
+               mockedPackWriter.enableSearchForReuseTimeout();
+
+               doNothing().when(mockedPackWriter).select(any(), any());
+
+               try (FileOutputStream packOS = new FileOutputStream(
+                               getPackFileToWrite(fileRepository, mockedPackWriter))) {
+                       mockedPackWriter.writePack(NullProgressMonitor.INSTANCE,
+                                       NullProgressMonitor.INSTANCE, packOS);
+               }
+
+               int expectedSelectCalls = 3; // Objects in packfiles
+               verify(mockedPackWriter, times(expectedSelectCalls)).select(any(),
+                               any());
+       }
+
+       /**
+        * Creates objects and packfiles in the following order:
+        * <ul>
+        * <li>Creates 2 objects (C1 = commit, T1 = tree)
+        * <li>Creates packfile P1 (containing C1, T1)
+        * <li>Creates 1 object (C2 commit)
+        * <li>Creates packfile P2 (containing C1, T1, C2)
+        * <li>Create 1 object (C3 commit)
+        * </ul>
+        *
+        * @throws Exception
+        */
+       private FileRepository setUpRepoWithMultiplePackfiles() throws Exception {
+               FileRepository fileRepository = createWorkRepository();
+               addRepoToClose(fileRepository);
+               try (Git git = new Git(fileRepository)) {
+                       // Creates 2 objects (C1 = commit, T1 = tree)
+                       git.commit().setMessage("First commit").call();
+                       GC gc = new GC(fileRepository);
+                       gc.setPackExpireAgeMillis(Long.MAX_VALUE);
+                       gc.setExpireAgeMillis(Long.MAX_VALUE);
+                       // Creates packfile P1 (containing C1, T1)
+                       gc.gc().get();
+                       // Creates 1 object (C2 commit)
+                       git.commit().setMessage("Second commit").call();
+                       // Creates packfile P2 (containing C1, T1, C2)
+                       gc.gc().get();
+                       // Create 1 object (C3 commit)
+                       git.commit().setMessage("Third commit").call();
+               }
+               return fileRepository;
+       }
+
+       private PackFile getPackFileToWrite(FileRepository fileRepository,
+                       PackWriter mockedPackWriter) throws IOException {
+               File packdir = fileRepository.getObjectDatabase().getPackDirectory();
+               PackFile packFile = new PackFile(packdir,
+                               mockedPackWriter.computeName(), PackExt.PACK);
+
+               Set<ObjectId> all = new HashSet<>();
+               for (Ref r : fileRepository.getRefDatabase().getRefs()) {
+                       all.add(r.getObjectId());
+               }
+
+               mockedPackWriter.preparePack(NullProgressMonitor.INSTANCE, all,
+                               PackWriter.NONE);
+               return packFile;
+       }
+
+       private FileRepository setupRepoForShallowFetch() throws Exception {
+               FileRepository repo = createBareRepository();
+               // TestRepository will close the repo, but we need to return an open
+               // one!
+               repo.incrementOpen();
+               try (TestRepository<Repository> r = new TestRepository<>(repo)) {
+                       BranchBuilder bb = r.branch("refs/heads/master");
+                       contentA = r.blob("A");
+                       contentB = r.blob("B");
+                       contentC = r.blob("C");
+                       contentD = r.blob("D");
+                       contentE = r.blob("E");
+                       c1 = bb.commit().add("a", contentA).create();
+                       c2 = bb.commit().add("b", contentB).create();
+                       c3 = bb.commit().add("c", contentC).create();
+                       c4 = bb.commit().add("d", contentD).create();
+                       c5 = bb.commit().add("e", contentE).create();
+                       r.getRevWalk().parseHeaders(c5); // fully initialize the tip RevCommit
+                       return repo;
+               }
+       }
+
+       private static PackIndex writePack(FileRepository repo,
+                       Set<? extends ObjectId> want, Set<ObjectIdSet> excludeObjects)
+                                       throws IOException {
+               try (RevWalk walk = new RevWalk(repo)) {
+                       return writePack(repo, walk, 0, want, NONE, excludeObjects);
+               }
+       }
+
+       private static PackIndex writeShallowPack(FileRepository repo, int depth,
+                       Set<? extends ObjectId> want, Set<? extends ObjectId> have,
+                       Set<? extends ObjectId> shallow) throws IOException {
+               // During negotiation, UploadPack would have set up a DepthWalk and
+               // marked the client's "shallow" commits. Emulate that here.
+               try (DepthWalk.RevWalk walk = new DepthWalk.RevWalk(repo, depth - 1)) {
+                       walk.assumeShallow(shallow);
+                       return writePack(repo, walk, depth, want, have, EMPTY_ID_SET);
+               }
+       }
+
+       private static PackIndex writePack(FileRepository repo, RevWalk walk,
+                       int depth, Set<? extends ObjectId> want,
+                       Set<? extends ObjectId> have, Set<ObjectIdSet> excludeObjects)
+                                       throws IOException {
+               try (PackWriter pw = new PackWriter(repo)) {
+                       pw.setDeltaBaseAsOffset(true);
+                       pw.setReuseDeltaCommits(false);
+                       for (ObjectIdSet idx : excludeObjects) {
+                               pw.excludeObjects(idx);
+                       }
+                       if (depth > 0) {
+                               pw.setShallowPack(depth, null);
+                       }
+                       // ow doesn't need to be closed; caller closes walk.
+                       ObjectWalk ow = walk.toObjectWalkWithSameObjects();
+
+                       pw.preparePack(NullProgressMonitor.INSTANCE, ow, want, have, NONE);
+                       File packdir = repo.getObjectDatabase().getPackDirectory();
+                       PackFile packFile = new PackFile(packdir, pw.computeName(),
+                                       PackExt.PACK);
+                       try (FileOutputStream packOS = new FileOutputStream(packFile)) {
+                               pw.writePack(NullProgressMonitor.INSTANCE,
+                                               NullProgressMonitor.INSTANCE, packOS);
+                       }
+                       PackFile idxFile = packFile.create(PackExt.INDEX);
+                       try (FileOutputStream idxOS = new FileOutputStream(idxFile)) {
+                               pw.writeIndex(idxOS);
+                       }
+                       return PackIndex.open(idxFile);
+               }
+       }
+
+       // TODO: testWritePackDeltasCycle()
+       // TODO: testWritePackDeltasDepth()
+
+       private void writeVerifyPack1() throws IOException {
+               final HashSet<ObjectId> interestings = new HashSet<>();
+               interestings.add(ObjectId
+                               .fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"));
+               createVerifyOpenPack(interestings, NONE, false, false);
+
+               final ObjectId expectedOrder[] = new ObjectId[] {
+                               ObjectId.fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"),
+                               ObjectId.fromString("c59759f143fb1fe21c197981df75a7ee00290799"),
+                               ObjectId.fromString("540a36d136cf413e4b064c2b0e0a4db60f77feab"),
+                               ObjectId.fromString("aabf2ffaec9b497f0950352b3e582d73035c2035"),
+                               ObjectId.fromString("902d5476fa249b7abc9d84c611577a81381f0327"),
+                               ObjectId.fromString("4b825dc642cb6eb9a060e54bf8d69288fbee4904"),
+                               ObjectId.fromString("6ff87c4664981e4397625791c8ea3bbb5f2279a3"),
+                               ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259") };
+
+               assertEquals(expectedOrder.length, writer.getObjectCount());
+               verifyObjectsOrder(expectedOrder);
+               assertEquals("34be9032ac282b11fa9babdc2b2a93ca996c9c2f", writer
+                               .computeName().name());
+       }
+
+       private void writeVerifyPack2(boolean deltaReuse) throws IOException {
+               config.setReuseDeltas(deltaReuse);
+               final HashSet<ObjectId> interestings = new HashSet<>();
+               interestings.add(ObjectId
+                               .fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"));
+               final HashSet<ObjectId> uninterestings = new HashSet<>();
+               uninterestings.add(ObjectId
+                               .fromString("540a36d136cf413e4b064c2b0e0a4db60f77feab"));
+               createVerifyOpenPack(interestings, uninterestings, false, false);
+
+               final ObjectId expectedOrder[] = new ObjectId[] {
+                               ObjectId.fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"),
+                               ObjectId.fromString("c59759f143fb1fe21c197981df75a7ee00290799"),
+                               ObjectId.fromString("aabf2ffaec9b497f0950352b3e582d73035c2035"),
+                               ObjectId.fromString("902d5476fa249b7abc9d84c611577a81381f0327"),
+                               ObjectId.fromString("6ff87c4664981e4397625791c8ea3bbb5f2279a3") ,
+                               ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259") };
+               if (!config.isReuseDeltas() && !config.isDeltaCompress()) {
+                       // If no deltas are in the file the final two entries swap places.
+                       swap(expectedOrder, 4, 5);
+               }
+               assertEquals(expectedOrder.length, writer.getObjectCount());
+               verifyObjectsOrder(expectedOrder);
+               assertEquals("ed3f96b8327c7c66b0f8f70056129f0769323d86", writer
+                               .computeName().name());
+       }
+
+       private static void swap(ObjectId[] arr, int a, int b) {
+               ObjectId tmp = arr[a];
+               arr[a] = arr[b];
+               arr[b] = tmp;
+       }
+
+       private void writeVerifyPack4(final boolean thin) throws IOException {
+               final HashSet<ObjectId> interestings = new HashSet<>();
+               interestings.add(ObjectId
+                               .fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"));
+               final HashSet<ObjectId> uninterestings = new HashSet<>();
+               uninterestings.add(ObjectId
+                               .fromString("c59759f143fb1fe21c197981df75a7ee00290799"));
+               createVerifyOpenPack(interestings, uninterestings, thin, false);
+
+               final ObjectId writtenObjects[] = new ObjectId[] {
+                               ObjectId.fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"),
+                               ObjectId.fromString("aabf2ffaec9b497f0950352b3e582d73035c2035"),
+                               ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259") };
+               assertEquals(writtenObjects.length, writer.getObjectCount());
+               ObjectId expectedObjects[];
+               if (thin) {
+                       expectedObjects = new ObjectId[4];
+                       System.arraycopy(writtenObjects, 0, expectedObjects, 0,
+                                       writtenObjects.length);
+                       expectedObjects[3] = ObjectId
+                                       .fromString("6ff87c4664981e4397625791c8ea3bbb5f2279a3");
+
+               } else {
+                       expectedObjects = writtenObjects;
+               }
+               verifyObjectsOrder(expectedObjects);
+               assertEquals("cded4b74176b4456afa456768b2b5aafb41c44fc", writer
+                               .computeName().name());
+       }
+
+       private void createVerifyOpenPack(final Set<ObjectId> interestings,
+                       final Set<ObjectId> uninterestings, final boolean thin,
+                       final boolean ignoreMissingUninteresting)
+                       throws MissingObjectException, IOException {
+               createVerifyOpenPack(interestings, uninterestings, thin,
+                               ignoreMissingUninteresting, false);
+       }
+
+       private void createVerifyOpenPack(final Set<ObjectId> interestings,
+                       final Set<ObjectId> uninterestings, final boolean thin,
+                       final boolean ignoreMissingUninteresting, boolean useBitmaps)
+                       throws MissingObjectException, IOException {
+               NullProgressMonitor m = NullProgressMonitor.INSTANCE;
+               writer = new PackWriter(config, db.newObjectReader());
+               writer.setUseBitmaps(useBitmaps);
+               writer.setThin(thin);
+               writer.setIgnoreMissingUninteresting(ignoreMissingUninteresting);
+               writer.preparePack(m, interestings, uninterestings);
+               writer.writePack(m, m, os);
+               writer.close();
+               verifyOpenPack(thin);
+       }
+
+       private void createVerifyOpenPack(List<RevObject> objectSource)
+                       throws MissingObjectException, IOException {
+               NullProgressMonitor m = NullProgressMonitor.INSTANCE;
+               writer = new PackWriter(config, db.newObjectReader());
+               writer.preparePack(objectSource.iterator());
+               assertEquals(objectSource.size(), writer.getObjectCount());
+               writer.writePack(m, m, os);
+               writer.close();
+               verifyOpenPack(false);
+       }
+
+       private void verifyOpenPack(boolean thin) throws IOException {
+               final byte[] packData = os.toByteArray();
+
+               if (thin) {
+                       PackParser p = index(packData);
+                       try {
+                               p.parse(NullProgressMonitor.INSTANCE);
+                               fail("indexer should grumble about missing object");
+                       } catch (IOException x) {
+                               // expected
+                       }
+               }
+
+               ObjectDirectoryPackParser p = (ObjectDirectoryPackParser) index(packData);
+               p.setKeepEmpty(true);
+               p.setAllowThin(thin);
+               p.setIndexVersion(2);
+               p.parse(NullProgressMonitor.INSTANCE);
+               pack = p.getPack();
+               assertNotNull("have PackFile after parsing", pack);
+       }
+
+       private PackParser index(byte[] packData) throws IOException {
+               if (inserter == null)
+                       inserter = dst.newObjectInserter();
+               return inserter.newPackParser(new ByteArrayInputStream(packData));
+       }
+
+       private void verifyObjectsOrder(ObjectId objectsOrder[]) {
+               final List<PackIndex.MutableEntry> entries = new ArrayList<>();
+
+               for (MutableEntry me : pack) {
+                       entries.add(me.cloneEntry());
+               }
+               Collections.sort(entries, (MutableEntry o1, MutableEntry o2) -> Long
+                               .signum(o1.getOffset() - o2.getOffset()));
+
+               int i = 0;
+               for (MutableEntry me : entries) {
+                       assertEquals(objectsOrder[i++].toObjectId(), me.toObjectId());
+               }
+       }
+
+       private static Set<ObjectId> haves(ObjectId... objects) {
+               return Sets.of(objects);
+       }
+
+       private static Set<ObjectId> wants(ObjectId... objects) {
+               return Sets.of(objects);
+       }
+
+       private static Set<ObjectId> shallows(ObjectId... objects) {
+               return Sets.of(objects);
+       }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/PackWriterTest.java
deleted file mode 100644 (file)
index 24a81b6..0000000
+++ /dev/null
@@ -1,1079 +0,0 @@
-/*
- * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com> and others
- *
- * 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.file;
-
-import static org.eclipse.jgit.internal.storage.pack.PackWriter.NONE;
-import static org.eclipse.jgit.lib.Constants.INFO_ALTERNATES;
-import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.time.Duration;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import org.eclipse.jgit.api.Git;
-import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry;
-import org.eclipse.jgit.internal.storage.pack.PackExt;
-import org.eclipse.jgit.internal.storage.pack.PackWriter;
-import org.eclipse.jgit.junit.JGitTestUtil;
-import org.eclipse.jgit.junit.TestRepository;
-import org.eclipse.jgit.junit.TestRepository.BranchBuilder;
-import org.eclipse.jgit.lib.NullProgressMonitor;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectIdSet;
-import org.eclipse.jgit.lib.ObjectInserter;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.lib.Sets;
-import org.eclipse.jgit.revwalk.DepthWalk;
-import org.eclipse.jgit.revwalk.ObjectWalk;
-import org.eclipse.jgit.revwalk.RevBlob;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevObject;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.storage.pack.PackConfig;
-import org.eclipse.jgit.storage.pack.PackStatistics;
-import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
-import org.eclipse.jgit.transport.PackParser;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.Mockito;
-
-public class PackWriterTest extends SampleDataRepositoryTestCase {
-
-       private static final List<RevObject> EMPTY_LIST_REVS = Collections
-                       .<RevObject> emptyList();
-
-       private static final Set<ObjectIdSet> EMPTY_ID_SET = Collections
-                       .<ObjectIdSet> emptySet();
-
-       private PackConfig config;
-
-       private PackWriter writer;
-
-       private ByteArrayOutputStream os;
-
-       private Pack pack;
-
-       private ObjectInserter inserter;
-
-       private FileRepository dst;
-
-       private RevBlob contentA;
-
-       private RevBlob contentB;
-
-       private RevBlob contentC;
-
-       private RevBlob contentD;
-
-       private RevBlob contentE;
-
-       private RevCommit c1;
-
-       private RevCommit c2;
-
-       private RevCommit c3;
-
-       private RevCommit c4;
-
-       private RevCommit c5;
-
-       @Override
-       @Before
-       public void setUp() throws Exception {
-               super.setUp();
-               os = new ByteArrayOutputStream();
-               config = new PackConfig(db);
-
-               dst = createBareRepository();
-               File alt = new File(dst.getObjectDatabase().getDirectory(), INFO_ALTERNATES);
-               alt.getParentFile().mkdirs();
-               write(alt, db.getObjectDatabase().getDirectory().getAbsolutePath() + "\n");
-       }
-
-       @Override
-       @After
-       public void tearDown() throws Exception {
-               if (writer != null) {
-                       writer.close();
-                       writer = null;
-               }
-               if (inserter != null) {
-                       inserter.close();
-                       inserter = null;
-               }
-               super.tearDown();
-       }
-
-       /**
-        * Test constructor for exceptions, default settings, initialization.
-        *
-        * @throws IOException
-        */
-       @Test
-       public void testContructor() throws IOException {
-               writer = new PackWriter(config, db.newObjectReader());
-               assertFalse(writer.isDeltaBaseAsOffset());
-               assertTrue(config.isReuseDeltas());
-               assertTrue(config.isReuseObjects());
-               assertEquals(0, writer.getObjectCount());
-       }
-
-       /**
-        * Change default settings and verify them.
-        */
-       @Test
-       public void testModifySettings() {
-               config.setReuseDeltas(false);
-               config.setReuseObjects(false);
-               config.setDeltaBaseAsOffset(false);
-               assertFalse(config.isReuseDeltas());
-               assertFalse(config.isReuseObjects());
-               assertFalse(config.isDeltaBaseAsOffset());
-
-               writer = new PackWriter(config, db.newObjectReader());
-               writer.setDeltaBaseAsOffset(true);
-               assertTrue(writer.isDeltaBaseAsOffset());
-               assertFalse(config.isDeltaBaseAsOffset());
-       }
-
-       /**
-        * Write empty pack by providing empty sets of interesting/uninteresting
-        * objects and check for correct format.
-        *
-        * @throws IOException
-        */
-       @Test
-       public void testWriteEmptyPack1() throws IOException {
-               createVerifyOpenPack(NONE, NONE, false, false);
-
-               assertEquals(0, writer.getObjectCount());
-               assertEquals(0, pack.getObjectCount());
-               assertEquals("da39a3ee5e6b4b0d3255bfef95601890afd80709", writer
-                               .computeName().name());
-       }
-
-       /**
-        * Write empty pack by providing empty iterator of objects to write and
-        * check for correct format.
-        *
-        * @throws IOException
-        */
-       @Test
-       public void testWriteEmptyPack2() throws IOException {
-               createVerifyOpenPack(EMPTY_LIST_REVS);
-
-               assertEquals(0, writer.getObjectCount());
-               assertEquals(0, pack.getObjectCount());
-       }
-
-       /**
-        * Try to pass non-existing object as uninteresting, with non-ignoring
-        * setting.
-        *
-        * @throws IOException
-        */
-       @Test
-       public void testNotIgnoreNonExistingObjects() throws IOException {
-               final ObjectId nonExisting = ObjectId
-                               .fromString("0000000000000000000000000000000000000001");
-               try {
-                       createVerifyOpenPack(NONE, haves(nonExisting), false, false);
-                       fail("Should have thrown MissingObjectException");
-               } catch (MissingObjectException x) {
-                       // expected
-               }
-       }
-
-       /**
-        * Try to pass non-existing object as uninteresting, with ignoring setting.
-        *
-        * @throws IOException
-        */
-       @Test
-       public void testIgnoreNonExistingObjects() throws IOException {
-               final ObjectId nonExisting = ObjectId
-                               .fromString("0000000000000000000000000000000000000001");
-               createVerifyOpenPack(NONE, haves(nonExisting), false, true);
-               // shouldn't throw anything
-       }
-
-       /**
-        * Try to pass non-existing object as uninteresting, with ignoring setting.
-        * Use a repo with bitmap indexes because then PackWriter will use
-        * PackWriterBitmapWalker which had problems with this situation.
-        *
-        * @throws Exception
-        */
-       @Test
-       public void testIgnoreNonExistingObjectsWithBitmaps() throws Exception {
-               final ObjectId nonExisting = ObjectId
-                               .fromString("0000000000000000000000000000000000000001");
-               new GC(db).gc().get();
-               createVerifyOpenPack(NONE, haves(nonExisting), false, true, true);
-               // shouldn't throw anything
-       }
-
-       /**
-        * Create pack basing on only interesting objects, then precisely verify
-        * content. No delta reuse here.
-        *
-        * @throws IOException
-        */
-       @Test
-       public void testWritePack1() throws IOException {
-               config.setReuseDeltas(false);
-               writeVerifyPack1();
-       }
-
-       /**
-        * Test writing pack without object reuse. Pack content/preparation as in
-        * {@link #testWritePack1()}.
-        *
-        * @throws IOException
-        */
-       @Test
-       public void testWritePack1NoObjectReuse() throws IOException {
-               config.setReuseDeltas(false);
-               config.setReuseObjects(false);
-               writeVerifyPack1();
-       }
-
-       /**
-        * Create pack basing on both interesting and uninteresting objects, then
-        * precisely verify content. No delta reuse here.
-        *
-        * @throws IOException
-        */
-       @Test
-       public void testWritePack2() throws IOException {
-               writeVerifyPack2(false);
-       }
-
-       /**
-        * Test pack writing with deltas reuse, delta-base first rule. Pack
-        * content/preparation as in {@link #testWritePack2()}.
-        *
-        * @throws IOException
-        */
-       @Test
-       public void testWritePack2DeltasReuseRefs() throws IOException {
-               writeVerifyPack2(true);
-       }
-
-       /**
-        * Test pack writing with delta reuse. Delta bases referred as offsets. Pack
-        * configuration as in {@link #testWritePack2DeltasReuseRefs()}.
-        *
-        * @throws IOException
-        */
-       @Test
-       public void testWritePack2DeltasReuseOffsets() throws IOException {
-               config.setDeltaBaseAsOffset(true);
-               writeVerifyPack2(true);
-       }
-
-       /**
-        * Test pack writing with delta reuse. Raw-data copy (reuse) is made on a
-        * pack with CRC32 index. Pack configuration as in
-        * {@link #testWritePack2DeltasReuseRefs()}.
-        *
-        * @throws IOException
-        */
-       @Test
-       public void testWritePack2DeltasCRC32Copy() throws IOException {
-               final File packDir = db.getObjectDatabase().getPackDirectory();
-               final PackFile crc32Pack = new PackFile(packDir,
-                               "pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f.pack");
-               final PackFile crc32Idx = new PackFile(packDir,
-                               "pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f.idx");
-               copyFile(JGitTestUtil.getTestResourceFile(
-                               "pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f.idxV2"),
-                               crc32Idx);
-               db.openPack(crc32Pack);
-
-               writeVerifyPack2(true);
-       }
-
-       /**
-        * Create pack basing on fixed objects list, then precisely verify content.
-        * No delta reuse here.
-        *
-        * @throws IOException
-        * @throws MissingObjectException
-        *
-        */
-       @Test
-       public void testWritePack3() throws MissingObjectException, IOException {
-               config.setReuseDeltas(false);
-               final ObjectId forcedOrder[] = new ObjectId[] {
-                               ObjectId.fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"),
-                               ObjectId.fromString("c59759f143fb1fe21c197981df75a7ee00290799"),
-                               ObjectId.fromString("aabf2ffaec9b497f0950352b3e582d73035c2035"),
-                               ObjectId.fromString("902d5476fa249b7abc9d84c611577a81381f0327"),
-                               ObjectId.fromString("6ff87c4664981e4397625791c8ea3bbb5f2279a3") ,
-                               ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259") };
-               try (RevWalk parser = new RevWalk(db)) {
-                       final RevObject forcedOrderRevs[] = new RevObject[forcedOrder.length];
-                       for (int i = 0; i < forcedOrder.length; i++)
-                               forcedOrderRevs[i] = parser.parseAny(forcedOrder[i]);
-
-                       createVerifyOpenPack(Arrays.asList(forcedOrderRevs));
-               }
-
-               assertEquals(forcedOrder.length, writer.getObjectCount());
-               verifyObjectsOrder(forcedOrder);
-               assertEquals("ed3f96b8327c7c66b0f8f70056129f0769323d86", writer
-                               .computeName().name());
-       }
-
-       /**
-        * Another pack creation: basing on both interesting and uninteresting
-        * objects. No delta reuse possible here, as this is a specific case when we
-        * write only 1 commit, associated with 1 tree, 1 blob.
-        *
-        * @throws IOException
-        */
-       @Test
-       public void testWritePack4() throws IOException {
-               writeVerifyPack4(false);
-       }
-
-       /**
-        * Test thin pack writing: 1 blob delta base is on objects edge. Pack
-        * configuration as in {@link #testWritePack4()}.
-        *
-        * @throws IOException
-        */
-       @Test
-       public void testWritePack4ThinPack() throws IOException {
-               writeVerifyPack4(true);
-       }
-
-       /**
-        * Compare sizes of packs created using {@link #testWritePack2()} and
-        * {@link #testWritePack2DeltasReuseRefs()}. The pack using deltas should
-        * be smaller.
-        *
-        * @throws Exception
-        */
-       @Test
-       public void testWritePack2SizeDeltasVsNoDeltas() throws Exception {
-               config.setReuseDeltas(false);
-               config.setDeltaCompress(false);
-               testWritePack2();
-               final long sizePack2NoDeltas = os.size();
-               tearDown();
-               setUp();
-               testWritePack2DeltasReuseRefs();
-               final long sizePack2DeltasRefs = os.size();
-
-               assertTrue(sizePack2NoDeltas > sizePack2DeltasRefs);
-       }
-
-       /**
-        * Compare sizes of packs created using
-        * {@link #testWritePack2DeltasReuseRefs()} and
-        * {@link #testWritePack2DeltasReuseOffsets()}. The pack with delta bases
-        * written as offsets should be smaller.
-        *
-        * @throws Exception
-        */
-       @Test
-       public void testWritePack2SizeOffsetsVsRefs() throws Exception {
-               testWritePack2DeltasReuseRefs();
-               final long sizePack2DeltasRefs = os.size();
-               tearDown();
-               setUp();
-               testWritePack2DeltasReuseOffsets();
-               final long sizePack2DeltasOffsets = os.size();
-
-               assertTrue(sizePack2DeltasRefs > sizePack2DeltasOffsets);
-       }
-
-       /**
-        * Compare sizes of packs created using {@link #testWritePack4()} and
-        * {@link #testWritePack4ThinPack()}. Obviously, the thin pack should be
-        * smaller.
-        *
-        * @throws Exception
-        */
-       @Test
-       public void testWritePack4SizeThinVsNoThin() throws Exception {
-               testWritePack4();
-               final long sizePack4 = os.size();
-               tearDown();
-               setUp();
-               testWritePack4ThinPack();
-               final long sizePack4Thin = os.size();
-
-               assertTrue(sizePack4 > sizePack4Thin);
-       }
-
-       @Test
-       public void testDeltaStatistics() throws Exception {
-               config.setDeltaCompress(true);
-               // TestRepository will close repo
-               FileRepository repo = createBareRepository();
-               ArrayList<RevObject> blobs = new ArrayList<>();
-               try (TestRepository<FileRepository> testRepo = new TestRepository<>(
-                               repo)) {
-                       blobs.add(testRepo.blob(genDeltableData(1000)));
-                       blobs.add(testRepo.blob(genDeltableData(1005)));
-                       try (PackWriter pw = new PackWriter(repo)) {
-                               NullProgressMonitor m = NullProgressMonitor.INSTANCE;
-                               pw.preparePack(blobs.iterator());
-                               pw.writePack(m, m, os);
-                               PackStatistics stats = pw.getStatistics();
-                               assertEquals(1, stats.getTotalDeltas());
-                               assertTrue("Delta bytes not set.",
-                                               stats.byObjectType(OBJ_BLOB).getDeltaBytes() > 0);
-                       }
-               }
-       }
-
-       // Generate consistent junk data for building files that delta well
-       private String genDeltableData(int length) {
-               assertTrue("Generated data must have a length > 0", length > 0);
-               char[] data = {'a', 'b', 'c', '\n'};
-               StringBuilder builder = new StringBuilder(length);
-               for (int i = 0; i < length; i++) {
-                       builder.append(data[i % 4]);
-               }
-               return builder.toString();
-       }
-
-
-       @Test
-       public void testWriteIndex() throws Exception {
-               config.setIndexVersion(2);
-               writeVerifyPack4(false);
-
-               PackFile packFile = pack.getPackFile();
-               PackFile indexFile = packFile.create(PackExt.INDEX);
-
-               // Validate that IndexPack came up with the right CRC32 value.
-               final PackIndex idx1 = PackIndex.open(indexFile);
-               assertTrue(idx1 instanceof PackIndexV2);
-               assertEquals(0x4743F1E4L, idx1.findCRC32(ObjectId
-                               .fromString("82c6b885ff600be425b4ea96dee75dca255b69e7")));
-
-               // Validate that an index written by PackWriter is the same.
-               final File idx2File = new File(indexFile.getAbsolutePath() + ".2");
-               try (FileOutputStream is = new FileOutputStream(idx2File)) {
-                       writer.writeIndex(is);
-               }
-               final PackIndex idx2 = PackIndex.open(idx2File);
-               assertTrue(idx2 instanceof PackIndexV2);
-               assertEquals(idx1.getObjectCount(), idx2.getObjectCount());
-               assertEquals(idx1.getOffset64Count(), idx2.getOffset64Count());
-
-               for (int i = 0; i < idx1.getObjectCount(); i++) {
-                       final ObjectId id = idx1.getObjectId(i);
-                       assertEquals(id, idx2.getObjectId(i));
-                       assertEquals(idx1.findOffset(id), idx2.findOffset(id));
-                       assertEquals(idx1.findCRC32(id), idx2.findCRC32(id));
-               }
-       }
-
-       @Test
-       public void testWriteObjectSizeIndex_noDeltas() throws Exception {
-               config.setMinBytesForObjSizeIndex(0);
-               HashSet<ObjectId> interesting = new HashSet<>();
-               interesting.add(ObjectId
-                               .fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"));
-
-               NullProgressMonitor m1 = NullProgressMonitor.INSTANCE;
-               writer = new PackWriter(config, db.newObjectReader());
-               writer.setUseBitmaps(false);
-               writer.setThin(false);
-               writer.setIgnoreMissingUninteresting(false);
-               writer.preparePack(m1, interesting, NONE);
-               writer.writePack(m1, m1, os);
-
-               PackIndex idx;
-               try (ByteArrayOutputStream is = new ByteArrayOutputStream()) {
-                       writer.writeIndex(is);
-                       idx = PackIndex.read(new ByteArrayInputStream(is.toByteArray()));
-               }
-
-               PackObjectSizeIndex objSizeIdx;
-               try (ByteArrayOutputStream objSizeStream = new ByteArrayOutputStream()) {
-                       writer.writeObjectSizeIndex(objSizeStream);
-                       objSizeIdx = PackObjectSizeIndexLoader.load(
-                                       new ByteArrayInputStream(objSizeStream.toByteArray()));
-               }
-               writer.close();
-
-               ObjectId knownBlob1 = ObjectId
-                               .fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259");
-               ObjectId knownBlob2 = ObjectId
-                               .fromString("6ff87c4664981e4397625791c8ea3bbb5f2279a3");
-               assertEquals(18009, objSizeIdx.getSize(idx.findPosition(knownBlob1)));
-               assertEquals(18787, objSizeIdx.getSize(idx.findPosition(knownBlob2)));
-       }
-
-       @Test
-       public void testWriteReverseIndexConfig() {
-               assertFalse(config.isWriteReverseIndex());
-               config.setWriteReverseIndex(true);
-               assertTrue(config.isWriteReverseIndex());
-       }
-
-       @Test
-       public void testWriteReverseIndexOff() throws Exception {
-               config.setWriteReverseIndex(false);
-               writer = new PackWriter(config, db.newObjectReader());
-               ByteArrayOutputStream reverseIndexOutput = new ByteArrayOutputStream();
-
-               writer.writeReverseIndex(reverseIndexOutput);
-
-               assertEquals(0, reverseIndexOutput.size());
-       }
-
-       @Test
-       public void testWriteReverseIndexOn() throws Exception {
-               config.setWriteReverseIndex(true);
-               writeVerifyPack4(false);
-               ByteArrayOutputStream reverseIndexOutput = new ByteArrayOutputStream();
-               int headerBytes = 12;
-               int bodyBytes = 12;
-               int footerBytes = 40;
-
-               writer.writeReverseIndex(reverseIndexOutput);
-
-               assertTrue(reverseIndexOutput.size() == headerBytes + bodyBytes
-                               + footerBytes);
-       }
-
-       @Test
-       public void testExclude() throws Exception {
-               // TestRepository closes repo
-               FileRepository repo = createBareRepository();
-
-               try (TestRepository<FileRepository> testRepo = new TestRepository<>(
-                               repo)) {
-                       BranchBuilder bb = testRepo.branch("refs/heads/master");
-                       contentA = testRepo.blob("A");
-                       c1 = bb.commit().add("f", contentA).create();
-                       testRepo.getRevWalk().parseHeaders(c1);
-                       PackIndex pf1 = writePack(repo, wants(c1), EMPTY_ID_SET);
-                       assertContent(pf1, Arrays.asList(c1.getId(), c1.getTree().getId(),
-                                       contentA.getId()));
-                       contentB = testRepo.blob("B");
-                       c2 = bb.commit().add("f", contentB).create();
-                       testRepo.getRevWalk().parseHeaders(c2);
-                       PackIndex pf2 = writePack(repo, wants(c2),
-                                       Sets.of((ObjectIdSet) pf1));
-                       assertContent(pf2, Arrays.asList(c2.getId(), c2.getTree().getId(),
-                                       contentB.getId()));
-               }
-       }
-
-       private static void assertContent(PackIndex pi, List<ObjectId> expected) {
-               assertEquals("Pack index has wrong size.", expected.size(),
-                               pi.getObjectCount());
-               for (int i = 0; i < pi.getObjectCount(); i++)
-                       assertTrue(
-                                       "Pack index didn't contain the expected id "
-                                                       + pi.getObjectId(i),
-                                       expected.contains(pi.getObjectId(i)));
-       }
-
-       @Test
-       public void testShallowIsMinimalDepth1() throws Exception {
-               try (FileRepository repo = setupRepoForShallowFetch()) {
-                       PackIndex idx = writeShallowPack(repo, 1, wants(c2), NONE, NONE);
-                       assertContent(idx, Arrays.asList(c2.getId(), c2.getTree().getId(),
-                                       contentA.getId(), contentB.getId()));
-
-                       // Client already has blobs A and B, verify those are not packed.
-                       idx = writeShallowPack(repo, 1, wants(c5), haves(c2), shallows(c2));
-                       assertContent(idx, Arrays.asList(c5.getId(), c5.getTree().getId(),
-                                       contentC.getId(), contentD.getId(), contentE.getId()));
-               }
-       }
-
-       @Test
-       public void testShallowIsMinimalDepth2() throws Exception {
-               try (FileRepository repo = setupRepoForShallowFetch()) {
-                       PackIndex idx = writeShallowPack(repo, 2, wants(c2), NONE, NONE);
-                       assertContent(idx,
-                                       Arrays.asList(c1.getId(), c2.getId(), c1.getTree().getId(),
-                                                       c2.getTree().getId(), contentA.getId(),
-                                                       contentB.getId()));
-
-                       // Client already has blobs A and B, verify those are not packed.
-                       idx = writeShallowPack(repo, 2, wants(c5), haves(c1, c2),
-                                       shallows(c1));
-                       assertContent(idx,
-                                       Arrays.asList(c4.getId(), c5.getId(), c4.getTree().getId(),
-                                                       c5.getTree().getId(), contentC.getId(),
-                                                       contentD.getId(), contentE.getId()));
-               }
-       }
-
-       @Test
-       public void testShallowFetchShallowParentDepth1() throws Exception {
-               try (FileRepository repo = setupRepoForShallowFetch()) {
-                       PackIndex idx = writeShallowPack(repo, 1, wants(c5), NONE, NONE);
-                       assertContent(idx, Arrays.asList(c5.getId(), c5.getTree().getId(),
-                                       contentA.getId(), contentB.getId(), contentC.getId(),
-                                       contentD.getId(), contentE.getId()));
-
-                       idx = writeShallowPack(repo, 1, wants(c4), haves(c5), shallows(c5));
-                       assertContent(idx, Arrays.asList(c4.getId(), c4.getTree().getId()));
-               }
-       }
-
-       @Test
-       public void testShallowFetchShallowParentDepth2() throws Exception {
-               try (FileRepository repo = setupRepoForShallowFetch()) {
-                       PackIndex idx = writeShallowPack(repo, 2, wants(c5), NONE, NONE);
-                       assertContent(idx,
-                                       Arrays.asList(c4.getId(), c5.getId(), c4.getTree().getId(),
-                                                       c5.getTree().getId(), contentA.getId(),
-                                                       contentB.getId(), contentC.getId(),
-                                                       contentD.getId(), contentE.getId()));
-
-                       idx = writeShallowPack(repo, 2, wants(c3), haves(c4, c5),
-                                       shallows(c4));
-                       assertContent(idx, Arrays.asList(c2.getId(), c3.getId(),
-                                       c2.getTree().getId(), c3.getTree().getId()));
-               }
-       }
-
-       @Test
-       public void testShallowFetchShallowAncestorDepth1() throws Exception {
-               try (FileRepository repo = setupRepoForShallowFetch()) {
-                       PackIndex idx = writeShallowPack(repo, 1, wants(c5), NONE, NONE);
-                       assertContent(idx, Arrays.asList(c5.getId(), c5.getTree().getId(),
-                                       contentA.getId(), contentB.getId(), contentC.getId(),
-                                       contentD.getId(), contentE.getId()));
-
-                       idx = writeShallowPack(repo, 1, wants(c3), haves(c5), shallows(c5));
-                       assertContent(idx, Arrays.asList(c3.getId(), c3.getTree().getId()));
-               }
-       }
-
-       @Test
-       public void testShallowFetchShallowAncestorDepth2() throws Exception {
-               try (FileRepository repo = setupRepoForShallowFetch()) {
-                       PackIndex idx = writeShallowPack(repo, 2, wants(c5), NONE, NONE);
-                       assertContent(idx,
-                                       Arrays.asList(c4.getId(), c5.getId(), c4.getTree().getId(),
-                                                       c5.getTree().getId(), contentA.getId(),
-                                                       contentB.getId(), contentC.getId(),
-                                                       contentD.getId(), contentE.getId()));
-
-                       idx = writeShallowPack(repo, 2, wants(c2), haves(c4, c5),
-                                       shallows(c4));
-                       assertContent(idx, Arrays.asList(c1.getId(), c2.getId(),
-                                       c1.getTree().getId(), c2.getTree().getId()));
-               }
-       }
-
-       @Test
-       public void testTotalPackFilesScanWhenSearchForReuseTimeoutNotSet()
-                       throws Exception {
-               FileRepository fileRepository = setUpRepoWithMultiplePackfiles();
-               PackWriter mockedPackWriter = Mockito
-                               .spy(new PackWriter(config, fileRepository.newObjectReader()));
-
-               doNothing().when(mockedPackWriter).select(any(), any());
-
-               try (FileOutputStream packOS = new FileOutputStream(
-                               getPackFileToWrite(fileRepository, mockedPackWriter))) {
-                       mockedPackWriter.writePack(NullProgressMonitor.INSTANCE,
-                                       NullProgressMonitor.INSTANCE, packOS);
-               }
-
-               long numberOfPackFiles = new GC(fileRepository)
-                               .getStatistics().numberOfPackFiles;
-               int expectedSelectCalls =
-                               // Objects contained in multiple packfiles * number of packfiles
-                               2 * (int) numberOfPackFiles +
-                               // Objects in single packfile
-                                               1;
-               verify(mockedPackWriter, times(expectedSelectCalls)).select(any(),
-                               any());
-       }
-
-       @Test
-       public void testTotalPackFilesScanWhenSkippingSearchForReuseTimeoutCheck()
-                       throws Exception {
-               FileRepository fileRepository = setUpRepoWithMultiplePackfiles();
-               PackConfig packConfig = new PackConfig();
-               packConfig.setSearchForReuseTimeout(Duration.ofSeconds(-1));
-               PackWriter mockedPackWriter = Mockito.spy(
-                               new PackWriter(packConfig, fileRepository.newObjectReader()));
-
-               doNothing().when(mockedPackWriter).select(any(), any());
-
-               try (FileOutputStream packOS = new FileOutputStream(
-                               getPackFileToWrite(fileRepository, mockedPackWriter))) {
-                       mockedPackWriter.writePack(NullProgressMonitor.INSTANCE,
-                                       NullProgressMonitor.INSTANCE, packOS);
-               }
-
-               long numberOfPackFiles = new GC(fileRepository)
-                               .getStatistics().numberOfPackFiles;
-               int expectedSelectCalls =
-                               // Objects contained in multiple packfiles * number of packfiles
-                               2 * (int) numberOfPackFiles +
-                               // Objects contained in single packfile
-                                               1;
-               verify(mockedPackWriter, times(expectedSelectCalls)).select(any(),
-                               any());
-       }
-
-       @Test
-       public void testPartialPackFilesScanWhenDoingSearchForReuseTimeoutCheck()
-                       throws Exception {
-               FileRepository fileRepository = setUpRepoWithMultiplePackfiles();
-               PackConfig packConfig = new PackConfig();
-               packConfig.setSearchForReuseTimeout(Duration.ofSeconds(-1));
-               PackWriter mockedPackWriter = Mockito.spy(
-                               new PackWriter(packConfig, fileRepository.newObjectReader()));
-               mockedPackWriter.enableSearchForReuseTimeout();
-
-               doNothing().when(mockedPackWriter).select(any(), any());
-
-               try (FileOutputStream packOS = new FileOutputStream(
-                               getPackFileToWrite(fileRepository, mockedPackWriter))) {
-                       mockedPackWriter.writePack(NullProgressMonitor.INSTANCE,
-                                       NullProgressMonitor.INSTANCE, packOS);
-               }
-
-               int expectedSelectCalls = 3; // Objects in packfiles
-               verify(mockedPackWriter, times(expectedSelectCalls)).select(any(),
-                               any());
-       }
-
-       /**
-        * Creates objects and packfiles in the following order:
-        * <ul>
-        * <li>Creates 2 objects (C1 = commit, T1 = tree)
-        * <li>Creates packfile P1 (containing C1, T1)
-        * <li>Creates 1 object (C2 commit)
-        * <li>Creates packfile P2 (containing C1, T1, C2)
-        * <li>Create 1 object (C3 commit)
-        * </ul>
-        *
-        * @throws Exception
-        */
-       private FileRepository setUpRepoWithMultiplePackfiles() throws Exception {
-               FileRepository fileRepository = createWorkRepository();
-               addRepoToClose(fileRepository);
-               try (Git git = new Git(fileRepository)) {
-                       // Creates 2 objects (C1 = commit, T1 = tree)
-                       git.commit().setMessage("First commit").call();
-                       GC gc = new GC(fileRepository);
-                       gc.setPackExpireAgeMillis(Long.MAX_VALUE);
-                       gc.setExpireAgeMillis(Long.MAX_VALUE);
-                       // Creates packfile P1 (containing C1, T1)
-                       gc.gc().get();
-                       // Creates 1 object (C2 commit)
-                       git.commit().setMessage("Second commit").call();
-                       // Creates packfile P2 (containing C1, T1, C2)
-                       gc.gc().get();
-                       // Create 1 object (C3 commit)
-                       git.commit().setMessage("Third commit").call();
-               }
-               return fileRepository;
-       }
-
-       private PackFile getPackFileToWrite(FileRepository fileRepository,
-                       PackWriter mockedPackWriter) throws IOException {
-               File packdir = fileRepository.getObjectDatabase().getPackDirectory();
-               PackFile packFile = new PackFile(packdir,
-                               mockedPackWriter.computeName(), PackExt.PACK);
-
-               Set<ObjectId> all = new HashSet<>();
-               for (Ref r : fileRepository.getRefDatabase().getRefs()) {
-                       all.add(r.getObjectId());
-               }
-
-               mockedPackWriter.preparePack(NullProgressMonitor.INSTANCE, all,
-                               PackWriter.NONE);
-               return packFile;
-       }
-
-       private FileRepository setupRepoForShallowFetch() throws Exception {
-               FileRepository repo = createBareRepository();
-               // TestRepository will close the repo, but we need to return an open
-               // one!
-               repo.incrementOpen();
-               try (TestRepository<Repository> r = new TestRepository<>(repo)) {
-                       BranchBuilder bb = r.branch("refs/heads/master");
-                       contentA = r.blob("A");
-                       contentB = r.blob("B");
-                       contentC = r.blob("C");
-                       contentD = r.blob("D");
-                       contentE = r.blob("E");
-                       c1 = bb.commit().add("a", contentA).create();
-                       c2 = bb.commit().add("b", contentB).create();
-                       c3 = bb.commit().add("c", contentC).create();
-                       c4 = bb.commit().add("d", contentD).create();
-                       c5 = bb.commit().add("e", contentE).create();
-                       r.getRevWalk().parseHeaders(c5); // fully initialize the tip RevCommit
-                       return repo;
-               }
-       }
-
-       private static PackIndex writePack(FileRepository repo,
-                       Set<? extends ObjectId> want, Set<ObjectIdSet> excludeObjects)
-                                       throws IOException {
-               try (RevWalk walk = new RevWalk(repo)) {
-                       return writePack(repo, walk, 0, want, NONE, excludeObjects);
-               }
-       }
-
-       private static PackIndex writeShallowPack(FileRepository repo, int depth,
-                       Set<? extends ObjectId> want, Set<? extends ObjectId> have,
-                       Set<? extends ObjectId> shallow) throws IOException {
-               // During negotiation, UploadPack would have set up a DepthWalk and
-               // marked the client's "shallow" commits. Emulate that here.
-               try (DepthWalk.RevWalk walk = new DepthWalk.RevWalk(repo, depth - 1)) {
-                       walk.assumeShallow(shallow);
-                       return writePack(repo, walk, depth, want, have, EMPTY_ID_SET);
-               }
-       }
-
-       private static PackIndex writePack(FileRepository repo, RevWalk walk,
-                       int depth, Set<? extends ObjectId> want,
-                       Set<? extends ObjectId> have, Set<ObjectIdSet> excludeObjects)
-                                       throws IOException {
-               try (PackWriter pw = new PackWriter(repo)) {
-                       pw.setDeltaBaseAsOffset(true);
-                       pw.setReuseDeltaCommits(false);
-                       for (ObjectIdSet idx : excludeObjects) {
-                               pw.excludeObjects(idx);
-                       }
-                       if (depth > 0) {
-                               pw.setShallowPack(depth, null);
-                       }
-                       // ow doesn't need to be closed; caller closes walk.
-                       ObjectWalk ow = walk.toObjectWalkWithSameObjects();
-
-                       pw.preparePack(NullProgressMonitor.INSTANCE, ow, want, have, NONE);
-                       File packdir = repo.getObjectDatabase().getPackDirectory();
-                       PackFile packFile = new PackFile(packdir, pw.computeName(),
-                                       PackExt.PACK);
-                       try (FileOutputStream packOS = new FileOutputStream(packFile)) {
-                               pw.writePack(NullProgressMonitor.INSTANCE,
-                                               NullProgressMonitor.INSTANCE, packOS);
-                       }
-                       PackFile idxFile = packFile.create(PackExt.INDEX);
-                       try (FileOutputStream idxOS = new FileOutputStream(idxFile)) {
-                               pw.writeIndex(idxOS);
-                       }
-                       return PackIndex.open(idxFile);
-               }
-       }
-
-       // TODO: testWritePackDeltasCycle()
-       // TODO: testWritePackDeltasDepth()
-
-       private void writeVerifyPack1() throws IOException {
-               final HashSet<ObjectId> interestings = new HashSet<>();
-               interestings.add(ObjectId
-                               .fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"));
-               createVerifyOpenPack(interestings, NONE, false, false);
-
-               final ObjectId expectedOrder[] = new ObjectId[] {
-                               ObjectId.fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"),
-                               ObjectId.fromString("c59759f143fb1fe21c197981df75a7ee00290799"),
-                               ObjectId.fromString("540a36d136cf413e4b064c2b0e0a4db60f77feab"),
-                               ObjectId.fromString("aabf2ffaec9b497f0950352b3e582d73035c2035"),
-                               ObjectId.fromString("902d5476fa249b7abc9d84c611577a81381f0327"),
-                               ObjectId.fromString("4b825dc642cb6eb9a060e54bf8d69288fbee4904"),
-                               ObjectId.fromString("6ff87c4664981e4397625791c8ea3bbb5f2279a3"),
-                               ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259") };
-
-               assertEquals(expectedOrder.length, writer.getObjectCount());
-               verifyObjectsOrder(expectedOrder);
-               assertEquals("34be9032ac282b11fa9babdc2b2a93ca996c9c2f", writer
-                               .computeName().name());
-       }
-
-       private void writeVerifyPack2(boolean deltaReuse) throws IOException {
-               config.setReuseDeltas(deltaReuse);
-               final HashSet<ObjectId> interestings = new HashSet<>();
-               interestings.add(ObjectId
-                               .fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"));
-               final HashSet<ObjectId> uninterestings = new HashSet<>();
-               uninterestings.add(ObjectId
-                               .fromString("540a36d136cf413e4b064c2b0e0a4db60f77feab"));
-               createVerifyOpenPack(interestings, uninterestings, false, false);
-
-               final ObjectId expectedOrder[] = new ObjectId[] {
-                               ObjectId.fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"),
-                               ObjectId.fromString("c59759f143fb1fe21c197981df75a7ee00290799"),
-                               ObjectId.fromString("aabf2ffaec9b497f0950352b3e582d73035c2035"),
-                               ObjectId.fromString("902d5476fa249b7abc9d84c611577a81381f0327"),
-                               ObjectId.fromString("6ff87c4664981e4397625791c8ea3bbb5f2279a3") ,
-                               ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259") };
-               if (!config.isReuseDeltas() && !config.isDeltaCompress()) {
-                       // If no deltas are in the file the final two entries swap places.
-                       swap(expectedOrder, 4, 5);
-               }
-               assertEquals(expectedOrder.length, writer.getObjectCount());
-               verifyObjectsOrder(expectedOrder);
-               assertEquals("ed3f96b8327c7c66b0f8f70056129f0769323d86", writer
-                               .computeName().name());
-       }
-
-       private static void swap(ObjectId[] arr, int a, int b) {
-               ObjectId tmp = arr[a];
-               arr[a] = arr[b];
-               arr[b] = tmp;
-       }
-
-       private void writeVerifyPack4(final boolean thin) throws IOException {
-               final HashSet<ObjectId> interestings = new HashSet<>();
-               interestings.add(ObjectId
-                               .fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"));
-               final HashSet<ObjectId> uninterestings = new HashSet<>();
-               uninterestings.add(ObjectId
-                               .fromString("c59759f143fb1fe21c197981df75a7ee00290799"));
-               createVerifyOpenPack(interestings, uninterestings, thin, false);
-
-               final ObjectId writtenObjects[] = new ObjectId[] {
-                               ObjectId.fromString("82c6b885ff600be425b4ea96dee75dca255b69e7"),
-                               ObjectId.fromString("aabf2ffaec9b497f0950352b3e582d73035c2035"),
-                               ObjectId.fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259") };
-               assertEquals(writtenObjects.length, writer.getObjectCount());
-               ObjectId expectedObjects[];
-               if (thin) {
-                       expectedObjects = new ObjectId[4];
-                       System.arraycopy(writtenObjects, 0, expectedObjects, 0,
-                                       writtenObjects.length);
-                       expectedObjects[3] = ObjectId
-                                       .fromString("6ff87c4664981e4397625791c8ea3bbb5f2279a3");
-
-               } else {
-                       expectedObjects = writtenObjects;
-               }
-               verifyObjectsOrder(expectedObjects);
-               assertEquals("cded4b74176b4456afa456768b2b5aafb41c44fc", writer
-                               .computeName().name());
-       }
-
-       private void createVerifyOpenPack(final Set<ObjectId> interestings,
-                       final Set<ObjectId> uninterestings, final boolean thin,
-                       final boolean ignoreMissingUninteresting)
-                       throws MissingObjectException, IOException {
-               createVerifyOpenPack(interestings, uninterestings, thin,
-                               ignoreMissingUninteresting, false);
-       }
-
-       private void createVerifyOpenPack(final Set<ObjectId> interestings,
-                       final Set<ObjectId> uninterestings, final boolean thin,
-                       final boolean ignoreMissingUninteresting, boolean useBitmaps)
-                       throws MissingObjectException, IOException {
-               NullProgressMonitor m = NullProgressMonitor.INSTANCE;
-               writer = new PackWriter(config, db.newObjectReader());
-               writer.setUseBitmaps(useBitmaps);
-               writer.setThin(thin);
-               writer.setIgnoreMissingUninteresting(ignoreMissingUninteresting);
-               writer.preparePack(m, interestings, uninterestings);
-               writer.writePack(m, m, os);
-               writer.close();
-               verifyOpenPack(thin);
-       }
-
-       private void createVerifyOpenPack(List<RevObject> objectSource)
-                       throws MissingObjectException, IOException {
-               NullProgressMonitor m = NullProgressMonitor.INSTANCE;
-               writer = new PackWriter(config, db.newObjectReader());
-               writer.preparePack(objectSource.iterator());
-               assertEquals(objectSource.size(), writer.getObjectCount());
-               writer.writePack(m, m, os);
-               writer.close();
-               verifyOpenPack(false);
-       }
-
-       private void verifyOpenPack(boolean thin) throws IOException {
-               final byte[] packData = os.toByteArray();
-
-               if (thin) {
-                       PackParser p = index(packData);
-                       try {
-                               p.parse(NullProgressMonitor.INSTANCE);
-                               fail("indexer should grumble about missing object");
-                       } catch (IOException x) {
-                               // expected
-                       }
-               }
-
-               ObjectDirectoryPackParser p = (ObjectDirectoryPackParser) index(packData);
-               p.setKeepEmpty(true);
-               p.setAllowThin(thin);
-               p.setIndexVersion(2);
-               p.parse(NullProgressMonitor.INSTANCE);
-               pack = p.getPack();
-               assertNotNull("have PackFile after parsing", pack);
-       }
-
-       private PackParser index(byte[] packData) throws IOException {
-               if (inserter == null)
-                       inserter = dst.newObjectInserter();
-               return inserter.newPackParser(new ByteArrayInputStream(packData));
-       }
-
-       private void verifyObjectsOrder(ObjectId objectsOrder[]) {
-               final List<PackIndex.MutableEntry> entries = new ArrayList<>();
-
-               for (MutableEntry me : pack) {
-                       entries.add(me.cloneEntry());
-               }
-               Collections.sort(entries, (MutableEntry o1, MutableEntry o2) -> Long
-                               .signum(o1.getOffset() - o2.getOffset()));
-
-               int i = 0;
-               for (MutableEntry me : entries) {
-                       assertEquals(objectsOrder[i++].toObjectId(), me.toObjectId());
-               }
-       }
-
-       private static Set<ObjectId> haves(ObjectId... objects) {
-               return Sets.of(objects);
-       }
-
-       private static Set<ObjectId> wants(ObjectId... objects) {
-               return Sets.of(objects);
-       }
-
-       private static Set<ObjectId> shallows(ObjectId... objects) {
-               return Sets.of(objects);
-       }
-}
index 86fbd445be410fde3c565b2f9994ca713949a743..8f0926167494e80b7a1548bf65e96169ce3f5e31 100644 (file)
@@ -42,7 +42,7 @@ import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.LargeObjectException;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.internal.storage.file.PackIndex;
-import org.eclipse.jgit.internal.storage.file.PackIndexWriter;
+import org.eclipse.jgit.internal.storage.file.BasePackIndexWriter;
 import org.eclipse.jgit.internal.storage.file.PackObjectSizeIndexWriter;
 import org.eclipse.jgit.internal.storage.pack.PackExt;
 import org.eclipse.jgit.lib.AbbreviatedObjectId;
@@ -333,7 +333,7 @@ public class DfsInserter extends ObjectInserter {
 
        private static void index(OutputStream out, byte[] packHash,
                        List<PackedObjectInfo> list) throws IOException {
-               PackIndexWriter.createVersion(out, INDEX_VERSION).write(list, packHash);
+               BasePackIndexWriter.createVersion(out, INDEX_VERSION).write(list, packHash);
        }
 
        void writeObjectSizeIndex(DfsPackDescription pack,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BasePackIndexWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/BasePackIndexWriter.java
new file mode 100644 (file)
index 0000000..b89cc1e
--- /dev/null
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others
+ *
+ * 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.file;
+
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.DigestOutputStream;
+import java.text.MessageFormat;
+import java.util.List;
+
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.storage.pack.PackIndexWriter;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.transport.PackedObjectInfo;
+import org.eclipse.jgit.util.NB;
+
+/**
+ * Creates a table of contents to support random access by
+ * {@link org.eclipse.jgit.internal.storage.file.Pack}.
+ * <p>
+ * Pack index files (the <code>.idx</code> suffix in a pack file pair) provides
+ * random access to any object in the pack by associating an ObjectId to the
+ * byte offset within the pack where the object's data can be read.
+ */
+public abstract class BasePackIndexWriter implements PackIndexWriter {
+       /** Magic constant indicating post-version 1 format. */
+       protected static final byte[] TOC = { -1, 't', 'O', 'c' };
+
+       /**
+        * Create a new writer for the oldest (most widely understood) format.
+        * <p>
+        * This method selects an index format that can accurate describe the
+        * supplied objects and that will be the most compatible format with older
+        * Git implementations.
+        * <p>
+        * Index version 1 is widely recognized by all Git implementations, but
+        * index version 2 (and later) is not as well recognized as it was
+        * introduced more than a year later. Index version 1 can only be used if
+        * the resulting pack file is under 4 gigabytes in size; packs larger than
+        * that limit must use index version 2.
+        *
+        * @param dst
+        *            the stream the index data will be written to. If not already
+        *            buffered it will be automatically wrapped in a buffered
+        *            stream. Callers are always responsible for closing the stream.
+        * @param objs
+        *            the objects the caller needs to store in the index. Entries
+        *            will be examined until a format can be conclusively selected.
+        * @return a new writer to output an index file of the requested format to
+        *         the supplied stream.
+        * @throws java.lang.IllegalArgumentException
+        *             no recognized pack index version can support the supplied
+        *             objects. This is likely a bug in the implementation.
+        * @see #oldestPossibleFormat(List)
+        */
+       public static PackIndexWriter createOldestPossible(final OutputStream dst,
+                       final List<? extends PackedObjectInfo> objs) {
+               return createVersion(dst, oldestPossibleFormat(objs));
+       }
+
+       /**
+        * Return the oldest (most widely understood) index format.
+        * <p>
+        * This method selects an index format that can accurate describe the
+        * supplied objects and that will be the most compatible format with older
+        * Git implementations.
+        * <p>
+        * Index version 1 is widely recognized by all Git implementations, but
+        * index version 2 (and later) is not as well recognized as it was
+        * introduced more than a year later. Index version 1 can only be used if
+        * the resulting pack file is under 4 gigabytes in size; packs larger than
+        * that limit must use index version 2.
+        *
+        * @param objs
+        *            the objects the caller needs to store in the index. Entries
+        *            will be examined until a format can be conclusively selected.
+        * @return the index format.
+        * @throws java.lang.IllegalArgumentException
+        *             no recognized pack index version can support the supplied
+        *             objects. This is likely a bug in the implementation.
+        */
+       public static int oldestPossibleFormat(
+                       final List<? extends PackedObjectInfo> objs) {
+               for (PackedObjectInfo oe : objs) {
+                       if (!PackIndexWriterV1.canStore(oe))
+                               return 2;
+               }
+               return 1;
+       }
+
+
+       /**
+        * Create a new writer instance for a specific index format version.
+        *
+        * @param dst
+        *            the stream the index data will be written to. If not already
+        *            buffered it will be automatically wrapped in a buffered
+        *            stream. Callers are always responsible for closing the stream.
+        * @param version
+        *            index format version number required by the caller. Exactly
+        *            this formatted version will be written.
+        * @return a new writer to output an index file of the requested format to
+        *         the supplied stream.
+        * @throws java.lang.IllegalArgumentException
+        *             the version requested is not supported by this
+        *             implementation.
+        */
+       public static PackIndexWriter createVersion(final OutputStream dst,
+                       final int version) {
+               switch (version) {
+               case 1:
+                       return new PackIndexWriterV1(dst);
+               case 2:
+                       return new PackIndexWriterV2(dst);
+               default:
+                       throw new IllegalArgumentException(MessageFormat.format(
+                                       JGitText.get().unsupportedPackIndexVersion,
+                                       Integer.valueOf(version)));
+               }
+       }
+
+       /** The index data stream we are responsible for creating. */
+       protected final DigestOutputStream out;
+
+       /** A temporary buffer for use during IO to {link #out}. */
+       protected final byte[] tmp;
+
+       /** The entries this writer must pack. */
+       protected List<? extends PackedObjectInfo> entries;
+
+       /** SHA-1 checksum for the entire pack data. */
+       protected byte[] packChecksum;
+
+       /**
+        * Create a new writer instance.
+        *
+        * @param dst
+        *            the stream this instance outputs to. If not already buffered
+        *            it will be automatically wrapped in a buffered stream.
+        */
+       protected BasePackIndexWriter(OutputStream dst) {
+               out = new DigestOutputStream(dst instanceof BufferedOutputStream ? dst
+                               : new BufferedOutputStream(dst),
+                               Constants.newMessageDigest());
+               tmp = new byte[4 + Constants.OBJECT_ID_LENGTH];
+       }
+
+       /**
+        * Write all object entries to the index stream.
+        * <p>
+        * After writing the stream passed to the factory is flushed but remains
+        * open. Callers are always responsible for closing the output stream.
+        *
+        * @param toStore
+        *            sorted list of objects to store in the index. The caller must
+        *            have previously sorted the list using
+        *            {@link org.eclipse.jgit.transport.PackedObjectInfo}'s native
+        *            {@link java.lang.Comparable} implementation.
+        * @param packDataChecksum
+        *            checksum signature of the entire pack data content. This is
+        *            traditionally the last 20 bytes of the pack file's own stream.
+        * @throws java.io.IOException
+        *             an error occurred while writing to the output stream, or this
+        *             index format cannot store the object data supplied.
+        */
+       @Override
+       public void write(final List<? extends PackedObjectInfo> toStore,
+                       final byte[] packDataChecksum) throws IOException {
+               entries = toStore;
+               packChecksum = packDataChecksum;
+               writeImpl();
+               out.flush();
+       }
+
+       /**
+        * Writes the index file to {@link #out}.
+        * <p>
+        * Implementations should go something like:
+        *
+        * <pre>
+        * writeFanOutTable();
+        * for (final PackedObjectInfo po : entries)
+        *      writeOneEntry(po);
+        * writeChecksumFooter();
+        * </pre>
+        *
+        * <p>
+        * Where the logic for <code>writeOneEntry</code> is specific to the index
+        * format in use. Additional headers/footers may be used if necessary and
+        * the {@link #entries} collection may be iterated over more than once if
+        * necessary. Implementors therefore have complete control over the data.
+        *
+        * @throws java.io.IOException
+        *             an error occurred while writing to the output stream, or this
+        *             index format cannot store the object data supplied.
+        */
+       protected abstract void writeImpl() throws IOException;
+
+       /**
+        * Output the version 2 (and later) TOC header, with version number.
+        * <p>
+        * Post version 1 all index files start with a TOC header that makes the
+        * file an invalid version 1 file, and then includes the version number.
+        * This header is necessary to recognize a version 1 from a version 2
+        * formatted index.
+        *
+        * @param version
+        *            version number of this index format being written.
+        * @throws java.io.IOException
+        *             an error occurred while writing to the output stream.
+        */
+       protected void writeTOC(int version) throws IOException {
+               out.write(TOC);
+               NB.encodeInt32(tmp, 0, version);
+               out.write(tmp, 0, 4);
+       }
+
+       /**
+        * Output the standard 256 entry first-level fan-out table.
+        * <p>
+        * The fan-out table is 4 KB in size, holding 256 32-bit unsigned integer
+        * counts. Each count represents the number of objects within this index
+        * whose {@link org.eclipse.jgit.lib.ObjectId#getFirstByte()} matches the
+        * count's position in the fan-out table.
+        *
+        * @throws java.io.IOException
+        *             an error occurred while writing to the output stream.
+        */
+       protected void writeFanOutTable() throws IOException {
+               final int[] fanout = new int[256];
+               for (PackedObjectInfo po : entries)
+                       fanout[po.getFirstByte() & 0xff]++;
+               for (int i = 1; i < 256; i++)
+                       fanout[i] += fanout[i - 1];
+               for (int n : fanout) {
+                       NB.encodeInt32(tmp, 0, n);
+                       out.write(tmp, 0, 4);
+               }
+       }
+
+       /**
+        * Output the standard two-checksum index footer.
+        * <p>
+        * The standard footer contains two checksums (20 byte SHA-1 values):
+        * <ol>
+        * <li>Pack data checksum - taken from the last 20 bytes of the pack file.</li>
+        * <li>Index data checksum - checksum of all index bytes written, including
+        * the pack data checksum above.</li>
+        * </ol>
+        *
+        * @throws java.io.IOException
+        *             an error occurred while writing to the output stream.
+        */
+       protected void writeChecksumFooter() throws IOException {
+               out.write(packChecksum);
+               out.on(false);
+               out.write(out.getMessageDigest().digest());
+       }
+}
index 9f27f4bd6e4776f04c6a1633c65b3df0b8ae95b0..746e124e1f4e6bf07fdf427d7382cbda9522974b 100644 (file)
@@ -28,6 +28,7 @@ import java.util.zip.Deflater;
 import org.eclipse.jgit.errors.LockFailedException;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.internal.storage.pack.PackExt;
+import org.eclipse.jgit.internal.storage.pack.PackIndexWriter;
 import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.CoreConfig;
@@ -110,7 +111,7 @@ public class ObjectDirectoryPackParser extends PackParser {
         * @param version
         *            the version to write. The special version 0 designates the
         *            oldest (most compatible) format available for the objects.
-        * @see PackIndexWriter
+        * @see BasePackIndexWriter
         */
        public void setIndexVersion(int version) {
                indexVersion = version;
@@ -386,9 +387,9 @@ public class ObjectDirectoryPackParser extends PackParser {
                try (FileOutputStream os = new FileOutputStream(tmpIdx)) {
                        final PackIndexWriter iw;
                        if (indexVersion <= 0)
-                               iw = PackIndexWriter.createOldestPossible(os, list);
+                               iw = BasePackIndexWriter.createOldestPossible(os, list);
                        else
-                               iw = PackIndexWriter.createVersion(os, indexVersion);
+                               iw = BasePackIndexWriter.createVersion(os, indexVersion);
                        iw.write(list, packHash);
                        os.getChannel().force(true);
                }
index dfea5c1c80f0a0b04f8bb5c6f89c2beef5b3785b..7189ce20a6cfce3d1694d5d53a2f73f14d2657d7 100644 (file)
@@ -109,7 +109,7 @@ public interface PackIndex
        }
 
        private static boolean isTOC(byte[] h) {
-               final byte[] toc = PackIndexWriter.TOC;
+               final byte[] toc = BasePackIndexWriter.TOC;
                for (int i = 0; i < toc.length; i++)
                        if (h[i] != toc[i])
                                return false;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexWriter.java
deleted file mode 100644 (file)
index 87e0b44..0000000
+++ /dev/null
@@ -1,267 +0,0 @@
-/*
- * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others
- *
- * 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.file;
-
-import java.io.BufferedOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.security.DigestOutputStream;
-import java.text.MessageFormat;
-import java.util.List;
-
-import org.eclipse.jgit.internal.JGitText;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.transport.PackedObjectInfo;
-import org.eclipse.jgit.util.NB;
-
-/**
- * Creates a table of contents to support random access by
- * {@link org.eclipse.jgit.internal.storage.file.Pack}.
- * <p>
- * Pack index files (the <code>.idx</code> suffix in a pack file pair) provides
- * random access to any object in the pack by associating an ObjectId to the
- * byte offset within the pack where the object's data can be read.
- */
-public abstract class PackIndexWriter {
-       /** Magic constant indicating post-version 1 format. */
-       protected static final byte[] TOC = { -1, 't', 'O', 'c' };
-
-       /**
-        * Create a new writer for the oldest (most widely understood) format.
-        * <p>
-        * This method selects an index format that can accurate describe the
-        * supplied objects and that will be the most compatible format with older
-        * Git implementations.
-        * <p>
-        * Index version 1 is widely recognized by all Git implementations, but
-        * index version 2 (and later) is not as well recognized as it was
-        * introduced more than a year later. Index version 1 can only be used if
-        * the resulting pack file is under 4 gigabytes in size; packs larger than
-        * that limit must use index version 2.
-        *
-        * @param dst
-        *            the stream the index data will be written to. If not already
-        *            buffered it will be automatically wrapped in a buffered
-        *            stream. Callers are always responsible for closing the stream.
-        * @param objs
-        *            the objects the caller needs to store in the index. Entries
-        *            will be examined until a format can be conclusively selected.
-        * @return a new writer to output an index file of the requested format to
-        *         the supplied stream.
-        * @throws java.lang.IllegalArgumentException
-        *             no recognized pack index version can support the supplied
-        *             objects. This is likely a bug in the implementation.
-        * @see #oldestPossibleFormat(List)
-        */
-       public static PackIndexWriter createOldestPossible(final OutputStream dst,
-                       final List<? extends PackedObjectInfo> objs) {
-               return createVersion(dst, oldestPossibleFormat(objs));
-       }
-
-       /**
-        * Return the oldest (most widely understood) index format.
-        * <p>
-        * This method selects an index format that can accurate describe the
-        * supplied objects and that will be the most compatible format with older
-        * Git implementations.
-        * <p>
-        * Index version 1 is widely recognized by all Git implementations, but
-        * index version 2 (and later) is not as well recognized as it was
-        * introduced more than a year later. Index version 1 can only be used if
-        * the resulting pack file is under 4 gigabytes in size; packs larger than
-        * that limit must use index version 2.
-        *
-        * @param objs
-        *            the objects the caller needs to store in the index. Entries
-        *            will be examined until a format can be conclusively selected.
-        * @return the index format.
-        * @throws java.lang.IllegalArgumentException
-        *             no recognized pack index version can support the supplied
-        *             objects. This is likely a bug in the implementation.
-        */
-       public static int oldestPossibleFormat(
-                       final List<? extends PackedObjectInfo> objs) {
-               for (PackedObjectInfo oe : objs) {
-                       if (!PackIndexWriterV1.canStore(oe))
-                               return 2;
-               }
-               return 1;
-       }
-
-
-       /**
-        * Create a new writer instance for a specific index format version.
-        *
-        * @param dst
-        *            the stream the index data will be written to. If not already
-        *            buffered it will be automatically wrapped in a buffered
-        *            stream. Callers are always responsible for closing the stream.
-        * @param version
-        *            index format version number required by the caller. Exactly
-        *            this formatted version will be written.
-        * @return a new writer to output an index file of the requested format to
-        *         the supplied stream.
-        * @throws java.lang.IllegalArgumentException
-        *             the version requested is not supported by this
-        *             implementation.
-        */
-       public static PackIndexWriter createVersion(final OutputStream dst,
-                       final int version) {
-               switch (version) {
-               case 1:
-                       return new PackIndexWriterV1(dst);
-               case 2:
-                       return new PackIndexWriterV2(dst);
-               default:
-                       throw new IllegalArgumentException(MessageFormat.format(
-                                       JGitText.get().unsupportedPackIndexVersion,
-                                       Integer.valueOf(version)));
-               }
-       }
-
-       /** The index data stream we are responsible for creating. */
-       protected final DigestOutputStream out;
-
-       /** A temporary buffer for use during IO to {link #out}. */
-       protected final byte[] tmp;
-
-       /** The entries this writer must pack. */
-       protected List<? extends PackedObjectInfo> entries;
-
-       /** SHA-1 checksum for the entire pack data. */
-       protected byte[] packChecksum;
-
-       /**
-        * Create a new writer instance.
-        *
-        * @param dst
-        *            the stream this instance outputs to. If not already buffered
-        *            it will be automatically wrapped in a buffered stream.
-        */
-       protected PackIndexWriter(OutputStream dst) {
-               out = new DigestOutputStream(dst instanceof BufferedOutputStream ? dst
-                               : new BufferedOutputStream(dst),
-                               Constants.newMessageDigest());
-               tmp = new byte[4 + Constants.OBJECT_ID_LENGTH];
-       }
-
-       /**
-        * Write all object entries to the index stream.
-        * <p>
-        * After writing the stream passed to the factory is flushed but remains
-        * open. Callers are always responsible for closing the output stream.
-        *
-        * @param toStore
-        *            sorted list of objects to store in the index. The caller must
-        *            have previously sorted the list using
-        *            {@link org.eclipse.jgit.transport.PackedObjectInfo}'s native
-        *            {@link java.lang.Comparable} implementation.
-        * @param packDataChecksum
-        *            checksum signature of the entire pack data content. This is
-        *            traditionally the last 20 bytes of the pack file's own stream.
-        * @throws java.io.IOException
-        *             an error occurred while writing to the output stream, or this
-        *             index format cannot store the object data supplied.
-        */
-       public void write(final List<? extends PackedObjectInfo> toStore,
-                       final byte[] packDataChecksum) throws IOException {
-               entries = toStore;
-               packChecksum = packDataChecksum;
-               writeImpl();
-               out.flush();
-       }
-
-       /**
-        * Writes the index file to {@link #out}.
-        * <p>
-        * Implementations should go something like:
-        *
-        * <pre>
-        * writeFanOutTable();
-        * for (final PackedObjectInfo po : entries)
-        *      writeOneEntry(po);
-        * writeChecksumFooter();
-        * </pre>
-        *
-        * <p>
-        * Where the logic for <code>writeOneEntry</code> is specific to the index
-        * format in use. Additional headers/footers may be used if necessary and
-        * the {@link #entries} collection may be iterated over more than once if
-        * necessary. Implementors therefore have complete control over the data.
-        *
-        * @throws java.io.IOException
-        *             an error occurred while writing to the output stream, or this
-        *             index format cannot store the object data supplied.
-        */
-       protected abstract void writeImpl() throws IOException;
-
-       /**
-        * Output the version 2 (and later) TOC header, with version number.
-        * <p>
-        * Post version 1 all index files start with a TOC header that makes the
-        * file an invalid version 1 file, and then includes the version number.
-        * This header is necessary to recognize a version 1 from a version 2
-        * formatted index.
-        *
-        * @param version
-        *            version number of this index format being written.
-        * @throws java.io.IOException
-        *             an error occurred while writing to the output stream.
-        */
-       protected void writeTOC(int version) throws IOException {
-               out.write(TOC);
-               NB.encodeInt32(tmp, 0, version);
-               out.write(tmp, 0, 4);
-       }
-
-       /**
-        * Output the standard 256 entry first-level fan-out table.
-        * <p>
-        * The fan-out table is 4 KB in size, holding 256 32-bit unsigned integer
-        * counts. Each count represents the number of objects within this index
-        * whose {@link org.eclipse.jgit.lib.ObjectId#getFirstByte()} matches the
-        * count's position in the fan-out table.
-        *
-        * @throws java.io.IOException
-        *             an error occurred while writing to the output stream.
-        */
-       protected void writeFanOutTable() throws IOException {
-               final int[] fanout = new int[256];
-               for (PackedObjectInfo po : entries)
-                       fanout[po.getFirstByte() & 0xff]++;
-               for (int i = 1; i < 256; i++)
-                       fanout[i] += fanout[i - 1];
-               for (int n : fanout) {
-                       NB.encodeInt32(tmp, 0, n);
-                       out.write(tmp, 0, 4);
-               }
-       }
-
-       /**
-        * Output the standard two-checksum index footer.
-        * <p>
-        * The standard footer contains two checksums (20 byte SHA-1 values):
-        * <ol>
-        * <li>Pack data checksum - taken from the last 20 bytes of the pack file.</li>
-        * <li>Index data checksum - checksum of all index bytes written, including
-        * the pack data checksum above.</li>
-        * </ol>
-        *
-        * @throws java.io.IOException
-        *             an error occurred while writing to the output stream.
-        */
-       protected void writeChecksumFooter() throws IOException {
-               out.write(packChecksum);
-               out.on(false);
-               out.write(out.getMessageDigest().digest());
-       }
-}
index 7e28b5eb2b56e4a0fb2ccd5bff7ba9c661e82b9d..f0b6193066c27820c67ad3fa178def9967557513 100644 (file)
@@ -21,10 +21,10 @@ import org.eclipse.jgit.util.NB;
 /**
  * Creates the version 1 (old style) pack table of contents files.
  *
- * @see PackIndexWriter
+ * @see BasePackIndexWriter
  * @see PackIndexV1
  */
-class PackIndexWriterV1 extends PackIndexWriter {
+class PackIndexWriterV1 extends BasePackIndexWriter {
        static boolean canStore(PackedObjectInfo oe) {
                // We are limited to 4 GB per pack as offset is 32 bit unsigned int.
                //
index fc5ef6191200c7eef5d9592c84f85ea7090af9e9..b72b35a464e3a6810c9d4e5aa0bb20fd004bbdaa 100644 (file)
@@ -19,10 +19,10 @@ import org.eclipse.jgit.util.NB;
 /**
  * Creates the version 2 pack table of contents files.
  *
- * @see PackIndexWriter
+ * @see BasePackIndexWriter
  * @see PackIndexV2
  */
-class PackIndexWriterV2 extends PackIndexWriter {
+class PackIndexWriterV2 extends BasePackIndexWriter {
        private static final int MAX_OFFSET_32 = 0x7fffffff;
        private static final int IS_OFFSET_64 = 0x80000000;
 
index 1b092a333256cf10c03e18e74fc1d19c34409c1b..55e047bd43088ea120800ab73071adc15b2c09ef 100644 (file)
@@ -77,6 +77,7 @@ import org.eclipse.jgit.errors.LargeObjectException;
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.internal.JGitText;
 import org.eclipse.jgit.internal.storage.pack.PackExt;
+import org.eclipse.jgit.internal.storage.pack.PackIndexWriter;
 import org.eclipse.jgit.lib.AbbreviatedObjectId;
 import org.eclipse.jgit.lib.AnyObjectId;
 import org.eclipse.jgit.lib.Constants;
@@ -320,7 +321,8 @@ public class PackInserter extends ObjectInserter {
        private static void writePackIndex(File idx, byte[] packHash,
                        List<PackedObjectInfo> list) throws IOException {
                try (OutputStream os = new FileOutputStream(idx)) {
-                       PackIndexWriter w = PackIndexWriter.createVersion(os, INDEX_VERSION);
+                       PackIndexWriter w = BasePackIndexWriter.createVersion(os,
+                                       INDEX_VERSION);
                        w.write(list, packHash);
                }
        }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackIndexWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackIndexWriter.java
new file mode 100644 (file)
index 0000000..f69e68d
--- /dev/null
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024, 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.pack;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.eclipse.jgit.transport.PackedObjectInfo;
+
+/**
+ * Represents a function that accepts a collection of objects to write into a
+ * primary pack index storage format.
+ */
+public interface PackIndexWriter {
+       /**
+        * Write all object entries to the index stream.
+        *
+        * @param toStore
+        *            sorted list of objects to store in the index. The caller must
+        *            have previously sorted the list using
+        *            {@link org.eclipse.jgit.transport.PackedObjectInfo}'s native
+        *            {@link java.lang.Comparable} implementation.
+        * @param packDataChecksum
+        *            checksum signature of the entire pack data content. This is
+        *            traditionally the last 20 bytes of the pack file's own stream.
+        * @throws java.io.IOException
+        *             an error occurred while writing to the output stream, or the
+        *             underlying format cannot store the object data supplied.
+        */
+       void write(List<? extends PackedObjectInfo> toStore,
+                       byte[] packDataChecksum) throws IOException;
+}
index d3e30f3f6c56643ea5235683e45c880cb4af285f..4fd2eb5798c7801dce6ef51b942e1fc813b3c232 100644 (file)
@@ -58,7 +58,7 @@ import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.errors.SearchForReuseTimeout;
 import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException;
 import org.eclipse.jgit.internal.JGitText;
-import org.eclipse.jgit.internal.storage.file.PackIndexWriter;
+import org.eclipse.jgit.internal.storage.file.BasePackIndexWriter;
 import org.eclipse.jgit.internal.storage.file.PackObjectSizeIndexWriter;
 import org.eclipse.jgit.internal.storage.file.PackReverseIndexWriter;
 import org.eclipse.jgit.internal.storage.file.PackBitmapIndexBuilder;
@@ -1078,7 +1078,7 @@ public class PackWriter implements AutoCloseable {
                if (indexVersion <= 0) {
                        for (BlockList<ObjectToPack> objs : objectsLists)
                                indexVersion = Math.max(indexVersion,
-                                               PackIndexWriter.oldestPossibleFormat(objs));
+                                               BasePackIndexWriter.oldestPossibleFormat(objs));
                }
                return indexVersion;
        }
@@ -1103,8 +1103,8 @@ public class PackWriter implements AutoCloseable {
                        throw new IOException(JGitText.get().cachedPacksPreventsIndexCreation);
 
                long writeStart = System.currentTimeMillis();
-               final PackIndexWriter iw = PackIndexWriter.createVersion(
-                               indexStream, getIndexVersion());
+               PackIndexWriter iw = BasePackIndexWriter.createVersion(indexStream,
+                               getIndexVersion());
                iw.write(sortByName(), packcsum);
                stats.timeWriting += System.currentTimeMillis() - writeStart;
        }
index 8373d6809acc1afe70a65bd67b189489a2ff71e0..863b79466af758ca19c115d4e938a94a7a1c241d 100644 (file)
@@ -50,7 +50,7 @@ import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 import java.util.zip.Deflater;
 
-import org.eclipse.jgit.internal.storage.file.PackIndexWriter;
+import org.eclipse.jgit.internal.storage.file.BasePackIndexWriter;
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.Repository;
 
@@ -995,7 +995,7 @@ public class PackConfig {
         *
         * @return the index version, the special version 0 designates the oldest
         *         (most compatible) format available for the objects.
-        * @see PackIndexWriter
+        * @see BasePackIndexWriter
         */
        public int getIndexVersion() {
                return indexVersion;
@@ -1009,7 +1009,7 @@ public class PackConfig {
         * @param version
         *            the version to write. The special version 0 designates the
         *            oldest (most compatible) format available for the objects.
-        * @see PackIndexWriter
+        * @see BasePackIndexWriter
         */
        public void setIndexVersion(int version) {
                indexVersion = version;