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