LCOV - code coverage report
Current view: top level - server/patch - PatchFile.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 58 67 86.6 %
Date: 2022-11-19 15:00:39 Functions: 5 5 100.0 %

          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             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750