Merge changes Ifbc1e3ed,I78c15e01

* changes:
  Migrate CommentSender to use the new diff cache
  Migrate Prolog Environment to use the new diff cache
diff --git a/java/com/google/gerrit/server/mail/send/ChangeEmail.java b/java/com/google/gerrit/server/mail/send/ChangeEmail.java
index 48ab397..1a2e150 100644
--- a/java/com/google/gerrit/server/mail/send/ChangeEmail.java
+++ b/java/com/google/gerrit/server/mail/send/ChangeEmail.java
@@ -41,11 +41,10 @@
 import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.mail.send.ProjectWatch.Watchers;
 import com.google.gerrit.server.notedb.ReviewerStateInternal;
-import com.google.gerrit.server.patch.PatchList;
-import com.google.gerrit.server.patch.PatchListEntry;
-import com.google.gerrit.server.patch.PatchListNotAvailableException;
-import com.google.gerrit.server.patch.PatchListObjectTooLargeException;
+import com.google.gerrit.server.patch.DiffNotAvailableException;
+import com.google.gerrit.server.patch.FilePathAdapter;
 import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
+import com.google.gerrit.server.patch.filediff.FileDiffOutput;
 import com.google.gerrit.server.permissions.ChangePermission;
 import com.google.gerrit.server.permissions.GlobalPermission;
 import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -66,6 +65,7 @@
 import org.apache.james.mime4j.dom.field.FieldName;
 import org.eclipse.jgit.diff.DiffFormatter;
 import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.util.RawParseUtils;
 import org.eclipse.jgit.util.TemporaryBuffer;
@@ -269,17 +269,23 @@
 
       if (patchSet != null) {
         detail.append("---\n");
-        PatchList patchList = getPatchList();
-        for (PatchListEntry p : patchList.getPatches()) {
-          if (Patch.isMagic(p.getNewName())) {
+        Map<String, FileDiffOutput> modifiedFiles = listModifiedFiles();
+        for (FileDiffOutput fileDiff : modifiedFiles.values()) {
+          if (fileDiff.newPath().isPresent() && Patch.isMagic(fileDiff.newPath().get())) {
             continue;
           }
           detail
-              .append(p.getChangeType().getCode())
+              .append(fileDiff.changeType().getCode())
               .append(" ")
-              .append(p.getNewName())
+              .append(
+                  FilePathAdapter.getNewPath(
+                      fileDiff.oldPath(), fileDiff.newPath(), fileDiff.changeType()))
               .append("\n");
         }
+        Integer insertions =
+            modifiedFiles.values().stream().map(FileDiffOutput::insertions).reduce(0, Integer::sum);
+        Integer deletions =
+            modifiedFiles.values().stream().map(FileDiffOutput::deletions).reduce(0, Integer::sum);
         detail.append(
             MessageFormat.format(
                 "" //
@@ -287,9 +293,9 @@
                     + "{1,choice,0#0 insertions|1#1 insertion|1<{1} insertions}(+), " //
                     + "{2,choice,0#0 deletions|1#1 deletion|1<{2} deletions}(-)" //
                     + "\n",
-                patchList.getPatches().size() - 1, //
-                patchList.getInsertions(), //
-                patchList.getDeletions()));
+                modifiedFiles.size() - 1, //
+                insertions, //
+                deletions));
         detail.append("\n");
       }
       return detail.toString();
@@ -300,7 +306,8 @@
   }
 
   /** Get the patch list corresponding to patch set patchSetId of this change. */
