1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44 package org.eclipse.jgit.internal.storage.dfs;
45
46 import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.GC;
47 import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.GC_TXN;
48 import static org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource.UNREACHABLE_GARBAGE;
49 import static org.eclipse.jgit.internal.storage.pack.PackExt.BITMAP_INDEX;
50 import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
51 import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
52
53 import java.io.IOException;
54 import java.util.ArrayList;
55 import java.util.Collection;
56 import java.util.HashSet;
57 import java.util.List;
58 import java.util.Set;
59
60 import org.eclipse.jgit.internal.JGitText;
61 import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource;
62 import org.eclipse.jgit.internal.storage.file.PackIndex;
63 import org.eclipse.jgit.internal.storage.pack.PackExt;
64 import org.eclipse.jgit.internal.storage.pack.PackWriter;
65 import org.eclipse.jgit.internal.storage.reftree.RefTreeNames;
66 import org.eclipse.jgit.lib.AnyObjectId;
67 import org.eclipse.jgit.lib.Constants;
68 import org.eclipse.jgit.lib.NullProgressMonitor;
69 import org.eclipse.jgit.lib.ObjectId;
70 import org.eclipse.jgit.lib.ObjectIdSet;
71 import org.eclipse.jgit.lib.ProgressMonitor;
72 import org.eclipse.jgit.lib.Ref;
73 import org.eclipse.jgit.lib.RefDatabase;
74 import org.eclipse.jgit.revwalk.RevWalk;
75 import org.eclipse.jgit.storage.pack.PackConfig;
76 import org.eclipse.jgit.storage.pack.PackStatistics;
77 import org.eclipse.jgit.util.io.CountingOutputStream;
78
79
80 public class DfsGarbageCollector {
81 private final DfsRepository repo;
82 private final RefDatabase refdb;
83 private final DfsObjDatabase objdb;
84
85 private final List<DfsPackDescription> newPackDesc;
86
87 private final List<PackStatistics> newPackStats;
88
89 private final List<ObjectIdSet> newPackObj;
90
91 private DfsReader ctx;
92
93 private PackConfig packConfig;
94
95 private long coalesceGarbageLimit = 50 << 20;
96
97 private List<DfsPackFile> packsBefore;
98
99 private Set<ObjectId> allHeads;
100 private Set<ObjectId> nonHeads;
101 private Set<ObjectId> txnHeads;
102 private Set<ObjectId> tagTargets;
103
104
105
106
107
108
109
110 public DfsGarbageCollector(DfsRepository repository) {
111 repo = repository;
112 refdb = repo.getRefDatabase();
113 objdb = repo.getObjectDatabase();
114 newPackDesc = new ArrayList<DfsPackDescription>(4);
115 newPackStats = new ArrayList<PackStatistics>(4);
116 newPackObj = new ArrayList<ObjectIdSet>(4);
117
118 packConfig = new PackConfig(repo);
119 packConfig.setIndexVersion(2);
120 }
121
122
123 public PackConfig getPackConfig() {
124 return packConfig;
125 }
126
127
128
129
130
131
132 public DfsGarbageCollector setPackConfig(PackConfig newConfig) {
133 packConfig = newConfig;
134 return this;
135 }
136
137
138 public long getCoalesceGarbageLimit() {
139 return coalesceGarbageLimit;
140 }
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164 public DfsGarbageCollector setCoalesceGarbageLimit(long limit) {
165 coalesceGarbageLimit = limit;
166 return this;
167 }
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185 public boolean pack(ProgressMonitor pm) throws IOException {
186 if (pm == null)
187 pm = NullProgressMonitor.INSTANCE;
188 if (packConfig.getIndexVersion() != 2)
189 throw new IllegalStateException(
190 JGitText.get().supportOnlyPackIndexVersion2);
191
192 ctx = (DfsReader) objdb.newReader();
193 try {
194 refdb.refresh();
195 objdb.clearCache();
196
197 Collection<Ref> refsBefore = getAllRefs();
198 packsBefore = packsToRebuild();
199 if (packsBefore.isEmpty())
200 return true;
201
202 allHeads = new HashSet<ObjectId>();
203 nonHeads = new HashSet<ObjectId>();
204 txnHeads = new HashSet<ObjectId>();
205 tagTargets = new HashSet<ObjectId>();
206 for (Ref ref : refsBefore) {
207 if (ref.isSymbolic() || ref.getObjectId() == null)
208 continue;
209 if (isHead(ref))
210 allHeads.add(ref.getObjectId());
211 else if (RefTreeNames.isRefTree(refdb, ref.getName()))
212 txnHeads.add(ref.getObjectId());
213 else
214 nonHeads.add(ref.getObjectId());
215 if (ref.getPeeledObjectId() != null)
216 tagTargets.add(ref.getPeeledObjectId());
217 }
218 tagTargets.addAll(allHeads);
219
220 boolean rollback = true;
221 try {
222 packHeads(pm);
223 packRest(pm);
224 packRefTreeGraph(pm);
225 packGarbage(pm);
226 objdb.commitPack(newPackDesc, toPrune());
227 rollback = false;
228 return true;
229 } finally {
230 if (rollback)
231 objdb.rollbackPack(newPackDesc);
232 }
233 } finally {
234 ctx.close();
235 }
236 }
237
238 private Collection<Ref> getAllRefs() throws IOException {
239 Collection<Ref> refs = refdb.getRefs(RefDatabase.ALL).values();
240 List<Ref> addl = refdb.getAdditionalRefs();
241 if (!addl.isEmpty()) {
242 List<Ref> all = new ArrayList<>(refs.size() + addl.size());
243 all.addAll(refs);
244
245 for (Ref r : addl) {
246 if (r.getName().startsWith(Constants.R_REFS)) {
247 all.add(r);
248 }
249 }
250 return all;
251 }
252 return refs;
253 }
254
255 private List<DfsPackFile> packsToRebuild() throws IOException {
256 DfsPackFile[] packs = objdb.getPacks();
257 List<DfsPackFile> out = new ArrayList<DfsPackFile>(packs.length);
258 for (DfsPackFile p : packs) {
259 DfsPackDescription d = p.getPackDescription();
260 if (d.getPackSource() != UNREACHABLE_GARBAGE)
261 out.add(p);
262 else if (d.getFileSize(PackExt.PACK) < coalesceGarbageLimit)
263 out.add(p);
264 }
265 return out;
266 }
267
268
269 public List<DfsPackDescription> getSourcePacks() {
270 return toPrune();
271 }
272
273
274 public List<DfsPackDescription> getNewPacks() {
275 return newPackDesc;
276 }
277
278
279 public List<PackStatistics> getNewPackStatistics() {
280 return newPackStats;
281 }
282
283 private List<DfsPackDescription> toPrune() {
284 int cnt = packsBefore.size();
285 List<DfsPackDescription> all = new ArrayList<DfsPackDescription>(cnt);
286 for (DfsPackFile pack : packsBefore)
287 all.add(pack.getPackDescription());
288 return all;
289 }
290
291 private void packHeads(ProgressMonitor pm) throws IOException {
292 if (allHeads.isEmpty())
293 return;
294
295 try (PackWriter pw = newPackWriter()) {
296 pw.setTagTargets(tagTargets);
297 pw.preparePack(pm, allHeads, PackWriter.NONE);
298 if (0 < pw.getObjectCount())
299 writePack(GC, pw, pm);
300 }
301 }
302 private void packRest(ProgressMonitor pm) throws IOException {
303 if (nonHeads.isEmpty())
304 return;
305
306 try (PackWriter pw = newPackWriter()) {
307 for (ObjectIdSet packedObjs : newPackObj)
308 pw.excludeObjects(packedObjs);
309 pw.preparePack(pm, nonHeads, allHeads);
310 if (0 < pw.getObjectCount())
311 writePack(GC, pw, pm);
312 }
313 }
314
315 private void packRefTreeGraph(ProgressMonitor pm) throws IOException {
316 if (txnHeads.isEmpty())
317 return;
318
319 try (PackWriter pw = newPackWriter()) {
320 for (ObjectIdSet packedObjs : newPackObj)
321 pw.excludeObjects(packedObjs);
322 pw.preparePack(pm, txnHeads, PackWriter.NONE);
323 if (0 < pw.getObjectCount())
324 writePack(GC_TXN, pw, pm);
325 }
326 }
327
328 private void packGarbage(ProgressMonitor pm) throws IOException {
329
330 PackConfig cfg = new PackConfig(packConfig);
331 cfg.setReuseDeltas(true);
332 cfg.setReuseObjects(true);
333 cfg.setDeltaCompress(false);
334 cfg.setBuildBitmaps(false);
335
336 try (PackWriter pw = new PackWriter(cfg, ctx);
337 RevWalk pool = new RevWalk(ctx)) {
338 pw.setDeltaBaseAsOffset(true);
339 pw.setReuseDeltaCommits(true);
340 pm.beginTask(JGitText.get().findingGarbage, objectsBefore());
341 for (DfsPackFile oldPack : packsBefore) {
342 PackIndex oldIdx = oldPack.getPackIndex(ctx);
343 for (PackIndex.MutableEntry ent : oldIdx) {
344 pm.update(1);
345 ObjectId id = ent.toObjectId();
346 if (pool.lookupOrNull(id) != null || anyPackHas(id))
347 continue;
348
349 int type = oldPack.getObjectType(ctx, ent.getOffset());
350 pw.addObject(pool.lookupAny(id, type));
351 }
352 }
353 pm.endTask();
354 if (0 < pw.getObjectCount())
355 writePack(UNREACHABLE_GARBAGE, pw, pm);
356 }
357 }
358
359 private boolean anyPackHas(AnyObjectId id) {
360 for (ObjectIdSet packedObjs : newPackObj)
361 if (packedObjs.contains(id))
362 return true;
363 return false;
364 }
365
366 private static boolean isHead(Ref ref) {
367 return ref.getName().startsWith(Constants.R_HEADS);
368 }
369
370 private int objectsBefore() {
371 int cnt = 0;
372 for (DfsPackFile p : packsBefore)
373 cnt += p.getPackDescription().getObjectCount();
374 return cnt;
375 }
376
377 private PackWriter newPackWriter() {
378 PackWriter pw = new PackWriter(packConfig, ctx);
379 pw.setDeltaBaseAsOffset(true);
380 pw.setReuseDeltaCommits(false);
381 return pw;
382 }
383
384 private DfsPackDescription writePack(PackSource source, PackWriter pw,
385 ProgressMonitor pm) throws IOException {
386 DfsOutputStream out;
387 DfsPackDescription pack = repo.getObjectDatabase().newPack(source);
388 newPackDesc.add(pack);
389
390 out = objdb.writeFile(pack, PACK);
391 try {
392 pw.writePack(pm, pm, out);
393 pack.addFileExt(PACK);
394 } finally {
395 out.close();
396 }
397
398 out = objdb.writeFile(pack, INDEX);
399 try {
400 CountingOutputStream cnt = new CountingOutputStream(out);
401 pw.writeIndex(cnt);
402 pack.addFileExt(INDEX);
403 pack.setFileSize(INDEX, cnt.getCount());
404 pack.setIndexVersion(pw.getIndexVersion());
405 } finally {
406 out.close();
407 }
408
409 if (pw.prepareBitmapIndex(pm)) {
410 out = objdb.writeFile(pack, BITMAP_INDEX);
411 try {
412 CountingOutputStream cnt = new CountingOutputStream(out);
413 pw.writeBitmapIndex(cnt);
414 pack.addFileExt(BITMAP_INDEX);
415 pack.setFileSize(BITMAP_INDEX, cnt.getCount());
416 } finally {
417 out.close();
418 }
419 }
420
421 PackStatistics stats = pw.getStatistics();
422 pack.setPackStats(stats);
423 newPackStats.add(stats);
424 newPackObj.add(pw.getObjectSet());
425
426 DfsBlockCache.getInstance().getOrCreate(pack, null);
427 return pack;
428 }
429 }