LCOV - code coverage report
Current view: top level - server/patch/filediff - AllDiffsEvaluator.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 80 82 97.6 %
Date: 2022-11-19 15:00:39 Functions: 19 19 100.0 %

          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 com.google.common.collect.ImmutableMap;
      18             : import com.google.common.flogger.FluentLogger;
      19             : import com.google.gerrit.server.patch.DiffNotAvailableException;
      20             : import com.google.gerrit.server.patch.DiffUtil;
      21             : import com.google.gerrit.server.patch.gitfilediff.GitFileDiff;
      22             : import com.google.gerrit.server.patch.gitfilediff.GitFileDiffCache;
      23             : import com.google.gerrit.server.patch.gitfilediff.GitFileDiffCacheKey;
      24             : import com.google.inject.Inject;
      25             : import com.google.inject.assistedinject.Assisted;
      26             : import java.io.IOException;
      27             : import java.util.HashMap;
      28             : import java.util.List;
      29             : import java.util.Map;
      30             : import java.util.Optional;
      31             : import java.util.function.Function;
      32             : import java.util.stream.Collectors;
      33             : import org.eclipse.jgit.lib.ObjectId;
      34             : import org.eclipse.jgit.revwalk.RevWalk;
      35             : 
      36             : /**
      37             :  * A helper class that computes the four {@link GitFileDiff}s for a list of {@link
      38             :  * FileDiffCacheKey}s:
      39             :  *
      40             :  * <ul>
      41             :  *   <li>old commit vs. new commit
      42             :  *   <li>old parent vs. old commit
      43             :  *   <li>new parent vs. new commit
      44             :  *   <li>old parent vs. new parent
      45             :  * </ul>
      46             :  *
      47             :  * The four {@link GitFileDiff} are stored in the entity class {@link AllFileGitDiffs}. We use these
      48             :  * diffs to identify the edits due to rebase using the {@link EditTransformer} class.
      49             :  */
      50             : class AllDiffsEvaluator {
      51         104 :   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
      52             : 
      53             :   private final RevWalk rw;
      54             :   private final GitFileDiffCache gitCache;
      55             : 
      56             :   interface Factory {
      57             :     AllDiffsEvaluator create(RevWalk rw);
      58             :   }
      59             : 
      60             :   @Inject
      61         104 :   private AllDiffsEvaluator(GitFileDiffCache gitCache, @Assisted RevWalk rw) {
      62         104 :     this.gitCache = gitCache;
      63         104 :     this.rw = rw;
      64         104 :   }
      65             : 
      66             :   Map<AugmentedFileDiffCacheKey, AllFileGitDiffs> execute(
      67             :       List<AugmentedFileDiffCacheKey> augmentedKeys) throws DiffNotAvailableException {
      68         104 :     ImmutableMap.Builder<AugmentedFileDiffCacheKey, AllFileGitDiffs> keyToAllDiffs =
      69         104 :         ImmutableMap.builderWithExpectedSize(augmentedKeys.size());
      70             : 
      71         104 :     List<AugmentedFileDiffCacheKey> keysWithRebaseEdits =
      72         104 :         augmentedKeys.stream().filter(k -> !k.ignoreRebase()).collect(Collectors.toList());
      73             : 
      74             :     // TODO(ghareeb): as an enhancement, you can batch these calls as follows.
      75             :     // First batch: "old commit vs. new commit" and "new parent vs. new commit"
      76             :     // Second batch: "old parent vs. old commit" and "old parent vs. new parent"
      77             : 
      78         104 :     Map<FileDiffCacheKey, GitDiffEntity> mainDiffs =
      79         104 :         computeGitFileDiffs(
      80         104 :             createGitKeys(
      81             :                 augmentedKeys,
      82          93 :                 k -> k.key().oldCommit(),
      83          93 :                 k -> k.key().newCommit(),
      84          93 :                 k -> k.key().newFilePath()));
      85             : 
      86         104 :     Map<FileDiffCacheKey, GitDiffEntity> oldVsParentDiffs =
      87         104 :         computeGitFileDiffs(
      88         104 :             createGitKeys(
      89             :                 keysWithRebaseEdits,
      90           4 :                 k -> k.oldParentId().get(), // oldParent is set for keysWithRebaseEdits
      91           4 :                 k -> k.key().oldCommit(),
      92           4 :                 k -> mainDiffs.get(k.key()).gitDiff().oldPath().orElse(null)));
      93             : 
      94         104 :     Map<FileDiffCacheKey, GitDiffEntity> newVsParentDiffs =
      95         104 :         computeGitFileDiffs(
      96         104 :             createGitKeys(
      97             :                 keysWithRebaseEdits,
      98           4 :                 k -> k.newParentId().get(), // newParent is set for keysWithRebaseEdits
      99           4 :                 k -> k.key().newCommit(),
     100           4 :                 k -> k.key().newFilePath()));
     101             : 
     102         104 :     Map<FileDiffCacheKey, GitDiffEntity> parentsDiffs =
     103         104 :         computeGitFileDiffs(
     104         104 :             createGitKeys(
     105             :                 keysWithRebaseEdits,
     106           4 :                 k -> k.oldParentId().get(),
     107           4 :                 k -> k.newParentId().get(),
     108             :                 k -> {
     109           4 :                   GitFileDiff newVsParDiff = newVsParentDiffs.get(k.key()).gitDiff();
     110             :                   // TODO(ghareeb): Follow up on replacing key.newFilePath as a fallback.
     111             :                   // If the file was added between newParent and newCommit, we actually wouldn't
     112             :                   // need to have to determine the oldParent vs. newParent diff as nothing in
     113             :                   // that file could be an edit due to rebase anymore. Only if the returned diff
     114             :                   // is empty, the oldParent vs. newParent diff becomes relevant again (e.g. to
     115             :                   // identify a file deletion which was due to rebase. Check if the structure
     116             :                   // can be improved to make this clearer. Can we maybe even skip the diff in
     117             :                   // the first situation described?
     118           4 :                   return newVsParDiff.oldPath().orElse(k.key().newFilePath());
     119             :                 }));
     120             : 
     121         104 :     for (AugmentedFileDiffCacheKey augmentedKey : augmentedKeys) {
     122          93 :       FileDiffCacheKey key = augmentedKey.key();
     123             :       AllFileGitDiffs.Builder builder =
     124          93 :           AllFileGitDiffs.builder().augmentedKey(augmentedKey).mainDiff(mainDiffs.get(key));
     125             : 
     126          93 :       if (augmentedKey.ignoreRebase()) {
     127          93 :         keyToAllDiffs.put(augmentedKey, builder.build());
     128          93 :         continue;
     129             :       }
     130             : 
     131           4 :       if (oldVsParentDiffs.containsKey(key) && !oldVsParentDiffs.get(key).gitDiff().isEmpty()) {
     132           4 :         builder.oldVsParentDiff(Optional.of(oldVsParentDiffs.get(key)));
     133             :       }
     134             : 
     135           4 :       if (newVsParentDiffs.containsKey(key) && !newVsParentDiffs.get(key).gitDiff().isEmpty()) {
     136           4 :         builder.newVsParentDiff(Optional.of(newVsParentDiffs.get(key)));
     137             :       }
     138             : 
     139           4 :       if (parentsDiffs.containsKey(key) && !parentsDiffs.get(key).gitDiff().isEmpty()) {
     140           3 :         builder.parentVsParentDiff(Optional.of(parentsDiffs.get(key)));
     141             :       }
     142             : 
     143           4 :       keyToAllDiffs.put(augmentedKey, builder.build());
     144           4 :     }
     145         104 :     return keyToAllDiffs.build();
     146             :   }
     147             : 
     148             :   /**
     149             :    * Computes the git diff for the git keys of the input map {@code keys} parameter. The computation
     150             :    * uses the underlying {@link GitFileDiffCache}.
     151             :    */
     152             :   private Map<FileDiffCacheKey, GitDiffEntity> computeGitFileDiffs(
     153             :       Map<FileDiffCacheKey, GitFileDiffCacheKey> keys) throws DiffNotAvailableException {
     154         104 :     ImmutableMap.Builder<FileDiffCacheKey, GitDiffEntity> result =
     155         104 :         ImmutableMap.builderWithExpectedSize(keys.size());
     156         104 :     ImmutableMap<GitFileDiffCacheKey, GitFileDiff> gitDiffs = gitCache.getAll(keys.values());
     157         104 :     for (FileDiffCacheKey key : keys.keySet()) {
     158          93 :       GitFileDiffCacheKey gitKey = keys.get(key);
     159          93 :       GitFileDiff gitFileDiff = gitDiffs.get(gitKey);
     160          93 :       result.put(key, GitDiffEntity.create(gitKey, gitFileDiff));
     161          93 :     }
     162         104 :     return result.build();
     163             :   }
     164             : 
     165             :   /**
     166             :    * Convert a list of {@link AugmentedFileDiffCacheKey} to their corresponding {@link
     167             :    * GitFileDiffCacheKey} which can be used to call the underlying {@link GitFileDiffCache}.
     168             :    *
     169             :    * @param keys a list of input {@link AugmentedFileDiffCacheKey}s.
     170             :    * @param aCommitFn a function to compute the aCommit that will be used in the git diff.
     171             :    * @param bCommitFn a function to compute the bCommit that will be used in the git diff.
     172             :    * @param newPathFn a function to compute the new path of the git key.
     173             :    * @return a map of the input {@link FileDiffCacheKey} to the {@link GitFileDiffCacheKey}.
     174             :    */
     175             :   private Map<FileDiffCacheKey, GitFileDiffCacheKey> createGitKeys(
     176             :       List<AugmentedFileDiffCacheKey> keys,
     177             :       Function<AugmentedFileDiffCacheKey, ObjectId> aCommitFn,
     178             :       Function<AugmentedFileDiffCacheKey, ObjectId> bCommitFn,
     179             :       Function<AugmentedFileDiffCacheKey, String> newPathFn) {
     180         104 :     Map<FileDiffCacheKey, GitFileDiffCacheKey> result = new HashMap<>();
     181         104 :     for (AugmentedFileDiffCacheKey key : keys) {
     182             :       try {
     183          93 :         String path = newPathFn.apply(key);
     184          93 :         if (path != null) {
     185          93 :           result.put(
     186          93 :               key.key(),
     187          93 :               createGitKey(key.key(), aCommitFn.apply(key), bCommitFn.apply(key), path, rw));
     188             :         }
     189           0 :       } catch (IOException e) {
     190             :         // TODO(ghareeb): This implies that the output keys may not have the same size as the input.
     191             :         // Check the caller's code path about the correctness of the computation in this case. If
     192             :         // errors are rare, it may be better to throw an exception and fail the whole computation.
     193           0 :         logger.atWarning().log("Failed to compute the git key for key %s: %s", key, e.getMessage());
     194          93 :       }
     195          93 :     }
     196         104 :     return result;
     197             :   }
     198             : 
     199             :   /** Returns the {@link GitFileDiffCacheKey} for the {@code key} input parameter. */
     200             :   private GitFileDiffCacheKey createGitKey(
     201             :       FileDiffCacheKey key, ObjectId aCommit, ObjectId bCommit, String pathNew, RevWalk rw)
     202             :       throws IOException {
     203             :     ObjectId oldTreeId =
     204          93 :         aCommit.equals(ObjectId.zeroId()) ? ObjectId.zeroId() : DiffUtil.getTreeId(rw, aCommit);
     205          93 :     ObjectId newTreeId = DiffUtil.getTreeId(rw, bCommit);
     206          93 :     return GitFileDiffCacheKey.builder()
     207          93 :         .project(key.project())
     208          93 :         .oldTree(oldTreeId)
     209          93 :         .newTree(newTreeId)
     210          93 :         .newFilePath(pathNew == null ? key.newFilePath() : pathNew)
     211          93 :         .renameScore(key.renameScore())
     212          93 :         .diffAlgorithm(key.diffAlgorithm())
     213          93 :         .whitespace(key.whitespace())
     214          93 :         .useTimeout(key.useTimeout())
     215          93 :         .build();
     216             :   }
     217             : }

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