]> source.dussan.org Git - jgit.git/commitdiff
Break up GCTest to run in parallel 93/17993/2
authorShawn Pearce <sop@google.com>
Fri, 1 Nov 2013 18:13:25 +0000 (12:13 -0600)
committerRobin Rosenberg <robin.rosenberg@dewire.com>
Sun, 24 Nov 2013 10:37:49 +0000 (11:37 +0100)
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

org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GCTest.java [deleted file]
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java [new file with mode: 0644]
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBranchPrunedTest.java [new file with mode: 0644]
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcConcurrentTest.java [new file with mode: 0644]
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcDirCacheSavesObjectsTest.java [new file with mode: 0644]
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcKeepFilesTest.java [new file with mode: 0644]
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java [new file with mode: 0644]
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPruneNonReferencedTest.java [new file with mode: 0644]
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcReflogTest.java [new file with mode: 0644]
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTagTest.java [new file with mode: 0644]
org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcTestCase.java [new file with mode: 0644]

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 (file)
index 35455f4..0000000
+++ /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 (file)
index 0000000..0f27099
--- /dev/null
@@ -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 (file)
index 0000000..c7ee925
--- /dev/null
@@ -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 (file)
index 0000000..07a7be7
--- /dev/null
@@ -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 (file)
index 0000000..50e497e
--- /dev/null
@@ -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 (file)
index 0000000..9e28298
--- /dev/null
@@ -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 (file)
index 0000000..0ade902
--- /dev/null
@@ -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 (file)
index 0000000..5b1a417
--- /dev/null
@@ -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 (file)
index 0000000..2a096fd
--- /dev/null
@@ -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 (file)
index 0000000..4afbeff
--- /dev/null
@@ -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 (file)
index 0000000..a764f0f
--- /dev/null
@@ -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);
+       }
+}