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.COMPACT;
47 import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
48 import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK;
49 import static org.eclipse.jgit.internal.storage.pack.StoredObjectRepresentation.PACK_DELTA;
50
51 import java.io.IOException;
52 import java.util.ArrayList;
53 import java.util.Collections;
54 import java.util.Comparator;
55 import java.util.List;
56
57 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
58 import org.eclipse.jgit.internal.JGitText;
59 import org.eclipse.jgit.internal.storage.file.PackIndex;
60 import org.eclipse.jgit.internal.storage.file.PackReverseIndex;
61 import org.eclipse.jgit.internal.storage.pack.PackWriter;
62 import org.eclipse.jgit.lib.AnyObjectId;
63 import org.eclipse.jgit.lib.NullProgressMonitor;
64 import org.eclipse.jgit.lib.ObjectId;
65 import org.eclipse.jgit.lib.ProgressMonitor;
66 import org.eclipse.jgit.revwalk.RevFlag;
67 import org.eclipse.jgit.revwalk.RevObject;
68 import org.eclipse.jgit.revwalk.RevWalk;
69 import org.eclipse.jgit.storage.pack.PackConfig;
70 import org.eclipse.jgit.util.BlockList;
71 import org.eclipse.jgit.util.io.CountingOutputStream;
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88 public class DfsPackCompactor {
89 private final DfsRepository repo;
90
91 private final List<DfsPackFile> srcPacks;
92
93 private final List<PackWriter.ObjectIdSet> exclude;
94
95 private final List<DfsPackDescription> newPacks;
96
97 private final List<PackWriter.Statistics> newStats;
98
99 private int autoAddSize;
100
101 private RevWalk rw;
102 private RevFlag added;
103 private RevFlag isBase;
104
105
106
107
108
109
110
111 public DfsPackCompactor(DfsRepository repository) {
112 repo = repository;
113 autoAddSize = 5 * 1024 * 1024;
114 srcPacks = new ArrayList<DfsPackFile>();
115 exclude = new ArrayList<PackWriter.ObjectIdSet>(4);
116 newPacks = new ArrayList<DfsPackDescription>(1);
117 newStats = new ArrayList<PackWriter.Statistics>(1);
118 }
119
120
121
122
123
124
125
126
127
128
129
130
131
132 public DfsPackCompactor add(DfsPackFile pack) {
133 srcPacks.add(pack);
134 return this;
135 }
136
137
138
139
140
141
142
143
144
145
146
147 public DfsPackCompactor autoAdd() throws IOException {
148 DfsObjDatabase objdb = repo.getObjectDatabase();
149 for (DfsPackFile pack : objdb.getPacks()) {
150 DfsPackDescription d = pack.getPackDescription();
151 if (d.getFileSize(PACK) < autoAddSize)
152 add(pack);
153 else
154 exclude(pack);
155 }
156 return this;
157 }
158
159
160
161
162
163
164
165
166 public DfsPackCompactor exclude(PackWriter.ObjectIdSet set) {
167 exclude.add(set);
168 return this;
169 }
170
171
172
173
174
175
176
177
178
179
180 public DfsPackCompactor exclude(DfsPackFile pack) throws IOException {
181 final PackIndex idx;
182 try (DfsReader ctx = (DfsReader) repo.newObjectReader()) {
183 idx = pack.getPackIndex(ctx);
184 }
185 return exclude(new PackWriter.ObjectIdSet() {
186 public boolean contains(AnyObjectId id) {
187 return idx.hasObject(id);
188 }
189 });
190 }
191
192
193
194
195
196
197
198
199
200
201 public void compact(ProgressMonitor pm) throws IOException {
202 if (pm == null)
203 pm = NullProgressMonitor.INSTANCE;
204
205 DfsObjDatabase objdb = repo.getObjectDatabase();
206 try (DfsReader ctx = (DfsReader) objdb.newReader()) {
207 PackConfig pc = new PackConfig(repo);
208 pc.setIndexVersion(2);
209 pc.setDeltaCompress(false);
210 pc.setReuseDeltas(true);
211 pc.setReuseObjects(true);
212
213 PackWriter pw = new PackWriter(pc, ctx);
214 try {
215 pw.setDeltaBaseAsOffset(true);
216 pw.setReuseDeltaCommits(false);
217
218 addObjectsToPack(pw, ctx, pm);
219 if (pw.getObjectCount() == 0) {
220 List<DfsPackDescription> remove = toPrune();
221 if (remove.size() > 0)
222 objdb.commitPack(
223 Collections.<DfsPackDescription>emptyList(),
224 remove);
225 return;
226 }
227
228 boolean rollback = true;
229 DfsPackDescription pack = objdb.newPack(COMPACT);
230 try {
231 writePack(objdb, pack, pw, pm);
232 writeIndex(objdb, pack, pw);
233
234 PackWriter.Statistics stats = pw.getStatistics();
235 pw.close();
236 pw = null;
237
238 pack.setPackStats(stats);
239 objdb.commitPack(Collections.singletonList(pack), toPrune());
240 newPacks.add(pack);
241 newStats.add(stats);
242 rollback = false;
243 } finally {
244 if (rollback)
245 objdb.rollbackPack(Collections.singletonList(pack));
246 }
247 } finally {
248 if (pw != null)
249 pw.close();
250 }
251 } finally {
252 rw = null;
253 }
254 }
255
256
257 public List<DfsPackDescription> getSourcePacks() {
258 return toPrune();
259 }
260
261
262 public List<DfsPackDescription> getNewPacks() {
263 return newPacks;
264 }
265
266
267 public List<PackWriter.Statistics> getNewPackStatistics() {
268 return newStats;
269 }
270
271 private List<DfsPackDescription> toPrune() {
272 int cnt = srcPacks.size();
273 List<DfsPackDescription> all = new ArrayList<DfsPackDescription>(cnt);
274 for (DfsPackFile pack : srcPacks)
275 all.add(pack.getPackDescription());
276 return all;
277 }
278
279 private void addObjectsToPack(PackWriter pw, DfsReader ctx,
280 ProgressMonitor pm) throws IOException,
281 IncorrectObjectTypeException {
282
283
284
285 Collections.sort(srcPacks, new Comparator<DfsPackFile>() {
286 public int compare(DfsPackFile a, DfsPackFile b) {
287 return a.getPackDescription().compareTo(b.getPackDescription());
288 }
289 });
290
291 rw = new RevWalk(ctx);
292 added = rw.newFlag("ADDED");
293 isBase = rw.newFlag("IS_BASE");
294 List<RevObject> baseObjects = new BlockList<RevObject>();
295
296 pm.beginTask(JGitText.get().countingObjects, ProgressMonitor.UNKNOWN);
297 for (DfsPackFile src : srcPacks) {
298 List<ObjectIdWithOffset> want = toInclude(src, ctx);
299 if (want.isEmpty())
300 continue;
301
302 PackReverseIndex rev = src.getReverseIdx(ctx);
303 DfsObjectRepresentation rep = new DfsObjectRepresentation(src);
304 for (ObjectIdWithOffset id : want) {
305 int type = src.getObjectType(ctx, id.offset);
306 RevObject obj = rw.lookupAny(id, type);
307 if (obj.has(added))
308 continue;
309
310 pm.update(1);
311 pw.addObject(obj);
312 obj.add(added);
313
314 src.representation(rep, id.offset, ctx, rev);
315 if (rep.getFormat() != PACK_DELTA)
316 continue;
317
318 RevObject base = rw.lookupAny(rep.getDeltaBase(), type);
319 if (!base.has(added) && !base.has(isBase)) {
320 baseObjects.add(base);
321 base.add(isBase);
322 }
323 }
324 }
325 for (RevObject obj : baseObjects) {
326 if (!obj.has(added)) {
327 pm.update(1);
328 pw.addObject(obj);
329 obj.add(added);
330 }
331 }
332 pm.endTask();
333 }
334
335 private List<ObjectIdWithOffset> toInclude(DfsPackFile src, DfsReader ctx)
336 throws IOException {
337 PackIndex srcIdx = src.getPackIndex(ctx);
338 List<ObjectIdWithOffset> want = new BlockList<ObjectIdWithOffset>(
339 (int) srcIdx.getObjectCount());
340 SCAN: for (PackIndex.MutableEntry ent : srcIdx) {
341 ObjectId id = ent.toObjectId();
342 RevObject obj = rw.lookupOrNull(id);
343 if (obj != null && (obj.has(added) || obj.has(isBase)))
344 continue;
345 for (PackWriter.ObjectIdSet e : exclude)
346 if (e.contains(id))
347 continue SCAN;
348 want.add(new ObjectIdWithOffset(id, ent.getOffset()));
349 }
350 Collections.sort(want, new Comparator<ObjectIdWithOffset>() {
351 public int compare(ObjectIdWithOffset a, ObjectIdWithOffset b) {
352 return Long.signum(a.offset - b.offset);
353 }
354 });
355 return want;
356 }
357
358 private static void writePack(DfsObjDatabase objdb,
359 DfsPackDescription pack,
360 PackWriter pw, ProgressMonitor pm) throws IOException {
361 DfsOutputStream out = objdb.writeFile(pack, PACK);
362 try {
363 pw.writePack(pm, pm, out);
364 pack.addFileExt(PACK);
365 } finally {
366 out.close();
367 }
368 }
369
370 private static void writeIndex(DfsObjDatabase objdb,
371 DfsPackDescription pack,
372 PackWriter pw) throws IOException {
373 DfsOutputStream out = objdb.writeFile(pack, INDEX);
374 try {
375 CountingOutputStream cnt = new CountingOutputStream(out);
376 pw.writeIndex(cnt);
377 pack.addFileExt(INDEX);
378 pack.setFileSize(INDEX, cnt.getCount());
379 pack.setIndexVersion(pw.getIndexVersion());
380 } finally {
381 out.close();
382 }
383 }
384
385 private static class ObjectIdWithOffset extends ObjectId {
386 final long offset;
387
388 ObjectIdWithOffset(AnyObjectId id, long ofs) {
389 super(id);
390 offset = ofs;
391 }
392 }
393 }