Robin Rosenberg
13 years ago
committed by
Gerrit Code Review @ Eclipse.org
9 changed files with 1368 additions and 2 deletions
@ -0,0 +1,58 @@
|
||||
/* |
||||
* Copyright (C) 2012, Christian Halstrick <christian.halstrick@sap.com> |
||||
* and other copyright owners as documented in the project's IP log. |
||||
* |
||||
* This program and the accompanying materials are made available |
||||
* under the terms of the Eclipse Distribution License v1.0 which |
||||
* accompanies this distribution, is reproduced below, and is |
||||
* available at http://www.eclipse.org/org/documents/edl-v10.php
|
||||
* |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or |
||||
* without modification, are permitted provided that the following |
||||
* conditions are met: |
||||
* |
||||
* - Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* |
||||
* - Redistributions in binary form must reproduce the above |
||||
* copyright notice, this list of conditions and the following |
||||
* disclaimer in the documentation and/or other materials provided |
||||
* with the distribution. |
||||
* |
||||
* - Neither the name of the Eclipse Foundation, Inc. nor the |
||||
* names of its contributors may be used to endorse or promote |
||||
* products derived from this software without specific prior |
||||
* written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND |
||||
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, |
||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
|
||||
package org.eclipse.jgit.pgm.debug; |
||||
|
||||
import org.eclipse.jgit.lib.TextProgressMonitor; |
||||
import org.eclipse.jgit.pgm.TextBuiltin; |
||||
import org.eclipse.jgit.storage.file.FileRepository; |
||||
import org.eclipse.jgit.storage.file.GC; |
||||
|
||||
class Gc extends TextBuiltin { |
||||
@Override |
||||
protected void run() throws Exception { |
||||
GC gc = new GC((FileRepository) db); |
||||
gc.setProgressMonitor(new TextProgressMonitor()); |
||||
gc.gc(); |
||||
} |
||||
} |
@ -0,0 +1,348 @@
|
||||
/* |
||||
* Copyright (C) 2012, Christian Halstrick <christian.halstrick@sap.com> |
||||
* and other copyright owners as documented in the project's IP log. |
||||
* |
||||
* This program and the accompanying materials are made available |
||||
* under the terms of the Eclipse Distribution License v1.0 which |
||||
* accompanies this distribution, is reproduced below, and is |
||||
* available at http://www.eclipse.org/org/documents/edl-v10.php
|
||||
* |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or |
||||
* without modification, are permitted provided that the following |
||||
* conditions are met: |
||||
* |
||||
* - Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* |
||||
* - Redistributions in binary form must reproduce the above |
||||
* copyright notice, this list of conditions and the following |
||||
* disclaimer in the documentation and/or other materials provided |
||||
* with the distribution. |
||||
* |
||||
* - Neither the name of the Eclipse Foundation, Inc. nor the |
||||
* names of its contributors may be used to endorse or promote |
||||
* products derived from this software without specific prior |
||||
* written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND |
||||
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, |
||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
package org.eclipse.jgit.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.util.Collection; |
||||
import java.util.Collections; |
||||
import java.util.Iterator; |
||||
|
||||
import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase; |
||||
import org.eclipse.jgit.junit.TestRepository; |
||||
import org.eclipse.jgit.junit.TestRepository.BranchBuilder; |
||||
import org.eclipse.jgit.lib.Constants; |
||||
import org.eclipse.jgit.lib.ObjectId; |
||||
import org.eclipse.jgit.revwalk.RevCommit; |
||||
import org.eclipse.jgit.storage.file.GC.RepoStatistics; |
||||
import org.eclipse.jgit.storage.file.PackIndex.MutableEntry; |
||||
import org.eclipse.jgit.util.FileUtils; |
||||
import org.junit.After; |
||||
import org.junit.Before; |
||||
import org.junit.Test; |
||||
|
||||
public class GCTest extends LocalDiskRepositoryTestCase { |
||||
private TestRepository<FileRepository> tr; |
||||
|
||||
private FileRepository repo; |
||||
|
||||
private GC gc; |
||||
|
||||
private RepoStatistics stats; |
||||
|
||||
@Before |
||||
public void setUp() throws Exception { |
||||
super.setUp(); |
||||
repo = createWorkRepository(); |
||||
tr = new TestRepository<FileRepository>((repo)); |
||||
gc = new GC(repo); |
||||
} |
||||
|
||||
@After |
||||
public void tearDown() throws Exception { |
||||
super.tearDown(); |
||||
} |
||||
|
||||
@Test |
||||
public void testPackAllObjectsInOnePack() 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); |
||||
gc.gc(); |
||||
stats = gc.getStatistics(); |
||||
assertEquals(0, stats.numberOfLooseObjects); |
||||
assertEquals(4, stats.numberOfPackedObjects); |
||||
assertEquals(1, stats.numberOfPackFiles); |
||||
} |
||||
|
||||
@Test |
||||
public void testKeepFiles() throws Exception { |
||||
BranchBuilder bb = tr.branch("refs/heads/master"); |
||||
bb.commit().add("A", "A").add("B", "B").create(); |
||||
stats = gc.getStatistics(); |
||||
assertEquals(4, stats.numberOfLooseObjects); |
||||
assertEquals(0, stats.numberOfPackedObjects); |
||||
assertEquals(0, stats.numberOfPackFiles); |
||||
gc.gc(); |
||||
stats = gc.getStatistics(); |
||||
assertEquals(0, stats.numberOfLooseObjects); |
||||
assertEquals(4, stats.numberOfPackedObjects); |
||||
assertEquals(1, stats.numberOfPackFiles); |
||||
|
||||
Iterator<PackFile> packIt = repo.getObjectDatabase().getPacks() |
||||
.iterator(); |
||||
PackFile singlePack = packIt.next(); |
||||
assertFalse(packIt.hasNext()); |
||||
File keepFile = new File(singlePack.getPackFile().getPath() + ".keep"); |
||||
assertFalse(keepFile.exists()); |
||||
assertTrue(keepFile.createNewFile()); |
||||
bb.commit().add("A", "A2").add("B", "B2").create(); |
||||
stats = gc.getStatistics(); |
||||
assertEquals(4, stats.numberOfLooseObjects); |
||||
assertEquals(4, stats.numberOfPackedObjects); |
||||
assertEquals(1, stats.numberOfPackFiles); |
||||
gc.gc(); |
||||
stats = gc.getStatistics(); |
||||
assertEquals(0, stats.numberOfLooseObjects); |
||||
assertEquals(8, stats.numberOfPackedObjects); |
||||
assertEquals(2, stats.numberOfPackFiles); |
||||
|
||||
// check that no object is packed twice
|
||||
Iterator<PackFile> packs = repo.getObjectDatabase().getPacks() |
||||
.iterator(); |
||||
PackIndex ind1 = packs.next().getIndex(); |
||||
assertEquals(4, ind1.getObjectCount()); |
||||
PackIndex ind2 = packs.next().getIndex(); |
||||
assertEquals(4, ind2.getObjectCount()); |
||||
for (MutableEntry e: ind1) |
||||
if (ind2.hasObject(e.toObjectId())) |
||||
assertFalse( |
||||
"the following object is in both packfiles: " |
||||
+ e.toObjectId(), ind2.hasObject(e.toObjectId())); |
||||
} |
||||
|
||||
@Test |
||||
public void testPackRepoWithNoRefs() throws Exception { |
||||
tr.commit().add("A", "A").add("B", "B").create(); |
||||
stats = gc.getStatistics(); |
||||
assertEquals(4, stats.numberOfLooseObjects); |
||||
assertEquals(0, stats.numberOfPackedObjects); |
||||
gc.gc(); |
||||
stats = gc.getStatistics(); |
||||
assertEquals(4, stats.numberOfLooseObjects); |
||||
assertEquals(0, stats.numberOfPackedObjects); |
||||
assertEquals(0, stats.numberOfPackFiles); |
||||
} |
||||
|
||||
@Test |
||||
public void testPack2Commits() throws Exception { |
||||
BranchBuilder bb = tr.branch("refs/heads/master"); |
||||
bb.commit().add("A", "A").add("B", "B").create(); |
||||
bb.commit().add("A", "A2").add("B", "B2").create(); |
||||
|
||||
stats = gc.getStatistics(); |
||||
assertEquals(8, stats.numberOfLooseObjects); |
||||
assertEquals(0, stats.numberOfPackedObjects); |
||||
gc.gc(); |
||||
stats = gc.getStatistics(); |
||||
assertEquals(0, stats.numberOfLooseObjects); |
||||
assertEquals(8, stats.numberOfPackedObjects); |
||||
assertEquals(1, stats.numberOfPackFiles); |
||||
} |
||||
|
||||
@Test |
||||
public void testPackCommitsAndLooseOne() throws Exception { |
||||
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); |
||||
gc.gc(); |
||||
stats = gc.getStatistics(); |
||||
assertEquals(0, stats.numberOfLooseObjects); |
||||
assertEquals(8, stats.numberOfPackedObjects); |
||||
assertEquals(2, stats.numberOfPackFiles); |
||||
} |
||||
|
||||
@Test |
||||
public void testNotPackTwice() throws Exception { |
||||
BranchBuilder bb = tr.branch("refs/heads/master"); |
||||
RevCommit first = bb.commit().message("M").add("M", "M").create(); |
||||
bb.commit().message("B").add("B", "Q").create(); |
||||
bb.commit().message("A").add("A", "A").create(); |
||||
RevCommit second = tr.commit().parent(first).message("R").add("R", "Q") |
||||
.create(); |
||||
tr.update("refs/tags/t1", second); |
||||
|
||||
Collection<PackFile> oldPacks = tr.getRepository().getObjectDatabase() |
||||
.getPacks(); |
||||
assertEquals(0, oldPacks.size()); |
||||
stats = gc.getStatistics(); |
||||
assertEquals(11, stats.numberOfLooseObjects); |
||||
assertEquals(0, stats.numberOfPackedObjects); |
||||
|
||||
gc.setExpireAgeMillis(0); |
||||
gc.gc(); |
||||
stats = gc.getStatistics(); |
||||
assertEquals(0, stats.numberOfLooseObjects); |
||||
|
||||
Iterator<PackFile> pIt = repo.getObjectDatabase().getPacks().iterator(); |
||||
long c = pIt.next().getObjectCount(); |
||||
if (c == 9) |
||||
assertEquals(2, pIt.next().getObjectCount()); |
||||
else { |
||||
assertEquals(2, c); |
||||
assertEquals(9, pIt.next().getObjectCount()); |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
public void testPackCommitsAndLooseOneNoReflog() throws Exception { |
||||
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); |
||||
|
||||
FileUtils.delete(new File(repo.getDirectory(), "logs/HEAD"), |
||||
FileUtils.RETRY | FileUtils.SKIP_MISSING); |
||||
FileUtils.delete( |
||||
new File(repo.getDirectory(), "logs/refs/heads/master"), |
||||
FileUtils.RETRY | FileUtils.SKIP_MISSING); |
||||
gc.gc(); |
||||
|
||||
stats = gc.getStatistics(); |
||||
assertEquals(4, stats.numberOfLooseObjects); |
||||
assertEquals(4, stats.numberOfPackedObjects); |
||||
assertEquals(1, stats.numberOfPackFiles); |
||||
} |
||||
|
||||
@Test |
||||
public void testPackCommitsAndLooseOneWithPruneNow() throws Exception { |
||||
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); |
||||
gc.setExpireAgeMillis(0); |
||||
gc.gc(); |
||||
stats = gc.getStatistics(); |
||||
assertEquals(0, stats.numberOfLooseObjects); |
||||
assertEquals(8, stats.numberOfPackedObjects); |
||||
assertEquals(2, stats.numberOfPackFiles); |
||||
} |
||||
|
||||
@Test |
||||
public void testPackCommitsAndLooseOneWithPruneNowNoReflog() |
||||
throws Exception { |
||||
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); |
||||
|
||||
FileUtils.delete(new File(repo.getDirectory(), "logs/HEAD"), |
||||
FileUtils.RETRY | FileUtils.SKIP_MISSING); |
||||
FileUtils.delete( |
||||
new File(repo.getDirectory(), "logs/refs/heads/master"), |
||||
FileUtils.RETRY | FileUtils.SKIP_MISSING); |
||||
gc.setExpireAgeMillis(0); |
||||
gc.gc(); |
||||
|
||||
stats = gc.getStatistics(); |
||||
assertEquals(0, stats.numberOfLooseObjects); |
||||
assertEquals(4, stats.numberOfPackedObjects); |
||||
assertEquals(1, stats.numberOfPackFiles); |
||||
} |
||||
|
||||
@Test |
||||
public void testIndexSavesObjects() throws Exception { |
||||
BranchBuilder bb = tr.branch("refs/heads/master"); |
||||
bb.commit().add("A", "A").add("B", "B").create(); |
||||
bb.commit().add("A", "A2").add("B", "B2").create(); |
||||
bb.commit().add("A", "A3"); // this new content in index should survive
|
||||
stats = gc.getStatistics(); |
||||
assertEquals(9, stats.numberOfLooseObjects); |
||||
assertEquals(0, stats.numberOfPackedObjects); |
||||
gc.gc(); |
||||
stats = gc.getStatistics(); |
||||
assertEquals(1, stats.numberOfLooseObjects); |
||||
assertEquals(8, stats.numberOfPackedObjects); |
||||
assertEquals(1, stats.numberOfPackFiles); |
||||
} |
||||
|
||||
@Test |
||||
public void testIndexSavesObjectsWithPruneNow() throws Exception { |
||||
BranchBuilder bb = tr.branch("refs/heads/master"); |
||||
bb.commit().add("A", "A").add("B", "B").create(); |
||||
bb.commit().add("A", "A2").add("B", "B2").create(); |
||||
bb.commit().add("A", "A3"); // this new content in index should survive
|
||||
stats = gc.getStatistics(); |
||||
assertEquals(9, stats.numberOfLooseObjects); |
||||
assertEquals(0, stats.numberOfPackedObjects); |
||||
gc.setExpireAgeMillis(0); |
||||
gc.gc(); |
||||
stats = gc.getStatistics(); |
||||
assertEquals(0, stats.numberOfLooseObjects); |
||||
assertEquals(8, stats.numberOfPackedObjects); |
||||
assertEquals(1, stats.numberOfPackFiles); |
||||
} |
||||
|
||||
@Test |
||||
public void testPruneNone() throws Exception { |
||||
BranchBuilder bb = tr.branch("refs/heads/master"); |
||||
bb.commit().add("A", "A").add("B", "B").create(); |
||||
bb.commit().add("A", "A2").add("B", "B2").create(); |
||||
new File(repo.getDirectory(), Constants.LOGS + "/refs/heads/master") |
||||
.delete(); |
||||
stats = gc.getStatistics(); |
||||
assertEquals(8, stats.numberOfLooseObjects); |
||||
gc.setExpireAgeMillis(0); |
||||
gc.prune(Collections.<ObjectId> emptySet()); |
||||
stats = gc.getStatistics(); |
||||
assertEquals(8, stats.numberOfLooseObjects); |
||||
tr.blob("x"); |
||||
stats = gc.getStatistics(); |
||||
assertEquals(9, stats.numberOfLooseObjects); |
||||
gc.prune(Collections.<ObjectId> emptySet()); |
||||
stats = gc.getStatistics(); |
||||
assertEquals(8, stats.numberOfLooseObjects); |
||||
} |
||||
} |
@ -0,0 +1,771 @@
|
||||
/* |
||||
* Copyright (C) 2012, Christian Halstrick <christian.halstrick@sap.com> |
||||
* Copyright (C) 2011, Shawn O. Pearce <spearce@spearce.org> |
||||
* and other copyright owners as documented in the project's IP log. |
||||
* |
||||
* This program and the accompanying materials are made available |
||||
* under the terms of the Eclipse Distribution License v1.0 which |
||||
* accompanies this distribution, is reproduced below, and is |
||||
* available at http://www.eclipse.org/org/documents/edl-v10.php
|
||||
* |
||||
* All rights reserved. |
||||
* |
||||
* Redistribution and use in source and binary forms, with or |
||||
* without modification, are permitted provided that the following |
||||
* conditions are met: |
||||
* |
||||
* - Redistributions of source code must retain the above copyright |
||||
* notice, this list of conditions and the following disclaimer. |
||||
* |
||||
* - Redistributions in binary form must reproduce the above |
||||
* copyright notice, this list of conditions and the following |
||||
* disclaimer in the documentation and/or other materials provided |
||||
* with the distribution. |
||||
* |
||||
* - Neither the name of the Eclipse Foundation, Inc. nor the |
||||
* names of its contributors may be used to endorse or promote |
||||
* products derived from this software without specific prior |
||||
* written permission. |
||||
* |
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND |
||||
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, |
||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||||
*/ |
||||
package org.eclipse.jgit.storage.file; |
||||
|
||||
import java.io.File; |
||||
import java.io.FileOutputStream; |
||||
import java.io.IOException; |
||||
import java.io.OutputStream; |
||||
import java.nio.channels.Channels; |
||||
import java.nio.channels.FileChannel; |
||||
import java.text.MessageFormat; |
||||
import java.util.ArrayList; |
||||
import java.util.Collection; |
||||
import java.util.Collections; |
||||
import java.util.HashMap; |
||||
import java.util.HashSet; |
||||
import java.util.Iterator; |
||||
import java.util.LinkedList; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.Map.Entry; |
||||
import java.util.Set; |
||||
|
||||
import org.eclipse.jgit.dircache.DirCacheIterator; |
||||
import org.eclipse.jgit.errors.CorruptObjectException; |
||||
import org.eclipse.jgit.errors.IncorrectObjectTypeException; |
||||
import org.eclipse.jgit.errors.MissingObjectException; |
||||
import org.eclipse.jgit.errors.NoWorkTreeException; |
||||
import org.eclipse.jgit.internal.JGitText; |
||||
import org.eclipse.jgit.lib.Constants; |
||||
import org.eclipse.jgit.lib.FileMode; |
||||
import org.eclipse.jgit.lib.NullProgressMonitor; |
||||
import org.eclipse.jgit.lib.ObjectId; |
||||
import org.eclipse.jgit.lib.ProgressMonitor; |
||||
import org.eclipse.jgit.lib.Ref; |
||||
import org.eclipse.jgit.revwalk.ObjectWalk; |
||||
import org.eclipse.jgit.revwalk.RevObject; |
||||
import org.eclipse.jgit.revwalk.RevWalk; |
||||
import org.eclipse.jgit.storage.pack.PackWriter; |
||||
import org.eclipse.jgit.treewalk.TreeWalk; |
||||
import org.eclipse.jgit.treewalk.filter.TreeFilter; |
||||
import org.eclipse.jgit.util.FileUtils; |
||||
|
||||
/** |
||||
* A garbage collector for git {@link FileRepository}. Instances of this class
|
||||
* are not thread-safe. Don't use the same instance from multiple threads. |
||||
* |
||||
* This class started as a copy of DfsGarbageCollector from Shawn O. Pearce |
||||
* adapted to FileRepositories. |
||||
*/ |
||||
public class GC { |
||||
private final FileRepository repo; |
||||
|
||||
private ProgressMonitor pm; |
||||
|
||||
private long expireAgeMillis; |
||||
|
||||
/** |
||||
* 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 |
||||
* difference between the current refs and the refs which existed during |
||||
* last {@link #repack()}. |
||||
*/ |
||||
private Map<String, Ref> lastPackedRefs; |
||||
|
||||
/** |
||||
* Holds the starting time of the last repack() execution. This is needed in |
||||
* prune() to inspect only those reflog entries which have been added since |
||||
* last repack(). |
||||
*/ |
||||
private long lastRepackTime; |
||||
|
||||
/** |
||||
* Creates a new garbage collector with default values. An expirationTime of |
||||
* two weeks and <code>null</code> as progress monitor will be used. |
||||
* |
||||
* @param repo |
||||
* the repo to work on |
||||
*/ |
||||
public GC(FileRepository repo) { |
||||
this.repo = repo; |
||||
this.pm = NullProgressMonitor.INSTANCE; |
||||
this.expireAgeMillis = 14 * 24 * 60 * 60 * 1000L; |
||||
} |
||||
|
||||
/** |
||||
* Runs a garbage collector on a {@link FileRepository}. It will |
||||
* <ul> |
||||
* <li>pack loose references into packed-refs</li> |
||||
* <li>repack all reachable objects into new pack files and delete the old |
||||
* pack files</li> |
||||
* <li>prune all loose objects which are now reachable by packs</li> |
||||
* </ul> |
||||
* |
||||
* @return the collection of {@link PackFile}'s which are newly created |
||||
* @throws IOException |
||||
*/ |
||||
public Collection<PackFile> gc() throws IOException { |
||||
packRefs(); |
||||
// TODO: implement reflog_expire(pm, repo);
|
||||
Collection<PackFile> newPacks = repack(); |
||||
prune(Collections.<ObjectId> emptySet()); |
||||
// TODO: implement rerere_gc(pm);
|
||||
return newPacks; |
||||
} |
||||
|
||||
/** |
||||
* 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. |
||||
* |
||||
* @param oldPacks |
||||
* @param newPacks |
||||
* @param ignoreErrors |
||||
* <code>true</code> if we should ignore the fact that a certain |
||||
* pack files or index files couldn't be deleted. |
||||
* <code>false</code> if an exception should be thrown in such |
||||
* cases |
||||
* @throws IOException |
||||
* if a pack file couldn't be deleted and |
||||
* <code>ignoreErrors</code> is set to <code>false</code> |
||||
*/ |
||||
private void deleteOldPacks(Collection<PackFile> oldPacks, |
||||
Collection<PackFile> newPacks, boolean ignoreErrors) |
||||
throws IOException { |
||||
int deleteOptions = FileUtils.RETRY | FileUtils.SKIP_MISSING; |
||||
if (ignoreErrors) |
||||
deleteOptions |= FileUtils.IGNORE_ERRORS; |
||||
oldPackLoop: for (PackFile oldPack : oldPacks) { |
||||
String oldName = oldPack.getPackName(); |
||||
// check whether an old pack file is also among the list of new
|
||||
// pack files. Then we must not delete it.
|
||||
for (PackFile newPack : newPacks) |
||||
if (oldName.equals(newPack.getPackName())) |
||||
continue oldPackLoop; |
||||
|
||||
if (!oldPack.shouldBeKept()) { |
||||
oldPack.close(); |
||||
FileUtils.delete(nameFor(oldName, ".pack"), deleteOptions); |
||||
FileUtils.delete(nameFor(oldName, ".idx"), deleteOptions); |
||||
} |
||||
} |
||||
// close the complete object database. Thats my only chance to force
|
||||
// rescanning and to detect that certain pack files are now deleted.
|
||||
repo.getObjectDatabase().close(); |
||||
} |
||||
|
||||
/** |
||||
* Like "git prune-packed" this method tries to prune all loose objects |
||||
* which can be found in packs. If certain objects can't be pruned (e.g. |
||||
* because the filesystem delete operation fails) this is silently ignored. |
||||
* |
||||
* @throws IOException |
||||
*/ |
||||
public void prunePacked() throws IOException { |
||||
ObjectDirectory objdb = repo.getObjectDatabase(); |
||||
Collection<PackFile> packs = objdb.getPacks(); |
||||
File objects = repo.getObjectsDirectory(); |
||||
String[] fanout = objects.list(); |
||||
|
||||
if (fanout != null && fanout.length > 0) { |
||||
pm.beginTask(JGitText.get().pruneLoosePackedObjects, fanout.length); |
||||
try { |
||||
for (String d : fanout) { |
||||
pm.update(1); |
||||
if (d.length() != 2) |
||||
continue; |
||||
String[] entries = new File(objects, d).list(); |
||||
if (entries == null) |
||||
continue; |
||||
for (String e : entries) { |
||||
if (e.length() != Constants.OBJECT_ID_STRING_LENGTH - 2) |
||||
continue; |
||||
ObjectId id; |
||||
try { |
||||
id = ObjectId.fromString(d + e); |
||||
} catch (IllegalArgumentException notAnObject) { |
||||
// ignoring the file that does not represent loose
|
||||
// object
|
||||
continue; |
||||
} |
||||
boolean found = false; |
||||
for (PackFile p : packs) |
||||
if (p.hasObject(id)) { |
||||
found = true; |
||||
break; |
||||
} |
||||
if (found) |
||||
FileUtils.delete(objdb.fileFor(id), FileUtils.RETRY |
||||
| FileUtils.SKIP_MISSING |
||||
| FileUtils.IGNORE_ERRORS); |
||||
} |
||||
} |
||||
} finally { |
||||
pm.endTask(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Like "git prune" this method tries to prune all loose objects which are |
||||
* unreferenced. If certain objects can't be pruned (e.g. because the |
||||
* filesystem delete operation fails) this is silently ignored. |
||||
* |
||||
* @param objectsToKeep |
||||
* a set of objects which should explicitly not be pruned |
||||
* |
||||
* @throws IOException |
||||
*/ |
||||
public void prune(Set<ObjectId> objectsToKeep) |
||||
throws IOException { |
||||
long expireDate = (expireAgeMillis == 0) ? Long.MAX_VALUE : System |
||||
.currentTimeMillis() - expireAgeMillis; |
||||
|
||||
// Collect all loose objects which are old enough, not referenced from
|
||||
// the index and not in objectsToKeep
|
||||
Map<ObjectId, File> deletionCandidates = new HashMap<ObjectId, File>(); |
||||
Set<ObjectId> indexObjects = null; |
||||
File objects = repo.getObjectsDirectory(); |
||||
String[] fanout = objects.list(); |
||||
if (fanout != null && fanout.length > 0) { |
||||
pm.beginTask(JGitText.get().pruneLooseUnreferencedObjects, |
||||
fanout.length); |
||||
for (String d : fanout) { |
||||
pm.update(1); |
||||
if (d.length() != 2) |
||||
continue; |
||||
File[] entries = new File(objects, d).listFiles(); |
||||
if (entries == null) |
||||
continue; |
||||
for (File f : entries) { |
||||
String fName = f.getName(); |
||||
if (fName.length() != Constants.OBJECT_ID_STRING_LENGTH - 2) |
||||
continue; |
||||
if (f.lastModified() >= expireDate) |
||||
continue; |
||||
try { |
||||
ObjectId id = ObjectId.fromString(d + fName); |
||||
if (objectsToKeep.contains(id)) |
||||
continue; |
||||
if (indexObjects == null) |
||||
indexObjects = listNonHEADIndexObjects(); |
||||
if (indexObjects.contains(id)) |
||||
continue; |
||||
deletionCandidates.put(id, f); |
||||
} catch (IllegalArgumentException notAnObject) { |
||||
// ignoring the file that does not represent loose
|
||||
// object
|
||||
continue; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
if (deletionCandidates.isEmpty()) |
||||
return; |
||||
|
||||
// From the set of current refs remove all those which have been handled
|
||||
// during last repack(). Only those refs will survive which have been
|
||||
// added or modified since the last repack. Only these can save existing
|
||||
// loose refs from being pruned.
|
||||
Map<String, Ref> newRefs; |
||||
if (lastPackedRefs == null || lastPackedRefs.isEmpty()) |
||||
newRefs = getAllRefs(); |
||||
else { |
||||
newRefs = new HashMap<String, Ref>(); |
||||
for (Iterator<Map.Entry<String, Ref>> i = getAllRefs().entrySet() |
||||
.iterator(); i.hasNext();) { |
||||
Entry<String, Ref> newEntry = i.next(); |
||||
Ref old = lastPackedRefs.get(newEntry.getKey()); |
||||
if (!equals(newEntry.getValue(), old)) |
||||
newRefs.put(newEntry.getKey(), newEntry.getValue()); |
||||
} |
||||
} |
||||
|
||||
if (!newRefs.isEmpty()) { |
||||
// There are new/modified refs! Check which loose objects are now
|
||||
// referenced by these modified refs (or their reflogentries).
|
||||
// Remove these loose objects
|
||||
// from the deletionCandidates. When the last candidate is removed
|
||||
// leave this method.
|
||||
ObjectWalk w = new ObjectWalk(repo); |
||||
try { |
||||
for (Ref cr : newRefs.values()) |
||||
w.markStart(w.parseAny(cr.getObjectId())); |
||||
if (lastPackedRefs != null) |
||||
for (Ref lpr : lastPackedRefs.values()) |
||||
w.markUninteresting(w.parseAny(lpr.getObjectId())); |
||||
removeReferenced(deletionCandidates, w); |
||||
} finally { |
||||
w.dispose(); |
||||
} |
||||
} |
||||
|
||||
if (deletionCandidates.isEmpty()) |
||||
return; |
||||
|
||||
// Since we have not left the method yet there are still
|
||||
// deletionCandidates. Last chance for these objects not to be pruned is
|
||||
// that they are referenced by reflog entries. Even refs which currently
|
||||
// point to the same object as during last repack() may have
|
||||
// additional reflog entries not handled during last repack()
|
||||
ObjectWalk w = new ObjectWalk(repo); |
||||
try { |
||||
for (Ref ar : getAllRefs().values()) |
||||
for (ObjectId id : listRefLogObjects(ar, lastRepackTime)) |
||||
w.markStart(w.parseAny(id)); |
||||
if (lastPackedRefs != null) |
||||
for (Ref lpr : lastPackedRefs.values()) |
||||
w.markUninteresting(w.parseAny(lpr.getObjectId())); |
||||
removeReferenced(deletionCandidates, w); |
||||
} finally { |
||||
w.dispose(); |
||||
} |
||||
|
||||
if (deletionCandidates.isEmpty()) |
||||
return; |
||||
|
||||
// delete all candidates which have survived: these are unreferenced
|
||||
// loose objects
|
||||
for (File f : deletionCandidates.values()) |
||||
f.delete(); |
||||
|
||||
repo.getObjectDatabase().close(); |
||||
} |
||||
|
||||
/** |
||||
* Remove all entries from a map which key is the id of an object referenced |
||||
* by the given ObjectWalk |
||||
* |
||||
* @param id2File |
||||
* @param w |
||||
* @throws MissingObjectException |
||||
* @throws IncorrectObjectTypeException |
||||
* @throws IOException |
||||
*/ |
||||
private void removeReferenced(Map<ObjectId, File> id2File, |
||||
ObjectWalk w) throws MissingObjectException, |
||||
IncorrectObjectTypeException, IOException { |
||||
RevObject ro = w.next(); |
||||
while (ro != null) { |
||||
if (id2File.remove(ro.getId()) != null) |
||||
if (id2File.isEmpty()) |
||||
return; |
||||
ro = w.next(); |
||||
} |
||||
ro = w.nextObject(); |
||||
while (ro != null) { |
||||
if (id2File.remove(ro.getId()) != null) |
||||
if (id2File.isEmpty()) |
||||
return; |
||||
ro = w.nextObject(); |
||||
} |
||||
} |
||||
|
||||
private static boolean equals(Ref r1, Ref r2) { |
||||
if (r1 == null || r2 == null) |
||||
return false; |
||||
if (r1.isSymbolic()) { |
||||
if (!r2.isSymbolic()) |
||||
return false; |
||||
return r1.getTarget().getName().equals(r2.getTarget().getName()); |
||||
} else { |
||||
if (r2.isSymbolic()) |
||||
return false; |
||||
return r1.getObjectId().equals(r2.getObjectId()); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Packs all non-symbolic, loose refs into packed-refs. |
||||
* |
||||
* @throws IOException |
||||
*/ |
||||
public void packRefs() throws IOException { |
||||
Collection<Ref> refs = repo.getAllRefs().values(); |
||||
List<String> refsToBePacked = new ArrayList<String>(refs.size()); |
||||
pm.beginTask(JGitText.get().packRefs, refs.size()); |
||||
try { |
||||
for (Ref ref : refs) { |
||||
if (!ref.isSymbolic() && ref.getStorage().isLoose()) |
||||
refsToBePacked.add(ref.getName()); |
||||
pm.update(1); |
||||
} |
||||
((RefDirectory) repo.getRefDatabase()).pack(refsToBePacked); |
||||
} finally { |
||||
pm.endTask(); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Packs all objects which reachable from any of the heads into one pack |
||||
* file. Additionally all objects which are not reachable from any head but |
||||
* which are reachable from any of the other refs (e.g. tags), special refs |
||||
* (e.g. FETCH_HEAD) or index are packed into a separate pack file. Objects |
||||
* included in pack files which have a .keep file associated are never |
||||
* repacked. All old pack files which existed before are deleted. |
||||
* |
||||
* @return a collection of the newly created pack files |
||||
* @throws IOException |
||||
* when during reading of refs, index, packfiles, objects, |
||||
* reflog-entries or during writing to the packfiles |
||||
* {@link IOException} occurs |
||||
*/ |
||||
public Collection<PackFile> repack() throws IOException { |
||||
Collection<PackFile> toBeDeleted = repo.getObjectDatabase().getPacks(); |
||||
|
||||
long time = System.currentTimeMillis(); |
||||
Map<String, Ref> refsBefore = getAllRefs(); |
||||
|
||||
Set<ObjectId> allHeads = new HashSet<ObjectId>(); |
||||
Set<ObjectId> nonHeads = new HashSet<ObjectId>(); |
||||
Set<ObjectId> tagTargets = new HashSet<ObjectId>(); |
||||
Set<ObjectId> indexObjects = listNonHEADIndexObjects(); |
||||
|
||||
for (Ref ref : refsBefore.values()) { |
||||
nonHeads.addAll(listRefLogObjects(ref, 0)); |
||||
if (ref.isSymbolic() || ref.getObjectId() == null) |
||||
continue; |
||||
if (ref.getName().startsWith(Constants.R_HEADS)) |
||||
allHeads.add(ref.getObjectId()); |
||||
else |
||||
nonHeads.add(ref.getObjectId()); |
||||
if (ref.getPeeledObjectId() != null) |
||||
tagTargets.add(ref.getPeeledObjectId()); |
||||
} |
||||
|
||||
List<PackIndex> excluded = new LinkedList<PackIndex>(); |
||||
for (PackFile f : repo.getObjectDatabase().getPacks()) |
||||
if (f.shouldBeKept()) |
||||
excluded.add(f.getIndex()); |
||||
|
||||
tagTargets.addAll(allHeads); |
||||
nonHeads.addAll(indexObjects); |
||||
|
||||
List<PackFile> ret = new ArrayList<PackFile>(2); |
||||
PackFile heads = null; |
||||
if (!allHeads.isEmpty()) { |
||||
heads = writePack(allHeads, Collections.<ObjectId> emptySet(), |
||||
tagTargets, excluded); |
||||
if (heads != null) { |
||||
ret.add(heads); |
||||
excluded.add(0, heads.getIndex()); |
||||
} |
||||
} |
||||
if (!nonHeads.isEmpty()) { |
||||
PackFile rest = writePack(nonHeads, allHeads, tagTargets, excluded); |
||||
if (rest != null) |
||||
ret.add(rest); |
||||
} |
||||
deleteOldPacks(toBeDeleted, ret, true); |
||||
prunePacked(); |
||||
|
||||
lastPackedRefs = refsBefore; |
||||
lastRepackTime = time; |
||||
return ret; |
||||
} |
||||
|
||||
/** |
||||
* @param ref |
||||
* the ref which log should be inspected |
||||
* @param minTime only reflog entries not older then this time are processed |
||||
* @return the {@link ObjectId}s contained in the reflog |
||||
* @throws IOException |
||||
*/ |
||||
private Set<ObjectId> listRefLogObjects(Ref ref, long minTime) throws IOException { |
||||
List<ReflogEntry> rlEntries = repo.getReflogReader(ref.getName()) |
||||
.getReverseEntries(); |
||||
if (rlEntries == null || rlEntries.isEmpty()) |
||||
return Collections.<ObjectId> emptySet(); |
||||
Set<ObjectId> ret = new HashSet<ObjectId>(); |
||||
for (ReflogEntry e : rlEntries) { |
||||
if (e.getWho().getWhen().getTime() < minTime) |
||||
break; |
||||
ret.add(e.getNewId()); |
||||
ObjectId oldId = e.getOldId(); |
||||
if (oldId != null && !ObjectId.zeroId().equals(oldId)) |
||||
ret.add(oldId); |
||||
} |
||||
return ret; |
||||
} |
||||
|
||||
/** |
||||
* Returns a map of all refs and additional refs (e.g. FETCH_HEAD, |
||||
* MERGE_HEAD, ...) |
||||
* |
||||
* @return a map where names of refs point to ref objects |
||||
* @throws IOException |
||||
*/ |
||||
private Map<String, Ref> getAllRefs() throws IOException { |
||||
Map<String, Ref> ret = repo.getAllRefs(); |
||||
for (Ref ref : repo.getRefDatabase().getAdditionalRefs()) |
||||
ret.put(ref.getName(), ref); |
||||
return ret; |
||||
} |
||||
|
||||
/** |
||||
* Return a list of those objects in the index which differ from whats in |
||||
* HEAD |
||||
* |
||||
* @return a set of ObjectIds of changed objects in the index |
||||
* @throws IOException |
||||
* @throws CorruptObjectException |
||||
* @throws NoWorkTreeException |
||||
*/ |
||||
private Set<ObjectId> listNonHEADIndexObjects() |
||||
throws CorruptObjectException, IOException { |
||||
RevWalk revWalk = null; |
||||
try { |
||||
if (repo.getIndexFile() == null) |
||||
return Collections.emptySet(); |
||||
} catch (NoWorkTreeException e) { |
||||
return Collections.emptySet(); |
||||
} |
||||
TreeWalk treeWalk = new TreeWalk(repo); |
||||
try { |
||||
treeWalk.addTree(new DirCacheIterator(repo.readDirCache())); |
||||
ObjectId headID = repo.resolve(Constants.HEAD); |
||||
if (headID != null) { |
||||
revWalk = new RevWalk(repo); |
||||
treeWalk.addTree(revWalk.parseTree(headID)); |
||||
revWalk.dispose(); |
||||
revWalk = null; |
||||
} |
||||
|
||||
treeWalk.setFilter(TreeFilter.ANY_DIFF); |
||||
treeWalk.setRecursive(true); |
||||
Set<ObjectId> ret = new HashSet<ObjectId>(); |
||||
|
||||
while (treeWalk.next()) { |
||||
ObjectId objectId = treeWalk.getObjectId(0); |
||||
switch (treeWalk.getRawMode(0) & FileMode.TYPE_MASK) { |
||||
case FileMode.TYPE_MISSING: |
||||
case FileMode.TYPE_GITLINK: |
||||
continue; |
||||
case FileMode.TYPE_TREE: |
||||
case FileMode.TYPE_FILE: |
||||
case FileMode.TYPE_SYMLINK: |
||||
ret.add(objectId); |
||||
continue; |
||||
default: |
||||
throw new IOException(MessageFormat.format( |
||||
JGitText.get().corruptObjectInvalidMode3, String |
||||
.format("%o", Integer.valueOf(treeWalk |
||||
.getRawMode(0)), |
||||
(objectId == null) ? "null" |
||||
: objectId.name(), treeWalk |
||||
.getPathString(), repo |
||||
.getIndexFile()))); |
||||
} |
||||
} |
||||
return ret; |
||||
} finally { |
||||
if (revWalk != null) |
||||
revWalk.dispose(); |
||||
treeWalk.release(); |
||||
} |
||||
} |
||||
|
||||
private PackFile writePack(Set<? extends ObjectId> want, |
||||
Set<? extends ObjectId> have, Set<ObjectId> tagTargets, |
||||
List<PackIndex> excludeObjects) throws IOException { |
||||
File tmpPack = null; |
||||
File tmpIdx = null; |
||||
PackWriter pw = new PackWriter(repo); |
||||
try { |
||||
// prepare the PackWriter
|
||||
pw.setDeltaBaseAsOffset(true); |
||||
pw.setReuseDeltaCommits(false); |
||||
if (tagTargets != null) |
||||
pw.setTagTargets(tagTargets); |
||||
if (excludeObjects != null) |
||||
for (PackIndex idx : excludeObjects) |
||||
pw.excludeObjects(idx); |
||||
pw.preparePack(pm, want, have); |
||||
if (pw.getObjectCount() == 0) |
||||
return null; |
||||
|
||||
// create temporary files
|
||||
String id = pw.computeName().getName(); |
||||
File packdir = new File(repo.getObjectsDirectory(), "pack"); |
||||
tmpPack = File.createTempFile("gc_", ".pack_tmp", packdir); |
||||
tmpIdx = new File(packdir, tmpPack.getName().substring(0, |
||||
tmpPack.getName().lastIndexOf('.')) |
||||
+ ".idx_tmp"); |
||||
|
||||
if (!tmpIdx.createNewFile()) |
||||
throw new IOException(MessageFormat.format( |
||||
JGitText.get().cannotCreateIndexfile, tmpIdx.getPath())); |
||||
|
||||
// write the packfile
|
||||
FileChannel channel = new FileOutputStream(tmpPack).getChannel(); |
||||
OutputStream channelStream = Channels.newOutputStream(channel); |
||||
try { |
||||
pw.writePack(pm, pm, channelStream); |
||||
} finally { |
||||
channel.force(true); |
||||
channelStream.close(); |
||||
channel.close(); |
||||
} |
||||
|
||||
// write the packindex
|
||||
FileChannel idxChannel = new FileOutputStream(tmpIdx).getChannel(); |
||||
OutputStream idxStream = Channels.newOutputStream(idxChannel); |
||||
try { |
||||
pw.writeIndex(idxStream); |
||||
} finally { |
||||
idxChannel.force(true); |
||||
idxStream.close(); |
||||
idxChannel.close(); |
||||
} |
||||
|
||||
// rename the temporary files to real files
|
||||
File realPack = nameFor(id, ".pack"); |
||||
tmpPack.setReadOnly(); |
||||
File realIdx = nameFor(id, ".idx"); |
||||
realIdx.setReadOnly(); |
||||
boolean delete = true; |
||||
try { |
||||
if (!tmpPack.renameTo(realPack)) |
||||
return null; |
||||
delete = false; |
||||
if (!tmpIdx.renameTo(realIdx)) { |
||||
File newIdx = new File(realIdx.getParentFile(), |
||||
realIdx.getName() + ".new"); |
||||
if (!tmpIdx.renameTo(newIdx)) |
||||
newIdx = tmpIdx; |
||||
throw new IOException(MessageFormat.format( |
||||
JGitText.get().panicCantRenameIndexFile, newIdx, |
||||
realIdx)); |
||||
} |
||||
} finally { |
||||
if (delete && tmpPack.exists()) |
||||
tmpPack.delete(); |
||||
if (delete && tmpIdx.exists()) |
||||
tmpIdx.delete(); |
||||
} |
||||
return repo.getObjectDatabase().openPack(realPack, realIdx); |
||||
} finally { |
||||
pw.release(); |
||||
if (tmpPack != null && tmpPack.exists()) |
||||
tmpPack.delete(); |
||||
if (tmpIdx != null && tmpIdx.exists()) |
||||
tmpIdx.delete(); |
||||
} |
||||
} |
||||
|
||||
private File nameFor(String name, String ext) { |
||||
File packdir = new File(repo.getObjectsDirectory(), "pack"); |
||||
return new File(packdir, "pack-" + name + ext); |
||||
} |
||||
|
||||
/** |
||||
* A class holding statistical data for a FileRepository regarding how many |
||||
* objects are stored as loose or packed objects |
||||
*/ |
||||
public class RepoStatistics { |
||||
/** |
||||
* The number of objects stored in pack files. If the same object is |
||||
* stored in multiple pack files then it is counted as often as it |
||||
* occurs in pack files. |
||||
*/ |
||||
public long numberOfPackedObjects; |
||||
|
||||
/** |
||||
* The number of pack files |
||||
*/ |
||||
public long numberOfPackFiles; |
||||
|
||||
/** |
||||
* The number of objects stored as loose objects. |
||||
*/ |
||||
public long numberOfLooseObjects; |
||||
} |
||||
|
||||
/** |
||||
* Returns the number of objects stored in pack files. If an object is |
||||
* contained in multiple pack files it is counted as often as it occurs. |
||||
* |
||||
* @return the number of objects stored in pack files |
||||
* @throws IOException |
||||
*/ |
||||
public RepoStatistics getStatistics() throws IOException { |
||||
RepoStatistics ret = new RepoStatistics(); |
||||
Collection<PackFile> packs = repo.getObjectDatabase().getPacks(); |
||||
for (PackFile f : packs) |
||||
ret.numberOfPackedObjects += f.getIndex().getObjectCount(); |
||||
ret.numberOfPackFiles = packs.size(); |
||||
File objDir = repo.getObjectsDirectory(); |
||||
String[] fanout = objDir.list(); |
||||
if (fanout != null && fanout.length > 0) { |
||||
for (String d : fanout) { |
||||
if (d.length() != 2) |
||||
continue; |
||||
String[] entries = new File(objDir, d).list(); |
||||
if (entries == null) |
||||
continue; |
||||
for (String e : entries) { |
||||
if (e.length() != Constants.OBJECT_ID_STRING_LENGTH - 2) |
||||
continue; |
||||
ret.numberOfLooseObjects++; |
||||
} |
||||
} |
||||
} |
||||
return ret; |
||||
} |
||||
|
||||
/** |
||||
* Set the progress monitor used for garbage collection methods. |
||||
* |
||||
* @param pm |
||||
* @return this |
||||
*/ |
||||
public GC setProgressMonitor(ProgressMonitor pm) { |
||||
this.pm = (pm == null) ? NullProgressMonitor.INSTANCE : pm; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* During gc() or prune() each unreferenced, loose object which has been |
||||
* created or modified in the last <code>expireAgeMillis</code> milliseconds |
||||
* will not be pruned. Only older objects may be pruned. If set to 0 then |
||||
* every object is a candidate for pruning. |
||||
* |
||||
* @param expireAgeMillis |
||||
* minimal age of objects to be pruned in milliseconds. |
||||
*/ |
||||
public void setExpireAgeMillis(long expireAgeMillis) { |
||||
this.expireAgeMillis = expireAgeMillis; |
||||
} |
||||
} |
Loading…
Reference in new issue