blob: e2c1bc5b970ee0814ffcef7b2b53255416a31c89 [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.google.gerrit.server.patch.filediff;
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
import com.google.common.collect.Iterables;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Patch;
import com.google.gerrit.entities.Patch.FileMode;
import com.google.gerrit.exceptions.StorageException;
import java.io.IOException;
import java.util.Collection;
import java.util.Optional;
import org.eclipse.jgit.lib.AbbreviatedObjectId;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.treewalk.TreeWalk;
/** Helper class for computing the size of a file in a given git tree. */
class FileSizeEvaluator {
private final ObjectReader reader;
private final RevTree tree;
FileSizeEvaluator(ObjectReader reader, RevTree tree) {
this.reader = reader;
this.tree = tree;
}
/**
* Computes the file size identified by the {@code path} parameter at the given git tree
* identified by {@code gitTreeId}.
*/
long compute(AbbreviatedObjectId gitTreeId, Patch.FileMode mode, String path) throws IOException {
if (!isBlob(mode)) {
return 0;
}
ObjectId fileId =
toObjectId(reader, gitTreeId).orElseGet(() -> lookupObjectId(reader, path, tree));
if (ObjectId.zeroId().equals(fileId)) {
return 0;
}
return reader.getObjectSize(fileId, OBJ_BLOB);
}
private static ObjectId lookupObjectId(ObjectReader reader, String path, RevTree tree) {
// This variant is very expensive.
try (TreeWalk treeWalk = TreeWalk.forPath(reader, path, tree)) {
return treeWalk != null ? treeWalk.getObjectId(0) : ObjectId.zeroId();
} catch (IOException e) {
throw new StorageException(e);
}
}
private static Optional<ObjectId> toObjectId(
ObjectReader reader, @Nullable AbbreviatedObjectId abbreviatedId) throws IOException {
if (abbreviatedId == null) {
// In theory, DiffEntry#getOldId or DiffEntry#getNewId can be null for pure renames or pure
// mode changes (e.g. DiffEntry#modify doesn't set the IDs). However, the method we call for
// diffs (DiffFormatter#scan) seems to always produce DiffEntries with set IDs, even for pure
// renames.
return Optional.empty();
}
if (abbreviatedId.isComplete()) {
// With the current JGit version and the method we call for diffs (DiffFormatter#scan),
// this
// is the only code path taken right now.
return Optional.ofNullable(abbreviatedId.toObjectId());
}
Collection<ObjectId> objectIds = reader.resolve(abbreviatedId);
// It seems very unlikely that an ObjectId which was just abbreviated by the diff
// computation
// now can't be resolved to exactly one ObjectId. The API allows this possibility, though.
return objectIds.size() == 1
? Optional.of(Iterables.getOnlyElement(objectIds))
: Optional.empty();
}
private static boolean isBlob(Patch.FileMode mode) {
return mode.equals(FileMode.REGULAR_FILE)
|| mode.equals(FileMode.EXECUTABLE_FILE)
|| mode.equals(FileMode.SYMLINK);
}
}