Add "aggressive" option to GC

JGit should offer the possibility to do a garbage collection in
"aggressive" mode. In this mode garbage collection more aggressively
optimize the repository at the expense of taking much more time.
Technically a aggressive mode garbage collection differs from a
non-aggressive one by:
- not reusing packed objects found in old packs. Recompress every object
- the configuration pack.window is set to 250 (the default is 10)
- the configuration pack.depths is set to 250 (the default is 50)

The associated classes in org.eclipse.jgit.api and the command line
command in org.eclipse.jgit.pgm expose this new option.

The configuration parameters gc.aggressiveDepth and gc.aggressiveWindow
have been introduced to configure this feature.

Bug: 444332
Change-Id: I024101f2810acf6be13ce144c9893d98f5c4ae76
This commit is contained in:
Christian Halstrick 2014-09-17 15:57:25 +02:00 committed by Matthias Sohn
parent f2ebc8d4c5
commit 227357f929
6 changed files with 124 additions and 20 deletions

View File

@ -184,6 +184,7 @@ unmergedPaths=Unmerged paths:
unsupportedOperation=Unsupported operation: {0} unsupportedOperation=Unsupported operation: {0}
untrackedFiles=Untracked files: untrackedFiles=Untracked files:
updating=Updating {0}..{1} updating=Updating {0}..{1}
usage_Aggressive=This option will cause gc to more aggressively optimize the repository at the expense of taking much more time
usage_Blame=Show what revision and author last modified each line usage_Blame=Show what revision and author last modified each line
usage_CommandLineClientForamazonsS3Service=Command line client for Amazon's S3 service usage_CommandLineClientForamazonsS3Service=Command line client for Amazon's S3 service
usage_CommitAll=commit all modified and deleted files usage_CommitAll=commit all modified and deleted files

View File

@ -43,16 +43,19 @@
package org.eclipse.jgit.pgm; package org.eclipse.jgit.pgm;
import org.eclipse.jgit.internal.storage.file.FileRepository; import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.internal.storage.file.GC;
import org.eclipse.jgit.lib.TextProgressMonitor; import org.eclipse.jgit.lib.TextProgressMonitor;
import org.kohsuke.args4j.Option;
@Command(common = true, usage = "usage_Gc") @Command(common = true, usage = "usage_Gc")
class Gc extends TextBuiltin { class Gc extends TextBuiltin {
@Option(name = "--aggressive", usage = "usage_Aggressive")
private boolean aggressive;
@Override @Override
protected void run() throws Exception { protected void run() throws Exception {
GC gc = new GC((FileRepository) db); Git git = Git.wrap(db);
gc.setProgressMonitor(new TextProgressMonitor()); git.gc().setAggressive(aggressive)
gc.gc(); .setProgressMonitor(new TextProgressMonitor()).call();
} }
} }

View File

