Line data Source code
1 : // Copyright (C) 2009 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; 16 : 17 : import static java.nio.charset.StandardCharsets.UTF_8; 18 : 19 : import com.google.gerrit.entities.Patch; 20 : import com.google.gerrit.exceptions.NoSuchEntityException; 21 : import com.google.gerrit.server.patch.filediff.FileDiffOutput; 22 : import java.io.IOException; 23 : import java.util.Map; 24 : import org.eclipse.jgit.errors.CorruptObjectException; 25 : import org.eclipse.jgit.errors.IncorrectObjectTypeException; 26 : import org.eclipse.jgit.errors.MissingObjectException; 27 : import org.eclipse.jgit.lib.Constants; 28 : import org.eclipse.jgit.lib.ObjectId; 29 : import org.eclipse.jgit.lib.ObjectReader; 30 : import org.eclipse.jgit.lib.Repository; 31 : import org.eclipse.jgit.revwalk.RevCommit; 32 : import org.eclipse.jgit.revwalk.RevObject; 33 : import org.eclipse.jgit.revwalk.RevTree; 34 : import org.eclipse.jgit.revwalk.RevWalk; 35 : import org.eclipse.jgit.treewalk.TreeWalk; 36 : 37 : /** State supporting processing of a single {@link Patch} instance. */ 38 : public class PatchFile { 39 : private final Repository repo; 40 : private final FileDiffOutput diff; 41 : private final RevTree aTree; 42 : private final RevTree bTree; 43 : 44 : // Full text of both sides of the file. For standard files, these are not directly reconstructable 45 : // from the PatchListEntry, which comes from the PatchListCache and only contains the diff between 46 : // the two blobs. This is intentional, to avoid storing entire large blobs in the cache. For 47 : // regular files, the full text is initialized from the repo lazily only when necessary, e.g. in 48 : // getLine. Although it's a safe assumption that any caller constructing a PatchSet will want to 49 : // read some content, we don't know in advance which side they are interested in. 50 : // 51 : // For special files like COMMIT_MSG, the full text is loaded eagerly during the constructor. 52 : // TODO(dborowitz): I see why the logic is different, but I don't see why it needs to be eager. 53 : private Text a; 54 : private Text b; 55 : 56 : public PatchFile(Repository repo, Map<String, FileDiffOutput> modifiedFiles, String fileName) 57 23 : throws IOException { 58 23 : this.repo = repo; 59 23 : this.diff = 60 23 : modifiedFiles.entrySet().stream() 61 23 : .filter(f -> f.getKey().equals(fileName)) 62 23 : .map(Map.Entry::getValue) 63 23 : .findFirst() 64 23 : .orElse(FileDiffOutput.empty(fileName, ObjectId.zeroId(), ObjectId.zeroId())); 65 : 66 23 : if (Patch.PATCHSET_LEVEL.equals(fileName)) { 67 5 : aTree = null; 68 5 : bTree = null; 69 5 : return; 70 : } 71 22 : try (ObjectReader reader = repo.newObjectReader(); 72 22 : RevWalk rw = new RevWalk(reader)) { 73 22 : final RevCommit bCommit = rw.parseCommit(diff.newCommitId()); 74 : 75 22 : if (Patch.COMMIT_MSG.equals(fileName)) { 76 10 : if (diff.comparisonType().isAgainstParentOrAutoMerge()) { 77 10 : a = Text.EMPTY; 78 : } else { 79 : // For the initial commit, we have an empty tree on Side A 80 0 : RevObject object = rw.parseAny(diff.oldCommitId()); 81 0 : a = object instanceof RevCommit ? Text.forCommit(reader, object) : Text.EMPTY; 82 : } 83 10 : b = Text.forCommit(reader, bCommit); 84 : 85 10 : aTree = null; 86 10 : bTree = null; 87 16 : } else if (Patch.MERGE_LIST.equals(fileName)) { 88 : // For the initial commit, we have an empty tree on Side A 89 1 : RevObject object = rw.parseAny(diff.oldCommitId()); 90 1 : a = 91 1 : object instanceof RevCommit 92 1 : ? Text.forMergeList(diff.comparisonType(), reader, object) 93 1 : : Text.EMPTY; 94 1 : b = Text.forMergeList(diff.comparisonType(), reader, bCommit); 95 : 96 1 : aTree = null; 97 1 : bTree = null; 98 1 : } else { 99 16 : if (diff.oldCommitId() != null) { 100 16 : if (diff.oldCommitId().equals(ObjectId.zeroId())) { 101 : // DiffOperations returns ObjectId.zeroId if newCommit is a root commit, i.e. has no 102 : // parents. 103 1 : aTree = null; 104 : } else { 105 16 : aTree = rw.parseTree(diff.oldCommitId()); 106 : } 107 : } else { 108 0 : final RevCommit p = bCommit.getParent(0); 109 0 : rw.parseHeaders(p); 110 0 : aTree = p.getTree(); 111 : } 112 16 : bTree = bCommit.getTree(); 113 : } 114 : } 115 22 : } 116 : 117 : private String getOldName() { 118 1 : String name = FilePathAdapter.getOldPath(diff.oldPath(), diff.changeType()); 119 1 : if (name != null) { 120 0 : return name; 121 : } 122 1 : return FilePathAdapter.getNewPath(diff.oldPath(), diff.newPath(), diff.changeType()); 123 : } 124 : 125 : /** 126 : * Extract a line from the file, as a string. 127 : * 128 : * @param file the file index to extract. 129 : * @param line the line number to extract (1 based; 1 is the first line). 130 : * @return the string version of the file line. 131 : * @throws IOException the patch or complete file content cannot be read. 132 : */ 133 : public String getLine(int file, int line) throws IOException, NoSuchEntityException { 134 21 : switch (file) { 135 : case 0: 136 1 : if (a == null) { 137 1 : a = load(aTree, getOldName()); 138 : } 139 1 : return a.getString(line - 1); 140 : 141 : case 1: 142 21 : if (b == null) { 143 15 : b = 144 15 : load( 145 : bTree, 146 15 : FilePathAdapter.getNewPath(diff.oldPath(), diff.newPath(), diff.changeType())); 147 : } 148 21 : return b.getString(line - 1); 149 : 150 : default: 151 1 : throw new NoSuchEntityException(); 152 : } 153 : } 154 : 155 : private Text load(ObjectId tree, String path) 156 : throws MissingObjectException, IncorrectObjectTypeException, CorruptObjectException, 157 : IOException { 158 15 : if (path == null || Patch.PATCHSET_LEVEL.equals(path)) { 159 0 : return Text.EMPTY; 160 : } 161 15 : final TreeWalk tw = TreeWalk.forPath(repo, path, tree); 162 15 : if (tw == null) { 163 0 : return Text.EMPTY; 164 : } 165 15 : if (tw.getFileMode(0).getObjectType() == Constants.OBJ_BLOB) { 166 15 : return new Text(repo.open(tw.getObjectId(0), Constants.OBJ_BLOB)); 167 1 : } else if (tw.getFileMode(0).getObjectType() == Constants.OBJ_COMMIT) { 168 1 : String str = "Subproject commit " + ObjectId.toString(tw.getObjectId(0)); 169 1 : return new Text(str.getBytes(UTF_8)); 170 : } else { 171 0 : return Text.EMPTY; 172 : } 173 : } 174 : }