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

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.equals(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. }