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.INSERT;
5 import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.UNREACHABLE_GARBAGE;
6 import static org.junit.Assert.assertEquals;
7 import static org.junit.Assert.assertFalse;
8 import static org.junit.Assert.assertNotEquals;
9 import static org.junit.Assert.assertNotNull;
10 import static org.junit.Assert.assertTrue;
11 import static org.junit.Assert.fail;
12
13 import java.io.IOException;
14 import java.util.concurrent.TimeUnit;
15
16 import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource;
17 import org.eclipse.jgit.junit.TestRepository;
18 import org.eclipse.jgit.lib.AnyObjectId;
19 import org.eclipse.jgit.lib.Ref;
20 import org.eclipse.jgit.lib.Repository;
21 import org.eclipse.jgit.revwalk.RevCommit;
22 import org.eclipse.jgit.revwalk.RevWalk;
23 import org.junit.Before;
24 import org.junit.Test;
25
26 public class DfsGarbageCollectorTest {
27 private TestRepository<InMemoryRepository> git;
28 private InMemoryRepository repo;
29 private DfsObjDatabase odb;
30
31 @Before
32 public void setUp() throws IOException {
33 DfsRepositoryDescription desc = new DfsRepositoryDescription("test");
34 git = new TestRepository<>(new InMemoryRepository(desc));
35 repo = git.getRepository();
36 odb = repo.getObjectDatabase();
37 }
38
39 @Test
40 public void testCollectionWithNoGarbage() throws Exception {
41 RevCommit commit0 = commit().message("0").create();
42 RevCommit commit1 = commit().message("1").parent(commit0).create();
43 git.update("master", commit1);
44
45 assertTrue("commit0 reachable", isReachable(repo, commit0));
46 assertTrue("commit1 reachable", isReachable(repo, commit1));
47
48
49 assertEquals(2, odb.getPacks().length);
50 for (DfsPackFile pack : odb.getPacks()) {
51 assertEquals(INSERT, pack.getPackDescription().getPackSource());
52 }
53
54 gcNoTtl();
55
56
57 assertEquals(1, odb.getPacks().length);
58 DfsPackFile pack = odb.getPacks()[0];
59 assertEquals(GC, pack.getPackDescription().getPackSource());
60 assertTrue("commit0 in pack", isObjectInPack(commit0, pack));
61 assertTrue("commit1 in pack", isObjectInPack(commit1, pack));
62 }
63
64 @Test
65 public void testCollectionWithGarbage() throws Exception {
66 RevCommit commit0 = commit().message("0").create();
67 RevCommit commit1 = commit().message("1").parent(commit0).create();
68 git.update("master", commit0);
69
70 assertTrue("commit0 reachable", isReachable(repo, commit0));
71 assertFalse("commit1 garbage", isReachable(repo, commit1));
72 gcNoTtl();
73
74 assertEquals(2, odb.getPacks().length);
75 DfsPackFile gc = null;
76 DfsPackFile garbage = null;
77 for (DfsPackFile pack : odb.getPacks()) {
78 DfsPackDescription d = pack.getPackDescription();
79 if (d.getPackSource() == GC) {
80 gc = pack;
81 } else if (d.getPackSource() == UNREACHABLE_GARBAGE) {
82 garbage = pack;
83 } else {
84 fail("unexpected " + d.getPackSource());
85 }
86 }
87
88 assertNotNull("created GC pack", gc);
89 assertTrue(isObjectInPack(commit0, gc));
90
91 assertNotNull("created UNREACHABLE_GARBAGE pack", garbage);
92 assertTrue(isObjectInPack(commit1, garbage));
93 }
94
95 @Test
96 public void testCollectionWithGarbageAndGarbagePacksPurged()
97 throws Exception {
98 RevCommit commit0 = commit().message("0").create();
99 RevCommit commit1 = commit().message("1").parent(commit0).create();
100 git.update("master", commit0);
101
102 gcNoTtl();
103 gcWithTtl();
104
105
106
107
108 boolean commit1Found = false;
109 for (DfsPackFile pack : odb.getPacks()) {
110 DfsPackDescription d = pack.getPackDescription();
111 if (d.getPackSource() == GC) {
112 assertTrue("has commit0", isObjectInPack(commit0, pack));
113 assertFalse("no commit1", isObjectInPack(commit1, pack));
114 } else if (d.getPackSource() == UNREACHABLE_GARBAGE) {
115 commit1Found |= isObjectInPack(commit1, pack);
116 } else {
117 fail("unexpected " + d.getPackSource());
118 }
119 }
120 assertTrue("garbage commit1 still readable", commit1Found);
121
122
123 DfsPackDescription oldestGarbagePack = null;
124 for (DfsPackFile pack : odb.getPacks()) {
125 DfsPackDescription d = pack.getPackDescription();
126 if (d.getPackSource() == UNREACHABLE_GARBAGE) {
127 oldestGarbagePack = oldestPack(oldestGarbagePack, d);
128 }
129 }
130 assertNotNull("has UNREACHABLE_GARBAGE", oldestGarbagePack);
131
132 gcWithTtl();
133 assertTrue("has packs", odb.getPacks().length > 0);
134 for (DfsPackFile pack : odb.getPacks()) {
135 assertNotEquals(oldestGarbagePack, pack.getPackDescription());
136 }
137 }
138
139 @Test
140 public void testCollectionWithGarbageCoalescence() throws Exception {
141 RevCommit commit0 = commit().message("0").create();
142 RevCommit commit1 = commit().message("1").parent(commit0).create();
143 git.update("master", commit0);
144
145 for (int i = 0; i < 3; i++) {
146 commit1 = commit().message("g" + i).parent(commit1).create();
147
148
149
150 gcNoTtl();
151 assertEquals(1, countPacks(UNREACHABLE_GARBAGE));
152 }
153 }
154
155 @Test
156 public void testCollectionWithGarbageNoCoalescence() throws Exception {
157 RevCommit commit0 = commit().message("0").create();
158 RevCommit commit1 = commit().message("1").parent(commit0).create();
159 git.update("master", commit0);
160
161 for (int i = 0; i < 3; i++) {
162 commit1 = commit().message("g" + i).parent(commit1).create();
163
164 DfsGarbageCollector gc = new DfsGarbageCollector(repo);
165 gc.setCoalesceGarbageLimit(0);
166 gc.setGarbageTtl(0, TimeUnit.MILLISECONDS);
167 run(gc);
168 assertEquals(1 + i, countPacks(UNREACHABLE_GARBAGE));
169 }
170 }
171
172 private TestRepository<InMemoryRepository>.CommitBuilder commit() {
173 return git.commit();
174 }
175
176 private void gcNoTtl() throws IOException {
177 DfsGarbageCollector gc = new DfsGarbageCollector(repo);
178 gc.setGarbageTtl(0, TimeUnit.MILLISECONDS);
179 run(gc);
180 }
181
182 private void gcWithTtl() throws InterruptedException, IOException {
183
184
185 long start = System.currentTimeMillis();
186 do {
187 Thread.sleep(10);
188 } while (System.currentTimeMillis() <= start);
189
190 DfsGarbageCollector gc = new DfsGarbageCollector(repo);
191 gc.setGarbageTtl(1, TimeUnit.MILLISECONDS);
192 run(gc);
193 }
194
195 private void run(DfsGarbageCollector gc) throws IOException {
196 assertTrue("gc repacked", gc.pack(null));
197 odb.clearCache();
198 }
199
200 private static boolean isReachable(Repository repo, AnyObjectId id)
201 throws IOException {
202 try (RevWalk rw = new RevWalk(repo)) {
203 for (Ref ref : repo.getAllRefs().values()) {
204 rw.markStart(rw.parseCommit(ref.getObjectId()));
205 }
206 for (RevCommit next; (next = rw.next()) != null;) {
207 if (AnyObjectId.equals(next, id)) {
208 return true;
209 }
210 }
211 }
212 return false;
213 }
214
215 private boolean isObjectInPack(AnyObjectId id, DfsPackFile pack)
216 throws IOException {
217 try (DfsReader reader = new DfsReader(odb)) {
218 return pack.hasObject(reader, id);
219 }
220 }
221
222 private static DfsPackDescription oldestPack(DfsPackDescription a,
223 DfsPackDescription b) {
224 if (a != null && a.getLastModified() < b.getLastModified()) {
225 return a;
226 }
227 return b;
228 }
229
230 private int countPacks(PackSource source) throws IOException {
231 int cnt = 0;
232 for (DfsPackFile pack : odb.getPacks()) {
233 if (pack.getPackDescription().getPackSource() == source) {
234 cnt++;
235 }
236 }
237 return cnt;
238 }
239 }