blob: 69a1e9d8656576433d131a1e92b2eb005104adbb [file] [log] [blame]
// Copyright (C) 2024 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.gitdiff;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.toImmutableList;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Patch;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffEntry.ChangeType;
import org.eclipse.jgit.diff.DiffFormatter;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.util.io.DisabledOutputStream;
/**
* /** Class to load the files that have been modified between two Git trees.
*
* <p>Rename detection is off unless {@link #withRenameDetection(int)} is called.
*
* <p>The commits and the commit trees are looked up via the {@link RevWalk} instance that is
* provided to the {@link #load(Config, ObjectReader, ObjectId, ObjectId)} method.
*/
public class GitModifiedFilesLoader {
private static final ImmutableMap<ChangeType, Patch.ChangeType> changeTypeMap =
ImmutableMap.of(
DiffEntry.ChangeType.ADD,
Patch.ChangeType.ADDED,
DiffEntry.ChangeType.MODIFY,
Patch.ChangeType.MODIFIED,
DiffEntry.ChangeType.DELETE,
Patch.ChangeType.DELETED,
DiffEntry.ChangeType.RENAME,
Patch.ChangeType.RENAMED,
DiffEntry.ChangeType.COPY,
Patch.ChangeType.COPIED);
@Nullable private Integer renameScore = null;
/**
* Enables rename detection
*
* @param renameScore the score that should be used for the rename detection.
*/
@CanIgnoreReturnValue
public GitModifiedFilesLoader withRenameDetection(int renameScore) {
checkState(renameScore >= 0);
this.renameScore = renameScore;
return this;
}
/**
* Loads the files that have been modified between {@code aTree} and {@code bTree}.
*
* <p>The trees are looked up via the given {@code revWalk} instance,
*/
public ImmutableList<ModifiedFile> load(
Config repoConfig, ObjectReader reader, ObjectId aTree, ObjectId bTree) throws IOException {
List<DiffEntry> entries = getGitTreeDiff(repoConfig, reader, aTree, bTree);
return entries.stream().map(GitModifiedFilesLoader::toModifiedFile).collect(toImmutableList());
}
private List<DiffEntry> getGitTreeDiff(
Config repoConfig, ObjectReader reader, ObjectId aTree, ObjectId bTree) throws IOException {
try (DiffFormatter df = new DiffFormatter(DisabledOutputStream.INSTANCE)) {
df.setReader(reader, repoConfig);
if (renameScore != null) {
df.setDetectRenames(true);
df.getRenameDetector().setRenameScore(renameScore);
// Skip detecting content renames for binary files.
df.getRenameDetector().setSkipContentRenamesForBinaryFiles(true);
}
// The scan method only returns the file paths that are different. Callers may choose to
// format these paths themselves.
return df.scan(aTree.equals(ObjectId.zeroId()) ? null : aTree, bTree);
}
}
private static ModifiedFile toModifiedFile(DiffEntry entry) {
String oldPath = entry.getOldPath();
String newPath = entry.getNewPath();
return ModifiedFile.builder()
.changeType(toChangeType(entry.getChangeType()))
.oldPath(oldPath.equals(DiffEntry.DEV_NULL) ? Optional.empty() : Optional.of(oldPath))
.newPath(newPath.equals(DiffEntry.DEV_NULL) ? Optional.empty() : Optional.of(newPath))
.build();
}
private static Patch.ChangeType toChangeType(DiffEntry.ChangeType changeType) {
if (!changeTypeMap.containsKey(changeType)) {
throw new IllegalArgumentException("Unsupported type " + changeType);
}
return changeTypeMap.get(changeType);
}
}