usage_MakeCacheTree=Show the current cache tree structure
usage_MergeBase=Find as good common ancestors as possible for a merge
usage_MergesTwoDevelopmentHistories=Merges two development histories
+usage_PreserveOldPacks=Preserve old pack files by moving them into the preserved subdirectory instead of deleting them after repacking
+usage_PrunePreserved=Remove the preserved subdirectory containing previously preserved old pack files before repacking, and before preserving more old pack files
usage_ReadDirCache= Read the DirCache 100 times
usage_RebuildCommitGraph=Recreate a repository from another one's commit graph
usage_RebuildRefTree=Copy references into a RefTree
@Option(name = "--aggressive", usage = "usage_Aggressive")
private boolean aggressive;
+ @Option(name = "--preserve-oldpacks", usage = "usage_PreserveOldPacks")
+ private boolean preserveOldPacks;
+
+ @Option(name = "--prune-preserved", usage = "usage_PrunePreserved")
+ private boolean prunePreserved;
+
@Override
protected void run() throws Exception {
Git git = Git.wrap(db);
git.gc().setAggressive(aggressive)
+ .setPreserveOldPacks(preserveOldPacks)
+ .setPrunePreserved(prunePreserved)
.setProgressMonitor(new TextProgressMonitor(errw)).call();
}
}
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;
}
- private void configureGc(GC myGc, boolean aggressive) {
+ @Test
+ public void testPreserveAndPruneOldPacks() throws Exception {
+ testPreserveOldPacks();
+ configureGc(gc, false).setPrunePreserved(true);
+ gc.gc();
+
+ assertFalse(repo.getObjectDatabase().getPreservedDirectory().exists());
+ }
+
+ private void testPreserveOldPacks() throws Exception {
+ BranchBuilder bb = tr.branch("refs/heads/master");
+ bb.commit().message("P").add("P", "P").create();
+
+ // pack loose object into packfile
+ gc.setExpireAgeMillis(0);
+ gc.gc();
+ File oldPackfile = tr.getRepository().getObjectDatabase().getPacks()
+ .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, false).setPreserveOldPacks(true);
+ gc.gc();
+
+ File oldPackDir = repo.getObjectDatabase().getPreservedDirectory();
+ String oldPackFileName = oldPackfile.getName();
+ String oldPackName = oldPackFileName.substring(0,
+ oldPackFileName.lastIndexOf('.')) + ".old-pack"; //$NON-NLS-1$
+ File preservePackFile = new File(oldPackDir, oldPackName);
+ assertTrue(preservePackFile.exists());
+ }
+
+ private PackConfig configureGc(GC myGc, boolean aggressive) {
PackConfig pconfig = new PackConfig(repo);
if (aggressive) {
pconfig.setDeltaSearchWindowSize(250);
} else
pconfig = new PackConfig(repo);
myGc.setPackConfig(pconfig);
+ return pconfig;
}
}
return this;
}
+ /**
+ * Whether to preserve old pack files instead of deleting them.
+ *
+ * @since 4.7
+ * @param preserveOldPacks
+ * whether to preserve old pack files
+ * @return this instance
+ */
+ public GarbageCollectCommand setPreserveOldPacks(boolean preserveOldPacks) {
+ if (pconfig == null)
+ pconfig = new PackConfig(repo);
+
+ pconfig.setPreserveOldPacks(preserveOldPacks);
+ return this;
+ }
+
+ /**
+ * Whether to prune preserved pack files in the preserved directory.
+ *
+ * @since 4.7
+ * @param prunePreserved
+ * whether to prune preserved pack files
+ * @return this instance
+ */
+ public GarbageCollectCommand setPrunePreserved(boolean prunePreserved) {
+ if (pconfig == null)
+ pconfig = new PackConfig(repo);
+
+ pconfig.setPrunePreserved(prunePreserved);
+ return this;
+ }
+
@Override
public Properties call() throws GitAPIException {
checkCallable();
/**
* Delete old pack files. What is 'old' is defined by specifying a set of
* old pack files and a set of new pack files. Each pack file contained in
- * old pack files but not contained in new pack files will be deleted. If an
- * expirationDate is set then pack files which are younger than the
- * expirationDate will not be deleted.
+ * old pack files but not contained in new pack files will be deleted. If
+ * preserveOldPacks is set, keep a copy of the pack file in the preserve
+ * directory. If an expirationDate is set then pack files which are younger
+ * than the expirationDate will not be deleted nor preserved.
*
* @param oldPacks
* @param newPacks
*/
private void deleteOldPacks(Collection<PackFile> oldPacks,
Collection<PackFile> newPacks) throws ParseException, IOException {
+ prunePreserved();
long packExpireDate = getPackExpireDate();
oldPackLoop: for (PackFile oldPack : oldPacks) {
String oldName = oldPack.getPackName();
prunePack(oldName);
}
}
- // close the complete object database. Thats my only chance to force
+ // close the complete object database. That's my only chance to force
// rescanning and to detect that certain pack files are now deleted.
repo.getObjectDatabase().close();
}
+ /**
+ * Deletes old pack file, unless 'preserve-oldpacks' is set, in which case it
+ * moves the pack file to the preserved directory
+ *
+ * @param packFile
+ * @param packName
+ * @param ext
+ * @param deleteOptions
+ * @throws IOException
+ */
+ private void removeOldPack(File packFile, String packName, PackExt ext,
+ int deleteOptions) throws IOException {
+ if (pconfig != null && pconfig.isPreserveOldPacks()) {
+ File oldPackDir = repo.getObjectDatabase().getPreservedDirectory();
+ FileUtils.mkdir(oldPackDir, true);
+
+ String oldPackName = "pack-" + packName + ".old-" + ext.getExtension(); //$NON-NLS-1$ //$NON-NLS-2$
+ File oldPackFile = new File(oldPackDir, oldPackName);
+ FileUtils.rename(packFile, oldPackFile);
+ } else {
+ FileUtils.delete(packFile, deleteOptions);
+ }
+ }
+
+ /**
+ * Delete the preserved directory including all pack files within
+ */
+ private void prunePreserved() {
+ if (pconfig != null && pconfig.isPrunePreserved()) {
+ try {
+ FileUtils.delete(repo.getObjectDatabase().getPreservedDirectory(),
+ FileUtils.RECURSIVE | FileUtils.RETRY | FileUtils.SKIP_MISSING);
+ } catch (IOException e) {
+ // Deletion of the preserved pack files failed. Silently return.
+ }
+ }
+ }
+
/**
* Delete files associated with a single pack file. First try to delete the
* ".pack" file because on some platforms the ".pack" file may be locked and
for (PackExt ext : extensions)
if (PackExt.PACK.equals(ext)) {
File f = nameFor(packName, "." + ext.getExtension()); //$NON-NLS-1$
- FileUtils.delete(f, deleteOptions);
+ removeOldPack(f, packName, ext, deleteOptions);
break;
}
// The .pack file has been deleted. Delete as many as the other
for (PackExt ext : extensions) {
if (!PackExt.PACK.equals(ext)) {
File f = nameFor(packName, "." + ext.getExtension()); //$NON-NLS-1$
- FileUtils.delete(f, deleteOptions);
+ removeOldPack(f, packName, ext, deleteOptions);
}
}
} catch (IOException e) {
private final File packDirectory;
+ private final File preservedDirectory;
+
private final File alternatesFile;
private final AtomicReference<PackList> packList;
objects = dir;
infoDirectory = new File(objects, "info"); //$NON-NLS-1$
packDirectory = new File(objects, "pack"); //$NON-NLS-1$
+ preservedDirectory = new File(packDirectory, "preserved"); //$NON-NLS-1$
alternatesFile = new File(infoDirectory, "alternates"); //$NON-NLS-1$
packList = new AtomicReference<PackList>(NO_PACKS);
unpackedObjectCache = new UnpackedObjectCache();
return objects;
}
+ /**
+ * @return the location of the <code>preserved</code> directory.
+ */
+ public final File getPreservedDirectory() {
+ return preservedDirectory;
+ }
+
@Override
public boolean exists() {
return fs.exists(objects);
*/
public static final boolean DEFAULT_REUSE_OBJECTS = true;
+ /**
+ * Default value of keep old packs option: {@value}
+ *
+ * @see #setPreserveOldPacks(boolean)
+ */
+ public static final boolean DEFAULT_PRESERVE_OLD_PACKS = false;
+
+ /**
+ * Default value of prune old packs option: {@value}
+ *
+ * @see #setPrunePreserved(boolean)
+ */
+ public static final boolean DEFAULT_PRUNE_PRESERVED = false;
+
/**
* Default value of delta compress option: {@value}
*
private boolean reuseObjects = DEFAULT_REUSE_OBJECTS;
+ private boolean preserveOldPacks = DEFAULT_PRESERVE_OLD_PACKS;
+
+ private boolean prunePreserved = DEFAULT_PRUNE_PRESERVED;
+
private boolean deltaBaseAsOffset = DEFAULT_DELTA_BASE_AS_OFFSET;
private boolean deltaCompress = DEFAULT_DELTA_COMPRESS;
this.compressionLevel = cfg.compressionLevel;
this.reuseDeltas = cfg.reuseDeltas;
this.reuseObjects = cfg.reuseObjects;
+ this.preserveOldPacks = cfg.preserveOldPacks;
+ this.prunePreserved = cfg.prunePreserved;
this.deltaBaseAsOffset = cfg.deltaBaseAsOffset;
this.deltaCompress = cfg.deltaCompress;
this.maxDeltaDepth = cfg.maxDeltaDepth;
this.reuseObjects = reuseObjects;
}
+ /**
+ * Checks whether to preserve old packs in a preserved directory
+ *
+ * Default setting: {@value #DEFAULT_PRESERVE_OLD_PACKS}
+ *
+ * @return true if repacking will preserve old pack files.
+ * @since 4.7
+ */
+ public boolean isPreserveOldPacks() {
+ return preserveOldPacks;
+ }
+
+ /**
+ * Set preserve old packs configuration option for repacking.
+ *
+ * If enabled, old pack files are moved into a preserved subdirectory instead
+ * of being deleted
+ *
+ * Default setting: {@value #DEFAULT_PRESERVE_OLD_PACKS}
+ *
+ * @param preserveOldPacks
+ * boolean indicating whether or not preserve old pack files
+ * @since 4.7
+ */
+ public void setPreserveOldPacks(boolean preserveOldPacks) {
+ this.preserveOldPacks = preserveOldPacks;
+ }
+
+ /**
+ * Checks whether to remove preserved pack files in a preserved directory
+ *
+ * Default setting: {@value #DEFAULT_PRUNE_PRESERVED}
+ *
+ * @return true if repacking will remove preserved pack files.
+ * @since 4.7
+ */
+ public boolean isPrunePreserved() {
+ return prunePreserved;
+ }
+
+ /**
+ * Set prune preserved configuration option for repacking.
+ *
+ * If enabled, preserved pack files are removed from a preserved subdirectory
+ *
+ * Default setting: {@value #DEFAULT_PRESERVE_OLD_PACKS}
+ *
+ * @param prunePreserved
+ * boolean indicating whether or not preserve old pack files
+ * @since 4.7
+ */
+ public void setPrunePreserved(boolean prunePreserved) {
+ this.prunePreserved = prunePreserved;
+ }
+
/**
* True if writer can use offsets to point to a delta base.
*