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.file;
45
46 import static org.eclipse.jgit.internal.storage.pack.PackExt.BITMAP_INDEX;
47 import static org.eclipse.jgit.internal.storage.pack.PackExt.INDEX;
48
49 import java.io.File;
50 import java.io.FileOutputStream;
51 import java.io.IOException;
52 import java.io.OutputStream;
53 import java.nio.channels.Channels;
54 import java.nio.channels.FileChannel;
55 import java.nio.file.DirectoryStream;
56 import java.nio.file.Files;
57 import java.nio.file.Path;
58 import java.nio.file.Paths;
59 import java.nio.file.StandardCopyOption;
60 import java.text.MessageFormat;
61 import java.text.ParseException;
62 import java.util.ArrayList;
63 import java.util.Collection;
64 import java.util.Collections;
65 import java.util.Comparator;
66 import java.util.Date;
67 import java.util.HashMap;
68 import java.util.HashSet;
69 import java.util.Iterator;
70 import java.util.LinkedList;
71 import java.util.List;
72 import java.util.Map;
73 import java.util.Objects;
74 import java.util.Set;
75 import java.util.TreeMap;
76 import java.util.regex.Pattern;
77 import java.util.stream.Collectors;
78 import java.util.stream.Stream;
79
80 import org.eclipse.jgit.annotations.NonNull;
81 import org.eclipse.jgit.dircache.DirCacheIterator;
82 import org.eclipse.jgit.errors.CorruptObjectException;
83 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
84 import org.eclipse.jgit.errors.MissingObjectException;
85 import org.eclipse.jgit.errors.NoWorkTreeException;
86 import org.eclipse.jgit.internal.JGitText;
87 import org.eclipse.jgit.internal.storage.pack.PackExt;
88 import org.eclipse.jgit.internal.storage.pack.PackWriter;
89 import org.eclipse.jgit.internal.storage.reftree.RefTreeNames;
90 import org.eclipse.jgit.lib.ConfigConstants;
91 import org.eclipse.jgit.lib.Constants;
92 import org.eclipse.jgit.lib.FileMode;
93 import org.eclipse.jgit.lib.NullProgressMonitor;
94 import org.eclipse.jgit.lib.ObjectId;
95 import org.eclipse.jgit.lib.ObjectIdSet;
96 import org.eclipse.jgit.lib.ProgressMonitor;
97 import org.eclipse.jgit.lib.Ref;
98 import org.eclipse.jgit.lib.Ref.Storage;
99 import org.eclipse.jgit.lib.RefDatabase;
100 import org.eclipse.jgit.lib.ReflogEntry;
101 import org.eclipse.jgit.lib.ReflogReader;
102 import org.eclipse.jgit.revwalk.ObjectWalk;
103 import org.eclipse.jgit.revwalk.RevObject;
104 import org.eclipse.jgit.revwalk.RevWalk;
105 import org.eclipse.jgit.storage.pack.PackConfig;
106 import org.eclipse.jgit.treewalk.TreeWalk;
107 import org.eclipse.jgit.treewalk.filter.TreeFilter;
108 import org.eclipse.jgit.util.FileUtils;
109 import org.eclipse.jgit.util.GitDateParser;
110 import org.eclipse.jgit.util.SystemReader;
111 import org.slf4j.Logger;
112 import org.slf4j.LoggerFactory;
113
114
115
116
117
118
119
120
121 public class GC {
122 private final static Logger LOG = LoggerFactory
123 .getLogger(GC.class);
124
125 private static final String PRUNE_EXPIRE_DEFAULT = "2.weeks.ago";
126
127 private static final String PRUNE_PACK_EXPIRE_DEFAULT = "1.hour.ago";
128
129 private static final Pattern PATTERN_LOOSE_OBJECT = Pattern
130 .compile("[0-9a-fA-F]{38}");
131
132 private static final String PACK_EXT = "." + PackExt.PACK.getExtension();
133
134 private static final String BITMAP_EXT = "."
135 + PackExt.BITMAP_INDEX.getExtension();
136
137 private static final String INDEX_EXT = "." + PackExt.INDEX.getExtension();
138
139 private static final int DEFAULT_AUTOPACKLIMIT = 50;
140
141 private static final int DEFAULT_AUTOLIMIT = 6700;
142
143 private final FileRepository repo;
144
145 private ProgressMonitor pm;
146
147 private long expireAgeMillis = -1;
148
149 private Date expire;
150
151 private long packExpireAgeMillis = -1;
152
153 private Date packExpire;
154
155 private PackConfig pconfig = null;
156
157
158
159
160
161
162
163 private Collection<Ref> lastPackedRefs;
164
165
166
167
168
169
170 private long lastRepackTime;
171
172
173
174
175 private boolean automatic;
176
177
178
179
180
181
182
183
184 public GC(FileRepository repo) {
185 this.repo = repo;
186 this.pm = NullProgressMonitor.INSTANCE;
187 }
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208 public Collection<PackFile> gc() throws IOException, ParseException {
209 if (automatic && !needGc()) {
210 return Collections.emptyList();
211 }
212 pm.start(6 );
213 packRefs();
214
215 Collection<PackFile> newPacks = repack();
216 prune(Collections.<ObjectId> emptySet());
217
218 return newPacks;
219 }
220
221
222
223
224
225
226
227
228
229
230
231
232
233 private void deleteOldPacks(Collection<PackFile> oldPacks,
234 Collection<PackFile> newPacks) throws ParseException, IOException {
235 long packExpireDate = getPackExpireDate();
236 oldPackLoop: for (PackFile oldPack : oldPacks) {
237 String oldName = oldPack.getPackName();
238
239
240 for (PackFile newPack : newPacks)
241 if (oldName.equals(newPack.getPackName()))
242 continue oldPackLoop;
243
244 if (!oldPack.shouldBeKept()
245 && repo.getFS().lastModified(
246 oldPack.getPackFile()) < packExpireDate) {
247 oldPack.close();
248 prunePack(oldName);
249 }
250 }
251
252
253 repo.getObjectDatabase().close();
254 }
255
256
257
258
259
260
261
262
263
264
265
266 private void prunePack(String packName) {
267 PackExt[] extensions = PackExt.values();
268 try {
269
270
271 int deleteOptions = FileUtils.RETRY | FileUtils.SKIP_MISSING;
272 for (PackExt ext : extensions)
273 if (PackExt.PACK.equals(ext)) {
274 File f = nameFor(packName, "." + ext.getExtension());
275 FileUtils.delete(f, deleteOptions);
276 break;
277 }
278
279
280 deleteOptions |= FileUtils.IGNORE_ERRORS;
281 for (PackExt ext : extensions) {
282 if (!PackExt.PACK.equals(ext)) {
283 File f = nameFor(packName, "." + ext.getExtension());
284 FileUtils.delete(f, deleteOptions);
285 }
286 }
287 } catch (IOException e) {
288
289 }
290 }
291
292
293
294
295
296
297
298
299 public void prunePacked() throws IOException {
300 ObjectDirectory objdb = repo.getObjectDatabase();
301 Collection<PackFile> packs = objdb.getPacks();
302 File objects = repo.getObjectsDirectory();
303 String[] fanout = objects.list();
304
305 if (fanout != null && fanout.length > 0) {
306 pm.beginTask(JGitText.get().pruneLoosePackedObjects, fanout.length);
307 try {
308 for (String d : fanout) {
309 pm.update(1);
310 if (d.length() != 2)
311 continue;
312 String[] entries = new File(objects, d).list();
313 if (entries == null)
314 continue;
315 for (String e : entries) {
316 if (e.length() != Constants.OBJECT_ID_STRING_LENGTH - 2)
317 continue;
318 ObjectId id;
319 try {
320 id = ObjectId.fromString(d + e);
321 } catch (IllegalArgumentException notAnObject) {
322
323
324 continue;
325 }
326 boolean found = false;
327 for (PackFile p : packs)
328 if (p.hasObject(id)) {
329 found = true;
330 break;
331 }
332 if (found)
333 FileUtils.delete(objdb.fileFor(id), FileUtils.RETRY
334 | FileUtils.SKIP_MISSING
335 | FileUtils.IGNORE_ERRORS);
336 }
337 }
338 } finally {
339 pm.endTask();
340 }
341 }
342 }
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357 public void prune(Set<ObjectId> objectsToKeep) throws IOException,
358 ParseException {
359 long expireDate = getExpireDate();
360
361
362
363 Map<ObjectId, File> deletionCandidates = new HashMap<ObjectId, File>();
364 Set<ObjectId> indexObjects = null;
365 File objects = repo.getObjectsDirectory();
366 String[] fanout = objects.list();
367 if (fanout == null || fanout.length == 0) {
368 return;
369 }
370 pm.beginTask(JGitText.get().pruneLooseUnreferencedObjects,
371 fanout.length);
372 try {
373 for (String d : fanout) {
374 pm.update(1);
375 if (d.length() != 2)
376 continue;
377 File[] entries = new File(objects, d).listFiles();
378 if (entries == null)
379 continue;
380 for (File f : entries) {
381 String fName = f.getName();
382 if (fName.length() != Constants.OBJECT_ID_STRING_LENGTH - 2)
383 continue;
384 if (repo.getFS().lastModified(f) >= expireDate)
385 continue;
386 try {
387 ObjectId id = ObjectId.fromString(d + fName);
388 if (objectsToKeep.contains(id))
389 continue;
390 if (indexObjects == null)
391 indexObjects = listNonHEADIndexObjects();
392 if (indexObjects.contains(id))
393 continue;
394 deletionCandidates.put(id, f);
395 } catch (IllegalArgumentException notAnObject) {
396
397
398 continue;
399 }
400 }
401 }
402 } finally {
403 pm.endTask();
404 }
405
406 if (deletionCandidates.isEmpty()) {
407 return;
408 }
409
410
411
412
413
414 Collection<Ref> newRefs;
415 if (lastPackedRefs == null || lastPackedRefs.isEmpty())
416 newRefs = getAllRefs();
417 else {
418 Map<String, Ref> last = new HashMap<>();
419 for (Ref r : lastPackedRefs) {
420 last.put(r.getName(), r);
421 }
422 newRefs = new ArrayList<>();
423 for (Ref r : getAllRefs()) {
424 Ref old = last.get(r.getName());
425 if (!equals(r, old)) {
426 newRefs.add(r);
427 }
428 }
429 }
430
431 if (!newRefs.isEmpty()) {
432
433
434
435
436
437 ObjectWalk w = new ObjectWalk(repo);
438 try {
439 for (Ref cr : newRefs)
440 w.markStart(w.parseAny(cr.getObjectId()));
441 if (lastPackedRefs != null)
442 for (Ref lpr : lastPackedRefs)
443 w.markUninteresting(w.parseAny(lpr.getObjectId()));
444 removeReferenced(deletionCandidates, w);
445 } finally {
446 w.dispose();
447 }
448 }
449
450 if (deletionCandidates.isEmpty())
451 return;
452
453
454
455
456
457
458 ObjectWalk w = new ObjectWalk(repo);
459 try {
460 for (Ref ar : getAllRefs())
461 for (ObjectId id : listRefLogObjects(ar, lastRepackTime))
462 w.markStart(w.parseAny(id));
463 if (lastPackedRefs != null)
464 for (Ref lpr : lastPackedRefs)
465 w.markUninteresting(w.parseAny(lpr.getObjectId()));
466 removeReferenced(deletionCandidates, w);
467 } finally {
468 w.dispose();
469 }
470
471 if (deletionCandidates.isEmpty())
472 return;
473
474
475
476
477
478 Set<File> touchedFanout = new HashSet<>();
479 for (File f : deletionCandidates.values()) {
480 if (f.lastModified() < expireDate) {
481 f.delete();
482 touchedFanout.add(f.getParentFile());
483 }
484 }
485
486 for (File f : touchedFanout) {
487 FileUtils.delete(f,
488 FileUtils.EMPTY_DIRECTORIES_ONLY | FileUtils.IGNORE_ERRORS);
489 }
490
491 repo.getObjectDatabase().close();
492 }
493
494 private long getExpireDate() throws ParseException {
495 long expireDate = Long.MAX_VALUE;
496
497 if (expire == null && expireAgeMillis == -1) {
498 String pruneExpireStr = repo.getConfig().getString(
499 ConfigConstants.CONFIG_GC_SECTION, null,
500 ConfigConstants.CONFIG_KEY_PRUNEEXPIRE);
501 if (pruneExpireStr == null)
502 pruneExpireStr = PRUNE_EXPIRE_DEFAULT;
503 expire = GitDateParser.parse(pruneExpireStr, null, SystemReader
504 .getInstance().getLocale());
505 expireAgeMillis = -1;
506 }
507 if (expire != null)
508 expireDate = expire.getTime();
509 if (expireAgeMillis != -1)
510 expireDate = System.currentTimeMillis() - expireAgeMillis;
511 return expireDate;
512 }
513
514 private long getPackExpireDate() throws ParseException {
515 long packExpireDate = Long.MAX_VALUE;
516
517 if (packExpire == null && packExpireAgeMillis == -1) {
518 String prunePackExpireStr = repo.getConfig().getString(
519 ConfigConstants.CONFIG_GC_SECTION, null,
520 ConfigConstants.CONFIG_KEY_PRUNEPACKEXPIRE);
521 if (prunePackExpireStr == null)
522 prunePackExpireStr = PRUNE_PACK_EXPIRE_DEFAULT;
523 packExpire = GitDateParser.parse(prunePackExpireStr, null,
524 SystemReader.getInstance().getLocale());
525 packExpireAgeMillis = -1;
526 }
527 if (packExpire != null)
528 packExpireDate = packExpire.getTime();
529 if (packExpireAgeMillis != -1)
530 packExpireDate = System.currentTimeMillis() - packExpireAgeMillis;
531 return packExpireDate;
532 }
533
534
535
536
537
538
539
540
541
542
543
544 private void removeReferenced(Map<ObjectId, File> id2File,
545 ObjectWalk w) throws MissingObjectException,
546 IncorrectObjectTypeException, IOException {
547 RevObject ro = w.next();
548 while (ro != null) {
549 if (id2File.remove(ro.getId()) != null)
550 if (id2File.isEmpty())
551 return;
552 ro = w.next();
553 }
554 ro = w.nextObject();
555 while (ro != null) {
556 if (id2File.remove(ro.getId()) != null)
557 if (id2File.isEmpty())
558 return;
559 ro = w.nextObject();
560 }
561 }
562
563 private static boolean equals(Ref r1, Ref r2) {
564 if (r1 == null || r2 == null)
565 return false;
566 if (r1.isSymbolic()) {
567 if (!r2.isSymbolic())
568 return false;
569 return r1.getTarget().getName().equals(r2.getTarget().getName());
570 } else {
571 if (r2.isSymbolic()) {
572 return false;
573 }
574 return Objects.equals(r1.getObjectId(), r2.getObjectId());
575 }
576 }
577
578
579
580
581
582
583 public void packRefs() throws IOException {
584 Collection<Ref> refs = repo.getRefDatabase().getRefs(Constants.R_REFS).values();
585 List<String> refsToBePacked = new ArrayList<String>(refs.size());
586 pm.beginTask(JGitText.get().packRefs, refs.size());
587 try {
588 for (Ref ref : refs) {
589 if (!ref.isSymbolic() && ref.getStorage().isLoose())
590 refsToBePacked.add(ref.getName());
591 pm.update(1);
592 }
593 ((RefDirectory) repo.getRefDatabase()).pack(refsToBePacked);
594 } finally {
595 pm.endTask();
596 }
597 }
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613 public Collection<PackFile> repack() throws IOException {
614 Collection<PackFile> toBeDeleted = repo.getObjectDatabase().getPacks();
615
616 long time = System.currentTimeMillis();
617 Collection<Ref> refsBefore = getAllRefs();
618
619 Set<ObjectId> allHeads = new HashSet<ObjectId>();
620 Set<ObjectId> nonHeads = new HashSet<ObjectId>();
621 Set<ObjectId> txnHeads = new HashSet<ObjectId>();
622 Set<ObjectId> tagTargets = new HashSet<ObjectId>();
623 Set<ObjectId> indexObjects = listNonHEADIndexObjects();
624 RefDatabase refdb = repo.getRefDatabase();
625
626 for (Ref ref : refsBefore) {
627 nonHeads.addAll(listRefLogObjects(ref, 0));
628 if (ref.isSymbolic() || ref.getObjectId() == null)
629 continue;
630 if (ref.getName().startsWith(Constants.R_HEADS))
631 allHeads.add(ref.getObjectId());
632 else if (RefTreeNames.isRefTree(refdb, ref.getName()))
633 txnHeads.add(ref.getObjectId());
634 else
635 nonHeads.add(ref.getObjectId());
636 if (ref.getPeeledObjectId() != null)
637 tagTargets.add(ref.getPeeledObjectId());
638 }
639
640 List<ObjectIdSet> excluded = new LinkedList<ObjectIdSet>();
641 for (final PackFile f : repo.getObjectDatabase().getPacks())
642 if (f.shouldBeKept())
643 excluded.add(f.getIndex());
644
645 tagTargets.addAll(allHeads);
646 nonHeads.addAll(indexObjects);
647
648 List<PackFile> ret = new ArrayList<PackFile>(2);
649 PackFile heads = null;
650 if (!allHeads.isEmpty()) {
651 heads = writePack(allHeads, Collections.<ObjectId> emptySet(),
652 tagTargets, excluded);
653 if (heads != null) {
654 ret.add(heads);
655 excluded.add(0, heads.getIndex());
656 }
657 }
658 if (!nonHeads.isEmpty()) {
659 PackFile rest = writePack(nonHeads, allHeads, tagTargets, excluded);
660 if (rest != null)
661 ret.add(rest);
662 }
663 if (!txnHeads.isEmpty()) {
664 PackFile txn = writePack(txnHeads, PackWriter.NONE, null, excluded);
665 if (txn != null)
666 ret.add(txn);
667 }
668 try {
669 deleteOldPacks(toBeDeleted, ret);
670 } catch (ParseException e) {
671
672
673
674 throw new IOException(e);
675 }
676 prunePacked();
677 deleteOrphans();
678
679 lastPackedRefs = refsBefore;
680 lastRepackTime = time;
681 return ret;
682 }
683
684
685
686
687
688
689
690
691 private void deleteOrphans() {
692 Path packDir = Paths.get(repo.getObjectsDirectory().getAbsolutePath(),
693 "pack");
694 List<String> fileNames = null;
695 try (Stream<Path> files = Files.list(packDir)) {
696 fileNames = files.map(path -> path.getFileName().toString())
697 .filter(name -> {
698 return (name.endsWith(PACK_EXT)
699 || name.endsWith(BITMAP_EXT)
700 || name.endsWith(INDEX_EXT));
701 }).sorted(Collections.reverseOrder())
702 .collect(Collectors.toList());
703 } catch (IOException e1) {
704
705 }
706 if (fileNames == null) {
707 return;
708 }
709
710 String base = null;
711 for (String n : fileNames) {
712 if (n.endsWith(PACK_EXT)) {
713 base = n.substring(0, n.lastIndexOf('.'));
714 } else {
715 if (base == null || !n.startsWith(base)) {
716 try {
717 Files.delete(new File(packDir.toFile(), n).toPath());
718 } catch (IOException e) {
719 LOG.error(e.getMessage(), e);
720 }
721 }
722 }
723 }
724 }
725
726
727
728
729
730
731
732
733 private Set<ObjectId> listRefLogObjects(Ref ref, long minTime) throws IOException {
734 ReflogReader reflogReader = repo.getReflogReader(ref.getName());
735 if (reflogReader == null) {
736 return Collections.emptySet();
737 }
738 List<ReflogEntry> rlEntries = reflogReader
739 .getReverseEntries();
740 if (rlEntries == null || rlEntries.isEmpty())
741 return Collections.<ObjectId> emptySet();
742 Set<ObjectId> ret = new HashSet<ObjectId>();
743 for (ReflogEntry e : rlEntries) {
744 if (e.getWho().getWhen().getTime() < minTime)
745 break;
746 ObjectId newId = e.getNewId();
747 if (newId != null && !ObjectId.zeroId().equals(newId))
748 ret.add(newId);
749 ObjectId oldId = e.getOldId();
750 if (oldId != null && !ObjectId.zeroId().equals(oldId))
751 ret.add(oldId);
752 }
753 return ret;
754 }
755
756
757
758
759
760
761
762
763
764
765
766
767 private Collection<Ref> getAllRefs() throws IOException {
768 RefDatabase refdb = repo.getRefDatabase();
769 Collection<Ref> refs = refdb.getRefs(RefDatabase.ALL).values();
770 List<Ref> addl = refdb.getAdditionalRefs();
771 if (!addl.isEmpty()) {
772 List<Ref> all = new ArrayList<>(refs.size() + addl.size());
773 all.addAll(refs);
774
775 for (Ref r : addl) {
776 if (r.getName().startsWith(Constants.R_REFS)) {
777 all.add(r);
778 }
779 }
780 return all;
781 }
782 return refs;
783 }
784
785
786
787
788
789
790
791
792
793
794 private Set<ObjectId> listNonHEADIndexObjects()
795 throws CorruptObjectException, IOException {
796 if (repo.isBare()) {
797 return Collections.emptySet();
798 }
799 try (TreeWalk treeWalk = new TreeWalk(repo)) {
800 treeWalk.addTree(new DirCacheIterator(repo.readDirCache()));
801 ObjectId headID = repo.resolve(Constants.HEAD);
802 if (headID != null) {
803 try (RevWalk revWalk = new RevWalk(repo)) {
804 treeWalk.addTree(revWalk.parseTree(headID));
805 }
806 }
807
808 treeWalk.setFilter(TreeFilter.ANY_DIFF);
809 treeWalk.setRecursive(true);
810 Set<ObjectId> ret = new HashSet<ObjectId>();
811
812 while (treeWalk.next()) {
813 ObjectId objectId = treeWalk.getObjectId(0);
814 switch (treeWalk.getRawMode(0) & FileMode.TYPE_MASK) {
815 case FileMode.TYPE_MISSING:
816 case FileMode.TYPE_GITLINK:
817 continue;
818 case FileMode.TYPE_TREE:
819 case FileMode.TYPE_FILE:
820 case FileMode.TYPE_SYMLINK:
821 ret.add(objectId);
822 continue;
823 default:
824 throw new IOException(MessageFormat.format(
825 JGitText.get().corruptObjectInvalidMode3,
826 String.format("%o",
827 Integer.valueOf(treeWalk.getRawMode(0))),
828 (objectId == null) ? "null" : objectId.name(),
829 treeWalk.getPathString(),
830 repo.getIndexFile()));
831 }
832 }
833 return ret;
834 }
835 }
836
837 private PackFile writePack(@NonNull Set<? extends ObjectId> want,
838 @NonNull Set<? extends ObjectId> have, Set<ObjectId> tagTargets,
839 List<ObjectIdSet> excludeObjects) throws IOException {
840 File tmpPack = null;
841 Map<PackExt, File> tmpExts = new TreeMap<PackExt, File>(
842 new Comparator<PackExt>() {
843 public int compare(PackExt o1, PackExt o2) {
844
845
846
847 if (o1 == o2)
848 return 0;
849 if (o1 == PackExt.INDEX)
850 return 1;
851 if (o2 == PackExt.INDEX)
852 return -1;
853 return Integer.signum(o1.hashCode() - o2.hashCode());
854 }
855
856 });
857 try (PackWriter pw = new PackWriter(
858 (pconfig == null) ? new PackConfig(repo) : pconfig,
859 repo.newObjectReader())) {
860
861 pw.setDeltaBaseAsOffset(true);
862 pw.setReuseDeltaCommits(false);
863 if (tagTargets != null)
864 pw.setTagTargets(tagTargets);
865 if (excludeObjects != null)
866 for (ObjectIdSet idx : excludeObjects)
867 pw.excludeObjects(idx);
868 pw.preparePack(pm, want, have);
869 if (pw.getObjectCount() == 0)
870 return null;
871
872
873 String id = pw.computeName().getName();
874 File packdir = new File(repo.getObjectsDirectory(), "pack");
875 tmpPack = File.createTempFile("gc_", ".pack_tmp", packdir);
876 final String tmpBase = tmpPack.getName()
877 .substring(0, tmpPack.getName().lastIndexOf('.'));
878 File tmpIdx = new File(packdir, tmpBase + ".idx_tmp");
879 tmpExts.put(INDEX, tmpIdx);
880
881 if (!tmpIdx.createNewFile())
882 throw new IOException(MessageFormat.format(
883 JGitText.get().cannotCreateIndexfile, tmpIdx.getPath()));
884
885
886 FileOutputStream fos = new FileOutputStream(tmpPack);
887 FileChannel channel = fos.getChannel();
888 OutputStream channelStream = Channels.newOutputStream(channel);
889 try {
890 pw.writePack(pm, pm, channelStream);
891 } finally {
892 channel.force(true);
893 channelStream.close();
894 fos.close();
895 }
896
897
898 fos = new FileOutputStream(tmpIdx);
899 FileChannel idxChannel = fos.getChannel();
900 OutputStream idxStream = Channels.newOutputStream(idxChannel);
901 try {
902 pw.writeIndex(idxStream);
903 } finally {
904 idxChannel.force(true);
905 idxStream.close();
906 fos.close();
907 }
908
909 if (pw.prepareBitmapIndex(pm)) {
910 File tmpBitmapIdx = new File(packdir, tmpBase + ".bitmap_tmp");
911 tmpExts.put(BITMAP_INDEX, tmpBitmapIdx);
912
913 if (!tmpBitmapIdx.createNewFile())
914 throw new IOException(MessageFormat.format(
915 JGitText.get().cannotCreateIndexfile,
916 tmpBitmapIdx.getPath()));
917
918 fos = new FileOutputStream(tmpBitmapIdx);
919 idxChannel = fos.getChannel();
920 idxStream = Channels.newOutputStream(idxChannel);
921 try {
922 pw.writeBitmapIndex(idxStream);
923 } finally {
924 idxChannel.force(true);
925 idxStream.close();
926 fos.close();
927 }
928 }
929
930
931 File realPack = nameFor(id, ".pack");
932
933
934
935
936
937 if (realPack.exists())
938 for (PackFile p : repo.getObjectDatabase().getPacks())
939 if (realPack.getPath().equals(p.getPackFile().getPath())) {
940 p.close();
941 break;
942 }
943 tmpPack.setReadOnly();
944
945 FileUtils.rename(tmpPack, realPack, StandardCopyOption.ATOMIC_MOVE);
946 for (Map.Entry<PackExt, File> tmpEntry : tmpExts.entrySet()) {
947 File tmpExt = tmpEntry.getValue();
948 tmpExt.setReadOnly();
949
950 File realExt = nameFor(id,
951 "." + tmpEntry.getKey().getExtension());
952 try {
953 FileUtils.rename(tmpExt, realExt,
954 StandardCopyOption.ATOMIC_MOVE);
955 } catch (IOException e) {
956 File newExt = new File(realExt.getParentFile(),
957 realExt.getName() + ".new");
958 try {
959 FileUtils.rename(tmpExt, newExt,
960 StandardCopyOption.ATOMIC_MOVE);
961 } catch (IOException e2) {
962 newExt = tmpExt;
963 e = e2;
964 }
965 throw new IOException(MessageFormat.format(
966 JGitText.get().panicCantRenameIndexFile, newExt,
967 realExt), e);
968 }
969 }
970
971 return repo.getObjectDatabase().openPack(realPack);
972 } finally {
973 if (tmpPack != null && tmpPack.exists())
974 tmpPack.delete();
975 for (File tmpExt : tmpExts.values()) {
976 if (tmpExt.exists())
977 tmpExt.delete();
978 }
979 }
980 }
981
982 private File nameFor(String name, String ext) {
983 File packdir = new File(repo.getObjectsDirectory(), "pack");
984 return new File(packdir, "pack-" + name + ext);
985 }
986
987
988
989
990
991 public static class RepoStatistics {
992
993
994
995
996
997 public long numberOfPackedObjects;
998
999
1000
1001
1002 public long numberOfPackFiles;
1003
1004
1005
1006
1007 public long numberOfLooseObjects;
1008
1009
1010
1011
1012 public long sizeOfLooseObjects;
1013
1014
1015
1016
1017 public long sizeOfPackedObjects;
1018
1019
1020
1021
1022 public long numberOfLooseRefs;
1023
1024
1025
1026
1027 public long numberOfPackedRefs;
1028
1029
1030
1031
1032 public long numberOfBitmaps;
1033
1034 public String toString() {
1035 final StringBuilder b = new StringBuilder();
1036 b.append("numberOfPackedObjects=").append(numberOfPackedObjects);
1037 b.append(", numberOfPackFiles=").append(numberOfPackFiles);
1038 b.append(", numberOfLooseObjects=").append(numberOfLooseObjects);
1039 b.append(", numberOfLooseRefs=").append(numberOfLooseRefs);
1040 b.append(", numberOfPackedRefs=").append(numberOfPackedRefs);
1041 b.append(", sizeOfLooseObjects=").append(sizeOfLooseObjects);
1042 b.append(", sizeOfPackedObjects=").append(sizeOfPackedObjects);
1043 b.append(", numberOfBitmaps=").append(numberOfBitmaps);
1044 return b.toString();
1045 }
1046 }
1047
1048
1049
1050
1051
1052
1053
1054 public RepoStatistics getStatistics() throws IOException {
1055 RepoStatistics ret = new RepoStatistics();
1056 Collection<PackFile> packs = repo.getObjectDatabase().getPacks();
1057 for (PackFile f : packs) {
1058 ret.numberOfPackedObjects += f.getIndex().getObjectCount();
1059 ret.numberOfPackFiles++;
1060 ret.sizeOfPackedObjects += f.getPackFile().length();
1061 if (f.getBitmapIndex() != null)
1062 ret.numberOfBitmaps += f.getBitmapIndex().getBitmapCount();
1063 }
1064 File objDir = repo.getObjectsDirectory();
1065 String[] fanout = objDir.list();
1066 if (fanout != null && fanout.length > 0) {
1067 for (String d : fanout) {
1068 if (d.length() != 2)
1069 continue;
1070 File[] entries = new File(objDir, d).listFiles();
1071 if (entries == null)
1072 continue;
1073 for (File f : entries) {
1074 if (f.getName().length() != Constants.OBJECT_ID_STRING_LENGTH - 2)
1075 continue;
1076 ret.numberOfLooseObjects++;
1077 ret.sizeOfLooseObjects += f.length();
1078 }
1079 }
1080 }
1081
1082 RefDatabase refDb = repo.getRefDatabase();
1083 for (Ref r : refDb.getRefs(RefDatabase.ALL).values()) {
1084 Storage storage = r.getStorage();
1085 if (storage == Storage.LOOSE || storage == Storage.LOOSE_PACKED)
1086 ret.numberOfLooseRefs++;
1087 if (storage == Storage.PACKED || storage == Storage.LOOSE_PACKED)
1088 ret.numberOfPackedRefs++;
1089 }
1090
1091 return ret;
1092 }
1093
1094
1095
1096
1097
1098
1099
1100 public GC setProgressMonitor(ProgressMonitor pm) {
1101 this.pm = (pm == null) ? NullProgressMonitor.INSTANCE : pm;
1102 return this;
1103 }
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114 public void setExpireAgeMillis(long expireAgeMillis) {
1115 this.expireAgeMillis = expireAgeMillis;
1116 expire = null;
1117 }
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128 public void setPackExpireAgeMillis(long packExpireAgeMillis) {
1129 this.packExpireAgeMillis = packExpireAgeMillis;
1130 expire = null;
1131 }
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142 public void setPackConfig(PackConfig pconfig) {
1143 this.pconfig = pconfig;
1144 }
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158 public void setExpire(Date expire) {
1159 this.expire = expire;
1160 expireAgeMillis = -1;
1161 }
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172 public void setPackExpire(Date packExpire) {
1173 this.packExpire = packExpire;
1174 packExpireAgeMillis = -1;
1175 }
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213 public void setAuto(boolean auto) {
1214 this.automatic = auto;
1215 }
1216
1217 private boolean needGc() {
1218 if (tooManyPacks()) {
1219 addRepackAllOption();
1220 } else if (!tooManyLooseObjects()) {
1221 return false;
1222 }
1223
1224 return true;
1225 }
1226
1227 private void addRepackAllOption() {
1228
1229
1230 }
1231
1232
1233
1234
1235 boolean tooManyPacks() {
1236 int autopacklimit = repo.getConfig().getInt(
1237 ConfigConstants.CONFIG_GC_SECTION,
1238 ConfigConstants.CONFIG_KEY_AUTOPACKLIMIT,
1239 DEFAULT_AUTOPACKLIMIT);
1240 if (autopacklimit <= 0) {
1241 return false;
1242 }
1243
1244
1245 return repo.getObjectDatabase().getPacks().size() > (autopacklimit + 1);
1246 }
1247
1248
1249
1250
1251
1252
1253
1254 boolean tooManyLooseObjects() {
1255 int auto = repo.getConfig().getInt(ConfigConstants.CONFIG_GC_SECTION,
1256 ConfigConstants.CONFIG_KEY_AUTO, DEFAULT_AUTOLIMIT);
1257 if (auto <= 0) {
1258 return false;
1259 }
1260 int n = 0;
1261 int threshold = (auto + 255) / 256;
1262 Path dir = repo.getObjectsDirectory().toPath().resolve("17");
1263 if (!Files.exists(dir)) {
1264 return false;
1265 }
1266 try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir,
1267 new DirectoryStream.Filter<Path>() {
1268
1269 public boolean accept(Path file) throws IOException {
1270 return Files.isRegularFile(file) && PATTERN_LOOSE_OBJECT
1271 .matcher(file.getFileName().toString())
1272 .matches();
1273 }
1274 })) {
1275 for (Iterator<Path> iter = stream.iterator(); iter.hasNext();
1276 iter.next()) {
1277 if (++n > threshold) {
1278 return true;
1279 }
1280 }
1281 } catch (IOException e) {
1282 LOG.error(e.getMessage(), e);
1283 }
1284 return false;
1285 }
1286 }