Line data Source code
1 : // Copyright (C) 2020 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.server.patch.filediff; 16 : 17 : import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; 18 : 19 : import com.google.common.collect.Iterables; 20 : import com.google.gerrit.common.Nullable; 21 : import com.google.gerrit.entities.Patch; 22 : import com.google.gerrit.entities.Patch.FileMode; 23 : import com.google.gerrit.exceptions.StorageException; 24 : import java.io.IOException; 25 : import java.util.Collection; 26 : import java.util.Optional; 27 : import org.eclipse.jgit.lib.AbbreviatedObjectId; 28 : import org.eclipse.jgit.lib.ObjectId; 29 : import org.eclipse.jgit.lib.ObjectReader; 30 : import org.eclipse.jgit.revwalk.RevTree; 31 : import org.eclipse.jgit.treewalk.TreeWalk; 32 : 33 : /** Helper class for computing the size of a file in a given git tree. */ 34 : class FileSizeEvaluator { 35 : private final ObjectReader reader; 36 : private final RevTree tree; 37 : 38 93 : FileSizeEvaluator(ObjectReader reader, RevTree tree) { 39 93 : this.reader = reader; 40 93 : this.tree = tree; 41 93 : } 42 : 43 : /** 44 : * Computes the file size identified by the {@code path} parameter at the given git tree 45 : * identified by {@code gitTreeId}. 46 : */ 47 : long compute(AbbreviatedObjectId gitTreeId, Patch.FileMode mode, String path) throws IOException { 48 93 : if (!isBlob(mode)) { 49 4 : return 0; 50 : } 51 93 : ObjectId fileId = 52 93 : toObjectId(reader, gitTreeId).orElseGet(() -> lookupObjectId(reader, path, tree)); 53 93 : if (ObjectId.zeroId().equals(fileId)) { 54 0 : return 0; 55 : } 56 93 : return reader.getObjectSize(fileId, OBJ_BLOB); 57 : } 58 : 59 : private static ObjectId lookupObjectId(ObjectReader reader, String path, RevTree tree) { 60 : // This variant is very expensive. 61 0 : try (TreeWalk treeWalk = TreeWalk.forPath(reader, path, tree)) { 62 0 : return treeWalk != null ? treeWalk.getObjectId(0) : ObjectId.zeroId(); 63 0 : } catch (IOException e) { 64 0 : throw new StorageException(e); 65 : } 66 : } 67 : 68 : private static Optional<ObjectId> toObjectId( 69 : ObjectReader reader, @Nullable AbbreviatedObjectId abbreviatedId) throws IOException { 70 93 : if (abbreviatedId == null) { 71 : // In theory, DiffEntry#getOldId or DiffEntry#getNewId can be null for pure renames or pure 72 : // mode changes (e.g. DiffEntry#modify doesn't set the IDs). However, the method we call for 73 : // diffs (DiffFormatter#scan) seems to always produce DiffEntries with set IDs, even for pure 74 : // renames. 75 0 : return Optional.empty(); 76 : } 77 93 : if (abbreviatedId.isComplete()) { 78 : // With the current JGit version and the method we call for diffs (DiffFormatter#scan), 79 : // this 80 : // is the only code path taken right now. 81 93 : return Optional.ofNullable(abbreviatedId.toObjectId()); 82 : } 83 0 : Collection<ObjectId> objectIds = reader.resolve(abbreviatedId); 84 : // It seems very unlikely that an ObjectId which was just abbreviated by the diff 85 : // computation 86 : // now can't be resolved to exactly one ObjectId. The API allows this possibility, though. 87 0 : return objectIds.size() == 1 88 0 : ? Optional.of(Iterables.getOnlyElement(objectIds)) 89 0 : : Optional.empty(); 90 : } 91 : 92 : private static boolean isBlob(Patch.FileMode mode) { 93 93 : return mode.equals(FileMode.REGULAR_FILE) 94 6 : || mode.equals(FileMode.EXECUTABLE_FILE) 95 93 : || mode.equals(FileMode.SYMLINK); 96 : } 97 : }