import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.IOException;
+import java.util.Collections;
import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevBlob;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.storage.pack.PackConfig;
import org.eclipse.jgit.util.SystemReader;
import org.junit.After;
import org.junit.Before;
assertTrue("commit1 in pack", isObjectInPack(commit1, pack));
}
+ @Test
+ public void testRacyNoReusePrefersSmaller() throws Exception {
+ StringBuilder msg = new StringBuilder();
+ for (int i = 0; i < 100; i++) {
+ msg.append(i).append(": i am a teapot\n");
+ }
+ RevBlob a = git.blob(msg.toString());
+ RevCommit c0 = git.commit()
+ .add("tea", a)
+ .message("0")
+ .create();
+
+ msg.append("short and stout\n");
+ RevBlob b = git.blob(msg.toString());
+ RevCommit c1 = git.commit().parent(c0).tick(1)
+ .add("tea", b)
+ .message("1")
+ .create();
+ git.update("master", c1);
+
+ PackConfig cfg = new PackConfig();
+ cfg.setReuseObjects(false);
+ cfg.setReuseDeltas(false);
+ cfg.setDeltaCompress(false);
+ cfg.setThreads(1);
+ DfsGarbageCollector gc = new DfsGarbageCollector(repo);
+ gc.setGarbageTtl(0, TimeUnit.MILLISECONDS); // disable TTL
+ gc.setPackConfig(cfg);
+ run(gc);
+
+ assertEquals(1, odb.getPacks().length);
+ DfsPackDescription large = odb.getPacks()[0].getPackDescription();
+ assertSame(PackSource.GC, large.getPackSource());
+
+ cfg.setDeltaCompress(true);
+ gc = new DfsGarbageCollector(repo);
+ gc.setGarbageTtl(0, TimeUnit.MILLISECONDS); // disable TTL
+ gc.setPackConfig(cfg);
+ run(gc);
+
+ assertEquals(1, odb.getPacks().length);
+ DfsPackDescription small = odb.getPacks()[0].getPackDescription();
+ assertSame(PackSource.GC, small.getPackSource());
+ assertTrue(
+ "delta compression pack is smaller",
+ small.getFileSize(PACK) < large.getFileSize(PACK));
+ assertTrue(
+ "large pack is older",
+ large.getLastModified() < small.getLastModified());
+
+ // Forcefully reinsert the older larger GC pack.
+ odb.commitPack(Collections.singleton(large), null);
+ odb.clearCache();
+ assertEquals(2, odb.getPacks().length);
+
+ gc = new DfsGarbageCollector(repo);
+ gc.setGarbageTtl(0, TimeUnit.MILLISECONDS); // disable TTL
+ run(gc);
+
+ assertEquals(1, odb.getPacks().length);
+ DfsPackDescription rebuilt = odb.getPacks()[0].getPackDescription();
+ assertEquals(small.getFileSize(PACK), rebuilt.getFileSize(PACK));
+ }
+
@Test
public void testCollectionWithGarbage() throws Exception {
RevCommit commit0 = commit().message("0").create();
return cmp;
}
+ // Tie break GC type packs by smallest first. There should be at most
+ // one of each source, but when multiple exist concurrent GCs may have
+ // run. Preferring the smaller file selects higher quality delta
+ // compression, placing less demand on the DfsBlockCache.
+ if (as != null && as == bs && isGC(as)) {
+ int cmp = Long.signum(getFileSize(PACK) - b.getFileSize(PACK));
+ if (cmp != 0) {
+ return cmp;
+ }
+ }
+
// Newer packs should sort first.
int cmp = Long.signum(b.getLastModified() - getLastModified());
if (cmp != 0)
return Long.signum(getObjectCount() - b.getObjectCount());
}
+ static boolean isGC(PackSource s) {
+ switch (s) {
+ case GC:
+ case GC_REST:
+ case GC_TXN:
+ return true;
+ default:
+ return false;
+ }
+ }
+
@Override
public String toString() {
return getFileName(PackExt.PACK);
package org.eclipse.jgit.internal.storage.dfs;
+import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackList;
+import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource;
import org.eclipse.jgit.internal.storage.file.BitmapIndexImpl;
import org.eclipse.jgit.internal.storage.file.PackBitmapIndex;
import org.eclipse.jgit.internal.storage.file.PackIndex;
throws IOException, MissingObjectException {
// Don't check dirty bit on PackList; assume ObjectToPacks all came from the
// current list.
- for (DfsPackFile pack : db.getPacks()) {
+ for (DfsPackFile pack : sortPacksForSelectRepresentation()) {
List<DfsObjectToPack> tmp = findAllFromPack(pack, objects);
if (tmp.isEmpty())
continue;
}
}
+ private static final Comparator<DfsPackFile> PACK_SORT_FOR_REUSE = new Comparator<DfsPackFile>() {
+ public int compare(DfsPackFile af, DfsPackFile bf) {
+ DfsPackDescription ad = af.getPackDescription();
+ DfsPackDescription bd = bf.getPackDescription();
+ PackSource as = ad.getPackSource();
+ PackSource bs = bd.getPackSource();
+
+ if (as != null && as == bs && DfsPackDescription.isGC(as)) {
+ // Push smaller GC files last; these likely have higher quality
+ // delta compression and the contained representation should be
+ // favored over other files.
+ return Long.signum(bd.getFileSize(PACK) - ad.getFileSize(PACK));
+ }
+
+ // DfsPackDescription.compareTo already did a reasonable sort.
+ // Rely on Arrays.sort being stable, leaving equal elements.
+ return 0;
+ }
+ };
+
+ private DfsPackFile[] sortPacksForSelectRepresentation()
+ throws IOException {
+ DfsPackFile[] packs = db.getPacks();
+ DfsPackFile[] sorted = new DfsPackFile[packs.length];
+ System.arraycopy(packs, 0, sorted, 0, packs.length);
+ Arrays.sort(sorted, PACK_SORT_FOR_REUSE);
+ return sorted;
+ }
+
private List<DfsObjectToPack> findAllFromPack(DfsPackFile pack,
Iterable<ObjectToPack> objects) throws IOException {
List<DfsObjectToPack> tmp = new BlockList<DfsObjectToPack>();