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

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