You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

DfsGarbageCollectorTest.java 33KB

Prefer smaller GC files during DFS garbage collection In 8ac65d33ed7a94f77cb066271669feebf9b882fc PackWriter changed its behavior to always prefer the last object representation presented to it by the ObjectReuseAsIs implementation. This was a fix to avoid delta chain cycles. Unfortunately it can lead to suboptimal compression when concurrent GCs are run on the same repository. One case is automatic GC running (with default settings) in parallel to a manual GC that has disabled delta reuse in order to generate new smaller deltas for the entire history of the repository. Running GC with no-reuse generally requires more CPU time, which also translates to a longer running time. This can lead to a race where the automatic GC completes before the no-reuse GC, leaving the repository in a state such as: no-reuse GC: size 1 GiB, mtime = 18:45 auto GC: size 8 GiB, mtime = 17:30 With the default sort ordering, the smaller no-reuse GC pack is sorted earlier in the pack list, due to its more recent mtime. During object reuse in a future GC, these smaller representations are considered first by PackWriter, but are all discarded when the auto GC file from 17:30 is examined second (due to its older mtime). Work around this in two ways. Well formed DFS repositories should have at most 1 GC pack. If 2 or more GC packs exist, break the sorting tie by selecting the smaller file earlier in the pack list. This allows all normal read code paths to favor the smaller file, which places less pressure on the DfsBlockCache. If any GC race happens, readers serving clone requests will prefer the file that is smaller. During object reuse, flip this ordering so that the smaller file is last. This allows PackWriter to see smaller deltas last, replacing larger representations that were previously considered from other pack files. Change-Id: I0b7dc8bb9711c82abd6bd16643f518cfccc6d31a
7 years ago
Prefer smaller GC files during DFS garbage collection In 8ac65d33ed7a94f77cb066271669feebf9b882fc PackWriter changed its behavior to always prefer the last object representation presented to it by the ObjectReuseAsIs implementation. This was a fix to avoid delta chain cycles. Unfortunately it can lead to suboptimal compression when concurrent GCs are run on the same repository. One case is automatic GC running (with default settings) in parallel to a manual GC that has disabled delta reuse in order to generate new smaller deltas for the entire history of the repository. Running GC with no-reuse generally requires more CPU time, which also translates to a longer running time. This can lead to a race where the automatic GC completes before the no-reuse GC, leaving the repository in a state such as: no-reuse GC: size 1 GiB, mtime = 18:45 auto GC: size 8 GiB, mtime = 17:30 With the default sort ordering, the smaller no-reuse GC pack is sorted earlier in the pack list, due to its more recent mtime. During object reuse in a future GC, these smaller representations are considered first by PackWriter, but are all discarded when the auto GC file from 17:30 is examined second (due to its older mtime). Work around this in two ways. Well formed DFS repositories should have at most 1 GC pack. If 2 or more GC packs exist, break the sorting tie by selecting the smaller file earlier in the pack list. This allows all normal read code paths to favor the smaller file, which places less pressure on the DfsBlockCache. If any GC race happens, readers serving clone requests will prefer the file that is smaller. During object reuse, flip this ordering so that the smaller file is last. This allows PackWriter to see smaller deltas last, replacing larger representations that were previously considered from other pack files. Change-Id: I0b7dc8bb9711c82abd6bd16643f518cfccc6d31a
7 years ago
Prefer smaller GC files during DFS garbage collection In 8ac65d33ed7a94f77cb066271669feebf9b882fc PackWriter changed its behavior to always prefer the last object representation presented to it by the ObjectReuseAsIs implementation. This was a fix to avoid delta chain cycles. Unfortunately it can lead to suboptimal compression when concurrent GCs are run on the same repository. One case is automatic GC running (with default settings) in parallel to a manual GC that has disabled delta reuse in order to generate new smaller deltas for the entire history of the repository. Running GC with no-reuse generally requires more CPU time, which also translates to a longer running time. This can lead to a race where the automatic GC completes before the no-reuse GC, leaving the repository in a state such as: no-reuse GC: size 1 GiB, mtime = 18:45 auto GC: size 8 GiB, mtime = 17:30 With the default sort ordering, the smaller no-reuse GC pack is sorted earlier in the pack list, due to its more recent mtime. During object reuse in a future GC, these smaller representations are considered first by PackWriter, but are all discarded when the auto GC file from 17:30 is examined second (due to its older mtime). Work around this in two ways. Well formed DFS repositories should have at most 1 GC pack. If 2 or more GC packs exist, break the sorting tie by selecting the smaller file earlier in the pack list. This allows all normal read code paths to favor the smaller file, which places less pressure on the DfsBlockCache. If any GC race happens, readers serving clone requests will prefer the file that is smaller. During object reuse, flip this ordering so that the smaller file is last. This allows PackWriter to see smaller deltas last, replacing larger representations that were previously considered from other pack files. Change-Id: I0b7dc8bb9711c82abd6bd16643f518cfccc6d31a
7 years ago
Prefer smaller GC files during DFS garbage collection In 8ac65d33ed7a94f77cb066271669feebf9b882fc PackWriter changed its behavior to always prefer the last object representation presented to it by the ObjectReuseAsIs implementation. This was a fix to avoid delta chain cycles. Unfortunately it can lead to suboptimal compression when concurrent GCs are run on the same repository. One case is automatic GC running (with default settings) in parallel to a manual GC that has disabled delta reuse in order to generate new smaller deltas for the entire history of the repository. Running GC with no-reuse generally requires more CPU time, which also translates to a longer running time. This can lead to a race where the automatic GC completes before the no-reuse GC, leaving the repository in a state such as: no-reuse GC: size 1 GiB, mtime = 18:45 auto GC: size 8 GiB, mtime = 17:30 With the default sort ordering, the smaller no-reuse GC pack is sorted earlier in the pack list, due to its more recent mtime. During object reuse in a future GC, these smaller representations are considered first by PackWriter, but are all discarded when the auto GC file from 17:30 is examined second (due to its older mtime). Work around this in two ways. Well formed DFS repositories should have at most 1 GC pack. If 2 or more GC packs exist, break the sorting tie by selecting the smaller file earlier in the pack list. This allows all normal read code paths to favor the smaller file, which places less pressure on the DfsBlockCache. If any GC race happens, readers serving clone requests will prefer the file that is smaller. During object reuse, flip this ordering so that the smaller file is last. This allows PackWriter to see smaller deltas last, replacing larger representations that were previously considered from other pack files. Change-Id: I0b7dc8bb9711c82abd6bd16643f518cfccc6d31a
7 years ago
Prefer smaller GC files during DFS garbage collection In 8ac65d33ed7a94f77cb066271669feebf9b882fc PackWriter changed its behavior to always prefer the last object representation presented to it by the ObjectReuseAsIs implementation. This was a fix to avoid delta chain cycles. Unfortunately it can lead to suboptimal compression when concurrent GCs are run on the same repository. One case is automatic GC running (with default settings) in parallel to a manual GC that has disabled delta reuse in order to generate new smaller deltas for the entire history of the repository. Running GC with no-reuse generally requires more CPU time, which also translates to a longer running time. This can lead to a race where the automatic GC completes before the no-reuse GC, leaving the repository in a state such as: no-reuse GC: size 1 GiB, mtime = 18:45 auto GC: size 8 GiB, mtime = 17:30 With the default sort ordering, the smaller no-reuse GC pack is sorted earlier in the pack list, due to its more recent mtime. During object reuse in a future GC, these smaller representations are considered first by PackWriter, but are all discarded when the auto GC file from 17:30 is examined second (due to its older mtime). Work around this in two ways. Well formed DFS repositories should have at most 1 GC pack. If 2 or more GC packs exist, break the sorting tie by selecting the smaller file earlier in the pack list. This allows all normal read code paths to favor the smaller file, which places less pressure on the DfsBlockCache. If any GC race happens, readers serving clone requests will prefer the file that is smaller. During object reuse, flip this ordering so that the smaller file is last. This allows PackWriter to see smaller deltas last, replacing larger representations that were previously considered from other pack files. Change-Id: I0b7dc8bb9711c82abd6bd16643f518cfccc6d31a
7 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002
  1. package org.eclipse.jgit.internal.storage.dfs;
  2. import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.GC;
  3. import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.GC_REST;
  4. import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.INSERT;
  5. import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.UNREACHABLE_GARBAGE;
  6. import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
  7. import static org.eclipse.jgit.internal.storage.pack.PackExt.REFTABLE;
  8. import static org.junit.Assert.assertEquals;
  9. import static org.junit.Assert.assertFalse;
  10. import static org.junit.Assert.assertNotNull;
  11. import static org.junit.Assert.assertNull;
  12. import static org.junit.Assert.assertSame;
  13. import static org.junit.Assert.assertTrue;
  14. import static org.junit.Assert.fail;
  15. import java.io.IOException;
  16. import java.util.Collections;
  17. import java.util.concurrent.TimeUnit;
  18. import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource;
  19. import org.eclipse.jgit.internal.storage.reftable.RefCursor;
  20. import org.eclipse.jgit.internal.storage.reftable.ReftableConfig;
  21. import org.eclipse.jgit.internal.storage.reftable.ReftableReader;
  22. import org.eclipse.jgit.internal.storage.reftable.ReftableWriter;
  23. import org.eclipse.jgit.junit.MockSystemReader;
  24. import org.eclipse.jgit.junit.TestRepository;
  25. import org.eclipse.jgit.lib.AnyObjectId;
  26. import org.eclipse.jgit.lib.BatchRefUpdate;
  27. import org.eclipse.jgit.lib.NullProgressMonitor;
  28. import org.eclipse.jgit.lib.ObjectId;
  29. import org.eclipse.jgit.lib.ObjectIdRef;
  30. import org.eclipse.jgit.lib.Ref;
  31. import org.eclipse.jgit.lib.Repository;
  32. import org.eclipse.jgit.revwalk.RevBlob;
  33. import org.eclipse.jgit.revwalk.RevCommit;
  34. import org.eclipse.jgit.revwalk.RevWalk;
  35. import org.eclipse.jgit.storage.pack.PackConfig;
  36. import org.eclipse.jgit.transport.ReceiveCommand;
  37. import org.eclipse.jgit.util.SystemReader;
  38. import org.junit.After;
  39. import org.junit.Before;
  40. import org.junit.Test;
  41. /** Tests for pack creation and garbage expiration. */
  42. public class DfsGarbageCollectorTest {
  43. private TestRepository<InMemoryRepository> git;
  44. private InMemoryRepository repo;
  45. private DfsObjDatabase odb;
  46. private MockSystemReader mockSystemReader;
  47. @Before
  48. public void setUp() throws IOException {
  49. DfsRepositoryDescription desc = new DfsRepositoryDescription("test");
  50. git = new TestRepository<>(new InMemoryRepository(desc));
  51. repo = git.getRepository();
  52. odb = repo.getObjectDatabase();
  53. mockSystemReader = new MockSystemReader();
  54. SystemReader.setInstance(mockSystemReader);
  55. }
  56. @After
  57. public void tearDown() {
  58. SystemReader.setInstance(null);
  59. }
  60. @Test
  61. public void testCollectionWithNoGarbage() throws Exception {
  62. RevCommit commit0 = commit().message("0").create();
  63. RevCommit commit1 = commit().message("1").parent(commit0).create();
  64. git.update("master", commit1);
  65. assertTrue("commit0 reachable", isReachable(repo, commit0));
  66. assertTrue("commit1 reachable", isReachable(repo, commit1));
  67. // Packs start out as INSERT.
  68. assertEquals(2, odb.getPacks().length);
  69. for (DfsPackFile pack : odb.getPacks()) {
  70. assertEquals(INSERT, pack.getPackDescription().getPackSource());
  71. }
  72. gcNoTtl();
  73. // Single GC pack present with all objects.
  74. assertEquals(1, odb.getPacks().length);
  75. DfsPackFile pack = odb.getPacks()[0];
  76. assertEquals(GC, pack.getPackDescription().getPackSource());
  77. assertTrue("commit0 in pack", isObjectInPack(commit0, pack));
  78. assertTrue("commit1 in pack", isObjectInPack(commit1, pack));
  79. }
  80. @Test
  81. public void testRacyNoReusePrefersSmaller() throws Exception {
  82. StringBuilder msg = new StringBuilder();
  83. for (int i = 0; i < 100; i++) {
  84. msg.append(i).append(": i am a teapot\n");
  85. }
  86. RevBlob a = git.blob(msg.toString());
  87. RevCommit c0 = git.commit()
  88. .add("tea", a)
  89. .message("0")
  90. .create();
  91. msg.append("short and stout\n");
  92. RevBlob b = git.blob(msg.toString());
  93. RevCommit c1 = git.commit().parent(c0).tick(1)
  94. .add("tea", b)
  95. .message("1")
  96. .create();
  97. git.update("master", c1);
  98. PackConfig cfg = new PackConfig();
  99. cfg.setReuseObjects(false);
  100. cfg.setReuseDeltas(false);
  101. cfg.setDeltaCompress(false);
  102. cfg.setThreads(1);
  103. DfsGarbageCollector gc = new DfsGarbageCollector(repo);
  104. gc.setGarbageTtl(0, TimeUnit.MILLISECONDS); // disable TTL
  105. gc.setPackConfig(cfg);
  106. run(gc);
  107. assertEquals(1, odb.getPacks().length);
  108. DfsPackDescription large = odb.getPacks()[0].getPackDescription();
  109. assertSame(PackSource.GC, large.getPackSource());
  110. cfg.setDeltaCompress(true);
  111. gc = new DfsGarbageCollector(repo);
  112. gc.setGarbageTtl(0, TimeUnit.MILLISECONDS); // disable TTL
  113. gc.setPackConfig(cfg);
  114. run(gc);
  115. assertEquals(1, odb.getPacks().length);
  116. DfsPackDescription small = odb.getPacks()[0].getPackDescription();
  117. assertSame(PackSource.GC, small.getPackSource());
  118. assertTrue(
  119. "delta compression pack is smaller",
  120. small.getFileSize(PACK) < large.getFileSize(PACK));
  121. assertTrue(
  122. "large pack is older",
  123. large.getLastModified() < small.getLastModified());
  124. // Forcefully reinsert the older larger GC pack.
  125. odb.commitPack(Collections.singleton(large), null);
  126. odb.clearCache();
  127. assertEquals(2, odb.getPacks().length);
  128. gc = new DfsGarbageCollector(repo);
  129. gc.setGarbageTtl(0, TimeUnit.MILLISECONDS); // disable TTL
  130. run(gc);
  131. assertEquals(1, odb.getPacks().length);
  132. DfsPackDescription rebuilt = odb.getPacks()[0].getPackDescription();
  133. assertEquals(small.getFileSize(PACK), rebuilt.getFileSize(PACK));
  134. }
  135. @Test
  136. public void testCollectionWithGarbage() throws Exception {
  137. RevCommit commit0 = commit().message("0").create();
  138. RevCommit commit1 = commit().message("1").parent(commit0).create();
  139. git.update("master", commit0);
  140. assertTrue("commit0 reachable", isReachable(repo, commit0));
  141. assertFalse("commit1 garbage", isReachable(repo, commit1));
  142. gcNoTtl();
  143. assertEquals(2, odb.getPacks().length);
  144. DfsPackFile gc = null;
  145. DfsPackFile garbage = null;
  146. for (DfsPackFile pack : odb.getPacks()) {
  147. DfsPackDescription d = pack.getPackDescription();
  148. if (d.getPackSource() == GC) {
  149. gc = pack;
  150. } else if (d.getPackSource() == UNREACHABLE_GARBAGE) {
  151. garbage = pack;
  152. } else {
  153. fail("unexpected " + d.getPackSource());
  154. }
  155. }
  156. assertNotNull("created GC pack", gc);
  157. assertTrue(isObjectInPack(commit0, gc));
  158. assertNotNull("created UNREACHABLE_GARBAGE pack", garbage);
  159. assertTrue(isObjectInPack(commit1, garbage));
  160. }
  161. @Test
  162. public void testCollectionWithGarbageAndGarbagePacksPurged()
  163. throws Exception {
  164. RevCommit commit0 = commit().message("0").create();
  165. RevCommit commit1 = commit().message("1").parent(commit0).create();
  166. git.update("master", commit0);
  167. gcWithTtl();
  168. // The repository should have a GC pack with commit0 and an
  169. // UNREACHABLE_GARBAGE pack with commit1.
  170. assertEquals(2, odb.getPacks().length);
  171. boolean gcPackFound = false;
  172. boolean garbagePackFound = false;
  173. for (DfsPackFile pack : odb.getPacks()) {
  174. DfsPackDescription d = pack.getPackDescription();
  175. if (d.getPackSource() == GC) {
  176. gcPackFound = true;
  177. assertTrue("has commit0", isObjectInPack(commit0, pack));
  178. assertFalse("no commit1", isObjectInPack(commit1, pack));
  179. } else if (d.getPackSource() == UNREACHABLE_GARBAGE) {
  180. garbagePackFound = true;
  181. assertFalse("no commit0", isObjectInPack(commit0, pack));
  182. assertTrue("has commit1", isObjectInPack(commit1, pack));
  183. } else {
  184. fail("unexpected " + d.getPackSource());
  185. }
  186. }
  187. assertTrue("gc pack found", gcPackFound);
  188. assertTrue("garbage pack found", garbagePackFound);
  189. gcWithTtl();
  190. // The gc operation should have removed UNREACHABLE_GARBAGE pack along with commit1.
  191. DfsPackFile[] packs = odb.getPacks();
  192. assertEquals(1, packs.length);
  193. assertEquals(GC, packs[0].getPackDescription().getPackSource());
  194. assertTrue("has commit0", isObjectInPack(commit0, packs[0]));
  195. assertFalse("no commit1", isObjectInPack(commit1, packs[0]));
  196. }
  197. @Test
  198. public void testCollectionWithGarbageAndRereferencingGarbage()
  199. throws Exception {
  200. RevCommit commit0 = commit().message("0").create();
  201. RevCommit commit1 = commit().message("1").parent(commit0).create();
  202. git.update("master", commit0);
  203. gcWithTtl();
  204. // The repository should have a GC pack with commit0 and an
  205. // UNREACHABLE_GARBAGE pack with commit1.
  206. assertEquals(2, odb.getPacks().length);
  207. boolean gcPackFound = false;
  208. boolean garbagePackFound = false;
  209. for (DfsPackFile pack : odb.getPacks()) {
  210. DfsPackDescription d = pack.getPackDescription();
  211. if (d.getPackSource() == GC) {
  212. gcPackFound = true;
  213. assertTrue("has commit0", isObjectInPack(commit0, pack));
  214. assertFalse("no commit1", isObjectInPack(commit1, pack));
  215. } else if (d.getPackSource() == UNREACHABLE_GARBAGE) {
  216. garbagePackFound = true;
  217. assertFalse("no commit0", isObjectInPack(commit0, pack));
  218. assertTrue("has commit1", isObjectInPack(commit1, pack));
  219. } else {
  220. fail("unexpected " + d.getPackSource());
  221. }
  222. }
  223. assertTrue("gc pack found", gcPackFound);
  224. assertTrue("garbage pack found", garbagePackFound);
  225. git.update("master", commit1);
  226. gcWithTtl();
  227. // The gc operation should have removed the UNREACHABLE_GARBAGE pack and
  228. // moved commit1 into GC pack.
  229. DfsPackFile[] packs = odb.getPacks();
  230. assertEquals(1, packs.length);
  231. assertEquals(GC, packs[0].getPackDescription().getPackSource());
  232. assertTrue("has commit0", isObjectInPack(commit0, packs[0]));
  233. assertTrue("has commit1", isObjectInPack(commit1, packs[0]));
  234. }
  235. @Test
  236. public void testCollectionWithPureGarbageAndGarbagePacksPurged()
  237. throws Exception {
  238. RevCommit commit0 = commit().message("0").create();
  239. RevCommit commit1 = commit().message("1").parent(commit0).create();
  240. gcWithTtl();
  241. // The repository should have a single UNREACHABLE_GARBAGE pack with commit0
  242. // and commit1.
  243. DfsPackFile[] packs = odb.getPacks();
  244. assertEquals(1, packs.length);
  245. assertEquals(UNREACHABLE_GARBAGE, packs[0].getPackDescription().getPackSource());
  246. assertTrue("has commit0", isObjectInPack(commit0, packs[0]));
  247. assertTrue("has commit1", isObjectInPack(commit1, packs[0]));
  248. gcWithTtl();
  249. // The gc operation should have removed UNREACHABLE_GARBAGE pack along
  250. // with commit0 and commit1.
  251. assertEquals(0, odb.getPacks().length);
  252. }
  253. @Test
  254. public void testCollectionWithPureGarbageAndRereferencingGarbage()
  255. throws Exception {
  256. RevCommit commit0 = commit().message("0").create();
  257. RevCommit commit1 = commit().message("1").parent(commit0).create();
  258. gcWithTtl();
  259. // The repository should have a single UNREACHABLE_GARBAGE pack with commit0
  260. // and commit1.
  261. DfsPackFile[] packs = odb.getPacks();
  262. assertEquals(1, packs.length);
  263. DfsPackDescription pack = packs[0].getPackDescription();
  264. assertEquals(UNREACHABLE_GARBAGE, pack.getPackSource());
  265. assertTrue("has commit0", isObjectInPack(commit0, packs[0]));
  266. assertTrue("has commit1", isObjectInPack(commit1, packs[0]));
  267. git.update("master", commit0);
  268. gcWithTtl();
  269. // The gc operation should have moved commit0 into the GC pack and
  270. // removed UNREACHABLE_GARBAGE along with commit1.
  271. packs = odb.getPacks();
  272. assertEquals(1, packs.length);
  273. pack = packs[0].getPackDescription();
  274. assertEquals(GC, pack.getPackSource());
  275. assertTrue("has commit0", isObjectInPack(commit0, packs[0]));
  276. assertFalse("no commit1", isObjectInPack(commit1, packs[0]));
  277. }
  278. @Test
  279. public void testCollectionWithGarbageCoalescence() throws Exception {
  280. RevCommit commit0 = commit().message("0").create();
  281. RevCommit commit1 = commit().message("1").parent(commit0).create();
  282. git.update("master", commit0);
  283. for (int i = 0; i < 3; i++) {
  284. commit1 = commit().message("g" + i).parent(commit1).create();
  285. // Make sure we don't have more than 1 UNREACHABLE_GARBAGE pack
  286. // because they're coalesced.
  287. gcNoTtl();
  288. assertEquals(1, countPacks(UNREACHABLE_GARBAGE));
  289. }
  290. }
  291. @Test
  292. public void testCollectionWithGarbageNoCoalescence() throws Exception {
  293. RevCommit commit0 = commit().message("0").create();
  294. RevCommit commit1 = commit().message("1").parent(commit0).create();
  295. git.update("master", commit0);
  296. for (int i = 0; i < 3; i++) {
  297. commit1 = commit().message("g" + i).parent(commit1).create();
  298. DfsGarbageCollector gc = new DfsGarbageCollector(repo);
  299. gc.setCoalesceGarbageLimit(0);
  300. gc.setGarbageTtl(0, TimeUnit.MILLISECONDS);
  301. run(gc);
  302. assertEquals(1 + i, countPacks(UNREACHABLE_GARBAGE));
  303. }
  304. }
  305. @Test
  306. public void testCollectionWithGarbageCoalescenceWithShortTtl()
  307. throws Exception {
  308. RevCommit commit0 = commit().message("0").create();
  309. RevCommit commit1 = commit().message("1").parent(commit0).create();
  310. git.update("master", commit0);
  311. // Create commits at 1 minute intervals with 1 hour ttl.
  312. for (int i = 0; i < 100; i++) {
  313. mockSystemReader.tick(60);
  314. commit1 = commit().message("g" + i).parent(commit1).create();
  315. DfsGarbageCollector gc = new DfsGarbageCollector(repo);
  316. gc.setGarbageTtl(1, TimeUnit.HOURS);
  317. run(gc);
  318. // Make sure we don't have more than 4 UNREACHABLE_GARBAGE packs
  319. // because all the packs that are created in a 20 minutes interval
  320. // should be coalesced and the packs older than 60 minutes should be
  321. // removed due to ttl.
  322. int count = countPacks(UNREACHABLE_GARBAGE);
  323. assertTrue("Garbage pack count should not exceed 4, but found "
  324. + count, count <= 4);
  325. }
  326. }
  327. @Test
  328. public void testCollectionWithGarbageCoalescenceWithLongTtl()
  329. throws Exception {
  330. RevCommit commit0 = commit().message("0").create();
  331. RevCommit commit1 = commit().message("1").parent(commit0).create();
  332. git.update("master", commit0);
  333. // Create commits at 1 hour intervals with 2 days ttl.
  334. for (int i = 0; i < 100; i++) {
  335. mockSystemReader.tick(3600);
  336. commit1 = commit().message("g" + i).parent(commit1).create();
  337. DfsGarbageCollector gc = new DfsGarbageCollector(repo);
  338. gc.setGarbageTtl(2, TimeUnit.DAYS);
  339. run(gc);
  340. // Make sure we don't have more than 3 UNREACHABLE_GARBAGE packs
  341. // because all the packs that are created in a single day should
  342. // be coalesced and the packs older than 2 days should be
  343. // removed due to ttl.
  344. int count = countPacks(UNREACHABLE_GARBAGE);
  345. assertTrue("Garbage pack count should not exceed 3, but found "
  346. + count, count <= 3);
  347. }
  348. }
  349. @Test
  350. public void testEstimateGcPackSizeInNewRepo() throws Exception {
  351. RevCommit commit0 = commit().message("0").create();
  352. RevCommit commit1 = commit().message("1").parent(commit0).create();
  353. git.update("master", commit1);
  354. // Packs start out as INSERT.
  355. long inputPacksSize = 32;
  356. assertEquals(2, odb.getPacks().length);
  357. for (DfsPackFile pack : odb.getPacks()) {
  358. assertEquals(INSERT, pack.getPackDescription().getPackSource());
  359. inputPacksSize += pack.getPackDescription().getFileSize(PACK) - 32;
  360. }
  361. gcNoTtl();
  362. // INSERT packs are combined into a single GC pack.
  363. assertEquals(1, odb.getPacks().length);
  364. DfsPackFile pack = odb.getPacks()[0];
  365. assertEquals(GC, pack.getPackDescription().getPackSource());
  366. assertEquals(inputPacksSize,
  367. pack.getPackDescription().getEstimatedPackSize());
  368. }
  369. @Test
  370. public void testEstimateGcPackSizeWithAnExistingGcPack() throws Exception {
  371. RevCommit commit0 = commit().message("0").create();
  372. RevCommit commit1 = commit().message("1").parent(commit0).create();
  373. git.update("master", commit1);
  374. gcNoTtl();
  375. RevCommit commit2 = commit().message("2").parent(commit1).create();
  376. git.update("master", commit2);
  377. // There will be one INSERT pack and one GC pack.
  378. assertEquals(2, odb.getPacks().length);
  379. boolean gcPackFound = false;
  380. boolean insertPackFound = false;
  381. long inputPacksSize = 32;
  382. for (DfsPackFile pack : odb.getPacks()) {
  383. DfsPackDescription d = pack.getPackDescription();
  384. if (d.getPackSource() == GC) {
  385. gcPackFound = true;
  386. } else if (d.getPackSource() == INSERT) {
  387. insertPackFound = true;
  388. } else {
  389. fail("unexpected " + d.getPackSource());
  390. }
  391. inputPacksSize += d.getFileSize(PACK) - 32;
  392. }
  393. assertTrue(gcPackFound);
  394. assertTrue(insertPackFound);
  395. gcNoTtl();
  396. // INSERT pack is combined into the GC pack.
  397. DfsPackFile pack = odb.getPacks()[0];
  398. assertEquals(GC, pack.getPackDescription().getPackSource());
  399. assertEquals(inputPacksSize,
  400. pack.getPackDescription().getEstimatedPackSize());
  401. }
  402. @Test
  403. public void testEstimateGcRestPackSizeInNewRepo() throws Exception {
  404. RevCommit commit0 = commit().message("0").create();
  405. RevCommit commit1 = commit().message("1").parent(commit0).create();
  406. git.update("refs/notes/note1", commit1);
  407. // Packs start out as INSERT.
  408. long inputPacksSize = 32;
  409. assertEquals(2, odb.getPacks().length);
  410. for (DfsPackFile pack : odb.getPacks()) {
  411. assertEquals(INSERT, pack.getPackDescription().getPackSource());
  412. inputPacksSize += pack.getPackDescription().getFileSize(PACK) - 32;
  413. }
  414. gcNoTtl();
  415. // INSERT packs are combined into a single GC_REST pack.
  416. assertEquals(1, odb.getPacks().length);
  417. DfsPackFile pack = odb.getPacks()[0];
  418. assertEquals(GC_REST, pack.getPackDescription().getPackSource());
  419. assertEquals(inputPacksSize,
  420. pack.getPackDescription().getEstimatedPackSize());
  421. }
  422. @Test
  423. public void testEstimateGcRestPackSizeWithAnExistingGcPack()
  424. throws Exception {
  425. RevCommit commit0 = commit().message("0").create();
  426. RevCommit commit1 = commit().message("1").parent(commit0).create();
  427. git.update("refs/notes/note1", commit1);
  428. gcNoTtl();
  429. RevCommit commit2 = commit().message("2").parent(commit1).create();
  430. git.update("refs/notes/note2", commit2);
  431. // There will be one INSERT pack and one GC_REST pack.
  432. assertEquals(2, odb.getPacks().length);
  433. boolean gcRestPackFound = false;
  434. boolean insertPackFound = false;
  435. long inputPacksSize = 32;
  436. for (DfsPackFile pack : odb.getPacks()) {
  437. DfsPackDescription d = pack.getPackDescription();
  438. if (d.getPackSource() == GC_REST) {
  439. gcRestPackFound = true;
  440. } else if (d.getPackSource() == INSERT) {
  441. insertPackFound = true;
  442. } else {
  443. fail("unexpected " + d.getPackSource());
  444. }
  445. inputPacksSize += d.getFileSize(PACK) - 32;
  446. }
  447. assertTrue(gcRestPackFound);
  448. assertTrue(insertPackFound);
  449. gcNoTtl();
  450. // INSERT pack is combined into the GC_REST pack.
  451. DfsPackFile pack = odb.getPacks()[0];
  452. assertEquals(GC_REST, pack.getPackDescription().getPackSource());
  453. assertEquals(inputPacksSize,
  454. pack.getPackDescription().getEstimatedPackSize());
  455. }
  456. @Test
  457. public void testEstimateGcPackSizesWithGcAndGcRestPacks() throws Exception {
  458. RevCommit commit0 = commit().message("0").create();
  459. git.update("head", commit0);
  460. RevCommit commit1 = commit().message("1").parent(commit0).create();
  461. git.update("refs/notes/note1", commit1);
  462. gcNoTtl();
  463. RevCommit commit2 = commit().message("2").parent(commit1).create();
  464. git.update("refs/notes/note2", commit2);
  465. // There will be one INSERT, one GC and one GC_REST packs.
  466. assertEquals(3, odb.getPacks().length);
  467. boolean gcPackFound = false;
  468. boolean gcRestPackFound = false;
  469. boolean insertPackFound = false;
  470. long gcPackSize = 0;
  471. long gcRestPackSize = 0;
  472. long insertPackSize = 0;
  473. for (DfsPackFile pack : odb.getPacks()) {
  474. DfsPackDescription d = pack.getPackDescription();
  475. if (d.getPackSource() == GC) {
  476. gcPackFound = true;
  477. gcPackSize = d.getFileSize(PACK);
  478. } else if (d.getPackSource() == GC_REST) {
  479. gcRestPackFound = true;
  480. gcRestPackSize = d.getFileSize(PACK);
  481. } else if (d.getPackSource() == INSERT) {
  482. insertPackFound = true;
  483. insertPackSize = d.getFileSize(PACK);
  484. } else {
  485. fail("unexpected " + d.getPackSource());
  486. }
  487. }
  488. assertTrue(gcPackFound);
  489. assertTrue(gcRestPackFound);
  490. assertTrue(insertPackFound);
  491. gcNoTtl();
  492. // In this test INSERT pack would be combined into the GC_REST pack.
  493. // But, as there is no good heuristic to know whether the new packs will
  494. // be combined into a GC pack or GC_REST packs, the new pick size is
  495. // considered while estimating both the GC and GC_REST packs.
  496. assertEquals(2, odb.getPacks().length);
  497. gcPackFound = false;
  498. gcRestPackFound = false;
  499. for (DfsPackFile pack : odb.getPacks()) {
  500. DfsPackDescription d = pack.getPackDescription();
  501. if (d.getPackSource() == GC) {
  502. gcPackFound = true;
  503. assertEquals(gcPackSize + insertPackSize - 32,
  504. pack.getPackDescription().getEstimatedPackSize());
  505. } else if (d.getPackSource() == GC_REST) {
  506. gcRestPackFound = true;
  507. assertEquals(gcRestPackSize + insertPackSize - 32,
  508. pack.getPackDescription().getEstimatedPackSize());
  509. } else {
  510. fail("unexpected " + d.getPackSource());
  511. }
  512. }
  513. assertTrue(gcPackFound);
  514. assertTrue(gcRestPackFound);
  515. }
  516. @Test
  517. public void testEstimateUnreachableGarbagePackSize() throws Exception {
  518. RevCommit commit0 = commit().message("0").create();
  519. RevCommit commit1 = commit().message("1").parent(commit0).create();
  520. git.update("master", commit0);
  521. assertTrue("commit0 reachable", isReachable(repo, commit0));
  522. assertFalse("commit1 garbage", isReachable(repo, commit1));
  523. // Packs start out as INSERT.
  524. long packSize0 = 0;
  525. long packSize1 = 0;
  526. assertEquals(2, odb.getPacks().length);
  527. for (DfsPackFile pack : odb.getPacks()) {
  528. DfsPackDescription d = pack.getPackDescription();
  529. assertEquals(INSERT, d.getPackSource());
  530. if (isObjectInPack(commit0, pack)) {
  531. packSize0 = d.getFileSize(PACK);
  532. } else if (isObjectInPack(commit1, pack)) {
  533. packSize1 = d.getFileSize(PACK);
  534. } else {
  535. fail("expected object not found in the pack");
  536. }
  537. }
  538. gcNoTtl();
  539. assertEquals(2, odb.getPacks().length);
  540. for (DfsPackFile pack : odb.getPacks()) {
  541. DfsPackDescription d = pack.getPackDescription();
  542. if (d.getPackSource() == GC) {
  543. // Even though just commit0 will end up in GC pack, because
  544. // there is no good way to know that up front, both the pack
  545. // sizes are considered while computing the estimated size of GC
  546. // pack.
  547. assertEquals(packSize0 + packSize1 - 32,
  548. d.getEstimatedPackSize());
  549. } else if (d.getPackSource() == UNREACHABLE_GARBAGE) {
  550. // commit1 is moved to UNREACHABLE_GARBAGE pack.
  551. assertEquals(packSize1, d.getEstimatedPackSize());
  552. } else {
  553. fail("unexpected " + d.getPackSource());
  554. }
  555. }
  556. }
  557. @Test
  558. public void testSinglePackForAllRefs() throws Exception {
  559. RevCommit commit0 = commit().message("0").create();
  560. git.update("head", commit0);
  561. RevCommit commit1 = commit().message("1").parent(commit0).create();
  562. git.update("refs/notes/note1", commit1);
  563. DfsGarbageCollector gc = new DfsGarbageCollector(repo);
  564. gc.setGarbageTtl(0, TimeUnit.MILLISECONDS);
  565. gc.getPackConfig().setSinglePack(true);
  566. run(gc);
  567. assertEquals(1, odb.getPacks().length);
  568. gc = new DfsGarbageCollector(repo);
  569. gc.setGarbageTtl(0, TimeUnit.MILLISECONDS);
  570. gc.getPackConfig().setSinglePack(false);
  571. run(gc);
  572. assertEquals(2, odb.getPacks().length);
  573. }
  574. @SuppressWarnings("boxing")
  575. @Test
  576. public void producesNewReftable() throws Exception {
  577. String master = "refs/heads/master";
  578. RevCommit commit0 = commit().message("0").create();
  579. RevCommit commit1 = commit().message("1").parent(commit0).create();
  580. BatchRefUpdate bru = git.getRepository().getRefDatabase()
  581. .newBatchUpdate();
  582. bru.addCommand(new ReceiveCommand(ObjectId.zeroId(), commit1, master));
  583. for (int i = 1; i <= 5100; i++) {
  584. bru.addCommand(new ReceiveCommand(ObjectId.zeroId(), commit0,
  585. String.format("refs/pulls/%04d", i)));
  586. }
  587. try (RevWalk rw = new RevWalk(git.getRepository())) {
  588. bru.execute(rw, NullProgressMonitor.INSTANCE);
  589. }
  590. DfsGarbageCollector gc = new DfsGarbageCollector(repo);
  591. gc.setReftableConfig(new ReftableConfig());
  592. run(gc);
  593. // Single GC pack present with all objects.
  594. assertEquals(1, odb.getPacks().length);
  595. DfsPackFile pack = odb.getPacks()[0];
  596. DfsPackDescription desc = pack.getPackDescription();
  597. assertEquals(GC, desc.getPackSource());
  598. assertTrue("commit0 in pack", isObjectInPack(commit0, pack));
  599. assertTrue("commit1 in pack", isObjectInPack(commit1, pack));
  600. // Sibling REFTABLE is also present.
  601. assertTrue(desc.hasFileExt(REFTABLE));
  602. ReftableWriter.Stats stats = desc.getReftableStats();
  603. assertNotNull(stats);
  604. assertTrue(stats.totalBytes() > 0);
  605. assertEquals(5101, stats.refCount());
  606. assertEquals(1, stats.minUpdateIndex());
  607. assertEquals(1, stats.maxUpdateIndex());
  608. DfsReftable table = new DfsReftable(DfsBlockCache.getInstance(), desc);
  609. try (DfsReader ctx = odb.newReader();
  610. ReftableReader rr = table.open(ctx);
  611. RefCursor rc = rr.seekRef("refs/pulls/5100")) {
  612. assertTrue(rc.next());
  613. assertEquals(commit0, rc.getRef().getObjectId());
  614. assertFalse(rc.next());
  615. }
  616. }
  617. @Test
  618. public void leavesNonGcReftablesIfNotConfigured() throws Exception {
  619. String master = "refs/heads/master";
  620. RevCommit commit0 = commit().message("0").create();
  621. RevCommit commit1 = commit().message("1").parent(commit0).create();
  622. git.update(master, commit1);
  623. DfsPackDescription t1 = odb.newPack(INSERT);
  624. try (DfsOutputStream out = odb.writeFile(t1, REFTABLE)) {
  625. new ReftableWriter().begin(out).finish();
  626. t1.addFileExt(REFTABLE);
  627. }
  628. odb.commitPack(Collections.singleton(t1), null);
  629. DfsGarbageCollector gc = new DfsGarbageCollector(repo);
  630. gc.setReftableConfig(null);
  631. run(gc);
  632. // Single GC pack present with all objects.
  633. assertEquals(1, odb.getPacks().length);
  634. DfsPackFile pack = odb.getPacks()[0];
  635. DfsPackDescription desc = pack.getPackDescription();
  636. assertEquals(GC, desc.getPackSource());
  637. assertTrue("commit0 in pack", isObjectInPack(commit0, pack));
  638. assertTrue("commit1 in pack", isObjectInPack(commit1, pack));
  639. // A GC and the older INSERT REFTABLE above is present.
  640. DfsReftable[] tables = odb.getReftables();
  641. assertEquals(2, tables.length);
  642. assertEquals(t1, tables[0].getPackDescription());
  643. }
  644. @Test
  645. public void prunesNonGcReftables() throws Exception {
  646. String master = "refs/heads/master";
  647. RevCommit commit0 = commit().message("0").create();
  648. RevCommit commit1 = commit().message("1").parent(commit0).create();
  649. git.update(master, commit1);
  650. DfsPackDescription t1 = odb.newPack(INSERT);
  651. try (DfsOutputStream out = odb.writeFile(t1, REFTABLE)) {
  652. new ReftableWriter().begin(out).finish();
  653. t1.addFileExt(REFTABLE);
  654. }
  655. odb.commitPack(Collections.singleton(t1), null);
  656. odb.clearCache();
  657. DfsGarbageCollector gc = new DfsGarbageCollector(repo);
  658. gc.setReftableConfig(new ReftableConfig());
  659. run(gc);
  660. // Single GC pack present with all objects.
  661. assertEquals(1, odb.getPacks().length);
  662. DfsPackFile pack = odb.getPacks()[0];
  663. DfsPackDescription desc = pack.getPackDescription();
  664. assertEquals(GC, desc.getPackSource());
  665. assertTrue("commit0 in pack", isObjectInPack(commit0, pack));
  666. assertTrue("commit1 in pack", isObjectInPack(commit1, pack));
  667. // Only sibling GC REFTABLE is present.
  668. DfsReftable[] tables = odb.getReftables();
  669. assertEquals(1, tables.length);
  670. assertEquals(desc, tables[0].getPackDescription());
  671. assertTrue(desc.hasFileExt(REFTABLE));
  672. }
  673. @Test
  674. public void compactsReftables() throws Exception {
  675. String master = "refs/heads/master";
  676. RevCommit commit0 = commit().message("0").create();
  677. RevCommit commit1 = commit().message("1").parent(commit0).create();
  678. git.update(master, commit1);
  679. DfsGarbageCollector gc = new DfsGarbageCollector(repo);
  680. gc.setReftableConfig(new ReftableConfig());
  681. run(gc);
  682. DfsPackDescription t1 = odb.newPack(INSERT);
  683. Ref next = new ObjectIdRef.PeeledNonTag(Ref.Storage.LOOSE,
  684. "refs/heads/next", commit0.copy());
  685. try (DfsOutputStream out = odb.writeFile(t1, REFTABLE)) {
  686. ReftableWriter w = new ReftableWriter();
  687. w.setMinUpdateIndex(42);
  688. w.setMaxUpdateIndex(42);
  689. w.begin(out);
  690. w.sortAndWriteRefs(Collections.singleton(next));
  691. w.finish();
  692. t1.addFileExt(REFTABLE);
  693. t1.setReftableStats(w.getStats());
  694. }
  695. odb.commitPack(Collections.singleton(t1), null);
  696. gc = new DfsGarbageCollector(repo);
  697. gc.setReftableConfig(new ReftableConfig());
  698. run(gc);
  699. // Single GC pack present with all objects.
  700. assertEquals(1, odb.getPacks().length);
  701. DfsPackFile pack = odb.getPacks()[0];
  702. DfsPackDescription desc = pack.getPackDescription();
  703. assertEquals(GC, desc.getPackSource());
  704. assertTrue("commit0 in pack", isObjectInPack(commit0, pack));
  705. assertTrue("commit1 in pack", isObjectInPack(commit1, pack));
  706. // Only sibling GC REFTABLE is present.
  707. DfsReftable[] tables = odb.getReftables();
  708. assertEquals(1, tables.length);
  709. assertEquals(desc, tables[0].getPackDescription());
  710. assertTrue(desc.hasFileExt(REFTABLE));
  711. // GC reftable contains the compaction.
  712. DfsReftable table = new DfsReftable(DfsBlockCache.getInstance(), desc);
  713. try (DfsReader ctx = odb.newReader();
  714. ReftableReader rr = table.open(ctx);
  715. RefCursor rc = rr.allRefs()) {
  716. assertEquals(1, rr.minUpdateIndex());
  717. assertEquals(42, rr.maxUpdateIndex());
  718. assertTrue(rc.next());
  719. assertEquals(master, rc.getRef().getName());
  720. assertEquals(commit1, rc.getRef().getObjectId());
  721. assertTrue(rc.next());
  722. assertEquals(next.getName(), rc.getRef().getName());
  723. assertEquals(commit0, rc.getRef().getObjectId());
  724. assertFalse(rc.next());
  725. }
  726. }
  727. @Test
  728. public void reftableWithoutTombstoneResurrected() throws Exception {
  729. RevCommit commit0 = commit().message("0").create();
  730. String NEXT = "refs/heads/next";
  731. DfsRefDatabase refdb = (DfsRefDatabase)repo.getRefDatabase();
  732. git.update(NEXT, commit0);
  733. Ref next = refdb.exactRef(NEXT);
  734. assertNotNull(next);
  735. assertEquals(commit0, next.getObjectId());
  736. git.delete(NEXT);
  737. refdb.clearCache();
  738. assertNull(refdb.exactRef(NEXT));
  739. DfsGarbageCollector gc = new DfsGarbageCollector(repo);
  740. gc.setReftableConfig(new ReftableConfig());
  741. gc.setIncludeDeletes(false);
  742. gc.setConvertToReftable(false);
  743. run(gc);
  744. assertEquals(1, odb.getReftables().length);
  745. try (DfsReader ctx = odb.newReader();
  746. ReftableReader rr = odb.getReftables()[0].open(ctx)) {
  747. rr.setIncludeDeletes(true);
  748. assertEquals(1, rr.minUpdateIndex());
  749. assertEquals(2, rr.maxUpdateIndex());
  750. assertNull(rr.exactRef(NEXT));
  751. }
  752. RevCommit commit1 = commit().message("1").create();
  753. DfsPackDescription t1 = odb.newPack(INSERT);
  754. Ref newNext = new ObjectIdRef.PeeledNonTag(Ref.Storage.LOOSE, NEXT,
  755. commit1);
  756. try (DfsOutputStream out = odb.writeFile(t1, REFTABLE)) {
  757. ReftableWriter w = new ReftableWriter();
  758. w.setMinUpdateIndex(1);
  759. w.setMaxUpdateIndex(1);
  760. w.begin(out);
  761. w.writeRef(newNext, 1);
  762. w.finish();
  763. t1.addFileExt(REFTABLE);
  764. t1.setReftableStats(w.getStats());
  765. }
  766. odb.commitPack(Collections.singleton(t1), null);
  767. assertEquals(2, odb.getReftables().length);
  768. refdb.clearCache();
  769. newNext = refdb.exactRef(NEXT);
  770. assertNotNull(newNext);
  771. assertEquals(commit1, newNext.getObjectId());
  772. }
  773. @Test
  774. public void reftableWithTombstoneNotResurrected() throws Exception {
  775. RevCommit commit0 = commit().message("0").create();
  776. String NEXT = "refs/heads/next";
  777. DfsRefDatabase refdb = (DfsRefDatabase)repo.getRefDatabase();
  778. git.update(NEXT, commit0);
  779. Ref next = refdb.exactRef(NEXT);
  780. assertNotNull(next);
  781. assertEquals(commit0, next.getObjectId());
  782. git.delete(NEXT);
  783. refdb.clearCache();
  784. assertNull(refdb.exactRef(NEXT));
  785. DfsGarbageCollector gc = new DfsGarbageCollector(repo);
  786. gc.setReftableConfig(new ReftableConfig());
  787. gc.setIncludeDeletes(true);
  788. gc.setConvertToReftable(false);
  789. run(gc);
  790. assertEquals(1, odb.getReftables().length);
  791. try (DfsReader ctx = odb.newReader();
  792. ReftableReader rr = odb.getReftables()[0].open(ctx)) {
  793. rr.setIncludeDeletes(true);
  794. assertEquals(1, rr.minUpdateIndex());
  795. assertEquals(2, rr.maxUpdateIndex());
  796. next = rr.exactRef(NEXT);
  797. assertNotNull(next);
  798. assertNull(next.getObjectId());
  799. }
  800. RevCommit commit1 = commit().message("1").create();
  801. DfsPackDescription t1 = odb.newPack(INSERT);
  802. Ref newNext = new ObjectIdRef.PeeledNonTag(Ref.Storage.LOOSE, NEXT,
  803. commit1);
  804. try (DfsOutputStream out = odb.writeFile(t1, REFTABLE)) {
  805. ReftableWriter w = new ReftableWriter();
  806. w.setMinUpdateIndex(1);
  807. w.setMaxUpdateIndex(1);
  808. w.begin(out);
  809. w.writeRef(newNext, 1);
  810. w.finish();
  811. t1.addFileExt(REFTABLE);
  812. t1.setReftableStats(w.getStats());
  813. }
  814. odb.commitPack(Collections.singleton(t1), null);
  815. assertEquals(2, odb.getReftables().length);
  816. refdb.clearCache();
  817. assertNull(refdb.exactRef(NEXT));
  818. }
  819. private TestRepository<InMemoryRepository>.CommitBuilder commit() {
  820. return git.commit();
  821. }
  822. private void gcNoTtl() throws IOException {
  823. DfsGarbageCollector gc = new DfsGarbageCollector(repo);
  824. gc.setGarbageTtl(0, TimeUnit.MILLISECONDS); // disable TTL
  825. run(gc);
  826. }
  827. private void gcWithTtl() throws IOException {
  828. // Move the clock forward by 1 minute and use the same as ttl.
  829. mockSystemReader.tick(60);
  830. DfsGarbageCollector gc = new DfsGarbageCollector(repo);
  831. gc.setGarbageTtl(1, TimeUnit.MINUTES);
  832. run(gc);
  833. }
  834. private void run(DfsGarbageCollector gc) throws IOException {
  835. // adjust the current time that will be used by the gc operation.
  836. mockSystemReader.tick(1);
  837. assertTrue("gc repacked", gc.pack(null));
  838. odb.clearCache();
  839. }
  840. private static boolean isReachable(Repository repo, AnyObjectId id)
  841. throws IOException {
  842. try (RevWalk rw = new RevWalk(repo)) {
  843. for (Ref ref : repo.getRefDatabase().getRefs()) {
  844. rw.markStart(rw.parseCommit(ref.getObjectId()));
  845. }
  846. for (RevCommit next; (next = rw.next()) != null;) {
  847. if (AnyObjectId.isEqual(next, id)) {
  848. return true;
  849. }
  850. }
  851. }
  852. return false;
  853. }
  854. private boolean isObjectInPack(AnyObjectId id, DfsPackFile pack)
  855. throws IOException {
  856. try (DfsReader reader = odb.newReader()) {
  857. return pack.hasObject(reader, id);
  858. }
  859. }
  860. private int countPacks(PackSource source) throws IOException {
  861. int cnt = 0;
  862. for (DfsPackFile pack : odb.getPacks()) {
  863. if (pack.getPackDescription().getPackSource() == source) {
  864. cnt++;
  865. }
  866. }
  867. return cnt;
  868. }
  869. }