diff options
Diffstat (limited to 'org.eclipse.jgit.test')
3 files changed, 507 insertions, 0 deletions
diff --git a/org.eclipse.jgit.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.test/META-INF/MANIFEST.MF index 8582bcf818..72bdd90b53 100644 --- a/org.eclipse.jgit.test/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.test/META-INF/MANIFEST.MF @@ -7,6 +7,7 @@ Bundle-Version: 7.4.0.qualifier Bundle-Localization: plugin Bundle-Vendor: %Bundle-Vendor Bundle-RequiredExecutionEnvironment: JavaSE-17 +Bundle-SCM: url=https://github.com/eclipse-jgit/jgit, connection=scm:git:https://eclipse.gerrithub.io/eclipse-jgit/jgit.git, developerConnection=scm:git:https://eclipse.gerrithub.io/a/eclipse-jgit/jgit.git Require-Bundle: org.hamcrest.core;bundle-version="[1.3.0,2.0.0)", org.hamcrest.library;bundle-version="[1.3.0,2.0.0)" Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)", diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcObjectSizeIndexTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcObjectSizeIndexTest.java new file mode 100644 index 0000000000..1a05d88583 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcObjectSizeIndexTest.java @@ -0,0 +1,279 @@ +/* + * Copyright (C) 2025, Google LLC. 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.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.util.Collection; + +import org.eclipse.jgit.internal.storage.pack.PackExt; +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.revwalk.RevBlob; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.storage.pack.PackConfig; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class GcObjectSizeIndexTest extends GcTestCase { + + @Test + public void gc_2commits_noSizeLimit_blobsInIndex() throws Exception { + TestRepository<FileRepository>.BranchBuilder bb = tr + .branch("refs/heads/master"); + RevBlob blobA1 = tr.blob("7-bytes"); + RevBlob blobA2 = tr.blob("11-bytes xx"); + RevBlob blobB1 = tr.blob("B"); + RevBlob blobB2 = tr.blob("B2"); + bb.commit().add("A", blobA1).add("B", blobB1).create(); + bb.commit().add("A", blobA2).add("B", blobB2).create(); + + stats = gc.getStatistics(); + assertEquals(8, stats.numberOfLooseObjects); + assertEquals(0, stats.numberOfPackedObjects); + configureGc(gc, 0); + gc.gc().get(); + + stats = gc.getStatistics(); + assertEquals(1, stats.numberOfPackFiles); + assertEquals(4, stats.numberOfSizeIndexedObjects); + + assertTrue(getOnlyPack(repo).hasObjectSizeIndex()); + Pack pack = getOnlyPack(repo); + assertEquals(7, pack.getIndexedObjectSize(blobA1)); + assertEquals(11, pack.getIndexedObjectSize(blobA2)); + assertEquals(1, pack.getIndexedObjectSize(blobB1)); + assertEquals(2, pack.getIndexedObjectSize(blobB2)); + } + + @Test + public void gc_2commits_sizeLimit_biggerBlobsInIndex() throws Exception { + TestRepository<FileRepository>.BranchBuilder bb = tr + .branch("refs/heads/master"); + RevBlob blobA1 = tr.blob("7-bytes"); + RevBlob blobA2 = tr.blob("11-bytes xx"); + RevBlob blobB1 = tr.blob("B"); + RevBlob blobB2 = tr.blob("B2"); + bb.commit().add("A", blobA1).add("B", blobB1).create(); + bb.commit().add("A", blobA2).add("B", blobB2).create(); + + stats = gc.getStatistics(); + assertEquals(8, stats.numberOfLooseObjects); + assertEquals(0, stats.numberOfPackedObjects); + configureGc(gc, 5); + gc.gc().get(); + + stats = gc.getStatistics(); + assertEquals(1, stats.numberOfPackFiles); + assertEquals(2, stats.numberOfSizeIndexedObjects); + + assertTrue(getOnlyPack(repo).hasObjectSizeIndex()); + Pack pack = getOnlyPack(repo); + assertEquals(7, pack.getIndexedObjectSize(blobA1)); + assertEquals(11, pack.getIndexedObjectSize(blobA2)); + assertEquals(-1, pack.getIndexedObjectSize(blobB1)); + assertEquals(-1, pack.getIndexedObjectSize(blobB2)); + } + + @Test + public void gc_2commits_disableSizeIdx_noIdx() throws Exception { + TestRepository<FileRepository>.BranchBuilder bb = tr + .branch("refs/heads/master"); + RevBlob blobA1 = tr.blob("7-bytes"); + RevBlob blobA2 = tr.blob("11-bytes xx"); + RevBlob blobB1 = tr.blob("B"); + RevBlob blobB2 = tr.blob("B2"); + bb.commit().add("A", blobA1).add("B", blobB1).create(); + bb.commit().add("A", blobA2).add("B", blobB2).create(); + + stats = gc.getStatistics(); + assertEquals(8, stats.numberOfLooseObjects); + assertEquals(0, stats.numberOfPackedObjects); + configureGc(gc, -1); + gc.gc().get(); + + + stats = gc.getStatistics(); + assertEquals(1, stats.numberOfPackFiles); + assertEquals(0, stats.numberOfSizeIndexedObjects); + } + + @Test + public void gc_alreadyPacked_noChanges() + throws Exception { + tr.branch("refs/heads/master").commit().add("A", "A").add("B", "B") + .create(); + stats = gc.getStatistics(); + assertEquals(4, stats.numberOfLooseObjects); + assertEquals(0, stats.numberOfPackedObjects); + configureGc(gc, 0); + gc.gc().get(); + + stats = gc.getStatistics(); + assertEquals(4, stats.numberOfPackedObjects); + assertEquals(1, stats.numberOfPackFiles); + assertTrue(getOnlyPack(repo).hasObjectSizeIndex()); + assertEquals(2, stats.numberOfSizeIndexedObjects); + + // Do the gc again and check that it hasn't changed anything + gc.gc().get(); + stats = gc.getStatistics(); + assertEquals(4, stats.numberOfPackedObjects); + assertEquals(1, stats.numberOfPackFiles); + assertTrue(getOnlyPack(repo).hasObjectSizeIndex()); + assertEquals(2, stats.numberOfSizeIndexedObjects); + } + + @Test + public void gc_twoReachableCommits_oneUnreachable_twoPacks() + throws Exception { + TestRepository<FileRepository>.BranchBuilder bb = tr + .branch("refs/heads/master"); + RevCommit first = bb.commit().add("A", "A").add("B", "B").create(); + bb.commit().add("A", "A2").add("B", "B2").create(); + tr.update("refs/heads/master", first); + + stats = gc.getStatistics(); + assertEquals(8, stats.numberOfLooseObjects); + assertEquals(0, stats.numberOfPackedObjects); + configureGc(gc, 0); + gc.gc().get(); + stats = gc.getStatistics(); + assertEquals(0, stats.numberOfLooseObjects); + assertEquals(8, stats.numberOfPackedObjects); + assertEquals(2, stats.numberOfPackFiles); + assertEquals(4, stats.numberOfSizeIndexedObjects); + } + + @Test + public void gc_preserved_objSizeIdxIsPreserved() throws Exception { + Collection<Pack> oldPacks = preserveOldPacks(); + assertEquals(1, oldPacks.size()); + PackFile preserved = oldPacks.iterator().next().getPackFile() + .create(PackExt.OBJECT_SIZE_INDEX) + .createPreservedForDirectory( + repo.getObjectDatabase().getPreservedDirectory()); + assertTrue(preserved.exists()); + } + + @Test + public void gc_preserved_prune_noPreserves() throws Exception { + preserveOldPacks(); + configureGc(gc, 0).setPrunePreserved(true); + gc.gc().get(); + + assertFalse(repo.getObjectDatabase().getPreservedDirectory().exists()); + } + + private Collection<Pack> preserveOldPacks() throws Exception { + TestRepository<FileRepository>.BranchBuilder bb = tr + .branch("refs/heads/master"); + bb.commit().message("P").add("P", "P").create(); + + // pack loose object into packfile + configureGc(gc, 0); + gc.setExpireAgeMillis(0); + gc.gc().get(); + Collection<Pack> oldPacks = tr.getRepository().getObjectDatabase() + .getPacks(); + PackFile oldPackfile = oldPacks.iterator().next().getPackFile(); + assertTrue(oldPackfile.exists()); + + fsTick(); + bb.commit().message("B").add("B", "Q").create(); + + // repack again but now without a grace period for packfiles. We should + // end up with a new packfile and the old one should be placed in the + // preserved directory + gc.setPackExpireAgeMillis(0); + configureGc(gc, 0).setPreserveOldPacks(true); + gc.gc().get(); + + File preservedPackFile = oldPackfile.createPreservedForDirectory( + repo.getObjectDatabase().getPreservedDirectory()); + assertTrue(preservedPackFile.exists()); + return oldPacks; + } + + @Ignore + public void testPruneAndRestoreOldPacks() throws Exception { + String tempRef = "refs/heads/soon-to-be-unreferenced"; + TestRepository<FileRepository>.BranchBuilder bb = tr.branch(tempRef); + bb.commit().add("A", "A").add("B", "B").create(); + + // Verify setup conditions + stats = gc.getStatistics(); + assertEquals(4, stats.numberOfLooseObjects); + assertEquals(0, stats.numberOfPackedObjects); + + // Force all referenced objects into packs (to avoid having loose objects) + configureGc(gc, 0); + gc.setExpireAgeMillis(0); + gc.setPackExpireAgeMillis(0); + gc.gc().get(); + stats = gc.getStatistics(); + assertEquals(0, stats.numberOfLooseObjects); + assertEquals(4, stats.numberOfPackedObjects); + assertEquals(1, stats.numberOfPackFiles); + + // Delete the temp ref, orphaning its commit + RefUpdate update = tr.getRepository().getRefDatabase().newUpdate(tempRef, false); + update.setForceUpdate(true); + ObjectId objectId = update.getOldObjectId(); // remember it so we can restore it! + RefUpdate.Result result = update.delete(); + assertEquals(RefUpdate.Result.FORCED, result); + + fsTick(); + + // Repack with only orphaned commit, so packfile will be pruned + configureGc(gc, 0).setPreserveOldPacks(true); + gc.gc().get(); + stats = gc.getStatistics(); + assertEquals(0, stats.numberOfLooseObjects); + assertEquals(0, stats.numberOfPackedObjects); + assertEquals(0, stats.numberOfPackFiles); + + // Restore the temp ref to the deleted commit, should restore old-packs! + update = tr.getRepository().getRefDatabase().newUpdate(tempRef, false); + update.setNewObjectId(objectId); + update.setExpectedOldObjectId(null); + result = update.update(); + assertEquals(RefUpdate.Result.NEW, result); + + stats = gc.getStatistics(); + assertEquals(4, stats.numberOfPackedObjects); + assertEquals(1, stats.numberOfPackFiles); + } + + private PackConfig configureGc(GC myGc, int minSize) { + PackConfig pconfig = new PackConfig(repo); + pconfig.setMinBytesForObjSizeIndex(minSize); + myGc.setPackConfig(pconfig); + return pconfig; + } + + private Pack getOnlyPack(FileRepository fileRepo) + throws IOException { + Collection<Pack> packs = fileRepo.getObjectDatabase().getPacks(); + if (packs.size() != 1) { + throw new IOException("More than one pack"); + } + + return packs.iterator().next(); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ObjectDirectoryPackParserTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ObjectDirectoryPackParserTest.java new file mode 100644 index 0000000000..b17c577087 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ObjectDirectoryPackParserTest.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2021, Google LLC. and others + * Copyright (C) 2008, Imran M Yousuf <imyousuf@smartitengineering.com> + * Copyright (C) 2007-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.transport; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.MessageDigest; +import java.util.zip.Deflater; + +import org.eclipse.jgit.internal.storage.file.ObjectDirectoryPackParser; +import org.eclipse.jgit.internal.storage.file.Pack; +import org.eclipse.jgit.junit.JGitTestUtil; +import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevBlob; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.util.NB; +import org.eclipse.jgit.util.TemporaryBuffer; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** + * Pack parsing is covered in {@link PackParserTest}. + * + * Here we test ObjectDirectoryPackParser specific parts. e.g. that is creates + * the object-size index. + */ +public class ObjectDirectoryPackParserTest extends RepositoryTestCase { + + @Before + public void setup() throws IOException { + FileBasedConfig jGitConfig = mockSystemReader.getJGitConfig(); + jGitConfig.setInt(ConfigConstants.CONFIG_PACK_SECTION, null, + ConfigConstants.CONFIG_KEY_MIN_BYTES_OBJ_SIZE_INDEX, 7); + jGitConfig.save(); + } + + /** + * Test indexing one of the test packs in the egit repo. It has deltas. + * + * @throws IOException + */ + @Test + public void testGitPack() throws IOException { + File packFile = JGitTestUtil.getTestResourceFile("pack-34be9032ac282b11fa9babdc2b2a93ca996c9c2f.pack"); + try (InputStream is = new FileInputStream(packFile)) { + ObjectDirectoryPackParser p = index(is); + p.parse(NullProgressMonitor.INSTANCE); + + Pack pack = p.getPack(); + assertTrue(pack.hasObjectSizeIndex()); + + // Only blobs in the pack + ObjectId blob1 = ObjectId + .fromString("6ff87c4664981e4397625791c8ea3bbb5f2279a3"); + ObjectId blob2 = ObjectId + .fromString("5b6e7c66c276e7610d4a73c70ec1a1f7c1003259"); + assertEquals(18787, pack.getIndexedObjectSize(blob1)); + assertEquals(18009, pack.getIndexedObjectSize(blob2)); + + // Indexed sizes match object db sizes + assertEquals(db.getObjectDatabase().open(blob1).getSize(), + pack.getIndexedObjectSize(blob1)); + assertEquals(db.getObjectDatabase().open(blob2).getSize(), + pack.getIndexedObjectSize(blob2)); + + } + } + + /** + * This is just another pack. It so happens that we have two convenient pack to + * test with in the repository. + * + * @throws IOException + */ + @Test + public void testAnotherGitPack() throws IOException { + File packFile = JGitTestUtil.getTestResourceFile("pack-df2982f284bbabb6bdb59ee3fcc6eb0983e20371.pack"); + try (InputStream is = new FileInputStream(packFile)) { + ObjectDirectoryPackParser p = index(is); + p.parse(NullProgressMonitor.INSTANCE); + Pack pack = p.getPack(); + + // Blob smaller than threshold: + assertEquals(-1, pack.getIndexedObjectSize(ObjectId + .fromString("15fae9e651043de0fd1deef588aa3fbf5a7a41c6"))); + + // Blob bigger than threshold + assertEquals(10, pack.getIndexedObjectSize(ObjectId + .fromString("8230f48330e0055d9e0bc5a2a77718f6dd9324b8"))); + + // A commit (not indexed) + assertEquals(-1, pack.getIndexedObjectSize(ObjectId + .fromString("d0114ab8ac326bab30e3a657a0397578c5a1af88"))); + + // Object not in pack + assertEquals(-1, pack.getIndexedObjectSize(ObjectId + .fromString("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"))); + } + } + + @Test + public void testTinyThinPack() throws Exception { + // less than 16 bytes, so its length fits in a single byte later + String base = "abcdefghijklmn"; + RevBlob a; + try (TestRepository d = new TestRepository<Repository>(db)) { + a = d.blob(base); + } + + TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024); + + packHeader(pack, 1); + + pack.write(Constants.OBJ_REF_DELTA << 4 | 4); + a.copyRawTo(pack); + deflate(pack, new byte[] { (byte) base.length(), // size of the base + (byte) (base.length() + 1), // size after reconstruction + 0x1, 'b' }); // append one byte + + digest(pack); + + ObjectDirectoryPackParser p = index(new ByteArrayInputStream(pack.toByteArray())); + p.setAllowThin(true); + p.parse(NullProgressMonitor.INSTANCE); + + Pack writtenPack = p.getPack(); + // base + assertEquals(base.length(), writtenPack.getIndexedObjectSize(a)); + // undeltified blob + assertEquals(base.length() + 1, + writtenPack.getIndexedObjectSize(ObjectId.fromString( + "f177875498138143c9657cc52b049ad4d20d5223"))); + } + + @Test + public void testPackWithDuplicateBlob() throws Exception { + final byte[] data = Constants.encode("0123456789abcdefg"); + RevBlob blob; + try (TestRepository<Repository> d = new TestRepository<>(db)) { + blob = d.blob(data); + assertTrue(db.getObjectDatabase().has(blob)); + } + + TemporaryBuffer.Heap pack = new TemporaryBuffer.Heap(1024); + packHeader(pack, 1); + pack.write(Constants.OBJ_BLOB << 4 | 0x80 | 1); + pack.write(1); + deflate(pack, data); + digest(pack); + + ObjectDirectoryPackParser p = index( + new ByteArrayInputStream(pack.toByteArray())); + p.setAllowThin(false); + p.parse(NullProgressMonitor.INSTANCE); + + assertEquals(data.length, p.getPack().getIndexedObjectSize(blob)); + } + + private static void packHeader(TemporaryBuffer.Heap tinyPack, int cnt) + throws IOException { + final byte[] hdr = new byte[8]; + NB.encodeInt32(hdr, 0, 2); + NB.encodeInt32(hdr, 4, cnt); + + tinyPack.write(Constants.PACK_SIGNATURE); + tinyPack.write(hdr, 0, 8); + } + + private static void deflate(TemporaryBuffer.Heap tinyPack, + final byte[] content) + throws IOException { + final Deflater deflater = new Deflater(); + final byte[] buf = new byte[128]; + deflater.setInput(content, 0, content.length); + deflater.finish(); + do { + final int n = deflater.deflate(buf, 0, buf.length); + if (n > 0) + tinyPack.write(buf, 0, n); + } while (!deflater.finished()); + } + + private static void digest(TemporaryBuffer.Heap buf) throws IOException { + MessageDigest md = Constants.newMessageDigest(); + md.update(buf.toByteArray()); + buf.write(md.digest()); + } + + private ObjectInserter inserter; + + @After + public void release() { + if (inserter != null) { + inserter.close(); + } + } + + private ObjectDirectoryPackParser index(InputStream in) throws IOException { + if (inserter == null) + inserter = db.newObjectInserter(); + return (ObjectDirectoryPackParser) inserter.newPackParser(in); + } +} |