-  protected PatchList getPatchList(int patchSetId) throws PatchListNotAvailableException {
+  protected Map<String, FileDiffOutput> listModifiedFiles(int patchSetId)
+      throws DiffNotAvailableException {
     PatchSet ps;
     if (patchSetId == patchSet.number()) {
       ps = patchSet;
@@ -308,18 +315,20 @@
       try {
         ps = args.patchSetUtil.get(changeData.notes(), PatchSet.id(change.getId(), patchSetId));
       } catch (StorageException e) {
-        throw new PatchListNotAvailableException("Failed to get patchSet", e);
+        throw new DiffNotAvailableException("Failed to get patchSet", e);
       }
     }
-    return args.patchListCache.get(change, ps);
+    return args.diffOperations.listModifiedFilesAgainstParent(
+        change.getProject(), ps.commitId(), /* parentNum= */ 0);
   }
 
   /** Get the patch list corresponding to this patch set. */
-  protected PatchList getPatchList() throws PatchListNotAvailableException {
+  protected Map<String, FileDiffOutput> listModifiedFiles() throws DiffNotAvailableException {
     if (patchSet != null) {
-      return args.patchListCache.get(change, patchSet);
+      return args.diffOperations.listModifiedFilesAgainstParent(
+          change.getProject(), patchSet.commitId(), /* parentNum= */ 0);
     }
-    throw new PatchListNotAvailableException("no patchSet specified");
+    throw new DiffNotAvailableException("no patchSet specified");
   }
 
   /** Get the project entity the change is in; null if its been deleted. */
@@ -566,18 +575,15 @@
 
   /** Show patch set as unified difference. */
   public String getUnifiedDiff() {
-    PatchList patchList;
+    Map<String, FileDiffOutput> modifiedFiles;
     try {
-      patchList = getPatchList();
-      if (patchList.getOldId() == null) {
+      modifiedFiles = listModifiedFiles();
+      if (modifiedFiles.isEmpty()) {
         // Octopus merges are not well supported for diff output by Gerrit.
         // Currently these always have a null oldId in the PatchList.
         return "[Octopus merge; cannot be formatted as a diff.]\n";
       }
-    } catch (PatchListObjectTooLargeException e) {
-      logger.atWarning().log("Cannot format patch %s", e.getMessage());
-      return "";
-    } catch (PatchListNotAvailableException e) {
+    } catch (DiffNotAvailableException e) {
       logger.atSevere().withCause(e).log("Cannot format patch");
       return "";
     }
@@ -587,9 +593,11 @@
     try (DiffFormatter fmt = new DiffFormatter(buf)) {
       try (Repository git = args.server.openRepository(change.getProject())) {
         try {
+          ObjectId oldId = modifiedFiles.values().iterator().next().oldCommitId();
+          ObjectId newId = modifiedFiles.values().iterator().next().newCommitId();
           fmt.setRepository(git);
           fmt.setDetectRenames(true);
-          fmt.format(patchList.getOldId(), patchList.getNewId());
+          fmt.format(oldId, newId);
           return RawParseUtils.decode(buf.toByteArray());
         } catch (IOException e) {
           if (JGitText.get().inMemoryBufferLimitExceeded.equals(e.getMessage())) {
diff --git a/java/com/google/gerrit/server/mail/send/CommentSender.java b/java/com/google/gerrit/server/mail/send/CommentSender.java
index ac6c2f3..4593584 100644
--- a/java/com/google/gerrit/server/mail/send/CommentSender.java
+++ b/java/com/google/gerrit/server/mail/send/CommentSender.java
@@ -36,10 +36,9 @@
 import com.google.gerrit.server.CommentsUtil;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.mail.receive.Protocol;
+import com.google.gerrit.server.patch.DiffNotAvailableException;
 import com.google.gerrit.server.patch.PatchFile;
-import com.google.gerrit.server.patch.PatchList;
-import com.google.gerrit.server.patch.PatchListNotAvailableException;
-import com.google.gerrit.server.patch.PatchListObjectTooLargeException;
+import com.google.gerrit.server.patch.filediff.FileDiffOutput;
 import com.google.gerrit.server.util.LabelVote;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
@@ -198,24 +197,24 @@
         currentGroup = new FileCommentGroup();
         currentGroup.filename = c.key.filename;
         currentGroup.patchSetId = c.key.patchSetId;
-        // Get the patch list:
-        PatchList patchList = null;
+        // Get the modified files:
+        Map<String, FileDiffOutput> modifiedFiles = null;
         try {
-          patchList = getPatchList(c.key.patchSetId);
-        } catch (PatchListObjectTooLargeException e) {
-          logger.atWarning().log("Failed to get patch list: %s", e.getMessage());
-        } catch (PatchListNotAvailableException e) {
-          logger.atSevere().withCause(e).log("Failed to get patch list");
+          modifiedFiles = listModifiedFiles(c.key.patchSetId);
+        } catch (DiffNotAvailableException e) {
+          logger.atSevere().withCause(e).log("Failed to get modified files");
         }
 
         groups.add(currentGroup);
-        if (patchList != null) {
+        if (modifiedFiles != null && !modifiedFiles.isEmpty()) {
           try {
-            currentGroup.fileData = new PatchFile(repo, patchList, c.key.filename);
+            currentGroup.fileData = new PatchFile(repo, modifiedFiles, c.key.filename);
           } catch (IOException e) {
             logger.atWarning().withCause(e).log(
                 "Cannot load %s from %s in %s",
-                c.key.filename, patchList.getNewId().name(), projectState.getName());
+                c.key.filename,
+                modifiedFiles.values().iterator().next().newCommitId().name(),
+                projectState.getName());
             currentGroup.fileData = null;
           }
         }
diff --git a/java/com/google/gerrit/server/mail/send/EmailArguments.java b/java/com/google/gerrit/server/mail/send/EmailArguments.java
index 735e34a..96effc1 100644
--- a/java/com/google/gerrit/server/mail/send/EmailArguments.java
+++ b/java/com/google/gerrit/server/mail/send/EmailArguments.java
@@ -35,7 +35,7 @@
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.mail.EmailSettings;
 import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gerrit.server.patch.PatchListCache;
+import com.google.gerrit.server.patch.DiffOperations;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
 import com.google.gerrit.server.permissions.PermissionBackend;
 import com.google.gerrit.server.project.ProjectCache;
@@ -72,7 +72,7 @@
   final PermissionBackend permissionBackend;
   final GroupBackend groupBackend;
   final AccountCache accountCache;
-  final PatchListCache patchListCache;
+  final DiffOperations diffOperations;
   final PatchSetUtil patchSetUtil;
   final ApprovalsUtil approvalsUtil;
   final Provider<FromAddressGenerator> fromAddressGenerator;
@@ -106,7 +106,7 @@
       PermissionBackend permissionBackend,
       GroupBackend groupBackend,
       AccountCache accountCache,
-      PatchListCache patchListCache,
+      DiffOperations diffOperations,
       PatchSetUtil patchSetUtil,
       ApprovalsUtil approvalsUtil,
       Provider<FromAddressGenerator> fromAddressGenerator,
@@ -137,7 +137,7 @@
     this.permissionBackend = permissionBackend;
     this.groupBackend = groupBackend;
     this.accountCache = accountCache;
-    this.patchListCache = patchListCache;
+    this.diffOperations = diffOperations;
     this.patchSetUtil = patchSetUtil;
     this.approvalsUtil = approvalsUtil;
     this.fromAddressGenerator = fromAddressGenerator;
diff --git a/java/com/google/gerrit/server/patch/PatchFile.java b/java/com/google/gerrit/server/patch/PatchFile.java
index ca5223d..3cc89f85 100644
--- a/java/com/google/gerrit/server/patch/PatchFile.java
+++ b/java/com/google/gerrit/server/patch/PatchFile.java
@@ -18,7 +18,9 @@
 
 import com.google.gerrit.entities.Patch;
 import com.google.gerrit.exceptions.NoSuchEntityException;
+import com.google.gerrit.server.patch.filediff.FileDiffOutput;
 import java.io.IOException;
+import java.util.Map;
 import org.eclipse.jgit.errors.CorruptObjectException;
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
@@ -35,7 +37,7 @@
 /** State supporting processing of a single {@link Patch} instance. */
 public class PatchFile {
   private final Repository repo;
-  private final PatchListEntry entry;
+  private final FileDiffOutput diff;
   private final RevTree aTree;
   private final RevTree bTree;
 
@@ -51,21 +53,25 @@
   private Text a;
   private Text b;
 
-  public PatchFile(Repository repo, PatchList patchList, String fileName)
-      throws MissingObjectException, IncorrectObjectTypeException, IOException {
+  public PatchFile(Repository repo, Map<String, FileDiffOutput> modifiedFiles, String fileName)
+      throws IOException {
     this.repo = repo;
-    this.entry = patchList.get(fileName);
+    this.diff =
+        modifiedFiles.values().stream()
+            .filter(f -> f.newPath().isPresent() && f.newPath().get().equals(fileName))
+            .findFirst()
+            .orElse(FileDiffOutput.empty(fileName, ObjectId.zeroId(), ObjectId.zeroId()));
 
     try (ObjectReader reader = repo.newObjectReader();
         RevWalk rw = new RevWalk(reader)) {
-      final RevCommit bCommit = rw.parseCommit(patchList.getNewId());
+      final RevCommit bCommit = rw.parseCommit(diff.newCommitId());
 
       if (Patch.COMMIT_MSG.equals(fileName)) {
-        if (patchList.getComparisonType().isAgainstParentOrAutoMerge()) {
+        if (diff.comparisonType().isAgainstParentOrAutoMerge()) {
           a = Text.EMPTY;
         } else {
           // For the initial commit, we have an empty tree on Side A
-          RevObject object = rw.parseAny(patchList.getOldId());
+          RevObject object = rw.parseAny(diff.oldCommitId());
           a = object instanceof RevCommit ? Text.forCommit(reader, object) : Text.EMPTY;
         }
         b = Text.forCommit(reader, bCommit);
@@ -74,18 +80,18 @@
         bTree = null;
       } else if (Patch.MERGE_LIST.equals(fileName)) {
         // For the initial commit, we have an empty tree on Side A
-        RevObject object = rw.parseAny(patchList.getOldId());
+        RevObject object = rw.parseAny(diff.oldCommitId());
         a =
             object instanceof RevCommit
-                ? Text.forMergeList(patchList.getComparisonType(), reader, object)
+                ? Text.forMergeList(diff.comparisonType(), reader, object)
                 : Text.EMPTY;
-        b = Text.forMergeList(patchList.getComparisonType(), reader, bCommit);
+        b = Text.forMergeList(diff.comparisonType(), reader, bCommit);
 
         aTree = null;
         bTree = null;
       } else {
-        if (patchList.getOldId() != null) {
-          aTree = rw.parseTree(patchList.getOldId());
+        if (diff.oldCommitId() != null) {
+          aTree = rw.parseTree(diff.oldCommitId());
         } else {
           final RevCommit p = bCommit.getParent(0);
           rw.parseHeaders(p);
@@ -97,11 +103,11 @@
   }
 
   private String getOldName() {
-    String name = entry.getOldName();
+    String name = FilePathAdapter.getOldPath(diff.oldPath(), diff.changeType());
     if (name != null) {
       return name;
     }
-    return entry.getNewName();
+    return FilePathAdapter.getNewPath(diff.oldPath(), diff.newPath(), diff.changeType());
   }
 
   /**
@@ -123,7 +129,10 @@
 
       case 1:
         if (b == null) {
-          b = load(bTree, entry.getNewName());
+          b =
+              load(
+                  bTree,
+                  FilePathAdapter.getNewPath(diff.oldPath(), diff.newPath(), diff.changeType()));
         }
         return b.getString(line - 1);
 
diff --git a/java/com/google/gerrit/server/rules/PrologEnvironment.java b/java/com/google/gerrit/server/rules/PrologEnvironment.java
index 7d626da..bc0bb1a 100644
--- a/java/com/google/gerrit/server/rules/PrologEnvironment.java
+++ b/java/com/google/gerrit/server/rules/PrologEnvironment.java
@@ -22,7 +22,7 @@
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.PluginConfigFactory;
 import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.patch.PatchListCache;
+import com.google.gerrit.server.patch.DiffOperations;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
 import com.google.gerrit.server.permissions.PermissionBackend;
 import com.google.gerrit.server.project.ProjectCache;
@@ -173,7 +173,7 @@
     private final PermissionBackend permissionBackend;
     private final GitRepositoryManager repositoryManager;
     private final PluginConfigFactory pluginConfigFactory;
-    private final PatchListCache patchListCache;
+    private final DiffOperations diffOperations;
     private final PatchSetInfoFactory patchSetInfoFactory;
     private final IdentifiedUser.GenericFactory userFactory;
     private final Provider<AnonymousUser> anonymousUser;
@@ -188,7 +188,7 @@
         PermissionBackend permissionBackend,
         GitRepositoryManager repositoryManager,
         PluginConfigFactory pluginConfigFactory,
-        PatchListCache patchListCache,
+        DiffOperations diffOperations,
         PatchSetInfoFactory patchSetInfoFactory,
         IdentifiedUser.GenericFactory userFactory,
         Provider<AnonymousUser> anonymousUser,
@@ -199,7 +199,7 @@
       this.permissionBackend = permissionBackend;
       this.repositoryManager = repositoryManager;
       this.pluginConfigFactory = pluginConfigFactory;
-      this.patchListCache = patchListCache;
+      this.diffOperations = diffOperations;
       this.patchSetInfoFactory = patchSetInfoFactory;
       this.userFactory = userFactory;
       this.anonymousUser = anonymousUser;
@@ -237,8 +237,8 @@
       return pluginConfigFactory;
     }
 
-    public PatchListCache getPatchListCache() {
-      return patchListCache;
+    public DiffOperations getDiffOperations() {
+      return diffOperations;
     }
 
     public PatchSetInfoFactory getPatchSetInfoFactory() {
diff --git a/java/com/google/gerrit/server/rules/StoredValues.java b/java/com/google/gerrit/server/rules/StoredValues.java
index 1e08a24..1d10c1f 100644
--- a/java/com/google/gerrit/server/rules/StoredValues.java
+++ b/java/com/google/gerrit/server/rules/StoredValues.java
@@ -21,7 +21,6 @@
 import com.google.gerrit.entities.PatchSet;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.exceptions.StorageException;
-import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
 import com.google.gerrit.server.AnonymousUser;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.PatchSetUtil;
@@ -30,10 +29,9 @@
 import com.google.gerrit.server.account.Emails;
 import com.google.gerrit.server.config.PluginConfigFactory;
 import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.patch.PatchList;
-import com.google.gerrit.server.patch.PatchListCache;
-import com.google.gerrit.server.patch.PatchListKey;
-import com.google.gerrit.server.patch.PatchListNotAvailableException;
+import com.google.gerrit.server.patch.DiffNotAvailableException;
+import com.google.gerrit.server.patch.DiffOperations;
+import com.google.gerrit.server.patch.filediff.FileDiffOutput;
 import com.google.gerrit.server.permissions.PermissionBackend;
 import com.google.gerrit.server.project.ProjectState;
 import com.google.gerrit.server.query.change.ChangeData;
@@ -87,24 +85,27 @@
         }
       };
 
-  public static final StoredValue<PatchList> PATCH_LIST =
-      new StoredValue<PatchList>() {
+  public static final StoredValue<Map<String, FileDiffOutput>> DIFF_LIST =
+      new StoredValue<Map<String, FileDiffOutput>>() {
         @Override
-        public PatchList createValue(Prolog engine) {
+        public Map<String, FileDiffOutput> createValue(Prolog engine) {
           PrologEnvironment env = (PrologEnvironment) engine.control;
           PatchSet ps = getPatchSet(engine);
-          PatchListCache plCache = env.getArgs().getPatchListCache();
+          DiffOperations diffOperations = env.getArgs().getDiffOperations();
           Change change = getChange(engine);
           Project.NameKey project = change.getProject();
-          Whitespace ws = Whitespace.IGNORE_NONE;
-          PatchListKey plKey = PatchListKey.againstDefaultBase(ps.commitId(), ws);
-          PatchList patchList;
+          Map<String, FileDiffOutput> diffList;
           try {
-            patchList = plCache.get(plKey, project);
-          } catch (PatchListNotAvailableException e) {
-            throw new SystemException(String.format("Cannot create %s: %s", plKey, e.getMessage()));
+            diffList =
+                diffOperations.listModifiedFilesAgainstParent(
+                    project, ps.commitId(), /* parentNum= */ 0);
+          } catch (DiffNotAvailableException e) {
+            throw new SystemException(
+                String.format(
+                    "Cannot create modified files for project %s, commit Id %s: %s",
+                    project, ps.commitId(), e.getMessage()));
           }
-          return patchList;
+          return diffList;
         }
       };
 
diff --git a/java/gerrit/BUILD b/java/gerrit/BUILD
index db831b7..fea2696 100644
--- a/java/gerrit/BUILD
+++ b/java/gerrit/BUILD
@@ -5,6 +5,7 @@
     srcs = glob(["**/*.java"]),
     visibility = ["//visibility:public"],
     deps = [
+        "//java/com/google/gerrit/common:annotations",
         "//java/com/google/gerrit/entities",
         "//java/com/google/gerrit/extensions:api",
         "//java/com/google/gerrit/server",
diff --git a/java/gerrit/PRED_commit_edits_2.java b/java/gerrit/PRED_commit_edits_2.java
index 12e7086..6083010 100644
--- a/java/gerrit/PRED_commit_edits_2.java
+++ b/java/gerrit/PRED_commit_edits_2.java
@@ -14,10 +14,13 @@
 
 package gerrit;
 
+import com.google.common.collect.Iterables;
+import com.google.gerrit.common.Nullable;
 import com.google.gerrit.entities.Patch;
-import com.google.gerrit.server.patch.PatchList;
-import com.google.gerrit.server.patch.PatchListEntry;
+import com.google.gerrit.server.patch.FilePathAdapter;
 import com.google.gerrit.server.patch.Text;
+import com.google.gerrit.server.patch.filediff.FileDiffOutput;
+import com.google.gerrit.server.patch.filediff.TaggedEdit;
 import com.google.gerrit.server.rules.StoredValues;
 import com.googlecode.prolog_cafe.exceptions.IllegalTypeException;
 import com.googlecode.prolog_cafe.exceptions.JavaException;
@@ -31,7 +34,9 @@
 import com.googlecode.prolog_cafe.lang.VariableTerm;
 import java.io.IOException;
 import java.util.List;
+import java.util.Map;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 import org.eclipse.jgit.diff.Edit;
 import org.eclipse.jgit.errors.CorruptObjectException;
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
@@ -40,7 +45,6 @@
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevTree;
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.treewalk.TreeWalk;
@@ -69,27 +73,26 @@
     Pattern fileRegex = getRegexParameter(a1);
     Pattern editRegex = getRegexParameter(a2);
 
-    PatchList pl = StoredValues.PATCH_LIST.get(engine);
+    Map<String, FileDiffOutput> modifiedFiles = StoredValues.DIFF_LIST.get(engine);
+    FileDiffOutput firstDiff = Iterables.getFirst(modifiedFiles.values(), /* defaultValue= */ null);
+    if (firstDiff == null) {
+      // No available diffs. We cannot identify old and new commit IDs.
+      engine.fail();
+    }
     Repository repo = StoredValues.REPOSITORY.get(engine);
 
     try (ObjectReader reader = repo.newObjectReader();
         RevWalk rw = new RevWalk(reader)) {
-      final RevTree aTree;
-      final RevTree bTree;
-      final RevCommit bCommit = rw.parseCommit(pl.getNewId());
+      final RevTree aTree =
+          firstDiff.oldCommitId().equals(ObjectId.zeroId())
+              ? null
+              : rw.parseTree(firstDiff.oldCommitId());
+      final RevTree bTree = rw.parseCommit(firstDiff.newCommitId()).getTree();
 
-      if (pl.getOldId() != null) {
-        aTree = rw.parseTree(pl.getOldId());
-      } else {
-        // Octopus merge with unknown automatic merge result, since the
-        // web UI returns no files to match against, just fail.
-        return engine.fail();
-      }
-      bTree = bCommit.getTree();
-
-      for (PatchListEntry entry : pl.getPatches()) {
-        String newName = entry.getNewName();
-        String oldName = entry.getOldName();
+      for (FileDiffOutput entry : modifiedFiles.values()) {
+        String newName =
+            FilePathAdapter.getNewPath(entry.oldPath(), entry.newPath(), entry.changeType());
+        String oldName = FilePathAdapter.getOldPath(entry.oldPath(), entry.changeType());
 
         if (Patch.isMagic(newName)) {
           continue;
@@ -97,7 +100,8 @@
 
         if (fileRegex.matcher(newName).find()
             || (oldName != null && fileRegex.matcher(oldName).find())) {
-          List<Edit> edits = entry.getEdits();
+          List<Edit> edits =
+              entry.edits().stream().map(TaggedEdit::jgitEdit).collect(Collectors.toList());
           if (edits.isEmpty()) {
             continue;
           }
@@ -141,10 +145,10 @@
     return Pattern.compile(term.name(), Pattern.MULTILINE);
   }
 
-  private Text load(ObjectId tree, String path, ObjectReader reader)
+  private Text load(@Nullable ObjectId tree, String path, ObjectReader reader)
       throws MissingObjectException, IncorrectObjectTypeException, CorruptObjectException,
           IOException {
-    if (path == null) {
+    if (tree == null || path == null) {
       return Text.EMPTY;
     }
     final TreeWalk tw = TreeWalk.forPath(reader, path, tree);
diff --git a/java/gerrit/PRED_commit_stats_3.java b/java/gerrit/PRED_commit_stats_3.java
index 286bc2c..82fad3d 100644
--- a/java/gerrit/PRED_commit_stats_3.java
+++ b/java/gerrit/PRED_commit_stats_3.java
@@ -15,8 +15,7 @@
 package gerrit;
 
 import com.google.gerrit.entities.Patch;
-import com.google.gerrit.server.patch.PatchList;
-import com.google.gerrit.server.patch.PatchListEntry;
+import com.google.gerrit.server.patch.filediff.FileDiffOutput;
 import com.google.gerrit.server.rules.StoredValues;
 import com.googlecode.prolog_cafe.exceptions.PrologException;
 import com.googlecode.prolog_cafe.lang.IntegerTerm;
@@ -24,7 +23,8 @@
 import com.googlecode.prolog_cafe.lang.Predicate;
 import com.googlecode.prolog_cafe.lang.Prolog;
 import com.googlecode.prolog_cafe.lang.Term;
-import java.util.List;
+import java.util.Collection;
+import java.util.Map;
 
 /**
  * Exports basic commit statistics.
@@ -49,25 +49,30 @@
     Term a2 = arg2.dereference();
     Term a3 = arg3.dereference();
 
-    PatchList pl = StoredValues.PATCH_LIST.get(engine);
+    Map<String, FileDiffOutput> modifiedFiles = StoredValues.DIFF_LIST.get(engine);
     // Account for magic files
     if (!a1.unify(
-        new IntegerTerm(pl.getPatches().size() - countMagicFiles(pl.getPatches())), engine.trail)) {
+        new IntegerTerm(modifiedFiles.size() - countMagicFiles(modifiedFiles.values())),
+        engine.trail)) {
       return engine.fail();
     }
-    if (!a2.unify(new IntegerTerm(pl.getInsertions()), engine.trail)) {
+    Integer insertions =
+        modifiedFiles.values().stream().map(FileDiffOutput::insertions).reduce(0, Integer::sum);
+    Integer deletions =
+        modifiedFiles.values().stream().map(FileDiffOutput::deletions).reduce(0, Integer::sum);
+    if (!a2.unify(new IntegerTerm(insertions), engine.trail)) {
       return engine.fail();
     }
-    if (!a3.unify(new IntegerTerm(pl.getDeletions()), engine.trail)) {
+    if (!a3.unify(new IntegerTerm(deletions), engine.trail)) {
       return engine.fail();
     }
     return cont;
   }
 
-  private int countMagicFiles(List<PatchListEntry> entries) {
+  private int countMagicFiles(Collection<FileDiffOutput> entries) {
     int count = 0;
-    for (PatchListEntry e : entries) {
-      if (Patch.isMagic(e.getNewName())) {
+    for (FileDiffOutput e : entries) {
+      if (e.newPath().isPresent() && Patch.isMagic(e.newPath().get())) {
         count++;
       }
     }
diff --git a/java/gerrit/PRED_files_1.java b/java/gerrit/PRED_files_1.java
index ac45449..dbf96da 100644
--- a/java/gerrit/PRED_files_1.java
+++ b/java/gerrit/PRED_files_1.java
@@ -15,7 +15,8 @@
 package gerrit;
 
 import com.google.gerrit.entities.Patch;
-import com.google.gerrit.server.patch.PatchListEntry;
+import com.google.gerrit.server.patch.FilePathAdapter;
+import com.google.gerrit.server.patch.filediff.FileDiffOutput;
 import com.google.gerrit.server.rules.StoredValues;
 import com.googlecode.prolog_cafe.exceptions.PrologException;
 import com.googlecode.prolog_cafe.lang.ListTerm;
@@ -26,8 +27,8 @@
 import com.googlecode.prolog_cafe.lang.SymbolTerm;
 import com.googlecode.prolog_cafe.lang.Term;
 import java.io.IOException;
+import java.util.Collection;
 import java.util.HashSet;
-import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
 import org.eclipse.jgit.lib.FileMode;
@@ -54,17 +55,20 @@
 
     try (RevWalk revWalk = new RevWalk(StoredValues.REPOSITORY.get(engine))) {
       RevCommit commit = revWalk.parseCommit(StoredValues.getPatchSet(engine).commitId());
-      List<PatchListEntry> patches = StoredValues.PATCH_LIST.get(engine).getPatches();
+      Collection<FileDiffOutput> modifiedFiles = StoredValues.DIFF_LIST.get(engine).values();
       Set<String> submodules =
-          getAllSubmodulePaths(StoredValues.REPOSITORY.get(engine), commit, patches);
-      for (PatchListEntry entry : patches) {
-        if (Patch.isMagic(entry.getNewName())) {
+          getAllSubmodulePaths(StoredValues.REPOSITORY.get(engine), commit, modifiedFiles);
+      for (FileDiffOutput fileDiff : modifiedFiles) {
+        if (fileDiff.newPath().isPresent() && Patch.isMagic(fileDiff.newPath().get())) {
           continue;
         }
-        SymbolTerm fileNameTerm = SymbolTerm.create(entry.getNewName());
-        SymbolTerm changeType = SymbolTerm.create(entry.getChangeType().getCode());
+        String newPath =
+            FilePathAdapter.getNewPath(
+                fileDiff.oldPath(), fileDiff.newPath(), fileDiff.changeType());
+        SymbolTerm fileNameTerm = SymbolTerm.create(newPath);
+        SymbolTerm changeType = SymbolTerm.create(fileDiff.changeType().getCode());
         SymbolTerm fileType;
-        if (submodules.contains(entry.getNewName())) {
+        if (submodules.contains(newPath)) {
           fileType = SymbolTerm.create("SUBMODULE");
         } else {
           fileType = SymbolTerm.create("REGULAR");
@@ -83,14 +87,14 @@
 
   /** Returns the paths for all {@code GITLINK} files. */
   private static Set<String> getAllSubmodulePaths(
-      Repository repository, RevCommit commit, List<PatchListEntry> patches)
+      Repository repository, RevCommit commit, Collection<FileDiffOutput> modifiedFiles)
       throws PrologException, IOException {
     Set<String> submodules = new HashSet<>();
     try (TreeWalk treeWalk = new TreeWalk(repository)) {
       treeWalk.addTree(commit.getTree());
       Set<String> allPaths =
-          patches.stream()
-              .map(PatchListEntry::getNewName)
+          modifiedFiles.stream()
+              .map(f -> FilePathAdapter.getNewPath(f.oldPath(), f.newPath(), f.changeType()))
               .filter(f -> !Patch.isMagic(f))
               .collect(Collectors.toSet());
       treeWalk.setFilter(PathFilterGroup.createFromStrings(allPaths));