blob: 5718249a1857ea76d624a2996e5652adf040fb9d [file] [log] [blame]
// Copyright (C) 2020 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.googlesource.gerrit.plugins.replication.pull;
import static com.googlesource.gerrit.plugins.replication.pull.PullReplicationLogger.repLog;
import com.google.common.collect.Lists;
import com.google.gerrit.entities.Project;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.inject.Inject;
import com.googlesource.gerrit.plugins.replication.ReplicationConfig;
import com.googlesource.gerrit.plugins.replication.pull.api.data.RevisionData;
import com.googlesource.gerrit.plugins.replication.pull.api.data.RevisionObjectData;
import com.googlesource.gerrit.plugins.replication.pull.api.exception.RefUpdateException;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffEntry.ChangeType;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.LargeObjectException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
public class RevisionReader {
private static final String CONFIG_MAX_API_PAYLOAD_SIZE = "maxApiPayloadSize";
private static final Long DEFAULT_MAX_PAYLOAD_SIZE_IN_BYTES = 10000L;
private GitRepositoryManager gitRepositoryManager;
private Long maxRefSize;
@Inject
public RevisionReader(GitRepositoryManager gitRepositoryManager, ReplicationConfig cfg) {
this.gitRepositoryManager = gitRepositoryManager;
this.maxRefSize =
cfg.getConfig()
.getLong("replication", CONFIG_MAX_API_PAYLOAD_SIZE, DEFAULT_MAX_PAYLOAD_SIZE_IN_BYTES);
}
public Optional<RevisionData> read(Project.NameKey project, String refName)
throws MissingObjectException, IncorrectObjectTypeException, CorruptObjectException,
RepositoryNotFoundException, RefUpdateException, IOException {
try (Repository git = gitRepositoryManager.openRepository(project)) {
Ref head = git.exactRef(refName);
if (head == null) {
throw new RefUpdateException(
String.format("Cannot find ref %s in project %s", refName, project.get()));
}
Long totalRefSize = 0l;
ObjectLoader commitLoader = git.open(head.getObjectId());
totalRefSize += commitLoader.getSize();
verifySize(totalRefSize, commitLoader);
RevCommit commit = RevCommit.parse(commitLoader.getCachedBytes());
RevisionObjectData commitRev =
new RevisionObjectData(commit.getType(), commitLoader.getCachedBytes());
RevTree tree = commit.getTree();
ObjectLoader treeLoader = git.open(commit.getTree().toObjectId());
totalRefSize += treeLoader.getSize();
verifySize(totalRefSize, treeLoader);
RevisionObjectData treeRev =
new RevisionObjectData(tree.getType(), treeLoader.getCachedBytes());
List<RevisionObjectData> blobs = Lists.newLinkedList();
try (TreeWalk walk = new TreeWalk(git)) {
if (commit.getParentCount() > 0) {
List<DiffEntry> diffEntries = readDiffs(git, commit, tree, walk);
blobs = readBlobs(git, totalRefSize, diffEntries);
} else {
walk.setRecursive(true);
walk.addTree(tree);
blobs = readBlobs(git, totalRefSize, walk);
}
}
return Optional.of(new RevisionData(commitRev, treeRev, blobs));
} catch (LargeObjectException e) {
repLog.trace(
"Ref %s size for project %s is greater than configured '%s'",
refName, project, CONFIG_MAX_API_PAYLOAD_SIZE);
return Optional.empty();
}
}
private List<DiffEntry> readDiffs(Repository git, RevCommit commit, RevTree tree, TreeWalk walk)
throws MissingObjectException, IncorrectObjectTypeException, CorruptObjectException,
IOException {
walk.setFilter(TreeFilter.ANY_DIFF);
walk.reset(getParentTree(git, commit), tree);
return DiffEntry.scan(walk, true);
}
private List<RevisionObjectData> readBlobs(Repository git, Long totalRefSize, TreeWalk walk)
throws MissingObjectException, IncorrectObjectTypeException, CorruptObjectException,
IOException {
List<RevisionObjectData> blobs = Lists.newLinkedList();
while (walk.next()) {
ObjectId objectId = walk.getObjectId(0);
ObjectLoader objectLoader = git.open(objectId);
totalRefSize += objectLoader.getSize();
verifySize(totalRefSize, objectLoader);
RevisionObjectData rev =
new RevisionObjectData(objectLoader.getType(), objectLoader.getCachedBytes());
blobs.add(rev);
}
return blobs;
}
private List<RevisionObjectData> readBlobs(
Repository git, Long totalRefSize, List<DiffEntry> diffEntries)
throws MissingObjectException, IOException {
List<RevisionObjectData> blobs = Lists.newLinkedList();
for (DiffEntry diffEntry : diffEntries) {
if (!ChangeType.DELETE.equals(diffEntry.getChangeType())) {
ObjectLoader objectLoader = git.open(diffEntry.getNewId().toObjectId());
totalRefSize += objectLoader.getSize();
verifySize(totalRefSize, objectLoader);
RevisionObjectData rev =
new RevisionObjectData(objectLoader.getType(), objectLoader.getCachedBytes());
blobs.add(rev);
}
}
return blobs;
}
private RevTree getParentTree(Repository git, RevCommit commit)
throws MissingObjectException, IOException {
RevCommit parent = commit.getParent(0);
ObjectLoader parentLoader = git.open(parent.getId());
RevCommit parentCommit = RevCommit.parse(parentLoader.getCachedBytes());
return parentCommit.getTree();
}
private void verifySize(Long totalRefSize, ObjectLoader loader) throws LargeObjectException {
if (loader.isLarge() || totalRefSize > maxRefSize) {
throw new LargeObjectException();
}
}
}