blob: b76e3a3caa6d928b63f3998dc952c86e29e77b32 [file] [log] [blame]
/*
* Copyright (c) 2019, Google LLC and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
package org.eclipse.jgit.internal.transport.connectivity;
import java.io.IOException;
import java.util.Set;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectIdSubclassMap;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.revwalk.ObjectWalk;
import org.eclipse.jgit.revwalk.RevBlob;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevFlag;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevSort;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.transport.ConnectivityChecker;
import org.eclipse.jgit.transport.ReceiveCommand;
import org.eclipse.jgit.transport.ReceiveCommand.Result;
/**
* A connectivity checker that uses the entire reference database to perform
* reachability checks when checking the connectivity of objects. If
* info.isCheckObjects() is set it will also check that objects referenced by
* deltas are either provided or reachable as well.
*/
public final class FullConnectivityChecker implements ConnectivityChecker {
@Override
public void checkConnectivity(ConnectivityCheckInfo connectivityCheckInfo,
Set<ObjectId> haves, ProgressMonitor pm)
throws MissingObjectException, IOException {
pm.beginTask(JGitText.get().countingObjects,
ProgressMonitor.UNKNOWN);
try (ObjectWalk ow = new ObjectWalk(connectivityCheckInfo.getRepository())) {
if (!markStartAndKnownNodes(connectivityCheckInfo, ow, haves,
pm)) {
return;
}
checkCommitTree(connectivityCheckInfo, ow, pm);
checkObjects(connectivityCheckInfo, ow, pm);
} finally {
pm.endTask();
}
}
/**
* @param connectivityCheckInfo
* Source for connectivity check.
* @param ow
* Walk which can also check blobs.
* @param haves
* Set of references known for client.
* @param pm
* Monitor to publish progress to.
* @return true if at least one new node was marked.
* @throws IOException
* an error occurred during connectivity checking.
*/
private boolean markStartAndKnownNodes(
ConnectivityCheckInfo connectivityCheckInfo,
ObjectWalk ow,
Set<ObjectId> haves, ProgressMonitor pm)
throws IOException {
boolean markTrees = connectivityCheckInfo
.isCheckObjects()
&& !connectivityCheckInfo.getParser().getBaseObjectIds()
.isEmpty();
if (connectivityCheckInfo.isCheckObjects()) {
ow.sort(RevSort.TOPO);
if (!connectivityCheckInfo.getParser().getBaseObjectIds()
.isEmpty()) {
ow.sort(RevSort.BOUNDARY, true);
}
}
boolean hasInteresting = false;
for (ReceiveCommand cmd : connectivityCheckInfo.getCommands()) {
if (cmd.getResult() != Result.NOT_ATTEMPTED) {
continue;
}
if (cmd.getType() == ReceiveCommand.Type.DELETE) {
continue;
}
if (haves.contains(cmd.getNewId())) {
continue;
}
ow.markStart(ow.parseAny(cmd.getNewId()));
pm.update(1);
hasInteresting = true;
}
if (!hasInteresting) {
return false;
}
for (ObjectId have : haves) {
RevObject o = ow.parseAny(have);
ow.markUninteresting(o);
pm.update(1);
if (markTrees) {
o = ow.peel(o);
if (o instanceof RevCommit) {
o = ((RevCommit) o).getTree();
}
if (o instanceof RevTree) {
ow.markUninteresting(o);
}
}
}
return true;
}
/**
* @param connectivityCheckInfo
* Source for connectivity check.
* @param ow
* Walk which can also check blobs.
* @param pm
* Monitor to publish progress to.
* @throws IOException
* an error occurred during connectivity checking.
*/
private void checkCommitTree(ConnectivityCheckInfo connectivityCheckInfo,
ObjectWalk ow,
ProgressMonitor pm) throws IOException {
RevCommit c;
ObjectIdSubclassMap<ObjectId> newObjectIds = connectivityCheckInfo
.getParser()
.getNewObjectIds();
while ((c = ow.next()) != null) {
pm.update(1);
if (connectivityCheckInfo.isCheckObjects()
&& !c.has(RevFlag.UNINTERESTING)
&& !newObjectIds.contains(c)) {
throw new MissingObjectException(c, Constants.TYPE_COMMIT);
}
}
}
/**
* @param connectivityCheckInfo
* Source for connectivity check.
* @param ow
* Walk which can also check blobs.
* @param pm
* Monitor to publish progress to.
* @throws IOException
* an error occurred during connectivity checking.
*
*/
private void checkObjects(ConnectivityCheckInfo connectivityCheckInfo,
ObjectWalk ow,
ProgressMonitor pm) throws IOException {
RevObject o;
ObjectIdSubclassMap<ObjectId> newObjectIds = connectivityCheckInfo
.getParser()
.getNewObjectIds();
while ((o = ow.nextObject()) != null) {
pm.update(1);
if (o.has(RevFlag.UNINTERESTING)) {
continue;
}
if (connectivityCheckInfo.isCheckObjects()) {
if (newObjectIds.contains(o)) {
continue;
}
throw new MissingObjectException(o, o.getType());
}
if (o instanceof RevBlob
&& !connectivityCheckInfo.getRepository().getObjectDatabase()
.has(o)) {
throw new MissingObjectException(o, Constants.TYPE_BLOB);
}
}
if (connectivityCheckInfo.isCheckObjects()) {
for (ObjectId id : connectivityCheckInfo.getParser()
.getBaseObjectIds()) {
o = ow.parseAny(id);
if (!o.has(RevFlag.UNINTERESTING)) {
throw new MissingObjectException(o, o.getType());
}
}
}
}
}