@ -51,21 +51,32 @@
import org.eclipse.jgit.junit.TestRepository.BranchBuilder; import org.eclipse.jgit.junit.TestRepository.BranchBuilder;
import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Test; import org.eclipse.jgit.storage.pack.PackConfig;
import org.junit.experimental.theories.DataPoints;
import org.junit.experimental.theories.Theories;
import org.junit.experimental.theories.Theory;
import org.junit.runner.RunWith;
@RunWith(Theories.class)
public class GcBasicPackingTest extends GcTestCase { public class GcBasicPackingTest extends GcTestCase {
@Test @DataPoints
public void repackEmptyRepo_noPackCreated() throws IOException { public static boolean[] aggressiveValues = { true, false };
@Theory
public void repackEmptyRepo_noPackCreated(boolean aggressive)
throws IOException {
configureGc(gc, aggressive);
gc.repack(); gc.repack();
assertEquals(0, repo.getObjectDatabase().getPacks().size()); assertEquals(0, repo.getObjectDatabase().getPacks().size());
} }
@Test @Theory
public void testPackRepoWithNoRefs() throws Exception { public void testPackRepoWithNoRefs(boolean aggressive) throws Exception {
tr.commit().add("A", "A").add("B", "B").create(); tr.commit().add("A", "A").add("B", "B").create();
stats = gc.getStatistics(); stats = gc.getStatistics();
assertEquals(4, stats.numberOfLooseObjects); assertEquals(4, stats.numberOfLooseObjects);
assertEquals(0, stats.numberOfPackedObjects); assertEquals(0, stats.numberOfPackedObjects);
configureGc(gc, aggressive);
gc.gc(); gc.gc();
stats = gc.getStatistics(); stats = gc.getStatistics();
assertEquals(4, stats.numberOfLooseObjects); assertEquals(4, stats.numberOfLooseObjects);
@ -73,8 +84,8 @@ public void testPackRepoWithNoRefs() throws Exception {
assertEquals(0, stats.numberOfPackFiles); assertEquals(0, stats.numberOfPackFiles);
} }
@Test @Theory
public void testPack2Commits() throws Exception { public void testPack2Commits(boolean aggressive) throws Exception {
BranchBuilder bb = tr.branch("refs/heads/master"); BranchBuilder bb = tr.branch("refs/heads/master");
bb.commit().add("A", "A").add("B", "B").create(); bb.commit().add("A", "A").add("B", "B").create();
bb.commit().add("A", "A2").add("B", "B2").create(); bb.commit().add("A", "A2").add("B", "B2").create();
@ -82,6 +93,7 @@ public void testPack2Commits() throws Exception {
stats = gc.getStatistics(); stats = gc.getStatistics();
assertEquals(8, stats.numberOfLooseObjects); assertEquals(8, stats.numberOfLooseObjects);
assertEquals(0, stats.numberOfPackedObjects); assertEquals(0, stats.numberOfPackedObjects);
configureGc(gc, aggressive);
gc.gc(); gc.gc();
stats = gc.getStatistics(); stats = gc.getStatistics();
assertEquals(0, stats.numberOfLooseObjects); assertEquals(0, stats.numberOfLooseObjects);
@ -89,13 +101,15 @@ public void testPack2Commits() throws Exception {
assertEquals(1, stats.numberOfPackFiles); assertEquals(1, stats.numberOfPackFiles);
} }
@Test @Theory
public void testPackAllObjectsInOnePack() throws Exception { public void testPackAllObjectsInOnePack(boolean aggressive)
throws Exception {
tr.branch("refs/heads/master").commit().add("A", "A").add("B", "B") tr.branch("refs/heads/master").commit().add("A", "A").add("B", "B")
.create(); .create();
stats = gc.getStatistics(); stats = gc.getStatistics();
assertEquals(4, stats.numberOfLooseObjects); assertEquals(4, stats.numberOfLooseObjects);
assertEquals(0, stats.numberOfPackedObjects); assertEquals(0, stats.numberOfPackedObjects);
configureGc(gc, aggressive);
gc.gc(); gc.gc();
stats = gc.getStatistics(); stats = gc.getStatistics();
assertEquals(0, stats.numberOfLooseObjects); assertEquals(0, stats.numberOfLooseObjects);
@ -110,8 +124,8 @@ public void testPackAllObjectsInOnePack() throws Exception {
assertEquals(1, stats.numberOfPackFiles); assertEquals(1, stats.numberOfPackFiles);
} }
@Test @Theory
public void testPackCommitsAndLooseOne() throws Exception { public void testPackCommitsAndLooseOne(boolean aggressive) throws Exception {
BranchBuilder bb = tr.branch("refs/heads/master"); BranchBuilder bb = tr.branch("refs/heads/master");
RevCommit first = bb.commit().add("A", "A").add("B", "B").create(); RevCommit first = bb.commit().add("A", "A").add("B", "B").create();
bb.commit().add("A", "A2").add("B", "B2").create(); bb.commit().add("A", "A2").add("B", "B2").create();
@ -120,6 +134,7 @@ public void testPackCommitsAndLooseOne() throws Exception {
stats = gc.getStatistics(); stats = gc.getStatistics();
assertEquals(8, stats.numberOfLooseObjects); assertEquals(8, stats.numberOfLooseObjects);
assertEquals(0, stats.numberOfPackedObjects); assertEquals(0, stats.numberOfPackedObjects);
configureGc(gc, aggressive);
gc.gc(); gc.gc();
stats = gc.getStatistics(); stats = gc.getStatistics();
assertEquals(0, stats.numberOfLooseObjects); assertEquals(0, stats.numberOfLooseObjects);
@ -127,8 +142,8 @@ public void testPackCommitsAndLooseOne() throws Exception {
assertEquals(2, stats.numberOfPackFiles); assertEquals(2, stats.numberOfPackFiles);
} }
@Test @Theory
public void testNotPackTwice() throws Exception { public void testNotPackTwice(boolean aggressive) throws Exception {
BranchBuilder bb = tr.branch("refs/heads/master"); BranchBuilder bb = tr.branch("refs/heads/master");
RevCommit first = bb.commit().message("M").add("M", "M").create(); RevCommit first = bb.commit().message("M").add("M", "M").create();
bb.commit().message("B").add("B", "Q").create(); bb.commit().message("B").add("B", "Q").create();
@ -146,6 +161,7 @@ public void testNotPackTwice() throws Exception {
gc.setExpireAgeMillis(0); gc.setExpireAgeMillis(0);
fsTick(); fsTick();
configureGc(gc, aggressive);
gc.gc(); gc.gc();
stats = gc.getStatistics(); stats = gc.getStatistics();
assertEquals(0, stats.numberOfLooseObjects); assertEquals(0, stats.numberOfLooseObjects);
@ -159,4 +175,15 @@ public void testNotPackTwice() throws Exception {
assertEquals(9, pIt.next().getObjectCount()); assertEquals(9, pIt.next().getObjectCount());
} }
} }
private void configureGc(GC myGc, boolean aggressive) {
PackConfig pconfig = new PackConfig(repo);
if (aggressive) {
pconfig.setDeltaSearchWindowSize(250);
pconfig.setMaxDeltaDepth(250);
pconfig.setReuseObjects(false);
} else
pconfig = new PackConfig(repo);
myGc.setPackConfig(pconfig);
}
} }

View File

@ -54,8 +54,11 @@
import org.eclipse.jgit.internal.storage.file.FileRepository; import org.eclipse.jgit.internal.storage.file.FileRepository;
import org.eclipse.jgit.internal.storage.file.GC; import org.eclipse.jgit.internal.storage.file.GC;
import org.eclipse.jgit.internal.storage.file.GC.RepoStatistics; import org.eclipse.jgit.internal.storage.file.GC.RepoStatistics;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.storage.pack.PackConfig;
import org.eclipse.jgit.util.GitDateParser; import org.eclipse.jgit.util.GitDateParser;
/** /**
@ -69,11 +72,28 @@
* >Git documentation about gc</a> * >Git documentation about gc</a>
*/ */
public class GarbageCollectCommand extends GitCommand<Properties> { public class GarbageCollectCommand extends GitCommand<Properties> {
/**
* Default value of maximum delta chain depth during aggressive garbage
* collection: {@value}
*
* @since 3.6
*/
public static final int DEFAULT_GC_AGGRESSIVE_DEPTH = 250;
/**
* Default window size during packing during aggressive garbage collection:
* * {@value}
*
* @since 3.6
*/
public static final int DEFAULT_GC_AGGRESSIVE_WINDOW = 250;
private ProgressMonitor monitor; private ProgressMonitor monitor;
private Date expire; private Date expire;
private PackConfig pconfig;
/** /**
* @param repo * @param repo
*/ */
@ -82,6 +102,7 @@ protected GarbageCollectCommand(Repository repo) {
if (!(repo instanceof FileRepository)) if (!(repo instanceof FileRepository))
throw new UnsupportedOperationException(MessageFormat.format( throw new UnsupportedOperationException(MessageFormat.format(
JGitText.get().unsupportedGC, repo.getClass().toString())); JGitText.get().unsupportedGC, repo.getClass().toString()));
pconfig = new PackConfig(repo);
} }
/** /**
@ -110,11 +131,41 @@ public GarbageCollectCommand setExpire(Date expire) {
return this; return this;
} }
/**
* Whether to use aggressive mode or not. If set to true JGit behaves more
* similar to native git's "git gc --aggressive". If set to
* <code>true</code> compressed objects found in old packs are not reused
* but every object is compressed again. Configuration variables
* pack.window and pack.depth are set to 250 for this GC.
*
* @since 3.6
* @param aggressive
* whether to turn on or off aggressive mode
* @return this instance
*/
public GarbageCollectCommand setAggressive(boolean aggressive) {
if (aggressive) {
StoredConfig repoConfig = repo.getConfig();
pconfig.setDeltaSearchWindowSize(repoConfig.getInt(
ConfigConstants.CONFIG_GC_SECTION,
ConfigConstants.CONFIG_KEY_AGGRESSIVE_WINDOW,
DEFAULT_GC_AGGRESSIVE_WINDOW));
pconfig.setMaxDeltaDepth(repoConfig.getInt(
ConfigConstants.CONFIG_GC_SECTION,
ConfigConstants.CONFIG_KEY_AGGRESSIVE_DEPTH,
DEFAULT_GC_AGGRESSIVE_DEPTH));
pconfig.setReuseObjects(false);
} else
pconfig = new PackConfig(repo);
return this;
}
@Override @Override
public Properties call() throws GitAPIException { public Properties call() throws GitAPIException {
checkCallable(); checkCallable();
GC gc = new GC((FileRepository) repo); GC gc = new GC((FileRepository) repo);
gc.setPackConfig(pconfig);
gc.setProgressMonitor(monitor); gc.setProgressMonitor(monitor);
if (this.expire != null) if (this.expire != null)
gc.setExpire(expire); gc.setExpire(expire);

View File

@ -93,6 +93,7 @@
import org.eclipse.jgit.revwalk.ObjectWalk; import org.eclipse.jgit.revwalk.ObjectWalk;
import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.storage.pack.PackConfig;
import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.TreeFilter; import org.eclipse.jgit.treewalk.filter.TreeFilter;
import org.eclipse.jgit.util.FileUtils; import org.eclipse.jgit.util.FileUtils;
@ -117,6 +118,8 @@ public class GC {
private Date expire; private Date expire;
private PackConfig pconfig = null;
/** /**
* the refs which existed during the last call to {@link #repack()}. This is * 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 * needed during {@link #prune(Set)} where we can optimize by looking at the
@ -686,7 +689,7 @@ public int compare(PackExt o1, PackExt o2) {
} }
}); });
PackWriter pw = new PackWriter(repo); PackWriter pw = new PackWriter((pconfig == null) ? new PackConfig(repo) : pconfig, repo.newObjectReader());
try { try {
// prepare the PackWriter // prepare the PackWriter
pw.setDeltaBaseAsOffset(true); pw.setDeltaBaseAsOffset(true);
@ -947,6 +950,19 @@ public void setExpireAgeMillis(long expireAgeMillis) {
expire = null; expire = null;
} }
/**
* Set the PackConfig used when (re-)writing packfiles. This allows to
* influence how packs are written and to implement something similar to
* "git gc --aggressive"
*
* @since 3.6
* @param pconfig
* the {@link PackConfig} used when writing packs
*/
public void setPackConfig(PackConfig pconfig) {
this.pconfig = pconfig;
}
/** /**
* During gc() or prune() each unreferenced, loose object which has been * During gc() or prune() each unreferenced, loose object which has been
* created or modified after or at <code>expire</code> will not be pruned. * created or modified after or at <code>expire</code> will not be pruned.

View File

@ -226,6 +226,12 @@ public class ConfigConstants {
/** The "pruneexpire" key */ /** The "pruneexpire" key */
public static final String CONFIG_KEY_PRUNEEXPIRE = "pruneexpire"; public static final String CONFIG_KEY_PRUNEEXPIRE = "pruneexpire";
/** The "aggressiveDepth" key */
public static final String CONFIG_KEY_AGGRESSIVE_DEPTH = "aggressiveDepth";
/** The "aggressiveWindow" key */
public static final String CONFIG_KEY_AGGRESSIVE_WINDOW = "aggressiveWindow";
/** The "mergeoptions" key */ /** The "mergeoptions" key */
public static final String CONFIG_KEY_MERGEOPTIONS = "mergeoptions"; public static final String CONFIG_KEY_MERGEOPTIONS = "mergeoptions";