LCOV - code coverage report
Current view: top level - server/patch/filediff - FileDiffCacheImpl.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 232 262 88.5 %
Date: 2022-11-19 15:00:39 Functions: 20 22 90.9 %

          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 com.google.common.collect.ImmutableList.toImmutableList;
      18             : import static java.nio.charset.StandardCharsets.UTF_8;
      19             : 
      20             : import com.google.common.cache.CacheLoader;
      21             : import com.google.common.cache.LoadingCache;
      22             : import com.google.common.collect.ImmutableList;
      23             : import com.google.common.collect.ImmutableMap;
      24             : import com.google.common.collect.Iterables;
      25             : import com.google.common.collect.Multimap;
      26             : import com.google.common.collect.Streams;
      27             : import com.google.common.flogger.FluentLogger;
      28             : import com.google.gerrit.common.Nullable;
      29             : import com.google.gerrit.entities.Patch;
      30             : import com.google.gerrit.entities.Project;
      31             : import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
      32             : import com.google.gerrit.server.cache.CacheModule;
      33             : import com.google.gerrit.server.git.GitRepositoryManager;
      34             : import com.google.gerrit.server.logging.Metadata;
      35             : import com.google.gerrit.server.logging.TraceContext;
      36             : import com.google.gerrit.server.logging.TraceContext.TraceTimer;
      37             : import com.google.gerrit.server.patch.AutoMerger;
      38             : import com.google.gerrit.server.patch.ComparisonType;
      39             : import com.google.gerrit.server.patch.DiffNotAvailableException;
      40             : import com.google.gerrit.server.patch.DiffUtil;
      41             : import com.google.gerrit.server.patch.Text;
      42             : import com.google.gerrit.server.patch.filediff.EditTransformer.ContextAwareEdit;
      43             : import com.google.gerrit.server.patch.gitfilediff.FileHeaderUtil;
      44             : import com.google.gerrit.server.patch.gitfilediff.GitFileDiff;
      45             : import com.google.gerrit.server.patch.gitfilediff.GitFileDiffCacheImpl;
      46             : import com.google.gerrit.server.patch.gitfilediff.GitFileDiffCacheImpl.DiffAlgorithmFactory;
      47             : import com.google.inject.Inject;
      48             : import com.google.inject.Module;
      49             : import com.google.inject.Singleton;
      50             : import com.google.inject.name.Named;
      51             : import java.io.IOException;
      52             : import java.util.ArrayList;
      53             : import java.util.Collection;
      54             : import java.util.HashMap;
      55             : import java.util.HashSet;
      56             : import java.util.List;
      57             : import java.util.Map;
      58             : import java.util.Optional;
      59             : import java.util.Set;
      60             : import java.util.concurrent.ExecutionException;
      61             : import java.util.stream.Collectors;
      62             : import org.eclipse.jgit.diff.EditList;
      63             : import org.eclipse.jgit.diff.RawText;
      64             : import org.eclipse.jgit.diff.RawTextComparator;
      65             : import org.eclipse.jgit.lib.ObjectId;
      66             : import org.eclipse.jgit.lib.ObjectReader;
      67             : import org.eclipse.jgit.lib.Repository;
      68             : import org.eclipse.jgit.patch.FileHeader;
      69             : import org.eclipse.jgit.patch.FileHeader.PatchType;
      70             : import org.eclipse.jgit.revwalk.RevCommit;
      71             : import org.eclipse.jgit.revwalk.RevTree;
      72             : import org.eclipse.jgit.revwalk.RevWalk;
      73             : 
      74             : /**
      75             :  * Cache for the single file diff between two commits for a single file path. This cache adds extra
      76             :  * Gerrit logic such as identifying edits due to rebase.
      77             :  *
      78             :  * <p>If the {@link FileDiffCacheKey#oldCommit()} is equal to {@link ObjectId#zeroId()}, the git
      79             :  * diff will be evaluated against the empty tree.
      80             :  */
      81             : @Singleton
      82             : public class FileDiffCacheImpl implements FileDiffCache {
      83         152 :   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
      84             : 
      85             :   private static final String DIFF = "gerrit_file_diff";
      86             : 
      87             :   private final LoadingCache<FileDiffCacheKey, FileDiffOutput> cache;
      88             : 
      89             :   public static Module module() {
      90         152 :     return new CacheModule() {
      91             :       @Override
      92             :       protected void configure() {
      93         152 :         bind(FileDiffCache.class).to(FileDiffCacheImpl.class);
      94             : 
      95         152 :         factory(AllDiffsEvaluator.Factory.class);
      96             : 
      97         152 :         persist(DIFF, FileDiffCacheKey.class, FileDiffOutput.class)
      98         152 :             .maximumWeight(10 << 20)
      99         152 :             .weigher(FileDiffWeigher.class)
     100         152 :             .version(9)
     101         152 :             .keySerializer(FileDiffCacheKey.Serializer.INSTANCE)
     102         152 :             .valueSerializer(FileDiffOutput.Serializer.INSTANCE)
     103         152 :             .loader(FileDiffLoader.class);
     104         152 :       }
     105             :     };
     106             :   }
     107             : 
     108         104 :   private enum MagicPath {
     109         104 :     COMMIT,
     110         104 :     MERGE_LIST
     111             :   }
     112             : 
     113             :   @Inject
     114         152 :   public FileDiffCacheImpl(@Named(DIFF) LoadingCache<FileDiffCacheKey, FileDiffOutput> cache) {
     115         152 :     this.cache = cache;
     116         152 :   }
     117             : 
     118             :   @Override
     119             :   public FileDiffOutput get(FileDiffCacheKey key) throws DiffNotAvailableException {
     120             :     try {
     121           0 :       return cache.get(key);
     122           0 :     } catch (ExecutionException e) {
     123           0 :       throw new DiffNotAvailableException(e);
     124             :     }
     125             :   }
     126             : 
     127             :   @Override
     128             :   public ImmutableMap<FileDiffCacheKey, FileDiffOutput> getAll(Iterable<FileDiffCacheKey> keys)
     129             :       throws DiffNotAvailableException {
     130             :     try {
     131         104 :       ImmutableMap<FileDiffCacheKey, FileDiffOutput> result = cache.getAll(keys);
     132         104 :       if (result.size() != Iterables.size(keys)) {
     133           0 :         throw new DiffNotAvailableException(
     134           0 :             String.format(
     135             :                 "Failed to load the value for all %d keys. Returned "
     136             :                     + "map contains only %d values",
     137           0 :                 Iterables.size(keys), result.size()));
     138             :       }
     139         104 :       return result;
     140           0 :     } catch (ExecutionException e) {
     141           0 :       throw new DiffNotAvailableException(e);
     142             :     }
     143             :   }
     144             : 
     145             :   static class FileDiffLoader extends CacheLoader<FileDiffCacheKey, FileDiffOutput> {
     146             :     private final GitRepositoryManager repoManager;
     147             :     private final AllDiffsEvaluator.Factory allDiffsEvaluatorFactory;
     148             : 
     149             :     @Inject
     150             :     FileDiffLoader(
     151         152 :         AllDiffsEvaluator.Factory allDiffsEvaluatorFactory, GitRepositoryManager manager) {
     152         152 :       this.allDiffsEvaluatorFactory = allDiffsEvaluatorFactory;
     153         152 :       this.repoManager = manager;
     154         152 :     }
     155             : 
     156             :     @Override
     157             :     public FileDiffOutput load(FileDiffCacheKey key) throws IOException, DiffNotAvailableException {
     158           0 :       try (TraceTimer timer =
     159           0 :           TraceContext.newTimer(
     160             :               "Loading a single key from file diff cache",
     161           0 :               Metadata.builder().filePath(key.newFilePath()).build())) {
     162           0 :         return loadAll(ImmutableList.of(key)).get(key);
     163             :       }
     164             :     }
     165             : 
     166             :     @Override
     167             :     public Map<FileDiffCacheKey, FileDiffOutput> loadAll(Iterable<? extends FileDiffCacheKey> keys)
     168             :         throws DiffNotAvailableException {
     169         104 :       try (TraceTimer timer = TraceContext.newTimer("Loading multiple keys from file diff cache")) {
     170         104 :         ImmutableMap.Builder<FileDiffCacheKey, FileDiffOutput> result = ImmutableMap.builder();
     171             : 
     172         104 :         Map<Project.NameKey, List<FileDiffCacheKey>> keysByProject =
     173         104 :             Streams.stream(keys)
     174         104 :                 .distinct()
     175         104 :                 .collect(Collectors.groupingBy(FileDiffCacheKey::project));
     176             : 
     177         104 :         for (Project.NameKey project : keysByProject.keySet()) {
     178         104 :           List<FileDiffCacheKey> fileKeys = new ArrayList<>();
     179             : 
     180         104 :           try (Repository repo = repoManager.openRepository(project);
     181         104 :               ObjectReader reader = repo.newObjectReader();
     182         104 :               RevWalk rw = new RevWalk(reader)) {
     183             : 
     184         104 :             for (FileDiffCacheKey key : keysByProject.get(project)) {
     185         104 :               if (key.newFilePath().equals(Patch.COMMIT_MSG)) {
     186         104 :                 result.put(key, createMagicPathEntry(key, reader, rw, MagicPath.COMMIT));
     187          93 :               } else if (key.newFilePath().equals(Patch.MERGE_LIST)) {
     188          31 :                 result.put(key, createMagicPathEntry(key, reader, rw, MagicPath.MERGE_LIST));
     189             :               } else {
     190          93 :                 fileKeys.add(key);
     191             :               }
     192         104 :             }
     193         104 :             result.putAll(createFileEntries(reader, fileKeys, rw));
     194           0 :           } catch (IOException e) {
     195           0 :             logger.atWarning().log("Failed to open the repository %s: %s", project, e.getMessage());
     196         104 :           }
     197         104 :         }
     198         104 :         return result.build();
     199             :       }
     200             :     }
     201             : 
     202             :     private ComparisonType getComparisonType(
     203             :         RevWalk rw, ObjectReader reader, ObjectId oldCommitId, ObjectId newCommitId)
     204             :         throws IOException {
     205         104 :       if (oldCommitId.equals(ObjectId.zeroId())) {
     206          32 :         return ComparisonType.againstRoot();
     207             :       }
     208         100 :       RevCommit oldCommit = DiffUtil.getRevCommit(rw, oldCommitId);
     209         100 :       RevCommit newCommit = DiffUtil.getRevCommit(rw, newCommitId);
     210         100 :       for (int i = 0; i < newCommit.getParentCount(); i++) {
     211         100 :         if (newCommit.getParent(i).equals(oldCommit)) {
     212         100 :           return ComparisonType.againstParent(i + 1);
     213             :         }
     214             :       }
     215             :       // TODO(ghareeb): it's not trivial to distinguish if diff with old commit is against another
     216             :       // patchset or auto-merge. Looking at the commit message of old commit gives a strong
     217             :       // signal that we are diffing against auto-merge, though not 100% accurate (e.g. if old commit
     218             :       // has the auto-merge prefix in the commit message). A better resolution would be to move the
     219             :       // COMMIT_MSG and MERGE_LIST evaluations outside of the diff cache. For more details, see
     220             :       // discussion in
     221             :       // https://gerrit-review.googlesource.com/c/gerrit/+/280519/6..18/java/com/google/gerrit/server/patch/FileDiffCache.java#b540
     222          37 :       String oldCommitMsgTxt = new String(Text.forCommit(reader, oldCommit).getContent(), UTF_8);
     223          37 :       if (oldCommitMsgTxt.contains(AutoMerger.AUTO_MERGE_MSG_PREFIX)) {
     224          31 :         return ComparisonType.againstAutoMerge();
     225             :       }
     226          11 :       return ComparisonType.againstOtherPatchSet();
     227             :     }
     228             : 
     229             :     /**
     230             :      * Creates a {@link FileDiffOutput} entry for the "Commit message" or "Merge list" magic paths.
     231             :      */
     232             :     private FileDiffOutput createMagicPathEntry(
     233             :         FileDiffCacheKey key, ObjectReader reader, RevWalk rw, MagicPath magicPath) {
     234             :       try {
     235         104 :         RawTextComparator cmp = comparatorFor(key.whitespace());
     236         104 :         ComparisonType comparisonType =
     237         104 :             getComparisonType(rw, reader, key.oldCommit(), key.newCommit());
     238             :         RevCommit aCommit =
     239         104 :             key.oldCommit().equals(ObjectId.zeroId())
     240          32 :                 ? null
     241         104 :                 : DiffUtil.getRevCommit(rw, key.oldCommit());
     242         104 :         RevCommit bCommit = DiffUtil.getRevCommit(rw, key.newCommit());
     243         104 :         return magicPath == MagicPath.COMMIT
     244         104 :             ? createCommitEntry(reader, aCommit, bCommit, comparisonType, cmp, key.diffAlgorithm())
     245          31 :             : createMergeListEntry(
     246          31 :                 reader, aCommit, bCommit, comparisonType, cmp, key.diffAlgorithm());
     247           0 :       } catch (IOException e) {
     248           0 :         logger.atWarning().log("Failed to compute commit entry for key %s", key);
     249             :       }
     250           0 :       return FileDiffOutput.empty(key.newFilePath(), key.oldCommit(), key.newCommit());
     251             :     }
     252             : 
     253             :     private static RawTextComparator comparatorFor(Whitespace ws) {
     254         104 :       switch (ws) {
     255             :         case IGNORE_ALL:
     256           0 :           return RawTextComparator.WS_IGNORE_ALL;
     257             : 
     258             :         case IGNORE_TRAILING:
     259           0 :           return RawTextComparator.WS_IGNORE_TRAILING;
     260             : 
     261             :         case IGNORE_LEADING_AND_TRAILING:
     262           3 :           return RawTextComparator.WS_IGNORE_CHANGE;
     263             : 
     264             :         case IGNORE_NONE:
     265             :         default:
     266         104 :           return RawTextComparator.DEFAULT;
     267             :       }
     268             :     }
     269             : 
     270             :     /**
     271             :      * Creates a commit entry. {@code oldCommit} is null if the comparison is against a root commit.
     272             :      */
     273             :     private FileDiffOutput createCommitEntry(
     274             :         ObjectReader reader,
     275             :         @Nullable RevCommit oldCommit,
     276             :         RevCommit newCommit,
     277             :         ComparisonType comparisonType,
     278             :         RawTextComparator rawTextComparator,
     279             :         GitFileDiffCacheImpl.DiffAlgorithm diffAlgorithm)
     280             :         throws IOException {
     281             :       Text aText =
     282         104 :           oldCommit == null || comparisonType.isAgainstParentOrAutoMerge()
     283         104 :               ? Text.EMPTY
     284         104 :               : Text.forCommit(reader, oldCommit);
     285         104 :       Text bText = Text.forCommit(reader, newCommit);
     286         104 :       return createMagicFileDiffOutput(
     287             :           oldCommit,
     288             :           newCommit,
     289             :           comparisonType,
     290             :           rawTextComparator,
     291             :           aText,
     292             :           bText,
     293             :           Patch.COMMIT_MSG,
     294             :           diffAlgorithm);
     295             :     }
     296             : 
     297             :     /**
     298             :      * Creates a merge list entry. {@code oldCommit} is null if the comparison is against a root
     299             :      * commit.
     300             :      */
     301             :     private FileDiffOutput createMergeListEntry(
     302             :         ObjectReader reader,
     303             :         @Nullable RevCommit oldCommit,
     304             :         RevCommit newCommit,
     305             :         ComparisonType comparisonType,
     306             :         RawTextComparator rawTextComparator,
     307             :         GitFileDiffCacheImpl.DiffAlgorithm diffAlgorithm)
     308             :         throws IOException {
     309             :       Text aText =
     310          31 :           oldCommit == null || comparisonType.isAgainstParentOrAutoMerge()
     311          31 :               ? Text.EMPTY
     312          31 :               : Text.forMergeList(comparisonType, reader, oldCommit);
     313          31 :       Text bText = Text.forMergeList(comparisonType, reader, newCommit);
     314          31 :       return createMagicFileDiffOutput(
     315             :           oldCommit,
     316             :           newCommit,
     317             :           comparisonType,
     318             :           rawTextComparator,
     319             :           aText,
     320             :           bText,
     321             :           Patch.MERGE_LIST,
     322             :           diffAlgorithm);
     323             :     }
     324             : 
     325             :     private static FileDiffOutput createMagicFileDiffOutput(
     326             :         @Nullable ObjectId oldCommit,
     327             :         ObjectId newCommit,
     328             :         ComparisonType comparisonType,
     329             :         RawTextComparator rawTextComparator,
     330             :         Text aText,
     331             :         Text bText,
     332             :         String fileName,
     333             :         GitFileDiffCacheImpl.DiffAlgorithm diffAlgorithm) {
     334         104 :       byte[] rawHdr = getRawHeader(!comparisonType.isAgainstParentOrAutoMerge(), fileName);
     335         104 :       byte[] aContent = aText.getContent();
     336         104 :       byte[] bContent = bText.getContent();
     337         104 :       long size = bContent.length;
     338         104 :       long sizeDelta = size - aContent.length;
     339         104 :       RawText aRawText = new RawText(aContent);
     340         104 :       RawText bRawText = new RawText(bContent);
     341         104 :       EditList edits =
     342         104 :           DiffAlgorithmFactory.create(diffAlgorithm).diff(rawTextComparator, aRawText, bRawText);
     343         104 :       FileHeader fileHeader = new FileHeader(rawHdr, edits, PatchType.UNIFIED);
     344         104 :       Patch.ChangeType changeType = FileHeaderUtil.getChangeType(fileHeader);
     345         104 :       return FileDiffOutput.builder()
     346         104 :           .oldCommitId(oldCommit == null ? ObjectId.zeroId() : oldCommit)
     347         104 :           .newCommitId(newCommit)
     348         104 :           .comparisonType(comparisonType)
     349         104 :           .oldPath(FileHeaderUtil.getOldPath(fileHeader))
     350         104 :           .newPath(FileHeaderUtil.getNewPath(fileHeader))
     351         104 :           .changeType(changeType)
     352         104 :           .patchType(Optional.of(FileHeaderUtil.getPatchType(fileHeader)))
     353         104 :           .headerLines(FileHeaderUtil.getHeaderLines(fileHeader))
     354         104 :           .edits(
     355         104 :               asTaggedEdits(
     356         104 :                   edits.stream().map(Edit::fromJGitEdit).collect(Collectors.toList()),
     357         104 :                   ImmutableList.of()))
     358         104 :           .size(size)
     359         104 :           .sizeDelta(sizeDelta)
     360         104 :           .build();
     361             :     }
     362             : 
     363             :     private static byte[] getRawHeader(boolean hasA, String fileName) {
     364         104 :       StringBuilder hdr = new StringBuilder();
     365         104 :       hdr.append("diff --git");
     366         104 :       if (hasA) {
     367          10 :         hdr.append(" a/").append(fileName);
     368             :       } else {
     369         104 :         hdr.append(" ").append(FileHeader.DEV_NULL);
     370             :       }
     371         104 :       hdr.append(" b/").append(fileName);
     372         104 :       hdr.append("\n");
     373             : 
     374         104 :       if (hasA) {
     375          10 :         hdr.append("--- a/").append(fileName).append("\n");
     376             :       } else {
     377         104 :         hdr.append("--- ").append(FileHeader.DEV_NULL).append("\n");
     378             :       }
     379         104 :       hdr.append("+++ b/").append(fileName).append("\n");
     380         104 :       return hdr.toString().getBytes(UTF_8);
     381             :     }
     382             : 
     383             :     private Map<FileDiffCacheKey, FileDiffOutput> createFileEntries(
     384             :         ObjectReader reader, List<FileDiffCacheKey> keys, RevWalk rw)
     385             :         throws DiffNotAvailableException, IOException {
     386         104 :       Map<AugmentedFileDiffCacheKey, AllFileGitDiffs> allFileDiffs =
     387         104 :           allDiffsEvaluatorFactory.create(rw).execute(wrapKeys(keys, rw));
     388             : 
     389         104 :       Map<FileDiffCacheKey, FileDiffOutput> result = new HashMap<>();
     390             : 
     391         104 :       for (AugmentedFileDiffCacheKey augmentedKey : allFileDiffs.keySet()) {
     392          93 :         AllFileGitDiffs allDiffs = allFileDiffs.get(augmentedKey);
     393          93 :         GitFileDiff mainGitDiff = allDiffs.mainDiff().gitDiff();
     394             : 
     395          93 :         if (mainGitDiff.isNegative()) {
     396             :           // If the result of the git diff computation was negative, i.e. due to timeout, cache a
     397             :           // negative result.
     398           0 :           result.put(
     399           0 :               augmentedKey.key(),
     400           0 :               FileDiffOutput.createNegative(
     401           0 :                   mainGitDiff.newPath().orElse(""),
     402           0 :                   augmentedKey.key().oldCommit(),
     403           0 :                   augmentedKey.key().newCommit()));
     404           0 :           continue;
     405             :         }
     406             : 
     407          93 :         FileEdits rebaseFileEdits = FileEdits.empty();
     408          93 :         if (!augmentedKey.ignoreRebase()) {
     409           4 :           rebaseFileEdits = computeRebaseEdits(allDiffs);
     410             :         }
     411          93 :         List<Edit> rebaseEdits = rebaseFileEdits.edits();
     412             : 
     413          93 :         ObjectId oldTreeId = allDiffs.mainDiff().gitKey().oldTree();
     414             : 
     415          93 :         RevTree aTree = oldTreeId.equals(ObjectId.zeroId()) ? null : rw.parseTree(oldTreeId);
     416          93 :         RevTree bTree = rw.parseTree(allDiffs.mainDiff().gitKey().newTree());
     417             : 
     418             :         Long oldSize =
     419          93 :             aTree != null && mainGitDiff.oldMode().isPresent() && mainGitDiff.oldPath().isPresent()
     420             :                 ? new FileSizeEvaluator(reader, aTree)
     421          54 :                     .compute(
     422          54 :                         mainGitDiff.oldId(),
     423          54 :                         mainGitDiff.oldMode().get(),
     424          54 :                         mainGitDiff.oldPath().get())
     425          90 :                 : 0;
     426             :         Long newSize =
     427          93 :             mainGitDiff.newMode().isPresent() && mainGitDiff.newPath().isPresent()
     428             :                 ? new FileSizeEvaluator(reader, bTree)
     429          93 :                     .compute(
     430          93 :                         mainGitDiff.newId(),
     431          93 :                         mainGitDiff.newMode().get(),
     432          93 :                         mainGitDiff.newPath().get())
     433          23 :                 : 0;
     434             : 
     435          93 :         ObjectId oldCommit = augmentedKey.key().oldCommit();
     436          93 :         ObjectId newCommit = augmentedKey.key().newCommit();
     437             :         FileDiffOutput fileDiff =
     438          93 :             FileDiffOutput.builder()
     439          93 :                 .oldCommitId(oldCommit)
     440          93 :                 .newCommitId(newCommit)
     441          93 :                 .comparisonType(getComparisonType(rw, reader, oldCommit, newCommit))
     442          93 :                 .changeType(mainGitDiff.changeType())
     443          93 :                 .patchType(mainGitDiff.patchType())
     444          93 :                 .oldPath(mainGitDiff.oldPath())
     445          93 :                 .newPath(mainGitDiff.newPath())
     446          93 :                 .oldMode(mainGitDiff.oldMode())
     447          93 :                 .newMode(mainGitDiff.newMode())
     448          93 :                 .headerLines(FileHeaderUtil.getHeaderLines(mainGitDiff.fileHeader()))
     449          93 :                 .edits(asTaggedEdits(mainGitDiff.edits(), rebaseEdits))
     450          93 :                 .size(newSize)
     451          93 :                 .sizeDelta(newSize - oldSize)
     452          93 :                 .build();
     453             : 
     454          93 :         result.put(augmentedKey.key(), fileDiff);
     455          93 :       }
     456             : 
     457         104 :       return result;
     458             :     }
     459             : 
     460             :     /**
     461             :      * Convert the list of input keys {@link FileDiffCacheKey} to a list of {@link
     462             :      * AugmentedFileDiffCacheKey} that also include the old and new parent commit IDs, and a boolean
     463             :      * that indicates whether we should include the rebase edits for each key.
     464             :      *
     465             :      * <p>The output list is expected to have the same size of the input list, i.e. we map all keys.
     466             :      */
     467             :     private List<AugmentedFileDiffCacheKey> wrapKeys(List<FileDiffCacheKey> keys, RevWalk rw) {
     468         104 :       List<AugmentedFileDiffCacheKey> result = new ArrayList<>();
     469         104 :       for (FileDiffCacheKey key : keys) {
     470          93 :         if (key.oldCommit().equals(ObjectId.zeroId())) {
     471          24 :           result.add(AugmentedFileDiffCacheKey.builder().key(key).ignoreRebase(true).build());
     472          24 :           continue;
     473             :         }
     474             :         try {
     475          91 :           RevCommit oldRevCommit = DiffUtil.getRevCommit(rw, key.oldCommit());
     476          91 :           RevCommit newRevCommit = DiffUtil.getRevCommit(rw, key.newCommit());
     477          91 :           if (!DiffUtil.areRelated(oldRevCommit, newRevCommit)) {
     478           4 :             result.add(
     479           4 :                 AugmentedFileDiffCacheKey.builder()
     480           4 :                     .key(key)
     481           4 :                     .oldParentId(Optional.of(oldRevCommit.getParent(0).getId()))
     482           4 :                     .newParentId(Optional.of(newRevCommit.getParent(0).getId()))
     483           4 :                     .ignoreRebase(false)
     484           4 :                     .build());
     485             :           } else {
     486          91 :             result.add(AugmentedFileDiffCacheKey.builder().key(key).ignoreRebase(true).build());
     487             :           }
     488           0 :         } catch (IOException e) {
     489           0 :           logger.atWarning().log(
     490             :               "Failed to evaluate commits relation for key "
     491             :                   + key
     492             :                   + ". Skipping this key: "
     493           0 :                   + e.getMessage(),
     494             :               e);
     495           0 :           result.add(AugmentedFileDiffCacheKey.builder().key(key).ignoreRebase(true).build());
     496          91 :         }
     497          91 :       }
     498         104 :       return result;
     499             :     }
     500             : 
     501             :     private static ImmutableList<TaggedEdit> asTaggedEdits(
     502             :         List<Edit> normalEdits, List<Edit> rebaseEdits) {
     503         104 :       Set<Edit> rebaseEditsSet = new HashSet<>(rebaseEdits);
     504         104 :       ImmutableList.Builder<TaggedEdit> result =
     505         104 :           ImmutableList.builderWithExpectedSize(normalEdits.size());
     506         104 :       for (Edit e : normalEdits) {
     507         104 :         result.add(TaggedEdit.create(e, rebaseEditsSet.contains(e)));
     508         104 :       }
     509         104 :       return result.build();
     510             :     }
     511             : 
     512             :     /**
     513             :      * Computes the subset of edits that are due to rebase between 2 commits.
     514             :      *
     515             :      * <p>The input parameter {@link AllFileGitDiffs#mainDiff} contains all the edits in
     516             :      * consideration. Of those, we identify the edits due to rebase as a function of:
     517             :      *
     518             :      * <ol>
     519             :      *   <li>The edits between the old commit and its parent {@link
     520             :      *       AllFileGitDiffs#oldVsParentDiff}.
     521             :      *   <li>The edits between the new commit and its parent {@link
     522             :      *       AllFileGitDiffs#newVsParentDiff}.
     523             :      *   <li>The edits between the parents of the old commit and new commits {@link
     524             :      *       AllFileGitDiffs#parentVsParentDiff}.
     525             :      * </ol>
     526             :      *
     527             :      * @param diffs an entity containing 4 sets of edits: those between the old and new commit,
     528             :      *     between the old and new commits vs. their parents, and between the old and new parents.
     529             :      * @return the list of edits that are due to rebase.
     530             :      */
     531             :     private FileEdits computeRebaseEdits(AllFileGitDiffs diffs) {
     532           4 :       if (!diffs.parentVsParentDiff().isPresent()) {
     533           3 :         return FileEdits.empty();
     534             :       }
     535             : 
     536           3 :       GitFileDiff parentVsParentDiff = diffs.parentVsParentDiff().get().gitDiff();
     537             : 
     538           3 :       EditTransformer editTransformer =
     539             :           new EditTransformer(
     540           3 :               ImmutableList.of(
     541           3 :                   FileEdits.create(
     542           3 :                       parentVsParentDiff.edits().stream().collect(toImmutableList()),
     543           3 :                       parentVsParentDiff.oldPath(),
     544           3 :                       parentVsParentDiff.newPath())));
     545             : 
     546           3 :       if (diffs.oldVsParentDiff().isPresent()) {
     547           3 :         GitFileDiff oldVsParDiff = diffs.oldVsParentDiff().get().gitDiff();
     548           3 :         editTransformer.transformReferencesOfSideA(
     549           3 :             ImmutableList.of(
     550           3 :                 FileEdits.create(
     551           3 :                     oldVsParDiff.edits().stream().collect(toImmutableList()),
     552           3 :                     oldVsParDiff.oldPath(),
     553           3 :                     oldVsParDiff.newPath())));
     554             :       }
     555             : 
     556           3 :       if (diffs.newVsParentDiff().isPresent()) {
     557           3 :         GitFileDiff newVsParDiff = diffs.newVsParentDiff().get().gitDiff();
     558           3 :         editTransformer.transformReferencesOfSideB(
     559           3 :             ImmutableList.of(
     560           3 :                 FileEdits.create(
     561           3 :                     newVsParDiff.edits().stream().collect(toImmutableList()),
     562           3 :                     newVsParDiff.oldPath(),
     563           3 :                     newVsParDiff.newPath())));
     564             :       }
     565             : 
     566           3 :       Multimap<String, ContextAwareEdit> editsPerFilePath = editTransformer.getEditsPerFilePath();
     567             : 
     568           3 :       if (editsPerFilePath.isEmpty()) {
     569           2 :         return FileEdits.empty();
     570             :       }
     571             : 
     572             :       // editsPerFilePath is expected to have a single item representing the file
     573           3 :       String filePath = editsPerFilePath.keys().iterator().next();
     574           3 :       Collection<ContextAwareEdit> edits = editsPerFilePath.get(filePath);
     575           3 :       return FileEdits.create(
     576           3 :           edits.stream()
     577           3 :               .map(ContextAwareEdit::toEdit)
     578           3 :               .filter(Optional::isPresent)
     579           3 :               .map(Optional::get)
     580           3 :               .map(Edit::fromJGitEdit)
     581           3 :               .collect(toImmutableList()),
     582           3 :           edits.iterator().next().getOldFilePath(),
     583           3 :           edits.iterator().next().getNewFilePath());
     584             :     }
     585             :   }
     586             : }

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