Merge changes I1bae3dcd,Id112f974,I01122bbc

* changes:
  Fix comparison type against another patchset vs. auto-merge
  Check for old and new file modes in FileDiffCacheImpl
  Handle single file diffs for non-existent files
diff --git a/java/com/google/gerrit/server/patch/AutoMerger.java b/java/com/google/gerrit/server/patch/AutoMerger.java
index 2e0214c..fe915c5 100644
--- a/java/com/google/gerrit/server/patch/AutoMerger.java
+++ b/java/com/google/gerrit/server/patch/AutoMerger.java
@@ -67,6 +67,8 @@
  * is that these refs should never be deleted.
  */
 public class AutoMerger {
+  public static final String AUTO_MERGE_MSG_PREFIX = "Auto-merge of ";
+
   @UsedAt(UsedAt.Project.GOOGLE)
   public static boolean cacheAutomerge(Config cfg) {
     return cfg.getBoolean("change", null, "cacheAutomerge", true);
@@ -198,7 +200,7 @@
     cb.setAuthor(ident);
     cb.setCommitter(ident);
     cb.setTreeId(tree);
-    cb.setMessage("Auto-merge of " + merge.name() + '\n');
+    cb.setMessage(AUTO_MERGE_MSG_PREFIX + merge.name() + '\n');
     for (RevCommit p : merge.getParents()) {
       cb.addParentId(p);
     }
diff --git a/java/com/google/gerrit/server/patch/DiffOperations.java b/java/com/google/gerrit/server/patch/DiffOperations.java
index 87a43c0..8b90531 100644
--- a/java/com/google/gerrit/server/patch/DiffOperations.java
+++ b/java/com/google/gerrit/server/patch/DiffOperations.java
@@ -74,7 +74,8 @@
   /**
    * Returns the diff for a single file between a patchset commit against its parent or the
    * auto-merge commit. For deleted files, the {@code fileName} parameter should contain the old
-   * name of the file.
+   * name of the file. This method will return {@link FileDiffOutput#empty(String)} if the requested
+   * file identified by {@code fileName} has unchanged content or does not exist at both commits.
    *
    * @param project a project name representing a git repository.
    * @param newCommit 20 bytes SHA-1 of the new commit used in the diff.
@@ -96,7 +97,9 @@
 
   /**
    * Returns the diff for a single file between two patchset commits. For deleted files, the {@code
-   * fileName} parameter should contain the old name of the file.
+   * fileName} parameter should contain the old name of the file. This method will return {@link
+   * FileDiffOutput#empty(String)} if the requested file identified by {@code fileName} has
+   * unchanged content or does not exist at both commits.
    *
    * @param project a project name representing a git repository.
    * @param oldCommit 20 bytes SHA-1 of the old commit used in the diff.
diff --git a/java/com/google/gerrit/server/patch/DiffOperationsImpl.java b/java/com/google/gerrit/server/patch/DiffOperationsImpl.java
index 45ba789..16bd135 100644
--- a/java/com/google/gerrit/server/patch/DiffOperationsImpl.java
+++ b/java/com/google/gerrit/server/patch/DiffOperationsImpl.java
@@ -124,7 +124,8 @@
       DiffParameters diffParams = computeDiffParameters(project, newCommit, parent);
       FileDiffCacheKey key =
           createFileDiffCacheKey(project, diffParams.baseCommit(), newCommit, fileName, whitespace);
-      return getModifiedFilesForKeys(ImmutableList.of(key)).get(fileName);
+      Map<String, FileDiffOutput> result = getModifiedFilesForKeys(ImmutableList.of(key));
+      return result.containsKey(fileName) ? result.get(fileName) : FileDiffOutput.empty(fileName);
     } catch (IOException e) {
       throw new DiffNotAvailableException(
           "Failed to evaluate the parent/base commit for commit " + newCommit, e);
@@ -141,7 +142,8 @@
       throws DiffNotAvailableException {
     FileDiffCacheKey key =
         createFileDiffCacheKey(project, oldCommit, newCommit, fileName, whitespace);
-    return getModifiedFilesForKeys(ImmutableList.of(key)).get(fileName);
+    Map<String, FileDiffOutput> result = getModifiedFilesForKeys(ImmutableList.of(key));
+    return result.containsKey(fileName) ? result.get(fileName) : FileDiffOutput.empty(fileName);
   }
 
   private Map<String, FileDiffOutput> getModifiedFiles(
diff --git a/java/com/google/gerrit/server/patch/filediff/FileDiffCacheImpl.java b/java/com/google/gerrit/server/patch/filediff/FileDiffCacheImpl.java
index fb2aa3d..63f311b 100644
--- a/java/com/google/gerrit/server/patch/filediff/FileDiffCacheImpl.java
+++ b/java/com/google/gerrit/server/patch/filediff/FileDiffCacheImpl.java
@@ -31,6 +31,7 @@
 import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
 import com.google.gerrit.server.cache.CacheModule;
 import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.patch.AutoMerger;
 import com.google.gerrit.server.patch.ComparisonType;
 import com.google.gerrit.server.patch.DiffNotAvailableException;
 import com.google.gerrit.server.patch.DiffUtil;
@@ -184,7 +185,8 @@
       return result.build();
     }
 
-    private ComparisonType getComparisonType(RevWalk rw, ObjectId oldCommitId, ObjectId newCommitId)
+    private ComparisonType getComparisonType(
+        RevWalk rw, ObjectReader reader, ObjectId oldCommitId, ObjectId newCommitId)
         throws IOException {
       RevCommit oldCommit = DiffUtil.getRevCommit(rw, oldCommitId);
       RevCommit newCommit = DiffUtil.getRevCommit(rw, newCommitId);
@@ -193,7 +195,16 @@
           return ComparisonType.againstParent(i + 1);
         }
       }
-      if (newCommit.getParentCount() > 0) {
+      // TODO(ghareeb): it's not trivial to distinguish if diff with old commit is against another
+      // patchset or auto-merge. Looking at the commit message of old commit gives a strong
+      // signal that we are diffing against auto-merge, though not 100% accurate (e.g. if old commit
+      // has the auto-merge prefix in the commit message). A better resolution would be to move the
+      // COMMIT_MSG and MERGE_LIST evaluations outside of the diff cache. For more details, see
+      // discussion in
+      // https://gerrit-review.googlesource.com/c/gerrit/+/280519/6..18/java/com/google/gerrit/server/patch/FileDiffCache.java#b540
+      Text oldCommitMsgTxt = Text.forCommit(reader, oldCommit);
+      if (oldCommitMsgTxt.size() > 0
+          && oldCommitMsgTxt.getString(0).startsWith(AutoMerger.AUTO_MERGE_MSG_PREFIX)) {
         return ComparisonType.againstAutoMerge();
       }
       return ComparisonType.againstOtherPatchSet();
@@ -206,7 +217,8 @@
         FileDiffCacheKey key, ObjectReader reader, RevWalk rw, MagicPath magicPath) {
       try {
         RawTextComparator cmp = comparatorFor(key.whitespace());
-        ComparisonType comparisonType = getComparisonType(rw, key.oldCommit(), key.newCommit());
+        ComparisonType comparisonType =
+            getComparisonType(rw, reader, key.oldCommit(), key.newCommit());
         RevCommit aCommit =
             comparisonType.isAgainstParentOrAutoMerge()
                 ? null
@@ -342,7 +354,7 @@
         GitFileDiff mainGitDiff = allDiffs.mainDiff().gitDiff();
 
         Long oldSize =
-            mainGitDiff.oldPath().isPresent()
+            mainGitDiff.oldMode().isPresent() && mainGitDiff.oldPath().isPresent()
                 ? new FileSizeEvaluator(reader, aTree)
                     .compute(
                         mainGitDiff.oldId(),
@@ -350,7 +362,7 @@
                         mainGitDiff.oldPath().get())
                 : 0;
         Long newSize =
-            mainGitDiff.newPath().isPresent()
+            mainGitDiff.newMode().isPresent() && mainGitDiff.newPath().isPresent()
                 ? new FileSizeEvaluator(reader, bTree)
                     .compute(
                         mainGitDiff.newId(),