Diff against root commits: use ObjectId#zeroId instead of EMPTY_TREE_ID
We used to reserve the special EMPTY_TREE_ID
4b825dc642cb6eb9a060e54bf8d69288fbee4904 for the
FileDiffCacheKey#oldCommit and the GitFileDiffCacheKey#oldTree. We also
use the same thing for the ModifiedFilesCache and GitModifiedFilesCache.
Callers pass this value to request the diff for a root commit. The cache
loader used to pass this value further to DiffFormatter#scan (in JGit)
to perform the scan.
Some repositories may not have the EMPTY_TREE_ID as a valid git object.
This resulted in a MissingObjectException in DiffFormatter#scan.
Here we change the following:
1) Use ObjectId#zeroId instead of EMPTY_TREE_ID in the cache keys to
signal requesting a diff against an added tree (i.e. root commit).
2) Change the handling in the loader by passing a null value to the old
tree parameter in DiffFormatter#scan. The method documents accepting
null values to perform this kind of diff. Check [1].
[1] https://git.eclipse.org/r/plugins/gitiles/jgit/jgit/+/f598e69529e0a1864e8224265ed82326f2a296f5/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java#426
Change-Id: I004dcf991f600052054aa167f24a925f1994eab3
diff --git a/java/com/google/gerrit/server/patch/DiffOperationsImpl.java b/java/com/google/gerrit/server/patch/DiffOperationsImpl.java
index fd30868..01a577a 100644
--- a/java/com/google/gerrit/server/patch/DiffOperationsImpl.java
+++ b/java/com/google/gerrit/server/patch/DiffOperationsImpl.java
@@ -16,7 +16,6 @@
import static com.google.gerrit.entities.Patch.COMMIT_MSG;
import static com.google.gerrit.entities.Patch.MERGE_LIST;
-import static org.eclipse.jgit.lib.Constants.EMPTY_TREE_ID;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
@@ -346,7 +345,7 @@
/**
* Base commit represents the old commit of the diff. For diffs against the root commit, this
- * should be set to {@code EMPTY_TREE_ID}.
+ * should be set to {@link ObjectId#zeroId()}.
*/
abstract ObjectId baseCommit();
@@ -394,7 +393,7 @@
}
int numParents = baseCommitUtil.getNumParents(project, newCommit);
if (numParents == 0) {
- result.baseCommit(EMPTY_TREE_ID);
+ result.baseCommit(ObjectId.zeroId());
result.comparisonType(ComparisonType.againstRoot());
return result.build();
}
diff --git a/java/com/google/gerrit/server/patch/diff/ModifiedFilesCache.java b/java/com/google/gerrit/server/patch/diff/ModifiedFilesCache.java
index bcae238..56f49c9 100644
--- a/java/com/google/gerrit/server/patch/diff/ModifiedFilesCache.java
+++ b/java/com/google/gerrit/server/patch/diff/ModifiedFilesCache.java
@@ -28,8 +28,8 @@
* files.
*
* <p>If the {@link ModifiedFilesCacheKey#aCommit()} is equal to {@link
- * org.eclipse.jgit.lib.Constants#EMPTY_TREE_ID}, the diff will be evaluated against the empty tree,
- * and the result will be exactly the same as the caller can get from {@link
+ * org.eclipse.jgit.lib.ObjectId#zeroId()}, the diff will be evaluated against the empty tree, and
+ * the result will be exactly the same as the caller can get from {@link
* GitModifiedFilesCache#get(GitModifiedFilesCacheKey)}
*/
public interface ModifiedFilesCache {
diff --git a/java/com/google/gerrit/server/patch/diff/ModifiedFilesCacheImpl.java b/java/com/google/gerrit/server/patch/diff/ModifiedFilesCacheImpl.java
index e168386..b779bf7 100644
--- a/java/com/google/gerrit/server/patch/diff/ModifiedFilesCacheImpl.java
+++ b/java/com/google/gerrit/server/patch/diff/ModifiedFilesCacheImpl.java
@@ -15,7 +15,6 @@
package com.google.gerrit.server.patch.diff;
import static com.google.common.collect.ImmutableList.toImmutableList;
-import static org.eclipse.jgit.lib.Constants.EMPTY_TREE_ID;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
@@ -52,10 +51,9 @@
* <p>The loader of this cache wraps a {@link GitModifiedFilesCache} to retrieve the git modified
* files.
*
- * <p>If the {@link ModifiedFilesCacheKey#aCommit()} is equal to {@link
- * org.eclipse.jgit.lib.Constants#EMPTY_TREE_ID}, the diff will be evaluated against the empty tree,
- * and the result will be exactly the same as the caller can get from {@link
- * GitModifiedFilesCache#get(GitModifiedFilesCacheKey)}
+ * <p>If the {@link ModifiedFilesCacheKey#aCommit()} is equal to {@link ObjectId#zeroId()}, the diff
+ * will be evaluated against the empty tree, and the result will be exactly the same as the caller
+ * can get from {@link GitModifiedFilesCache#get(GitModifiedFilesCacheKey)}
*/
@Singleton
public class ModifiedFilesCacheImpl implements ModifiedFilesCache {
@@ -130,7 +128,7 @@
private ImmutableList<ModifiedFile> loadModifiedFiles(ModifiedFilesCacheKey key, RevWalk rw)
throws IOException, DiffNotAvailableException {
ObjectId aTree =
- key.aCommit().equals(EMPTY_TREE_ID)
+ key.aCommit().equals(ObjectId.zeroId())
? key.aCommit()
: DiffUtil.getTreeId(rw, key.aCommit());
ObjectId bTree = DiffUtil.getTreeId(rw, key.bCommit());
@@ -142,7 +140,7 @@
.renameScore(key.renameScore())
.build();
List<ModifiedFile> modifiedFiles = gitCache.get(gitKey);
- if (key.aCommit().equals(EMPTY_TREE_ID)) {
+ if (key.aCommit().equals(ObjectId.zeroId())) {
return ImmutableList.copyOf(modifiedFiles);
}
RevCommit revCommitA = DiffUtil.getRevCommit(rw, key.aCommit());
diff --git a/java/com/google/gerrit/server/patch/filediff/AllDiffsEvaluator.java b/java/com/google/gerrit/server/patch/filediff/AllDiffsEvaluator.java
index 12decc3..9afa1e0 100644
--- a/java/com/google/gerrit/server/patch/filediff/AllDiffsEvaluator.java
+++ b/java/com/google/gerrit/server/patch/filediff/AllDiffsEvaluator.java
@@ -14,8 +14,6 @@
package com.google.gerrit.server.patch.filediff;
-import static org.eclipse.jgit.lib.Constants.EMPTY_TREE_ID;
-
import com.google.common.collect.ImmutableMap;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.server.patch.DiffNotAvailableException;
@@ -203,7 +201,7 @@
FileDiffCacheKey key, ObjectId aCommit, ObjectId bCommit, String pathNew, RevWalk rw)
throws IOException {
ObjectId oldTreeId =
- aCommit.equals(EMPTY_TREE_ID) ? EMPTY_TREE_ID : DiffUtil.getTreeId(rw, aCommit);
+ aCommit.equals(ObjectId.zeroId()) ? ObjectId.zeroId() : DiffUtil.getTreeId(rw, aCommit);
ObjectId newTreeId = DiffUtil.getTreeId(rw, bCommit);
return GitFileDiffCacheKey.builder()
.project(key.project())
diff --git a/java/com/google/gerrit/server/patch/filediff/FileDiffCacheImpl.java b/java/com/google/gerrit/server/patch/filediff/FileDiffCacheImpl.java
index ee5e156..c05aae0 100644
--- a/java/com/google/gerrit/server/patch/filediff/FileDiffCacheImpl.java
+++ b/java/com/google/gerrit/server/patch/filediff/FileDiffCacheImpl.java
@@ -16,7 +16,6 @@
import static com.google.common.collect.ImmutableList.toImmutableList;
import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.eclipse.jgit.lib.Constants.EMPTY_TREE_ID;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
@@ -76,9 +75,8 @@
* Cache for the single file diff between two commits for a single file path. This cache adds extra
* Gerrit logic such as identifying edits due to rebase.
*
- * <p>If the {@link FileDiffCacheKey#oldCommit()} is equal to {@link
- * org.eclipse.jgit.lib.Constants#EMPTY_TREE_ID}, the git diff will be evaluated against the empty
- * tree.
+ * <p>If the {@link FileDiffCacheKey#oldCommit()} is equal to {@link ObjectId#zeroId()}, the git
+ * diff will be evaluated against the empty tree.
*/
@Singleton
public class FileDiffCacheImpl implements FileDiffCache {
@@ -204,7 +202,7 @@
private ComparisonType getComparisonType(
RevWalk rw, ObjectReader reader, ObjectId oldCommitId, ObjectId newCommitId)
throws IOException {
- if (oldCommitId.equals(EMPTY_TREE_ID)) {
+ if (oldCommitId.equals(ObjectId.zeroId())) {
return ComparisonType.againstRoot();
}
RevCommit oldCommit = DiffUtil.getRevCommit(rw, oldCommitId);
@@ -238,7 +236,7 @@
ComparisonType comparisonType =
getComparisonType(rw, reader, key.oldCommit(), key.newCommit());
RevCommit aCommit =
- key.oldCommit().equals(EMPTY_TREE_ID)
+ key.oldCommit().equals(ObjectId.zeroId())
? null
: DiffUtil.getRevCommit(rw, key.oldCommit());
RevCommit bCommit = DiffUtil.getRevCommit(rw, key.newCommit());
@@ -345,7 +343,7 @@
FileHeader fileHeader = new FileHeader(rawHdr, edits, PatchType.UNIFIED);
Patch.ChangeType changeType = FileHeaderUtil.getChangeType(fileHeader);
return FileDiffOutput.builder()
- .oldCommitId(oldCommit == null ? EMPTY_TREE_ID : oldCommit)
+ .oldCommitId(oldCommit == null ? ObjectId.zeroId() : oldCommit)
.newCommitId(newCommit)
.comparisonType(comparisonType)
.oldPath(FileHeaderUtil.getOldPath(fileHeader))
@@ -399,12 +397,14 @@
}
List<Edit> rebaseEdits = rebaseFileEdits.edits();
- RevTree aTree = rw.parseTree(allDiffs.mainDiff().gitKey().oldTree());
+ ObjectId oldTreeId = allDiffs.mainDiff().gitKey().oldTree();
+
+ RevTree aTree = oldTreeId.equals(ObjectId.zeroId()) ? null : rw.parseTree(oldTreeId);
RevTree bTree = rw.parseTree(allDiffs.mainDiff().gitKey().newTree());
GitFileDiff mainGitDiff = allDiffs.mainDiff().gitDiff();
Long oldSize =
- mainGitDiff.oldMode().isPresent() && mainGitDiff.oldPath().isPresent()
+ aTree != null && mainGitDiff.oldMode().isPresent() && mainGitDiff.oldPath().isPresent()
? new FileSizeEvaluator(reader, aTree)
.compute(
mainGitDiff.oldId(),
@@ -453,7 +453,7 @@
private List<AugmentedFileDiffCacheKey> wrapKeys(List<FileDiffCacheKey> keys, RevWalk rw) {
List<AugmentedFileDiffCacheKey> result = new ArrayList<>();
for (FileDiffCacheKey key : keys) {
- if (key.oldCommit().equals(EMPTY_TREE_ID)) {
+ if (key.oldCommit().equals(ObjectId.zeroId())) {
result.add(AugmentedFileDiffCacheKey.builder().key(key).ignoreRebase(true).build());
continue;
}
diff --git a/java/com/google/gerrit/server/patch/filediff/FileDiffCacheKey.java b/java/com/google/gerrit/server/patch/filediff/FileDiffCacheKey.java
index 7ac9343..1c0e141 100644
--- a/java/com/google/gerrit/server/patch/filediff/FileDiffCacheKey.java
+++ b/java/com/google/gerrit/server/patch/filediff/FileDiffCacheKey.java
@@ -26,7 +26,6 @@
import com.google.gerrit.server.cache.serialize.CacheSerializer;
import com.google.gerrit.server.cache.serialize.ObjectIdConverter;
import com.google.gerrit.server.patch.gitfilediff.GitFileDiffCacheImpl.DiffAlgorithm;
-import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
/** Cache key for the {@link FileDiffCache}. */
@@ -38,7 +37,7 @@
/**
* The 20 bytes SHA-1 commit ID of the old commit used in the diff. If set to {@link
- * Constants#EMPTY_TREE_ID}, the diff is performed against the root commit.
+ * ObjectId#zeroId()}, an empty tree is used for the diff.
*/
public abstract ObjectId oldCommit();
diff --git a/java/com/google/gerrit/server/patch/gitdiff/GitModifiedFilesCacheImpl.java b/java/com/google/gerrit/server/patch/gitdiff/GitModifiedFilesCacheImpl.java
index 9b96da1..b7144d7 100644
--- a/java/com/google/gerrit/server/patch/gitdiff/GitModifiedFilesCacheImpl.java
+++ b/java/com/google/gerrit/server/patch/gitdiff/GitModifiedFilesCacheImpl.java
@@ -39,6 +39,7 @@
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffEntry.ChangeType;
import org.eclipse.jgit.diff.DiffFormatter;
+import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.util.io.DisabledOutputStream;
@@ -132,7 +133,7 @@
}
// The scan method only returns the file paths that are different. Callers may choose to
// format these paths themselves.
- return df.scan(key.aTree(), key.bTree());
+ return df.scan(key.aTree().equals(ObjectId.zeroId()) ? null : key.aTree(), key.bTree());
}
}
diff --git a/java/com/google/gerrit/server/patch/gitdiff/GitModifiedFilesCacheKey.java b/java/com/google/gerrit/server/patch/gitdiff/GitModifiedFilesCacheKey.java
index fb8fce1..16b0e65 100644
--- a/java/com/google/gerrit/server/patch/gitdiff/GitModifiedFilesCacheKey.java
+++ b/java/com/google/gerrit/server/patch/gitdiff/GitModifiedFilesCacheKey.java
@@ -37,7 +37,8 @@
/**
* The git SHA-1 {@link ObjectId} of the first git tree object for which the diff should be
- * computed.
+ * computed. If equals to {@link ObjectId#zeroId()}, a null tree is used for the diff scan, and
+ * {@link #bTree()} is treated as an added tree.
*/
public abstract ObjectId aTree();
diff --git a/java/com/google/gerrit/server/patch/gitfilediff/GitFileDiffCacheImpl.java b/java/com/google/gerrit/server/patch/gitfilediff/GitFileDiffCacheImpl.java
index 31afd17..761910f 100644
--- a/java/com/google/gerrit/server/patch/gitfilediff/GitFileDiffCacheImpl.java
+++ b/java/com/google/gerrit/server/patch/gitfilediff/GitFileDiffCacheImpl.java
@@ -219,7 +219,9 @@
throws IOException {
Set<String> filePathsSet = ImmutableSet.copyOf(filePaths);
List<DiffEntry> diffEntries =
- diffFormatter.scan(diffOptions.oldTree(), diffOptions.newTree());
+ diffFormatter.scan(
+ diffOptions.oldTree().equals(ObjectId.zeroId()) ? null : diffOptions.oldTree(),
+ diffOptions.newTree());
return diffEntries.stream()
.filter(d -> filePathsSet.contains(pathExtractor.apply(d)))
diff --git a/java/com/google/gerrit/server/patch/gitfilediff/GitFileDiffCacheKey.java b/java/com/google/gerrit/server/patch/gitfilediff/GitFileDiffCacheKey.java
index f104388..4fc5279 100644
--- a/java/com/google/gerrit/server/patch/gitfilediff/GitFileDiffCacheKey.java
+++ b/java/com/google/gerrit/server/patch/gitfilediff/GitFileDiffCacheKey.java
@@ -34,7 +34,11 @@
/** A specific git project / repository. */
public abstract Project.NameKey project();
- /** The old 20 bytes SHA-1 git tree ID used in the git tree diff */
+ /**
+ * The old 20 bytes SHA-1 git tree ID used in the git tree diff. If equals to {@link
+ * ObjectId#zeroId()}, a null tree is used for the diff scan, and {@link #newTree()} ()} is
+ * treated as an added tree.
+ */
public abstract ObjectId oldTree();
/** The new 20 bytes SHA-1 git tree ID used in the git tree diff */
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java b/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java
index 1a55b82..d8dab33 100644
--- a/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/revision/RevisionDiffIT.java
@@ -26,7 +26,6 @@
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toMap;
-import static org.eclipse.jgit.lib.Constants.EMPTY_TREE_ID;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
@@ -161,7 +160,7 @@
modifiedFiles.values().stream()
.map(FileDiffOutput::oldCommitId)
.collect(Collectors.toSet()))
- .containsExactly(EMPTY_TREE_ID);
+ .containsExactly(ObjectId.zeroId());
assertThat(modifiedFiles.get("/COMMIT_MSG").changeType()).isEqualTo(Patch.ChangeType.ADDED);
assertThat(modifiedFiles.get("f.txt").changeType()).isEqualTo(Patch.ChangeType.ADDED);
}