summaryrefslogtreecommitdiffstats
path: root/org.eclipse.jgit.test
diff options
context:
space:
mode:
authorMartin Fick <quic_mfick@quicinc.com>2020-12-15 14:20:44 -0700
committerMatthias Sohn <matthias.sohn@sap.com>2021-03-04 22:31:40 +0100
commit6167641834e28f8ad322f8fde60866b339bfb7fe (patch)
tree3bdbbddb3021282f0fe52f5b1c306deea9c963ce /org.eclipse.jgit.test
parent7fbff35887a4179d33b17fce96191c82b397ebd7 (diff)
downloadjgit-6167641834e28f8ad322f8fde60866b339bfb7fe.tar.gz
jgit-6167641834e28f8ad322f8fde60866b339bfb7fe.zip
Restore preserved packs during missing object seeks
Provide a recovery path for objects being referenced during the pack pruning race. Due to the pack pruning race, it is possible for objects to become referenced after a pack has been deemed safe to prune, but before it actually gets pruned. If this happened previously, the newly referenced objects would be missing and potentially result in a corrupted ref. Add the ability to recover from this situation when an object is missing but happens to still be available in a pack in the "preserved" directory. This is likely only useful when used in conjunction with the --preserve-old-packs GC option, which prunes packs by hard-linking to the preserved directory. If an object is missing and found in a pack in the preserved directory, immediately recover that pack and its associated files (idx, bitmaps...) by moving them back to the original pack directory, and then retry the operation that would have failed due to the missing object. This retry can now succeed and the repository may avoid corruption. This approach should drastically reduce the chance of a corrupt repository during pack pruning at very little extra cost. This extra cost should only be incurred when objects are missing and a failure would normally occur. Change-Id: I2a704e3276b88cc892159d9bfe2455c6eec64252 Signed-off-by: Martin Fick <quic_mfick@quicinc.com> Signed-off-by: Nasser Grainawi <quic_nasserg@quicinc.com>
Diffstat (limited to 'org.eclipse.jgit.test')
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcBasicPackingTest.java51
1 files changed, 51 insertions, 0 deletions
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
index 61538c9c24..8dc1ddb9f6 100644
--- 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
@@ -23,6 +23,7 @@ import java.util.List;
import org.eclipse.jgit.junit.TestRepository.BranchBuilder;
import org.eclipse.jgit.lib.ConfigConstants;
+import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.storage.file.FileBasedConfig;
@@ -314,6 +315,56 @@ public class GcBasicPackingTest extends GcTestCase {
assertTrue(preservedPackFile.exists());
}
+ @Test
+ public void testPruneAndRestoreOldPacks() throws Exception {
+ String tempRef = "refs/heads/soon-to-be-unreferenced";
+ BranchBuilder bb = tr.branch(tempRef);
+ bb.commit().add("A", "A").add("B", "B").create();
+
+ // Verify setup conditions
+ stats = gc.getStatistics();
+ assertEquals(4, stats.numberOfLooseObjects);
+ assertEquals(0, stats.numberOfPackedObjects);
+
+ // Force all referenced objects into packs (to avoid having loose objects)
+ configureGc(gc, false);
+ gc.setExpireAgeMillis(0);
+ gc.setPackExpireAgeMillis(0);
+ gc.gc();
+ stats = gc.getStatistics();
+ assertEquals(0, stats.numberOfLooseObjects);
+ assertEquals(4, stats.numberOfPackedObjects);
+ assertEquals(1, stats.numberOfPackFiles);
+
+ // Delete the temp ref, orphaning its commit
+ RefUpdate update = tr.getRepository().getRefDatabase().newUpdate(tempRef, false);
+ update.setForceUpdate(true);
+ ObjectId objectId = update.getOldObjectId(); // remember it so we can restore it!
+ RefUpdate.Result result = update.delete();
+ assertEquals(RefUpdate.Result.FORCED, result);
+
+ fsTick();
+
+ // Repack with only orphaned commit, so packfile will be pruned
+ configureGc(gc, false).setPreserveOldPacks(true);
+ gc.gc();
+ stats = gc.getStatistics();
+ assertEquals(0, stats.numberOfLooseObjects);
+ assertEquals(0, stats.numberOfPackedObjects);
+ assertEquals(0, stats.numberOfPackFiles);
+
+ // Restore the temp ref to the deleted commit, should restore old-packs!
+ update = tr.getRepository().getRefDatabase().newUpdate(tempRef, false);
+ update.setNewObjectId(objectId);
+ update.setExpectedOldObjectId(null);
+ result = update.update();
+ assertEquals(RefUpdate.Result.NEW, result);
+
+ stats = gc.getStatistics();
+ assertEquals(4, stats.numberOfPackedObjects);
+ assertEquals(1, stats.numberOfPackFiles);
+ }
+
private PackConfig configureGc(GC myGc, boolean aggressive) {
PackConfig pconfig = new PackConfig(repo);
if (aggressive) {