Merge changes I70d0e59d,I10ed8daf,Icdf7a34f * changes: Add missing LF at the end of GarbageCollectCommandTest Add GarbageCollectCommand#setGcConfig GC: add flag to control whether gc should pack all refs
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/GarbageCollectCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/GarbageCollectCommandTest.java index 6090d5e..2954570 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/GarbageCollectCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/GarbageCollectCommandTest.java
@@ -9,12 +9,16 @@ */ package org.eclipse.jgit.api; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import java.io.IOException; import java.time.Instant; import java.util.Properties; import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.lib.GcConfig; +import org.eclipse.jgit.lib.GcConfig.PackRefsMode; import org.eclipse.jgit.util.GitTimeParser; import org.junit.Before; import org.junit.Test; @@ -34,6 +38,22 @@ public void setUp() throws Exception { } @Test + public void testPackRefs() throws Exception { + assertTrue(hasLooseRef(git)); + + // by default, refs should be packed + git.gc().call(); + assertFalse(hasLooseRef(git)); + + // now create a loose ref again + git.branchCreate().setName("foo").call(); + assertTrue(hasLooseRef(git)); + + git.gc().setGcConfig(new GcConfig(PackRefsMode.FALSE)).call(); + assertTrue(hasLooseRef(git)); + } + + @Test public void testGConeCommit() throws Exception { Instant expireNow = GitTimeParser.parseInstant("now"); Properties res = git.gc().setExpire(expireNow).call(); @@ -54,4 +74,10 @@ public void testGCmoreCommits() throws Exception { Properties res = git.gc().setExpire(expireNow).call(); assertTrue(res.size() == 8); } + + private static boolean hasLooseRef(Git git) throws IOException { + return git.getRepository().getRefDatabase().getRefs().stream() + .filter(r -> !r.isSymbolic()) + .anyMatch(r -> r.getStorage().isLoose()); + } }
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java index 434f7e4..101feec 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java
@@ -21,8 +21,11 @@ import java.util.Collection; import java.util.List; +import org.eclipse.jgit.api.Git; import org.eclipse.jgit.junit.TestRepository.BranchBuilder; import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.lib.GcConfig; +import org.eclipse.jgit.lib.GcConfig.PackRefsMode; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.revwalk.RevCommit; @@ -376,4 +379,106 @@ private PackConfig configureGc(GC myGc, boolean aggressive) { myGc.setPackConfig(pconfig); return pconfig; } + + @Test + public void testPackRefs() throws Exception { + tr.branch("refs/heads/master").commit().add("A", "A").add("B", "B") + .create(); + assertHasLooseRef(repo); + + // by default, refs should be packed + gc.gc().get(); + assertHasNoLooseRef(repo); + + // now create a loose ref again + tr.branch("refs/heads/foo").commit().add("C", "C").create(); + assertHasLooseRef(repo); + // and disable packing of refs + gc.setGcConfig(new GcConfig(PackRefsMode.FALSE)); + gc.gc().get(); + assertHasLooseRef(repo); + } + + @Test + public void testPackRefsWithConfig() throws Exception { + tr.branch("refs/heads/master").commit().add("A", "A").add("B", "B") + .create(); + assertHasLooseRef(repo); + + // disable packing of refs via config + FileBasedConfig config = repo.getConfig(); + config.setEnum(ConfigConstants.CONFIG_GC_SECTION, null, + ConfigConstants.CONFIG_KEY_PACK_REFS, PackRefsMode.FALSE); + config.save(); + reloadGcFromRepoConfig(); + + gc.gc().get(); + assertHasLooseRef(repo); + + // now enable packing of refs via API + gc.setGcConfig(new GcConfig(PackRefsMode.TRUE)); + gc.gc().get(); + assertHasNoLooseRef(repo); + } + + @Test + public void testPackRefsWithNotBareConfig() throws Exception { + // non-bare repo + tr.branch("refs/heads/master").commit().add("A", "A").add("B", "B") + .create(); + assertHasLooseRef(repo); + + // configure packing of refs only for non-bare repositories + FileBasedConfig config = repo.getConfig(); + config.setEnum(ConfigConstants.CONFIG_GC_SECTION, null, + ConfigConstants.CONFIG_KEY_PACK_REFS, PackRefsMode.NOTBARE); + config.save(); + reloadGcFromRepoConfig(); + + gc.gc().get(); + assertHasNoLooseRef(repo); + + // bare repo + String repoUri = repo.getDirectory().toURI().toString(); + File dir = createUniqueTestGitDir(true); + try (FileRepository bareRepo = (FileRepository) Git.cloneRepository() + .setBare(true).setURI(repoUri).setDirectory(dir).call() + .getRepository(); Git git = new Git(bareRepo)) { + git.branchCreate().setName("refs/heads/branch-in-bare-repo") + .setStartPoint("refs/heads/master").call(); + assertHasLooseRef(bareRepo); + + // configure packing of refs only for non-bare repositories + FileBasedConfig bareConfig = bareRepo.getConfig(); + bareConfig.setEnum(ConfigConstants.CONFIG_GC_SECTION, null, + ConfigConstants.CONFIG_KEY_PACK_REFS, PackRefsMode.NOTBARE); + bareConfig.save(); + // create a new GC instance to reread the config + gc = new GC(bareRepo); + + gc.gc().get(); + assertHasLooseRef(bareRepo); + } + } + + private static boolean hasLooseRef(FileRepository repository) + throws IOException { + return repository.getRefDatabase().getRefs().stream() + .filter(r -> !r.isSymbolic()) + .anyMatch(r -> r.getStorage().isLoose()); + } + + private void reloadGcFromRepoConfig() { + gc = new GC(repo); + } + + private static void assertHasLooseRef(FileRepository repository) + throws IOException { + assertTrue("should have loose ref", hasLooseRef(repository)); + } + + private static void assertHasNoLooseRef(FileRepository repository) + throws IOException { + assertFalse("should have no loose ref", hasLooseRef(repository)); + } }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/GarbageCollectCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/GarbageCollectCommand.java index f6935e1..a16be79 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/GarbageCollectCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/GarbageCollectCommand.java
@@ -26,6 +26,7 @@ import org.eclipse.jgit.internal.storage.file.GC; import org.eclipse.jgit.internal.storage.file.GC.RepoStatistics; import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.lib.GcConfig; import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.StoredConfig; @@ -66,6 +67,8 @@ public class GarbageCollectCommand extends GitCommand<Properties> { private Boolean packKeptObjects; + private GcConfig gcConfig; + /** * Constructor for GarbageCollectCommand. * @@ -75,6 +78,7 @@ public class GarbageCollectCommand extends GitCommand<Properties> { protected GarbageCollectCommand(Repository repo) { super(repo); pconfig = new PackConfig(repo); + gcConfig = repo.getConfig().get(GcConfig.KEY); } /** @@ -200,6 +204,19 @@ public GarbageCollectCommand setPrunePreserved(boolean prunePreserved) { return this; } + /** + * Set the gc configuration + * + * @param gcConfig + * the gc configuration + * @return {@code this} + * @since 7.6 + */ + public GarbageCollectCommand setGcConfig(GcConfig gcConfig) { + this.gcConfig = gcConfig; + return this; + } + @Override public Properties call() throws GitAPIException { checkCallable(); @@ -214,6 +231,7 @@ public Properties call() throws GitAPIException { if (this.packKeptObjects != null) { gc.setPackKeptObjects(packKeptObjects.booleanValue()); } + gc.setGcConfig(gcConfig); try { gc.gc().get(); return toProperties(gc.getStatistics());
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java index 7c6a304..6a78271 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
@@ -82,6 +82,8 @@ import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.CoreConfig; import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.GcConfig; +import org.eclipse.jgit.lib.GcConfig.PackRefsMode; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdSet; @@ -171,6 +173,8 @@ public static void setExecutor(ExecutorService e) { private PackConfig pconfig; + private GcConfig gcConfig; + /** * the refs which existed during the last call to {@link #repack()}. This is * needed during {@link #prune(Set)} where we can optimize by looking at the @@ -207,6 +211,7 @@ public GC(FileRepository repo) { this.repo = repo; this.pconfig = new PackConfig(repo); this.pm = NullProgressMonitor.INSTANCE; + this.gcConfig = repo.getConfig().get(GcConfig.KEY); } /** @@ -282,6 +287,18 @@ private ExecutorService executor() { return (executor != null) ? executor : WorkQueue.getExecutor(); } + /** + * Set the gc configuration. + * + * @param gcConfig + * the gc configuration + * @return this instance + */ + public GC setGcConfig(GcConfig gcConfig) { + this.gcConfig = gcConfig; + return this; + } + private Collection<Pack> doGc() throws IOException, ParseException, GitAPIException { if (automatic && !needGc()) { @@ -292,8 +309,13 @@ private Collection<Pack> doGc() return Collections.emptyList(); } pm.start(6 /* tasks */); - new PackRefsCommand(repo).setProgressMonitor(pm).setAll(true) - .call(); + boolean packRefs = gcConfig.getPackRefs() == PackRefsMode.TRUE + || (gcConfig.getPackRefs() == PackRefsMode.NOTBARE + && !repo.isBare()); + if (packRefs) { + new PackRefsCommand(repo).setProgressMonitor(pm).setAll(true) + .call(); + } // TODO: implement reflog_expire(pm, repo); Collection<Pack> newPacks = repack(); prune(Collections.emptySet()); @@ -747,6 +769,15 @@ private long getPackExpireDate() throws ParseException { } /** + * Get the gc configuration. + * + * @return the gc configuration. + */ + public GcConfig getGcConfig() { + return gcConfig; + } + + /** * Remove all entries from a map which key is the id of an object referenced * by the given ObjectWalk *
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java index 56b801e..73c7006 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
@@ -1122,4 +1122,11 @@ public final class ConfigConstants { * @since 7.5 */ public static final String CONFIG_KEY_MULTIPACKINDEX = "multiPackIndex"; + + /** + * The "packRefs" key + * + * @since 7.6 + */ + public static final String CONFIG_KEY_PACK_REFS = "packRefs"; }
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GcConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GcConfig.java new file mode 100644 index 0000000..7239bd7 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GcConfig.java
@@ -0,0 +1,73 @@ +/* + * Copyright (C) 2026, Matthias Sohn <matthias.sohn@sap.com> + * + * 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.lib; + +import org.eclipse.jgit.lib.Config.SectionParser; + +/** + * GC configuration settings. + * + * @since 7.6 + */ +public class GcConfig { + /** Key for {@link Config#get(SectionParser)}. */ + public static final Config.SectionParser<GcConfig> KEY = GcConfig::new; + + /** + * Whether to pack refs during gc. + */ + public enum PackRefsMode { + /** + * Pack no refs. + */ + FALSE, + /** + * Pack all refs except symbolic refs. + */ + TRUE, + /** + * Pack all refs except symbolic refs within all non-bare repositories. + */ + NOTBARE + } + + private final PackRefsMode packRefs; + + /** + * Create a new gc configuration from the passed configuration. + * + * @param rc + * git configuration + */ + public GcConfig(Config rc) { + this.packRefs = rc.getEnum(ConfigConstants.CONFIG_GC_SECTION, null, + ConfigConstants.CONFIG_KEY_PACK_REFS, PackRefsMode.TRUE); + } + + /** + * Create a new gc configuration with the given settings. + * + * @param packRefs + * whether to pack refs during gc + */ + public GcConfig(PackRefsMode packRefs) { + this.packRefs = packRefs; + } + + /** + * Get the pack refs mode configuring which refs to pack during gc. + * + * @return the pack refs mode configuring which refs to pack during gc. + */ + public PackRefsMode getPackRefs() { + return packRefs; + } + +}