All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gerrit.server.patch.filediff.AllDiffsEvaluator Maven / Gradle / Ivy

There is a newer version: 3.10.0-rc7
Show newest version
// Copyright (C) 2020 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.gerrit.server.patch.filediff;

import com.google.common.collect.ImmutableMap;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.server.patch.DiffNotAvailableException;
import com.google.gerrit.server.patch.DiffUtil;
import com.google.gerrit.server.patch.gitfilediff.GitFileDiff;
import com.google.gerrit.server.patch.gitfilediff.GitFileDiffCache;
import com.google.gerrit.server.patch.gitfilediff.GitFileDiffCacheKey;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.revwalk.RevWalk;

/**
 * A helper class that computes the four {@link GitFileDiff}s for a list of {@link
 * FileDiffCacheKey}s:
 *
 * 
    *
  • old commit vs. new commit *
  • old parent vs. old commit *
  • new parent vs. new commit *
  • old parent vs. new parent *
* * The four {@link GitFileDiff} are stored in the entity class {@link AllFileGitDiffs}. We use these * diffs to identify the edits due to rebase using the {@link EditTransformer} class. */ class AllDiffsEvaluator { private static final FluentLogger logger = FluentLogger.forEnclosingClass(); private final RevWalk rw; private final GitFileDiffCache gitCache; interface Factory { AllDiffsEvaluator create(RevWalk rw); } @Inject private AllDiffsEvaluator(GitFileDiffCache gitCache, @Assisted RevWalk rw) { this.gitCache = gitCache; this.rw = rw; } Map execute( List augmentedKeys) throws DiffNotAvailableException { ImmutableMap.Builder keyToAllDiffs = ImmutableMap.builderWithExpectedSize(augmentedKeys.size()); List keysWithRebaseEdits = augmentedKeys.stream().filter(k -> !k.ignoreRebase()).collect(Collectors.toList()); // TODO(ghareeb): as an enhancement, you can batch these calls as follows. // First batch: "old commit vs. new commit" and "new parent vs. new commit" // Second batch: "old parent vs. old commit" and "old parent vs. new parent" Map mainDiffs = computeGitFileDiffs( createGitKeys( augmentedKeys, k -> k.key().oldCommit(), k -> k.key().newCommit(), k -> k.key().newFilePath())); Map oldVsParentDiffs = computeGitFileDiffs( createGitKeys( keysWithRebaseEdits, k -> k.oldParentId().get(), // oldParent is set for keysWithRebaseEdits k -> k.key().oldCommit(), k -> mainDiffs.get(k.key()).gitDiff().oldPath().orElse(null))); Map newVsParentDiffs = computeGitFileDiffs( createGitKeys( keysWithRebaseEdits, k -> k.newParentId().get(), // newParent is set for keysWithRebaseEdits k -> k.key().newCommit(), k -> k.key().newFilePath())); Map parentsDiffs = computeGitFileDiffs( createGitKeys( keysWithRebaseEdits, k -> k.oldParentId().get(), k -> k.newParentId().get(), k -> { GitFileDiff newVsParDiff = newVsParentDiffs.get(k.key()).gitDiff(); // TODO(ghareeb): Follow up on replacing key.newFilePath as a fallback. // If the file was added between newParent and newCommit, we actually wouldn't // need to have to determine the oldParent vs. newParent diff as nothing in // that file could be an edit due to rebase anymore. Only if the returned diff // is empty, the oldParent vs. newParent diff becomes relevant again (e.g. to // identify a file deletion which was due to rebase. Check if the structure // can be improved to make this clearer. Can we maybe even skip the diff in // the first situation described? return newVsParDiff.oldPath().orElse(k.key().newFilePath()); })); for (AugmentedFileDiffCacheKey augmentedKey : augmentedKeys) { FileDiffCacheKey key = augmentedKey.key(); AllFileGitDiffs.Builder builder = AllFileGitDiffs.builder().augmentedKey(augmentedKey).mainDiff(mainDiffs.get(key)); if (augmentedKey.ignoreRebase()) { keyToAllDiffs.put(augmentedKey, builder.build()); continue; } if (oldVsParentDiffs.containsKey(key) && !oldVsParentDiffs.get(key).gitDiff().isEmpty()) { builder.oldVsParentDiff(Optional.of(oldVsParentDiffs.get(key))); } if (newVsParentDiffs.containsKey(key) && !newVsParentDiffs.get(key).gitDiff().isEmpty()) { builder.newVsParentDiff(Optional.of(newVsParentDiffs.get(key))); } if (parentsDiffs.containsKey(key) && !parentsDiffs.get(key).gitDiff().isEmpty()) { builder.parentVsParentDiff(Optional.of(parentsDiffs.get(key))); } keyToAllDiffs.put(augmentedKey, builder.build()); } return keyToAllDiffs.build(); } /** * Computes the git diff for the git keys of the input map {@code keys} parameter. The computation * uses the underlying {@link GitFileDiffCache}. */ private Map computeGitFileDiffs( Map keys) throws DiffNotAvailableException { ImmutableMap.Builder result = ImmutableMap.builderWithExpectedSize(keys.size()); ImmutableMap gitDiffs = gitCache.getAll(keys.values()); for (FileDiffCacheKey key : keys.keySet()) { GitFileDiffCacheKey gitKey = keys.get(key); GitFileDiff gitFileDiff = gitDiffs.get(gitKey); result.put(key, GitDiffEntity.create(gitKey, gitFileDiff)); } return result.build(); } /** * Convert a list of {@link AugmentedFileDiffCacheKey} to their corresponding {@link * GitFileDiffCacheKey} which can be used to call the underlying {@link GitFileDiffCache}. * * @param keys a list of input {@link AugmentedFileDiffCacheKey}s. * @param aCommitFn a function to compute the aCommit that will be used in the git diff. * @param bCommitFn a function to compute the bCommit that will be used in the git diff. * @param newPathFn a function to compute the new path of the git key. * @return a map of the input {@link FileDiffCacheKey} to the {@link GitFileDiffCacheKey}. */ private Map createGitKeys( List keys, Function aCommitFn, Function bCommitFn, Function newPathFn) { Map result = new HashMap<>(); for (AugmentedFileDiffCacheKey key : keys) { try { String path = newPathFn.apply(key); if (path != null) { result.put( key.key(), createGitKey(key.key(), aCommitFn.apply(key), bCommitFn.apply(key), path, rw)); } } catch (IOException e) { // TODO(ghareeb): This implies that the output keys may not have the same size as the input. // Check the caller's code path about the correctness of the computation in this case. If // errors are rare, it may be better to throw an exception and fail the whole computation. logger.atWarning().log("Failed to compute the git key for key %s: %s", key, e.getMessage()); } } return result; } /** Returns the {@link GitFileDiffCacheKey} for the {@code key} input parameter. */ private GitFileDiffCacheKey createGitKey( FileDiffCacheKey key, ObjectId aCommit, ObjectId bCommit, String pathNew, RevWalk rw) throws IOException { ObjectId oldTreeId = aCommit.equals(ObjectId.zeroId()) ? ObjectId.zeroId() : DiffUtil.getTreeId(rw, aCommit); ObjectId newTreeId = DiffUtil.getTreeId(rw, bCommit); return GitFileDiffCacheKey.builder() .project(key.project()) .oldTree(oldTreeId) .newTree(newTreeId) .newFilePath(pathNew == null ? key.newFilePath() : pathNew) .renameScore(key.renameScore()) .diffAlgorithm(key.diffAlgorithm()) .whitespace(key.whitespace()) .useTimeout(key.useTimeout()) .build(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy