LCOV - code coverage report
Current view: top level - server/patch/diff - ModifiedFilesCacheImpl.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 63 69 91.3 %
Date: 2022-11-19 15:00:39 Functions: 15 15 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.diff;
      16             : 
      17             : import static com.google.common.collect.ImmutableList.toImmutableList;
      18             : 
      19             : import com.google.common.cache.CacheLoader;
      20             : import com.google.common.cache.LoadingCache;
      21             : import com.google.common.collect.ImmutableList;
      22             : import com.google.common.collect.ImmutableSet;
      23             : import com.google.common.collect.Sets;
      24             : import com.google.common.collect.Streams;
      25             : import com.google.common.flogger.FluentLogger;
      26             : import com.google.gerrit.server.cache.CacheModule;
      27             : import com.google.gerrit.server.git.GitRepositoryManager;
      28             : import com.google.gerrit.server.patch.DiffNotAvailableException;
      29             : import com.google.gerrit.server.patch.DiffUtil;
      30             : import com.google.gerrit.server.patch.gitdiff.GitModifiedFilesCache;
      31             : import com.google.gerrit.server.patch.gitdiff.GitModifiedFilesCacheImpl;
      32             : import com.google.gerrit.server.patch.gitdiff.GitModifiedFilesCacheKey;
      33             : import com.google.gerrit.server.patch.gitdiff.ModifiedFile;
      34             : import com.google.inject.Inject;
      35             : import com.google.inject.Module;
      36             : import com.google.inject.Singleton;
      37             : import com.google.inject.TypeLiteral;
      38             : import com.google.inject.name.Named;
      39             : import java.io.IOException;
      40             : import java.util.List;
      41             : import java.util.Set;
      42             : import java.util.stream.Stream;
      43             : import org.eclipse.jgit.lib.ObjectId;
      44             : import org.eclipse.jgit.lib.Repository;
      45             : import org.eclipse.jgit.revwalk.RevCommit;
      46             : import org.eclipse.jgit.revwalk.RevWalk;
      47             : 
      48             : /**
      49             :  * A cache for the list of Git modified files between 2 commits (patchsets) with extra Gerrit logic.
      50             :  *
      51             :  * <p>The loader of this cache wraps a {@link GitModifiedFilesCache} to retrieve the git modified
      52             :  * files.
      53             :  *
      54             :  * <p>If the {@link ModifiedFilesCacheKey#aCommit()} is equal to {@link ObjectId#zeroId()}, the diff
      55             :  * will be evaluated against the empty tree, and the result will be exactly the same as the caller
      56             :  * can get from {@link GitModifiedFilesCache#get(GitModifiedFilesCacheKey)}
      57             :  */
      58             : @Singleton
      59             : public class ModifiedFilesCacheImpl implements ModifiedFilesCache {
      60         152 :   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
      61             : 
      62             :   private static final String MODIFIED_FILES = "modified_files";
      63             : 
      64             :   private final LoadingCache<ModifiedFilesCacheKey, ImmutableList<ModifiedFile>> cache;
      65             : 
      66             :   public static Module module() {
      67         152 :     return new CacheModule() {
      68             :       @Override
      69             :       protected void configure() {
      70         152 :         bind(ModifiedFilesCache.class).to(ModifiedFilesCacheImpl.class);
      71             : 
      72             :         // The documentation has some defaults and recommendations for setting the cache
      73             :         // attributes:
      74             :         // https://gerrit-review.googlesource.com/Documentation/config-gerrit.html#cache.
      75             :         // The cache is using the default disk limit as per section cache.<name>.diskLimit
      76             :         // in the cache documentation link.
      77         152 :         persist(
      78             :                 ModifiedFilesCacheImpl.MODIFIED_FILES,
      79             :                 ModifiedFilesCacheKey.class,
      80         152 :                 new TypeLiteral<ImmutableList<ModifiedFile>>() {})
      81         152 :             .keySerializer(ModifiedFilesCacheKey.Serializer.INSTANCE)
      82         152 :             .valueSerializer(GitModifiedFilesCacheImpl.ValueSerializer.INSTANCE)
      83         152 :             .maximumWeight(10 << 20)
      84         152 :             .weigher(ModifiedFilesWeigher.class)
      85         152 :             .version(4)
      86         152 :             .loader(ModifiedFilesLoader.class);
      87         152 :       }
      88             :     };
      89             :   }
      90             : 
      91             :   @Inject
      92             :   public ModifiedFilesCacheImpl(
      93             :       @Named(ModifiedFilesCacheImpl.MODIFIED_FILES)
      94         152 :           LoadingCache<ModifiedFilesCacheKey, ImmutableList<ModifiedFile>> cache) {
      95         152 :     this.cache = cache;
      96         152 :   }
      97             : 
      98             :   @Override
      99             :   public ImmutableList<ModifiedFile> get(ModifiedFilesCacheKey key)
     100             :       throws DiffNotAvailableException {
     101             :     try {
     102         104 :       return cache.get(key);
     103           0 :     } catch (Exception e) {
     104           0 :       throw new DiffNotAvailableException(e);
     105             :     }
     106             :   }
     107             : 
     108             :   static class ModifiedFilesLoader
     109             :       extends CacheLoader<ModifiedFilesCacheKey, ImmutableList<ModifiedFile>> {
     110             :     private final GitModifiedFilesCache gitCache;
     111             :     private final GitRepositoryManager repoManager;
     112             : 
     113             :     @Inject
     114         152 :     ModifiedFilesLoader(GitModifiedFilesCache gitCache, GitRepositoryManager repoManager) {
     115         152 :       this.gitCache = gitCache;
     116         152 :       this.repoManager = repoManager;
     117         152 :     }
     118             : 
     119             :     @Override
     120             :     public ImmutableList<ModifiedFile> load(ModifiedFilesCacheKey key)
     121             :         throws IOException, DiffNotAvailableException {
     122         104 :       try (Repository repo = repoManager.openRepository(key.project());
     123         104 :           RevWalk rw = new RevWalk(repo.newObjectReader())) {
     124         104 :         return loadModifiedFiles(key, rw);
     125             :       }
     126             :     }
     127             : 
     128             :     private ImmutableList<ModifiedFile> loadModifiedFiles(ModifiedFilesCacheKey key, RevWalk rw)
     129             :         throws IOException, DiffNotAvailableException {
     130             :       ObjectId aTree =
     131         104 :           key.aCommit().equals(ObjectId.zeroId())
     132          32 :               ? key.aCommit()
     133         104 :               : DiffUtil.getTreeId(rw, key.aCommit());
     134         104 :       ObjectId bTree = DiffUtil.getTreeId(rw, key.bCommit());
     135             :       GitModifiedFilesCacheKey gitKey =
     136         104 :           GitModifiedFilesCacheKey.builder()
     137         104 :               .project(key.project())
     138         104 :               .aTree(aTree)
     139         104 :               .bTree(bTree)
     140         104 :               .renameScore(key.renameScore())
     141         104 :               .build();
     142         104 :       ImmutableList<ModifiedFile> modifiedFiles =
     143         104 :           DiffUtil.mergeRewrittenModifiedFiles(gitCache.get(gitKey));
     144         104 :       if (key.aCommit().equals(ObjectId.zeroId())) {
     145          32 :         return modifiedFiles;
     146             :       }
     147         100 :       RevCommit revCommitA = DiffUtil.getRevCommit(rw, key.aCommit());
     148         100 :       RevCommit revCommitB = DiffUtil.getRevCommit(rw, key.bCommit());
     149         100 :       if (DiffUtil.areRelated(revCommitA, revCommitB)) {
     150         100 :         return modifiedFiles;
     151             :       }
     152           4 :       Set<String> touchedFiles =
     153           4 :           getTouchedFilesWithParents(
     154           4 :               key, revCommitA.getParent(0).getId(), revCommitB.getParent(0).getId(), rw);
     155           4 :       return modifiedFiles.stream()
     156           4 :           .filter(f -> isTouched(touchedFiles, f))
     157           4 :           .collect(toImmutableList());
     158             :     }
     159             : 
     160             :     /**
     161             :      * Returns the paths of files that were modified between the old and new commits versus their
     162             :      * parents (i.e. old commit vs. its parent, and new commit vs. its parent).
     163             :      *
     164             :      * @param key the {@link ModifiedFilesCacheKey} representing the commits we are diffing
     165             :      * @param rw a {@link RevWalk} for the repository
     166             :      * @return The list of modified files between the old/new commits and their parents
     167             :      */
     168             :     private Set<String> getTouchedFilesWithParents(
     169             :         ModifiedFilesCacheKey key, ObjectId parentOfA, ObjectId parentOfB, RevWalk rw)
     170             :         throws IOException {
     171             :       try {
     172             :         // TODO(ghareeb): as an enhancement: the 3 calls of the underlying git cache can be combined
     173           4 :         GitModifiedFilesCacheKey oldVsBaseKey =
     174           4 :             GitModifiedFilesCacheKey.create(
     175           4 :                 key.project(), parentOfA, key.aCommit(), key.renameScore(), rw);
     176           4 :         List<ModifiedFile> oldVsBase = gitCache.get(oldVsBaseKey);
     177             : 
     178           4 :         GitModifiedFilesCacheKey newVsBaseKey =
     179           4 :             GitModifiedFilesCacheKey.create(
     180           4 :                 key.project(), parentOfB, key.bCommit(), key.renameScore(), rw);
     181           4 :         List<ModifiedFile> newVsBase = gitCache.get(newVsBaseKey);
     182             : 
     183           4 :         return Sets.union(getOldAndNewPaths(oldVsBase), getOldAndNewPaths(newVsBase));
     184           0 :       } catch (DiffNotAvailableException e) {
     185           0 :         logger.atWarning().log(
     186             :             "Failed to retrieve the touched files' commits (%s, %s) and parents (%s, %s): %s",
     187           0 :             key.aCommit(), key.bCommit(), parentOfA, parentOfB, e.getMessage());
     188           0 :         return ImmutableSet.of();
     189             :       }
     190             :     }
     191             : 
     192             :     private ImmutableSet<String> getOldAndNewPaths(List<ModifiedFile> files) {
     193           4 :       return files.stream()
     194           4 :           .flatMap(
     195           4 :               file -> Stream.concat(Streams.stream(file.oldPath()), Streams.stream(file.newPath())))
     196           4 :           .collect(ImmutableSet.toImmutableSet());
     197             :     }
     198             : 
     199             :     private static boolean isTouched(Set<String> touchedFilePaths, ModifiedFile modifiedFile) {
     200           4 :       String oldFilePath = modifiedFile.oldPath().orElse(null);
     201           4 :       String newFilePath = modifiedFile.newPath().orElse(null);
     202             :       // One of the above file paths could be /dev/null but we need not explicitly check for this
     203             :       // value as the set of file paths shouldn't contain it.
     204           4 :       return touchedFilePaths.contains(oldFilePath) || touchedFilePaths.contains(newFilePath);
     205             :     }
     206             :   }
     207             : }

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