in org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java [492:644]
public void prune(Set<ObjectId> objectsToKeep) throws IOException,
ParseException {
long expireDate = getExpireDate();
// Collect all loose objects which are old enough, not referenced from
// the index and not in objectsToKeep
Map<ObjectId, File> deletionCandidates = new HashMap<>();
Set<ObjectId> indexObjects = null;
File objects = repo.getObjectsDirectory();
String[] fanout = objects.list();
if (fanout == null || fanout.length == 0) {
return;
}
pm.beginTask(JGitText.get().pruneLooseUnreferencedObjects,
fanout.length);
try {
for (String d : fanout) {
checkCancelled();
pm.update(1);
if (d.length() != 2)
continue;
File dir = new File(objects, d);
File[] entries = dir.listFiles();
if (entries == null || entries.length == 0) {
FileUtils.delete(dir, FileUtils.IGNORE_ERRORS);
continue;
}
for (File f : entries) {
checkCancelled();
String fName = f.getName();
if (fName.length() != Constants.OBJECT_ID_STRING_LENGTH - 2)
continue;
if (repo.getFS().lastModifiedInstant(f)
.toEpochMilli() >= expireDate) {
continue;
}
try {
ObjectId id = ObjectId.fromString(d + fName);
if (objectsToKeep.contains(id))
continue;
if (indexObjects == null)
indexObjects = listNonHEADIndexObjects();
if (indexObjects.contains(id))
continue;
deletionCandidates.put(id, f);
} catch (IllegalArgumentException notAnObject) {
// ignoring the file that does not represent loose
// object
}
}
}
} finally {
pm.endTask();
}
if (deletionCandidates.isEmpty()) {
return;
}
checkCancelled();
// From the set of current refs remove all those which have been handled
// during last repack(). Only those refs will survive which have been
// added or modified since the last repack. Only these can save existing
// loose refs from being pruned.
Collection<Ref> newRefs;
if (lastPackedRefs == null || lastPackedRefs.isEmpty())
newRefs = getAllRefs();
else {
Map<String, Ref> last = new HashMap<>();
for (Ref r : lastPackedRefs) {
last.put(r.getName(), r);
}
newRefs = new ArrayList<>();
for (Ref r : getAllRefs()) {
Ref old = last.get(r.getName());
if (!equals(r, old)) {
newRefs.add(r);
}
}
}
if (!newRefs.isEmpty()) {
// There are new/modified refs! Check which loose objects are now
// referenced by these modified refs (or their reflogentries).
// Remove these loose objects
// from the deletionCandidates. When the last candidate is removed
// leave this method.
ObjectWalk w = new ObjectWalk(repo);
try {
for (Ref cr : newRefs) {
checkCancelled();
w.markStart(w.parseAny(cr.getObjectId()));
}
if (lastPackedRefs != null)
for (Ref lpr : lastPackedRefs) {
w.markUninteresting(w.parseAny(lpr.getObjectId()));
}
removeReferenced(deletionCandidates, w);
} finally {
w.dispose();
}
}
if (deletionCandidates.isEmpty())
return;
// Since we have not left the method yet there are still
// deletionCandidates. Last chance for these objects not to be pruned is
// that they are referenced by reflog entries. Even refs which currently
// point to the same object as during last repack() may have
// additional reflog entries not handled during last repack()
ObjectWalk w = new ObjectWalk(repo);
try {
for (Ref ar : getAllRefs())
for (ObjectId id : listRefLogObjects(ar, lastRepackTime)) {
checkCancelled();
w.markStart(w.parseAny(id));
}
if (lastPackedRefs != null)
for (Ref lpr : lastPackedRefs) {
checkCancelled();
w.markUninteresting(w.parseAny(lpr.getObjectId()));
}
removeReferenced(deletionCandidates, w);
} finally {
w.dispose();
}
if (deletionCandidates.isEmpty())
return;
checkCancelled();
// delete all candidates which have survived: these are unreferenced
// loose objects. Make a last check, though, to avoid deleting objects
// that could have been referenced while the candidates list was being
// built (by an incoming push, for example).
Set<File> touchedFanout = new HashSet<>();
for (File f : deletionCandidates.values()) {
if (f.lastModified() < expireDate) {
f.delete();
touchedFanout.add(f.getParentFile());
}
}
for (File f : touchedFanout) {
FileUtils.delete(f,
FileUtils.EMPTY_DIRECTORIES_ONLY | FileUtils.IGNORE_ERRORS);
}
repo.getObjectDatabase().close();
}