aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorShawn Pearce <sop@google.com>2013-11-01 12:13:25 -0600
committerRobin Rosenberg <robin.rosenberg@dewire.com>2013-11-24 11:37:49 +0100
commit53616a85ef5621187d97af1097821be80db1d39a (patch)
treeb5934e80aeae8c66f00f6469f8a44eb3f0e7a13c
parent531d5776584926f2532847be63c2213c0793a68e (diff)
downloadjgit-53616a85ef5621187d97af1097821be80db1d39a.tar.gz
jgit-53616a85ef5621187d97af1097821be80db1d39a.zip
Break up GCTest to run in parallel
Separate some sections of the GC tests into different test classes. Individual classes permits running in parallel under Buck, reducing test latency if there are sufficient CPUs available. Change-Id: I5eb177f78efd4aa8ac857d0d8b1a1fd81ca07790
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GCTest.java777
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java162
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBranchPrunedTest.java119
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcConcurrentTest.java119
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcDirCacheSavesObjectsTest.java85
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcKeepFilesTest.java104
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java180
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPruneNonReferencedTest.java120
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcReflogTest.java146
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTagTest.java78
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java113
11 files changed, 1226 insertions, 777 deletions
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GCTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GCTest.java
deleted file mode 100644
index 35455f48a9..0000000000
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GCTest.java
+++ /dev/null
@@ -1,777 +0,0 @@
-/*
- * 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.internal.storage.file;
-
-import static java.lang.Integer.valueOf;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Date;
-import java.util.Iterator;
-import java.util.concurrent.BrokenBarrierException;
-import java.util.concurrent.Callable;
-import java.util.concurrent.CyclicBarrier;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-
-import org.eclipse.jgit.api.Git;
-import org.eclipse.jgit.internal.JGitText;
-import org.eclipse.jgit.internal.storage.file.GC.RepoStatistics;
-import org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry;
-import org.eclipse.jgit.internal.storage.pack.PackExt;
-import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
-import org.eclipse.jgit.junit.RepositoryTestCase;
-import org.eclipse.jgit.junit.TestRepository;
-import org.eclipse.jgit.junit.TestRepository.BranchBuilder;
-import org.eclipse.jgit.junit.TestRepository.CommitBuilder;
-import org.eclipse.jgit.lib.AnyObjectId;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.EmptyProgressMonitor;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Ref.Storage;
-import org.eclipse.jgit.lib.RefUpdate;
-import org.eclipse.jgit.lib.RefUpdate.Result;
-import org.eclipse.jgit.merge.MergeStrategy;
-import org.eclipse.jgit.merge.Merger;
-import org.eclipse.jgit.revwalk.RevBlob;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevTag;
-import org.eclipse.jgit.revwalk.RevTree;
-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();
- }
-
- // GC.packRefs tests
-
- @Test
- public void packRefs_looseRefPacked() throws Exception {
- RevBlob a = tr.blob("a");
- tr.lightweightTag("t", a);
-
- gc.packRefs();
- assertSame(repo.getRef("t").getStorage(), Storage.PACKED);
- }
-
- @Test
- public void concurrentPackRefs_onlyOneWritesPackedRefs() throws Exception {
- RevBlob a = tr.blob("a");
- tr.lightweightTag("t", a);
-
- final CyclicBarrier syncPoint = new CyclicBarrier(2);
-
- Callable<Integer> packRefs = new Callable<Integer>() {
-
- /** @return 0 for success, 1 in case of error when writing pack */
- public Integer call() throws Exception {
- syncPoint.await();
- try {
- gc.packRefs();
- return valueOf(0);
- } catch (IOException e) {
- return valueOf(1);
- }
- }
- };
- ExecutorService pool = Executors.newFixedThreadPool(2);
- try {
- Future<Integer> p1 = pool.submit(packRefs);
- Future<Integer> p2 = pool.submit(packRefs);
- assertEquals(1, p1.get().intValue() + p2.get().intValue());
- } finally {
- pool.shutdown();
- pool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
- }
- }
-
- @Test
- public void packRefsWhileRefLocked_refNotPackedNoError()
- throws Exception {
- RevBlob a = tr.blob("a");
- tr.lightweightTag("t1", a);
- tr.lightweightTag("t2", a);
- LockFile refLock = new LockFile(new File(repo.getDirectory(),
- "refs/tags/t1"), repo.getFS());
- try {
- refLock.lock();
- gc.packRefs();
- } finally {
- refLock.unlock();
- }
-
- assertSame(repo.getRef("refs/tags/t1").getStorage(), Storage.LOOSE);
- assertSame(repo.getRef("refs/tags/t2").getStorage(), Storage.PACKED);
- }
-
- @Test
- public void packRefsWhileRefUpdated_refUpdateSucceeds()
- throws Exception {
- RevBlob a = tr.blob("a");
- tr.lightweightTag("t", a);
- final RevBlob b = tr.blob("b");
-
- final CyclicBarrier refUpdateLockedRef = new CyclicBarrier(2);
- final CyclicBarrier packRefsDone = new CyclicBarrier(2);
- ExecutorService pool = Executors.newFixedThreadPool(2);
- try {
- Future<Result> result = pool.submit(new Callable<Result>() {
-
- public Result call() throws Exception {
- RefUpdate update = new RefDirectoryUpdate(
- (RefDirectory) repo.getRefDatabase(),
- repo.getRef("refs/tags/t")) {
- @Override
- public boolean isForceUpdate() {
- try {
- refUpdateLockedRef.await();
- packRefsDone.await();
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- } catch (BrokenBarrierException e) {
- Thread.currentThread().interrupt();
- }
- return super.isForceUpdate();
- }
- };
- update.setForceUpdate(true);
- update.setNewObjectId(b);
- return update.update();
- }
- });
-
- pool.submit(new Callable<Void>() {
- public Void call() throws Exception {
- refUpdateLockedRef.await();
- gc.packRefs();
- packRefsDone.await();
- return null;
- }
- });
-
- assertSame(result.get(), Result.FORCED);
-
- } finally {
- pool.shutdownNow();
- pool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
- }
-
- assertEquals(repo.getRef("refs/tags/t").getObjectId(), b);
- }
-
- // GC.repack tests
-
- @Test
- public void repackEmptyRepo_noPackCreated() throws IOException {
- gc.repack();
- assertEquals(0, repo.getObjectDatabase().getPacks().size());
- }
-
- @Test
- public void concurrentRepack() throws Exception {
- final CyclicBarrier syncPoint = new CyclicBarrier(2);
-
- class DoRepack extends EmptyProgressMonitor implements
- Callable<Integer> {
-
- public void beginTask(String title, int totalWork) {
- if (title.equals(JGitText.get().writingObjects)) {
- try {
- syncPoint.await();
- } catch (InterruptedException e) {
- Thread.currentThread().interrupt();
- } catch (BrokenBarrierException ignored) {
- //
- }
- }
- }
-
- /** @return 0 for success, 1 in case of error when writing pack */
- public Integer call() throws Exception {
- try {
- gc.setProgressMonitor(this);
- gc.repack();
- return valueOf(0);
- } catch (IOException e) {
- // leave the syncPoint in broken state so any awaiting
- // threads and any threads that call await in the future get
- // the BrokenBarrierException
- Thread.currentThread().interrupt();
- try {
- syncPoint.await();
- } catch (InterruptedException ignored) {
- //
- }
- return valueOf(1);
- }
- }
- }
-
- RevBlob a = tr.blob("a");
- tr.lightweightTag("t", a);
-
- ExecutorService pool = Executors.newFixedThreadPool(2);
- try {
- DoRepack repack1 = new DoRepack();
- DoRepack repack2 = new DoRepack();
- Future<Integer> result1 = pool.submit(repack1);
- Future<Integer> result2 = pool.submit(repack2);
- assertEquals(0, result1.get().intValue() + result2.get().intValue());
- } finally {
- pool.shutdown();
- pool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
- }
- }
-
- // GC.prune tests
-
- @Test
- public void nonReferencedNonExpiredObject_notPruned() throws Exception {
- RevBlob a = tr.blob("a");
- gc.setExpire(new Date(lastModified(a)));
- gc.prune(Collections.<ObjectId> emptySet());
- assertTrue(repo.hasObject(a));
- }
-
- @Test
- public void nonReferencedExpiredObject_pruned() throws Exception {
- RevBlob a = tr.blob("a");
- gc.setExpireAgeMillis(0);
- fsTick();
- gc.prune(Collections.<ObjectId> emptySet());
- assertFalse(repo.hasObject(a));
- }
-
- @Test
- public void nonReferencedExpiredObjectTree_pruned() throws Exception {
- RevBlob a = tr.blob("a");
- RevTree t = tr.tree(tr.file("a", a));
- gc.setExpireAgeMillis(0);
- fsTick();
- gc.prune(Collections.<ObjectId> emptySet());
- assertFalse(repo.hasObject(t));
- assertFalse(repo.hasObject(a));
- }
-
- @Test
- public void nonReferencedObjects_onlyExpiredPruned() throws Exception {
- RevBlob a = tr.blob("a");
- gc.setExpire(new Date(lastModified(a) + 1));
-
- fsTick();
- RevBlob b = tr.blob("b");
-
- gc.prune(Collections.<ObjectId> emptySet());
- assertFalse(repo.hasObject(a));
- assertTrue(repo.hasObject(b));
- }
-
- @Test
- public void lightweightTag_objectNotPruned() throws Exception {
- RevBlob a = tr.blob("a");
- tr.lightweightTag("t", a);
- gc.setExpireAgeMillis(0);
- fsTick();
- gc.prune(Collections.<ObjectId> emptySet());
- assertTrue(repo.hasObject(a));
- }
-
- @Test
- public void annotatedTag_objectNotPruned() throws Exception {
- RevBlob a = tr.blob("a");
- RevTag t = tr.tag("t", a); // this doesn't create the refs/tags/t ref
- tr.lightweightTag("t", t);
-
- gc.setExpireAgeMillis(0);
- fsTick();
- gc.prune(Collections.<ObjectId> emptySet());
- assertTrue(repo.hasObject(t));
- assertTrue(repo.hasObject(a));
- }
-
- @Test
- public void branch_historyNotPruned() throws Exception {
- RevCommit tip = commitChain(10);
- tr.branch("b").update(tip);
- gc.setExpireAgeMillis(0);
- fsTick();
- gc.prune(Collections.<ObjectId> emptySet());
- do {
- assertTrue(repo.hasObject(tip));
- tr.parseBody(tip);
- RevTree t = tip.getTree();
- assertTrue(repo.hasObject(t));
- assertTrue(repo.hasObject(tr.get(t, "a")));
- tip = tip.getParentCount() > 0 ? tip.getParent(0) : null;
- } while (tip != null);
- }
-
- @Test
- public void deleteBranch_historyPruned() throws Exception {
- RevCommit tip = commitChain(10);
- tr.branch("b").update(tip);
- RefUpdate update = repo.updateRef("refs/heads/b");
- update.setForceUpdate(true);
- update.delete();
- gc.setExpireAgeMillis(0);
- fsTick();
- gc.prune(Collections.<ObjectId> emptySet());
- assertTrue(gc.getStatistics().numberOfLooseObjects == 0);
- }
-
- @Test
- public void deleteMergedBranch_historyNotPruned() throws Exception {
- RevCommit parent = tr.commit().create();
- RevCommit b1Tip = tr.branch("b1").commit().parent(parent).add("x", "x")
- .create();
- RevCommit b2Tip = tr.branch("b2").commit().parent(parent).add("y", "y")
- .create();
-
- // merge b1Tip and b2Tip and update refs/heads/b1 to the merge commit
- Merger merger = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(repo);
- merger.merge(b1Tip, b2Tip);
- CommitBuilder cb = tr.commit();
- cb.parent(b1Tip).parent(b2Tip);
- cb.setTopLevelTree(merger.getResultTreeId());
- RevCommit mergeCommit = cb.create();
- RefUpdate u = repo.updateRef("refs/heads/b1");
- u.setNewObjectId(mergeCommit);
- u.update();
-
- RefUpdate update = repo.updateRef("refs/heads/b2");
- update.setForceUpdate(true);
- update.delete();
-
- gc.setExpireAgeMillis(0);
- fsTick();
- gc.prune(Collections.<ObjectId> emptySet());
- assertTrue(repo.hasObject(b2Tip));
- }
-
- @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);
-
- // Do the gc again and check that it hasn't changed anything
- gc.gc();
- stats = gc.getStatistics();
- assertEquals(0, stats.numberOfLooseObjects);
- assertEquals(4, stats.numberOfPackedObjects);
- assertEquals(1, stats.numberOfPackFiles);
- }
-
- @Test
- public void testPackRepoWithCorruptReflog() throws Exception {
- // create a reflog entry "0000... 0000... foobar" by doing an initial
- // refupdate for HEAD which points to a non-existing ref. The
- // All-Projects repo of gerrit instances had such entries
- RefUpdate ru = repo.updateRef(Constants.HEAD);
- ru.link("refs/to/garbage");
- tr.branch("refs/heads/master").commit().add("A", "A").add("B", "B")
- .create();
- // make sure HEAD exists
- Git.wrap(repo).checkout().setName("refs/heads/master").call();
- gc.gc();
- }
-
- @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);
- fsTick();
- 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);
- fsTick();
- gc.gc();
- stats = gc.getStatistics();
- assertEquals(0, stats.numberOfLooseObjects);
- assertEquals(8, stats.numberOfPackedObjects);
- assertEquals(2, stats.numberOfPackFiles);
- }
-
- @Test
- public void testPruneOldPacksWithOpenHandleOnPack() throws Exception {
- gc.setExpireAgeMillis(0);
-
- BranchBuilder bb = tr.branch("refs/heads/master");
- bb.commit().add("A", "A").add("B", "B").create();
- fsTick();
- gc.gc();
-
- Collection<PackFile> packs = repo.getObjectDatabase().getPacks();
- assertEquals(1, packs.size());
- PackFile pack = packs.iterator().next();
- File packFile = pack.getPackFile();
- File indexFile = new File(packFile.getParentFile(), "pack-"
- + pack.getPackName()
- + "."
- + PackExt.INDEX.getExtension());
- FileInputStream fis = new FileInputStream(packFile);
- try {
- bb.commit().add("A", "A2").add("B", "B2").create();
- fsTick();
- gc.gc();
- if (packFile.exists()) {
- assertTrue(
- "The pack was present but the index file was missing.",
- indexFile.exists());
- }
-
- } finally {
- fis.close();
- }
-
- }
-
- @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);
- fsTick();
- 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);
- fsTick();
- 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);
- }
-
- /**
- * Create a chain of commits of given depth.
- * <p>
- * Each commit contains one file named "a" containing the index of the
- * commit in the chain as its content. The created commit chain is
- * referenced from any ref.
- * <p>
- * A chain of depth = N will create 3*N objects in Gits object database. For
- * each depth level three objects are created: the commit object, the
- * top-level tree object and a blob for the content of the file "a".
- *
- * @param depth
- * the depth of the commit chain.
- * @return the commit that is the tip of the commit chain
- * @throws Exception
- */
- private RevCommit commitChain(int depth) throws Exception {
- if (depth <= 0)
- throw new IllegalArgumentException("Chain depth must be > 0");
- CommitBuilder cb = tr.commit();
- RevCommit tip;
- do {
- --depth;
- tip = cb.add("a", "" + depth).message("" + depth).create();
- cb = cb.child();
- } while (depth > 0);
- return tip;
- }
-
- private long lastModified(AnyObjectId objectId) {
- return repo.getObjectDatabase().fileFor(objectId).lastModified();
- }
-
- private static void fsTick() throws InterruptedException, IOException {
- RepositoryTestCase.fsTick(null);
- }
-}
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
new file mode 100644
index 0000000000..0f27099c09
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java
@@ -0,0 +1,162 @@
+/*
+ * 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.internal.storage.file;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.eclipse.jgit.junit.TestRepository.BranchBuilder;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.Test;
+
+public class GcBasicPackingTest extends GcTestCase {
+ @Test
+ public void repackEmptyRepo_noPackCreated() throws IOException {
+ gc.repack();
+ assertEquals(0, repo.getObjectDatabase().getPacks().size());
+ }
+
+ @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 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);
+
+ // Do the gc again and check that it hasn't changed anything
+ gc.gc();
+ stats = gc.getStatistics();
+ assertEquals(0, stats.numberOfLooseObjects);
+ assertEquals(4, 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);
+ fsTick();
+ 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());
+ }
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBranchPrunedTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBranchPrunedTest.java
new file mode 100644
index 0000000000..c7ee9256d7
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBranchPrunedTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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.internal.storage.file;
+
+import static org.junit.Assert.assertTrue;
+
+import java.util.Collections;
+
+import org.eclipse.jgit.junit.TestRepository.CommitBuilder;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.merge.MergeStrategy;
+import org.eclipse.jgit.merge.Merger;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevTree;
+import org.junit.Test;
+
+public class GcBranchPrunedTest extends GcTestCase {
+
+ @Test
+ public void branch_historyNotPruned() throws Exception {
+ RevCommit tip = commitChain(10);
+ tr.branch("b").update(tip);
+ gc.setExpireAgeMillis(0);
+ fsTick();
+ gc.prune(Collections.<ObjectId> emptySet());
+ do {
+ assertTrue(repo.hasObject(tip));
+ tr.parseBody(tip);
+ RevTree t = tip.getTree();
+ assertTrue(repo.hasObject(t));
+ assertTrue(repo.hasObject(tr.get(t, "a")));
+ tip = tip.getParentCount() > 0 ? tip.getParent(0) : null;
+ } while (tip != null);
+ }
+
+ @Test
+ public void deleteBranch_historyPruned() throws Exception {
+ RevCommit tip = commitChain(10);
+ tr.branch("b").update(tip);
+ RefUpdate update = repo.updateRef("refs/heads/b");
+ update.setForceUpdate(true);
+ update.delete();
+ gc.setExpireAgeMillis(0);
+ fsTick();
+ gc.prune(Collections.<ObjectId> emptySet());
+ assertTrue(gc.getStatistics().numberOfLooseObjects == 0);
+ }
+
+ @Test
+ public void deleteMergedBranch_historyNotPruned() throws Exception {
+ RevCommit parent = tr.commit().create();
+ RevCommit b1Tip = tr.branch("b1").commit().parent(parent).add("x", "x")
+ .create();
+ RevCommit b2Tip = tr.branch("b2").commit().parent(parent).add("y", "y")
+ .create();
+
+ // merge b1Tip and b2Tip and update refs/heads/b1 to the merge commit
+ Merger merger = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(repo);
+ merger.merge(b1Tip, b2Tip);
+ CommitBuilder cb = tr.commit();
+ cb.parent(b1Tip).parent(b2Tip);
+ cb.setTopLevelTree(merger.getResultTreeId());
+ RevCommit mergeCommit = cb.create();
+ RefUpdate u = repo.updateRef("refs/heads/b1");
+ u.setNewObjectId(mergeCommit);
+ u.update();
+
+ RefUpdate update = repo.updateRef("refs/heads/b2");
+ update.setForceUpdate(true);
+ update.delete();
+
+ gc.setExpireAgeMillis(0);
+ fsTick();
+ gc.prune(Collections.<ObjectId> emptySet());
+ assertTrue(repo.hasObject(b2Tip));
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcConcurrentTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcConcurrentTest.java
new file mode 100644
index 0000000000..07a7be7467
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcConcurrentTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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.internal.storage.file;
+
+import static java.lang.Integer.valueOf;
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.util.concurrent.BrokenBarrierException;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.EmptyProgressMonitor;
+import org.eclipse.jgit.revwalk.RevBlob;
+import org.junit.Test;
+
+public class GcConcurrentTest extends GcTestCase {
+ @Test
+ public void concurrentRepack() throws Exception {
+ final CyclicBarrier syncPoint = new CyclicBarrier(2);
+
+ class DoRepack extends EmptyProgressMonitor implements
+ Callable<Integer> {
+
+ public void beginTask(String title, int totalWork) {
+ if (title.equals(JGitText.get().writingObjects)) {
+ try {
+ syncPoint.await();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ } catch (BrokenBarrierException ignored) {
+ //
+ }
+ }
+ }
+
+ /** @return 0 for success, 1 in case of error when writing pack */
+ public Integer call() throws Exception {
+ try {
+ gc.setProgressMonitor(this);
+ gc.repack();
+ return valueOf(0);
+ } catch (IOException e) {
+ // leave the syncPoint in broken state so any awaiting
+ // threads and any threads that call await in the future get
+ // the BrokenBarrierException
+ Thread.currentThread().interrupt();
+ try {
+ syncPoint.await();
+ } catch (InterruptedException ignored) {
+ //
+ }
+ return valueOf(1);
+ }
+ }
+ }
+
+ RevBlob a = tr.blob("a");
+ tr.lightweightTag("t", a);
+
+ ExecutorService pool = Executors.newFixedThreadPool(2);
+ try {
+ DoRepack repack1 = new DoRepack();
+ DoRepack repack2 = new DoRepack();
+ Future<Integer> result1 = pool.submit(repack1);
+ Future<Integer> result2 = pool.submit(repack2);
+ assertEquals(0, result1.get().intValue() + result2.get().intValue());
+ } finally {
+ pool.shutdown();
+ pool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
+ }
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcDirCacheSavesObjectsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcDirCacheSavesObjectsTest.java
new file mode 100644
index 0000000000..50e497e67a
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcDirCacheSavesObjectsTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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.internal.storage.file;
+
+import static org.junit.Assert.assertEquals;
+
+import org.eclipse.jgit.junit.TestRepository.BranchBuilder;
+import org.junit.Test;
+
+public class GcDirCacheSavesObjectsTest extends GcTestCase {
+ @Test
+ public void testDirCacheSavesObjects() 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 testDirCacheSavesObjectsWithPruneNow() 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);
+ fsTick();
+ gc.gc();
+ stats = gc.getStatistics();
+ assertEquals(0, stats.numberOfLooseObjects);
+ assertEquals(8, stats.numberOfPackedObjects);
+ assertEquals(1, stats.numberOfPackFiles);
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcKeepFilesTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcKeepFilesTest.java
new file mode 100644
index 0000000000..9e28298823
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcKeepFilesTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.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.util.Iterator;
+
+import org.eclipse.jgit.internal.storage.file.PackIndex.MutableEntry;
+import org.eclipse.jgit.junit.TestRepository.BranchBuilder;
+import org.junit.Test;
+
+public class GcKeepFilesTest extends GcTestCase {
+ @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()));
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java
new file mode 100644
index 0000000000..0ade902601
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java
@@ -0,0 +1,180 @@
+/*
+ * 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.internal.storage.file;
+
+import static java.lang.Integer.valueOf;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.concurrent.BrokenBarrierException;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jgit.lib.Ref.Storage;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.RefUpdate.Result;
+import org.eclipse.jgit.revwalk.RevBlob;
+import org.junit.Test;
+
+public class GcPackRefsTest extends GcTestCase {
+ @Test
+ public void looseRefPacked() throws Exception {
+ RevBlob a = tr.blob("a");
+ tr.lightweightTag("t", a);
+
+ gc.packRefs();
+ assertSame(repo.getRef("t").getStorage(), Storage.PACKED);
+ }
+
+ @Test
+ public void concurrentOnlyOneWritesPackedRefs() throws Exception {
+ RevBlob a = tr.blob("a");
+ tr.lightweightTag("t", a);
+
+ final CyclicBarrier syncPoint = new CyclicBarrier(2);
+
+ Callable<Integer> packRefs = new Callable<Integer>() {
+
+ /** @return 0 for success, 1 in case of error when writing pack */
+ public Integer call() throws Exception {
+ syncPoint.await();
+ try {
+ gc.packRefs();
+ return valueOf(0);
+ } catch (IOException e) {
+ return valueOf(1);
+ }
+ }
+ };
+ ExecutorService pool = Executors.newFixedThreadPool(2);
+ try {
+ Future<Integer> p1 = pool.submit(packRefs);
+ Future<Integer> p2 = pool.submit(packRefs);
+ assertEquals(1, p1.get().intValue() + p2.get().intValue());
+ } finally {
+ pool.shutdown();
+ pool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
+ }
+ }
+
+ @Test
+ public void whileRefLockedRefNotPackedNoError()
+ throws Exception {
+ RevBlob a = tr.blob("a");
+ tr.lightweightTag("t1", a);
+ tr.lightweightTag("t2", a);
+ LockFile refLock = new LockFile(new File(repo.getDirectory(),
+ "refs/tags/t1"), repo.getFS());
+ try {
+ refLock.lock();
+ gc.packRefs();
+ } finally {
+ refLock.unlock();
+ }
+
+ assertSame(repo.getRef("refs/tags/t1").getStorage(), Storage.LOOSE);
+ assertSame(repo.getRef("refs/tags/t2").getStorage(), Storage.PACKED);
+ }
+
+ @Test
+ public void whileRefUpdatedRefUpdateSucceeds()
+ throws Exception {
+ RevBlob a = tr.blob("a");
+ tr.lightweightTag("t", a);
+ final RevBlob b = tr.blob("b");
+
+ final CyclicBarrier refUpdateLockedRef = new CyclicBarrier(2);
+ final CyclicBarrier packRefsDone = new CyclicBarrier(2);
+ ExecutorService pool = Executors.newFixedThreadPool(2);
+ try {
+ Future<Result> result = pool.submit(new Callable<Result>() {
+
+ public Result call() throws Exception {
+ RefUpdate update = new RefDirectoryUpdate(
+ (RefDirectory) repo.getRefDatabase(),
+ repo.getRef("refs/tags/t")) {
+ @Override
+ public boolean isForceUpdate() {
+ try {
+ refUpdateLockedRef.await();
+ packRefsDone.await();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ } catch (BrokenBarrierException e) {
+ Thread.currentThread().interrupt();
+ }
+ return super.isForceUpdate();
+ }
+ };
+ update.setForceUpdate(true);
+ update.setNewObjectId(b);
+ return update.update();
+ }
+ });
+
+ pool.submit(new Callable<Void>() {
+ public Void call() throws Exception {
+ refUpdateLockedRef.await();
+ gc.packRefs();
+ packRefsDone.await();
+ return null;
+ }
+ });
+
+ assertSame(result.get(), Result.FORCED);
+
+ } finally {
+ pool.shutdownNow();
+ pool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
+ }
+
+ assertEquals(repo.getRef("refs/tags/t").getObjectId(), b);
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPruneNonReferencedTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPruneNonReferencedTest.java
new file mode 100644
index 0000000000..5b1a4178a6
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPruneNonReferencedTest.java
@@ -0,0 +1,120 @@
+/*
+ * 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.internal.storage.file;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Collections;
+import java.util.Date;
+
+import org.eclipse.jgit.junit.TestRepository.BranchBuilder;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.revwalk.RevBlob;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevTree;
+import org.junit.Test;
+
+public class GcPruneNonReferencedTest extends GcTestCase {
+ @Test
+ public void nonReferencedNonExpiredObject_notPruned() throws Exception {
+ RevBlob a = tr.blob("a");
+ gc.setExpire(new Date(lastModified(a)));
+ gc.prune(Collections.<ObjectId> emptySet());
+ assertTrue(repo.hasObject(a));
+ }
+
+ @Test
+ public void nonReferencedExpiredObject_pruned() throws Exception {
+ RevBlob a = tr.blob("a");
+ gc.setExpireAgeMillis(0);
+ fsTick();
+ gc.prune(Collections.<ObjectId> emptySet());
+ assertFalse(repo.hasObject(a));
+ }
+
+ @Test
+ public void nonReferencedExpiredObjectTree_pruned() throws Exception {
+ RevBlob a = tr.blob("a");
+ RevTree t = tr.tree(tr.file("a", a));
+ gc.setExpireAgeMillis(0);
+ fsTick();
+ gc.prune(Collections.<ObjectId> emptySet());
+ assertFalse(repo.hasObject(t));
+ assertFalse(repo.hasObject(a));
+ }
+
+ @Test
+ public void nonReferencedObjects_onlyExpiredPruned() throws Exception {
+ RevBlob a = tr.blob("a");
+ gc.setExpire(new Date(lastModified(a) + 1));
+
+ fsTick();
+ RevBlob b = tr.blob("b");
+
+ gc.prune(Collections.<ObjectId> emptySet());
+ assertFalse(repo.hasObject(a));
+ assertTrue(repo.hasObject(b));
+ }
+
+ @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);
+ fsTick();
+ gc.gc();
+ stats = gc.getStatistics();
+ assertEquals(0, stats.numberOfLooseObjects);
+ assertEquals(8, stats.numberOfPackedObjects);
+ assertEquals(2, stats.numberOfPackFiles);
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcReflogTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcReflogTest.java
new file mode 100644
index 0000000000..2a096fd1c4
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcReflogTest.java
@@ -0,0 +1,146 @@
+/*
+ * 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.internal.storage.file;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.File;
+import java.util.Collections;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.junit.TestRepository.BranchBuilder;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.util.FileUtils;
+import org.junit.Test;
+
+public class GcReflogTest extends GcTestCase {
+ @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);
+ fsTick();
+ 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);
+ }
+
+ @Test
+ public void testPackRepoWithCorruptReflog() throws Exception {
+ // create a reflog entry "0000... 0000... foobar" by doing an initial
+ // refupdate for HEAD which points to a non-existing ref. The
+ // All-Projects repo of gerrit instances had such entries
+ RefUpdate ru = repo.updateRef(Constants.HEAD);
+ ru.link("refs/to/garbage");
+ tr.branch("refs/heads/master").commit().add("A", "A").add("B", "B")
+ .create();
+ // make sure HEAD exists
+ Git.wrap(repo).checkout().setName("refs/heads/master").call();
+ gc.gc();
+ }
+
+ @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 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);
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTagTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTagTest.java
new file mode 100644
index 0000000000..4afbeff3ec
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTagTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.internal.storage.file;
+
+import static org.junit.Assert.assertTrue;
+
+import java.util.Collections;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.revwalk.RevBlob;
+import org.eclipse.jgit.revwalk.RevTag;
+import org.junit.Test;
+
+public class GcTagTest extends GcTestCase {
+ @Test
+ public void lightweightTag_objectNotPruned() throws Exception {
+ RevBlob a = tr.blob("a");
+ tr.lightweightTag("t", a);
+ gc.setExpireAgeMillis(0);
+ fsTick();
+ gc.prune(Collections.<ObjectId> emptySet());
+ assertTrue(repo.hasObject(a));
+ }
+
+ @Test
+ public void annotatedTag_objectNotPruned() throws Exception {
+ RevBlob a = tr.blob("a");
+ RevTag t = tr.tag("t", a); // this doesn't create the refs/tags/t ref
+ tr.lightweightTag("t", t);
+
+ gc.setExpireAgeMillis(0);
+ fsTick();
+ gc.prune(Collections.<ObjectId> emptySet());
+ assertTrue(repo.hasObject(t));
+ assertTrue(repo.hasObject(a));
+ }
+}
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java
new file mode 100644
index 0000000000..a764f0fddd
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java
@@ -0,0 +1,113 @@
+/*
+ * 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.internal.storage.file;
+
+import java.io.IOException;
+
+import org.eclipse.jgit.internal.storage.file.GC.RepoStatistics;
+import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.junit.TestRepository.CommitBuilder;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.After;
+import org.junit.Before;
+
+public abstract class GcTestCase extends LocalDiskRepositoryTestCase {
+ protected TestRepository<FileRepository> tr;
+ protected FileRepository repo;
+ protected GC gc;
+ protected 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();
+ }
+
+ /**
+ * Create a chain of commits of given depth.
+ * <p>
+ * Each commit contains one file named "a" containing the index of the
+ * commit in the chain as its content. The created commit chain is
+ * referenced from any ref.
+ * <p>
+ * A chain of depth = N will create 3*N objects in Gits object database. For
+ * each depth level three objects are created: the commit object, the
+ * top-level tree object and a blob for the content of the file "a".
+ *
+ * @param depth
+ * the depth of the commit chain.
+ * @return the commit that is the tip of the commit chain
+ * @throws Exception
+ */
+ protected RevCommit commitChain(int depth) throws Exception {
+ if (depth <= 0)
+ throw new IllegalArgumentException("Chain depth must be > 0");
+ CommitBuilder cb = tr.commit();
+ RevCommit tip;
+ do {
+ --depth;
+ tip = cb.add("a", "" + depth).message("" + depth).create();
+ cb = cb.child();
+ } while (depth > 0);
+ return tip;
+ }
+
+ protected long lastModified(AnyObjectId objectId) {
+ return repo.getObjectDatabase().fileFor(objectId).lastModified();
+ }
+
+ protected static void fsTick() throws InterruptedException, IOException {
+ RepositoryTestCase.fsTick(null);
+ }
+}