diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java index 5d21e3d85..382afc5ca 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java @@ -587,6 +587,7 @@ public T update(String ref, T obj) throws Exception { public void delete(String ref) throws Exception { ref = normalizeRef(ref); RefUpdate u = db.updateRef(ref); + u.setForceUpdate(true); switch (u.delete()) { case FAST_FORWARD: case FORCED: diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollectorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollectorTest.java index 92399e04e..5b567d00f 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollectorTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollectorTest.java @@ -9,6 +9,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -18,6 +19,7 @@ import java.util.concurrent.TimeUnit; import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource; +import org.eclipse.jgit.internal.storage.dfs.DfsRefDatabase; import org.eclipse.jgit.internal.storage.reftable.RefCursor; import org.eclipse.jgit.internal.storage.reftable.ReftableConfig; import org.eclipse.jgit.internal.storage.reftable.ReftableReader; @@ -842,6 +844,106 @@ public void compactsReftables() throws Exception { } } + @Test + public void reftableWithoutTombstoneResurrected() throws Exception { + RevCommit commit0 = commit().message("0").create(); + String NEXT = "refs/heads/next"; + DfsRefDatabase refdb = (DfsRefDatabase)repo.getRefDatabase(); + git.update(NEXT, commit0); + Ref next = refdb.exactRef(NEXT); + assertNotNull(next); + assertEquals(commit0, next.getObjectId()); + + git.delete(NEXT); + refdb.clearCache(); + assertNull(refdb.exactRef(NEXT)); + + DfsGarbageCollector gc = new DfsGarbageCollector(repo); + gc.setReftableConfig(new ReftableConfig()); + gc.setIncludeDeletes(false); + gc.setConvertToReftable(false); + run(gc); + assertEquals(1, odb.getReftables().length); + try (DfsReader ctx = odb.newReader(); + ReftableReader rr = odb.getReftables()[0].open(ctx)) { + rr.setIncludeDeletes(true); + assertEquals(1, rr.minUpdateIndex()); + assertEquals(2, rr.maxUpdateIndex()); + assertNull(rr.exactRef(NEXT)); + } + + RevCommit commit1 = commit().message("1").create(); + DfsPackDescription t1 = odb.newPack(INSERT); + Ref newNext = new ObjectIdRef.PeeledNonTag(Ref.Storage.LOOSE, NEXT, + commit1); + try (DfsOutputStream out = odb.writeFile(t1, REFTABLE)) { + ReftableWriter w = new ReftableWriter(); + w.setMinUpdateIndex(1); + w.setMaxUpdateIndex(1); + w.begin(out); + w.writeRef(newNext, 1); + w.finish(); + t1.addFileExt(REFTABLE); + t1.setReftableStats(w.getStats()); + } + odb.commitPack(Collections.singleton(t1), null); + assertEquals(2, odb.getReftables().length); + refdb.clearCache(); + newNext = refdb.exactRef(NEXT); + assertNotNull(newNext); + assertEquals(commit1, newNext.getObjectId()); + } + + @Test + public void reftableWithTombstoneNotResurrected() throws Exception { + RevCommit commit0 = commit().message("0").create(); + String NEXT = "refs/heads/next"; + DfsRefDatabase refdb = (DfsRefDatabase)repo.getRefDatabase(); + git.update(NEXT, commit0); + Ref next = refdb.exactRef(NEXT); + assertNotNull(next); + assertEquals(commit0, next.getObjectId()); + + git.delete(NEXT); + refdb.clearCache(); + assertNull(refdb.exactRef(NEXT)); + + DfsGarbageCollector gc = new DfsGarbageCollector(repo); + gc.setReftableConfig(new ReftableConfig()); + gc.setIncludeDeletes(true); + gc.setConvertToReftable(false); + run(gc); + assertEquals(1, odb.getReftables().length); + try (DfsReader ctx = odb.newReader(); + ReftableReader rr = odb.getReftables()[0].open(ctx)) { + rr.setIncludeDeletes(true); + assertEquals(1, rr.minUpdateIndex()); + assertEquals(2, rr.maxUpdateIndex()); + next = rr.exactRef(NEXT); + assertNotNull(next); + assertNull(next.getObjectId()); + } + + RevCommit commit1 = commit().message("1").create(); + DfsPackDescription t1 = odb.newPack(INSERT); + Ref newNext = new ObjectIdRef.PeeledNonTag(Ref.Storage.LOOSE, NEXT, + commit1); + try (DfsOutputStream out = odb.writeFile(t1, REFTABLE)) { + ReftableWriter w = new ReftableWriter(); + w.setMinUpdateIndex(1); + w.setMaxUpdateIndex(1); + w.begin(out); + w.writeRef(newNext, 1); + w.finish(); + t1.addFileExt(REFTABLE); + t1.setReftableStats(w.getStats()); + } + odb.commitPack(Collections.singleton(t1), null); + assertEquals(2, odb.getReftables().length); + refdb.clearCache(); + assertNull(refdb.exactRef(NEXT)); + } + private TestRepository.CommitBuilder commit() { return git.commit(); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java index 8a63b8ba7..dc08b1cd0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java @@ -108,6 +108,7 @@ public class DfsGarbageCollector { private PackConfig packConfig; private ReftableConfig reftableConfig; private boolean convertToReftable = true; + private boolean includeDeletes; private long reftableInitialMinUpdateIndex = 1; private long reftableInitialMaxUpdateIndex = 1; @@ -185,6 +186,17 @@ public DfsGarbageCollector setConvertToReftable(boolean convert) { return this; } + /** + * @param include + * if true, the garbage collector will include tombstones for + * deleted references in the reftable. Default is {@code false}. + * @return {@code this} + */ + public DfsGarbageCollector setIncludeDeletes(boolean include) { + includeDeletes = include; + return this; + } + /** * Set minUpdateIndex for the initial reftable created during conversion. * @@ -699,7 +711,7 @@ private void writeReftable(DfsPackDescription pack) throws IOException { try (ReftableStack stack = ReftableStack.open(ctx, reftablesBefore)) { ReftableCompactor compact = new ReftableCompactor(); compact.addAll(stack.readers()); - compact.setIncludeDeletes(false); + compact.setIncludeDeletes(includeDeletes); compactReftable(pack, compact); } }