Merge "Fix display of gr-commit-info in inline header"
diff --git a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index 2b899df..dda1e6c 100644
--- a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -84,7 +84,7 @@
 import com.google.gerrit.server.account.Accounts;
 import com.google.gerrit.server.account.GroupBackend;
 import com.google.gerrit.server.account.GroupCache;
-import com.google.gerrit.server.change.Abandon;
+import com.google.gerrit.server.change.BatchAbandon;
 import com.google.gerrit.server.change.ChangeResource;
 import com.google.gerrit.server.change.FileContentUtil;
 import com.google.gerrit.server.change.RevisionResource;
@@ -237,7 +237,7 @@
   @Inject protected SystemGroupBackend systemGroupBackend;
   @Inject protected MutableNotesMigration notesMigration;
   @Inject protected ChangeNotes.Factory notesFactory;
-  @Inject protected Abandon changeAbandoner;
+  @Inject protected BatchAbandon batchAbandon;
 
   protected EventRecorder eventRecorder;
   protected GerritServer server;
diff --git a/java/com/google/gerrit/acceptance/BUILD b/java/com/google/gerrit/acceptance/BUILD
index 732c1f7..ccdb415 100644
--- a/java/com/google/gerrit/acceptance/BUILD
+++ b/java/com/google/gerrit/acceptance/BUILD
@@ -59,6 +59,7 @@
     "//java/com/google/gerrit/metrics",
     "//java/com/google/gerrit/reviewdb:server",
     "//java/com/google/gerrit/server",
+    "//java/com/google/gerrit/server/schema",
     "//java/com/google/gerrit/pgm/init",
     "//java/com/google/gerrit/server/git/receive",
     "//lib:gson",
diff --git a/java/com/google/gerrit/httpd/init/BUILD b/java/com/google/gerrit/httpd/init/BUILD
index 7fc3074..936044d 100644
--- a/java/com/google/gerrit/httpd/init/BUILD
+++ b/java/com/google/gerrit/httpd/init/BUILD
@@ -20,6 +20,7 @@
         "//java/com/google/gerrit/server/api",
         "//java/com/google/gerrit/server/cache/h2",
         "//java/com/google/gerrit/server/git/receive",
+        "//java/com/google/gerrit/server/schema",
         "//java/com/google/gerrit/sshd",
         "//lib:guava",
         "//lib:gwtorm",
diff --git a/java/com/google/gerrit/pgm/BUILD b/java/com/google/gerrit/pgm/BUILD
index 4dfaf1c..3dce217 100644
--- a/java/com/google/gerrit/pgm/BUILD
+++ b/java/com/google/gerrit/pgm/BUILD
@@ -38,6 +38,7 @@
         "//java/com/google/gerrit/server/api",
         "//java/com/google/gerrit/server/cache/h2",
         "//java/com/google/gerrit/server/git/receive",
+        "//java/com/google/gerrit/server/schema",
         "//java/com/google/gerrit/sshd",
         "//java/com/google/gwtexpui/linker:server",
         "//java/com/google/gwtexpui/server",
diff --git a/java/com/google/gerrit/pgm/init/BUILD b/java/com/google/gerrit/pgm/init/BUILD
index ef3a063..4b53b67 100644
--- a/java/com/google/gerrit/pgm/init/BUILD
+++ b/java/com/google/gerrit/pgm/init/BUILD
@@ -17,6 +17,7 @@
         "//java/com/google/gerrit/pgm/util",
         "//java/com/google/gerrit/reviewdb:server",
         "//java/com/google/gerrit/server",
+        "//java/com/google/gerrit/server/schema",
         "//lib:guava",
         "//lib:gwtjsonrpc",
         "//lib:gwtorm",
diff --git a/java/com/google/gerrit/pgm/util/BUILD b/java/com/google/gerrit/pgm/util/BUILD
index 42cdf6f..d94211c 100644
--- a/java/com/google/gerrit/pgm/util/BUILD
+++ b/java/com/google/gerrit/pgm/util/BUILD
@@ -13,6 +13,7 @@
         "//java/com/google/gerrit/server:module",
         "//java/com/google/gerrit/server/cache/h2",
         "//java/com/google/gerrit/server/git/receive",
+        "//java/com/google/gerrit/server/schema",
         "//java/com/google/gerrit/util/cli",
         "//lib:args4j",
         "//lib:guava",
diff --git a/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java b/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
index 701384b..f368c17 100644
--- a/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
+++ b/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
@@ -64,7 +64,6 @@
 import com.google.gerrit.server.change.GetAssignee;
 import com.google.gerrit.server.change.GetHashtags;
 import com.google.gerrit.server.change.GetPastAssignees;
-import com.google.gerrit.server.change.GetPureRevert;
 import com.google.gerrit.server.change.GetTopic;
 import com.google.gerrit.server.change.Ignore;
 import com.google.gerrit.server.change.Index;
@@ -77,6 +76,7 @@
 import com.google.gerrit.server.change.PostHashtags;
 import com.google.gerrit.server.change.PostPrivate;
 import com.google.gerrit.server.change.PostReviewers;
+import com.google.gerrit.server.change.PureRevert;
 import com.google.gerrit.server.change.PutAssignee;
 import com.google.gerrit.server.change.PutMessage;
 import com.google.gerrit.server.change.PutTopic;
@@ -147,7 +147,7 @@
   private final SetWorkInProgress setWip;
   private final SetReadyForReview setReady;
   private final PutMessage putMessage;
-  private final GetPureRevert getPureRevert;
+  private final PureRevert pureRevert;
   private final StarredChangesUtil stars;
 
   @Inject
@@ -192,7 +192,7 @@
       SetWorkInProgress setWip,
       SetReadyForReview setReady,
       PutMessage putMessage,
-      GetPureRevert getPureRevert,
+      PureRevert pureRevert,
       StarredChangesUtil stars,
       @Assisted ChangeResource change) {
     this.changeApi = changeApi;
@@ -235,7 +235,7 @@
     this.setWip = setWip;
     this.setReady = setReady;
     this.putMessage = putMessage;
-    this.getPureRevert = getPureRevert;
+    this.pureRevert = pureRevert;
     this.stars = stars;
     this.change = change;
   }
@@ -702,7 +702,7 @@
   @Override
   public PureRevertInfo pureRevert(@Nullable String claimedOriginal) throws RestApiException {
     try {
-      return getPureRevert.setClaimedOriginal(claimedOriginal).apply(change);
+      return pureRevert.get(change.getNotes(), claimedOriginal);
     } catch (Exception e) {
       throw asRestApiException("Cannot compute pure revert", e);
     }
diff --git a/java/com/google/gerrit/server/change/Abandon.java b/java/com/google/gerrit/server/change/Abandon.java
index c7addff..f07efea 100644
--- a/java/com/google/gerrit/server/change/Abandon.java
+++ b/java/com/google/gerrit/server/change/Abandon.java
@@ -23,18 +23,15 @@
 import com.google.gerrit.extensions.api.changes.NotifyHandling;
 import com.google.gerrit.extensions.api.changes.RecipientType;
 import com.google.gerrit.extensions.common.ChangeInfo;
-import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.extensions.webui.UiAction;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.permissions.ChangePermission;
 import com.google.gerrit.server.permissions.PermissionBackendException;
-import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.update.BatchUpdate;
 import com.google.gerrit.server.update.RetryHelper;
 import com.google.gerrit.server.update.RetryingRestModifyView;
@@ -44,7 +41,6 @@
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
 import java.io.IOException;
-import java.util.Collection;
 import org.eclipse.jgit.errors.ConfigInvalidException;
 
 @Singleton
@@ -132,69 +128,6 @@
     return op.getChange();
   }
 
-  /**
-   * If an extension has more than one changes to abandon that belong to the same project, they
-   * should use the batch instead of abandoning one by one.
-   *
-   * <p>It's the caller's responsibility to ensure that all jobs inside the same batch have the
-   * matching project from its ChangeData. Violations will result in a ResourceConflictException.
-   */
-  public void batchAbandon(
-      BatchUpdate.Factory updateFactory,
-      Project.NameKey project,
-      CurrentUser user,
-      Collection<ChangeData> changes,
-      String msgTxt,
-      NotifyHandling notifyHandling,
-      ListMultimap<RecipientType, Account.Id> accountsToNotify)
-      throws RestApiException, UpdateException {
-    if (changes.isEmpty()) {
-      return;
-    }
-    Account account = user.isIdentifiedUser() ? user.asIdentifiedUser().getAccount() : null;
-    try (BatchUpdate u = updateFactory.create(dbProvider.get(), project, user, TimeUtil.nowTs())) {
-      for (ChangeData change : changes) {
-        if (!project.equals(change.project())) {
-          throw new ResourceConflictException(
-              String.format(
-                  "Project name \"%s\" doesn't match \"%s\"",
-                  change.project().get(), project.get()));
-        }
-        u.addOp(
-            change.getId(),
-            abandonOpFactory.create(account, msgTxt, notifyHandling, accountsToNotify));
-      }
-      u.execute();
-    }
-  }
-
-  public void batchAbandon(
-      BatchUpdate.Factory updateFactory,
-      Project.NameKey project,
-      CurrentUser user,
-      Collection<ChangeData> changes,
-      String msgTxt)
-      throws RestApiException, UpdateException {
-    batchAbandon(
-        updateFactory,
-        project,
-        user,
-        changes,
-        msgTxt,
-        NotifyHandling.ALL,
-        ImmutableListMultimap.of());
-  }
-
-  public void batchAbandon(
-      BatchUpdate.Factory updateFactory,
-      Project.NameKey project,
-      CurrentUser user,
-      Collection<ChangeData> changes)
-      throws RestApiException, UpdateException {
-    batchAbandon(
-        updateFactory, project, user, changes, "", NotifyHandling.ALL, ImmutableListMultimap.of());
-  }
-
   @Override
   public UiAction.Description getDescription(ChangeResource rsrc) {
     Change change = rsrc.getChange();
diff --git a/java/com/google/gerrit/server/change/AbandonUtil.java b/java/com/google/gerrit/server/change/AbandonUtil.java
index 3239813..9866ea9 100644
--- a/java/com/google/gerrit/server/change/AbandonUtil.java
+++ b/java/com/google/gerrit/server/change/AbandonUtil.java
@@ -42,7 +42,7 @@
   private final ChangeCleanupConfig cfg;
   private final Provider<ChangeQueryProcessor> queryProvider;
   private final ChangeQueryBuilder queryBuilder;
-  private final Abandon abandon;
+  private final BatchAbandon batchAbandon;
   private final InternalUser internalUser;
 
   @Inject
@@ -51,11 +51,11 @@
       InternalUser.Factory internalUserFactory,
       Provider<ChangeQueryProcessor> queryProvider,
       ChangeQueryBuilder queryBuilder,
-      Abandon abandon) {
+      BatchAbandon batchAbandon) {
     this.cfg = cfg;
     this.queryProvider = queryProvider;
     this.queryBuilder = queryBuilder;
-    this.abandon = abandon;
+    this.batchAbandon = batchAbandon;
     internalUser = internalUserFactory.create();
   }
 
@@ -85,7 +85,7 @@
       for (Project.NameKey project : abandons.keySet()) {
         Collection<ChangeData> changes = getValidChanges(abandons.get(project), query);
         try {
-          abandon.batchAbandon(updateFactory, project, internalUser, changes, message);
+          batchAbandon.batchAbandon(updateFactory, project, internalUser, changes, message);
           count += changes.size();
         } catch (Throwable e) {
           StringBuilder msg = new StringBuilder("Failed to auto-abandon inactive change(s):");
diff --git a/java/com/google/gerrit/server/change/BatchAbandon.java b/java/com/google/gerrit/server/change/BatchAbandon.java
new file mode 100644
index 0000000..059f110
--- /dev/null
+++ b/java/com/google/gerrit/server/change/BatchAbandon.java
@@ -0,0 +1,109 @@
+// Copyright (C) 2012 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.change;
+
+import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.ListMultimap;
+import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.extensions.api.changes.NotifyHandling;
+import com.google.gerrit.extensions.api.changes.RecipientType;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.update.BatchUpdate;
+import com.google.gerrit.server.update.UpdateException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import java.util.Collection;
+
+@Singleton
+public class BatchAbandon {
+  private final Provider<ReviewDb> dbProvider;
+  private final AbandonOp.Factory abandonOpFactory;
+
+  @Inject
+  BatchAbandon(Provider<ReviewDb> dbProvider, AbandonOp.Factory abandonOpFactory) {
+    this.dbProvider = dbProvider;
+    this.abandonOpFactory = abandonOpFactory;
+  }
+
+  /**
+   * If an extension has more than one changes to abandon that belong to the same project, they
+   * should use the batch instead of abandoning one by one.
+   *
+   * <p>It's the caller's responsibility to ensure that all jobs inside the same batch have the
+   * matching project from its ChangeData. Violations will result in a ResourceConflictException.
+   */
+  public void batchAbandon(
+      BatchUpdate.Factory updateFactory,
+      Project.NameKey project,
+      CurrentUser user,
+      Collection<ChangeData> changes,
+      String msgTxt,
+      NotifyHandling notifyHandling,
+      ListMultimap<RecipientType, Account.Id> accountsToNotify)
+      throws RestApiException, UpdateException {
+    if (changes.isEmpty()) {
+      return;
+    }
+    Account account = user.isIdentifiedUser() ? user.asIdentifiedUser().getAccount() : null;
+    try (BatchUpdate u = updateFactory.create(dbProvider.get(), project, user, TimeUtil.nowTs())) {
+      for (ChangeData change : changes) {
+        if (!project.equals(change.project())) {
+          throw new ResourceConflictException(
+              String.format(
+                  "Project name \"%s\" doesn't match \"%s\"",
+                  change.project().get(), project.get()));
+        }
+        u.addOp(
+            change.getId(),
+            abandonOpFactory.create(account, msgTxt, notifyHandling, accountsToNotify));
+      }
+      u.execute();
+    }
+  }
+
+  public void batchAbandon(
+      BatchUpdate.Factory updateFactory,
+      Project.NameKey project,
+      CurrentUser user,
+      Collection<ChangeData> changes,
+      String msgTxt)
+      throws RestApiException, UpdateException {
+    batchAbandon(
+        updateFactory,
+        project,
+        user,
+        changes,
+        msgTxt,
+        NotifyHandling.ALL,
+        ImmutableListMultimap.of());
+  }
+
+  public void batchAbandon(
+      BatchUpdate.Factory updateFactory,
+      Project.NameKey project,
+      CurrentUser user,
+      Collection<ChangeData> changes)
+      throws RestApiException, UpdateException {
+    batchAbandon(
+        updateFactory, project, user, changes, "", NotifyHandling.ALL, ImmutableListMultimap.of());
+  }
+}
diff --git a/java/com/google/gerrit/server/change/DeleteReviewerOp.java b/java/com/google/gerrit/server/change/DeleteReviewerOp.java
index ad1cf60..a398195 100644
--- a/java/com/google/gerrit/server/change/DeleteReviewerOp.java
+++ b/java/com/google/gerrit/server/change/DeleteReviewerOp.java
@@ -60,7 +60,7 @@
 import org.slf4j.LoggerFactory;
 
 public class DeleteReviewerOp implements BatchUpdateOp {
-  private static final Logger log = LoggerFactory.getLogger(DeleteReviewer.class);
+  private static final Logger log = LoggerFactory.getLogger(DeleteReviewerOp.class);
 
   public interface Factory {
     DeleteReviewerOp create(Account reviewerAccount, DeleteReviewerInput input);
diff --git a/java/com/google/gerrit/server/change/GetPureRevert.java b/java/com/google/gerrit/server/change/GetPureRevert.java
index 27c5d49..815ce07 100644
--- a/java/com/google/gerrit/server/change/GetPureRevert.java
+++ b/java/com/google/gerrit/server/change/GetPureRevert.java
@@ -20,38 +20,14 @@
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.PatchSetUtil;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.MergeUtil;
-import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gerrit.server.project.ProjectCache;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
-import com.google.inject.Provider;
-import java.io.ByteArrayOutputStream;
 import java.io.IOException;
-import java.util.List;
-import org.eclipse.jgit.diff.DiffEntry;
-import org.eclipse.jgit.diff.DiffFormatter;
-import org.eclipse.jgit.errors.InvalidObjectIdException;
-import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectInserter;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.merge.ThreeWayMerger;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevWalk;
 import org.kohsuke.args4j.Option;
 
 public class GetPureRevert implements RestReadView<ChangeResource> {
-  private final MergeUtil.Factory mergeUtilFactory;
-  private final GitRepositoryManager repoManager;
-  private final ProjectCache projectCache;
-  private final ChangeNotes.Factory notesFactory;
-  private final Provider<ReviewDb> dbProvider;
-  private final PatchSetUtil psUtil;
+
+  private final PureRevert pureRevert;
 
   @Option(
     name = "--claimed-original",
@@ -62,93 +38,14 @@
   private String claimedOriginal;
 
   @Inject
-  GetPureRevert(
-      MergeUtil.Factory mergeUtilFactory,
-      GitRepositoryManager repoManager,
-      ProjectCache projectCache,
-      ChangeNotes.Factory notesFactory,
-      Provider<ReviewDb> dbProvider,
-      PatchSetUtil psUtil) {
-    this.mergeUtilFactory = mergeUtilFactory;
-    this.repoManager = repoManager;
-    this.projectCache = projectCache;
-    this.notesFactory = notesFactory;
-    this.dbProvider = dbProvider;
-    this.psUtil = psUtil;
+  GetPureRevert(PureRevert pureRevert) {
+    this.pureRevert = pureRevert;
   }
 
   @Override
   public PureRevertInfo apply(ChangeResource rsrc)
       throws ResourceConflictException, IOException, BadRequestException, OrmException,
           AuthException {
-    PatchSet currentPatchSet = psUtil.current(dbProvider.get(), rsrc.getNotes());
-    if (currentPatchSet == null) {
-      throw new ResourceConflictException("current revision is missing");
-    }
-    return getPureRevert(rsrc.getNotes());
-  }
-
-  public PureRevertInfo getPureRevert(ChangeNotes notes)
-      throws OrmException, IOException, BadRequestException, ResourceConflictException {
-    PatchSet currentPatchSet = psUtil.current(dbProvider.get(), notes);
-    if (currentPatchSet == null) {
-      throw new ResourceConflictException("current revision is missing");
-    }
-
-    if (claimedOriginal == null) {
-      if (notes.getChange().getRevertOf() == null) {
-        throw new BadRequestException("no ID was provided and change isn't a revert");
-      }
-      PatchSet ps =
-          psUtil.current(
-              dbProvider.get(),
-              notesFactory.createChecked(
-                  dbProvider.get(), notes.getProjectName(), notes.getChange().getRevertOf()));
-      claimedOriginal = ps.getRevision().get();
-    }
-
-    try (Repository repo = repoManager.openRepository(notes.getProjectName());
-        ObjectInserter oi = repo.newObjectInserter();
-        RevWalk rw = new RevWalk(repo)) {
-      RevCommit claimedOriginalCommit;
-      try {
-        claimedOriginalCommit = rw.parseCommit(ObjectId.fromString(claimedOriginal));
-      } catch (InvalidObjectIdException | MissingObjectException e) {
-        throw new BadRequestException("invalid object ID");
-      }
-      if (claimedOriginalCommit.getParentCount() == 0) {
-        throw new BadRequestException("can't check against initial commit");
-      }
-      RevCommit claimedRevertCommit =
-          rw.parseCommit(ObjectId.fromString(currentPatchSet.getRevision().get()));
-      if (claimedRevertCommit.getParentCount() == 0) {
-        throw new BadRequestException("claimed revert has no parents");
-      }
-      // Rebase claimed revert onto claimed original
-      ThreeWayMerger merger =
-          mergeUtilFactory
-              .create(projectCache.checkedGet(notes.getProjectName()))
-              .newThreeWayMerger(oi, repo.getConfig());
-      merger.setBase(claimedRevertCommit.getParent(0));
-      merger.merge(claimedRevertCommit, claimedOriginalCommit);
-      if (merger.getResultTreeId() == null) {
-        // Merge conflict during rebase
-        return new PureRevertInfo(false);
-      }
-
-      // Any differences between claimed original's parent and the rebase result indicate that the
-      // claimedRevert is not a pure revert but made content changes
-      try (DiffFormatter df = new DiffFormatter(new ByteArrayOutputStream())) {
-        df.setRepository(repo);
-        List<DiffEntry> entries =
-            df.scan(claimedOriginalCommit.getParent(0), merger.getResultTreeId());
-        return new PureRevertInfo(entries.isEmpty());
-      }
-    }
-  }
-
-  public GetPureRevert setClaimedOriginal(String claimedOriginal) {
-    this.claimedOriginal = claimedOriginal;
-    return this;
+    return pureRevert.get(rsrc.getNotes(), claimedOriginal);
   }
 }
diff --git a/java/com/google/gerrit/server/change/PureRevert.java b/java/com/google/gerrit/server/change/PureRevert.java
new file mode 100644
index 0000000..850f33a
--- /dev/null
+++ b/java/com/google/gerrit/server/change/PureRevert.java
@@ -0,0 +1,127 @@
+// Copyright (C) 2017 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.change;
+
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.extensions.common.PureRevertInfo;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.PatchSetUtil;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MergeUtil;
+import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.List;
+import org.eclipse.jgit.diff.DiffEntry;
+import org.eclipse.jgit.diff.DiffFormatter;
+import org.eclipse.jgit.errors.InvalidObjectIdException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.merge.ThreeWayMerger;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+public class PureRevert {
+  private final MergeUtil.Factory mergeUtilFactory;
+  private final GitRepositoryManager repoManager;
+  private final ProjectCache projectCache;
+  private final ChangeNotes.Factory notesFactory;
+  private final Provider<ReviewDb> dbProvider;
+  private final PatchSetUtil psUtil;
+
+  @Inject
+  PureRevert(
+      MergeUtil.Factory mergeUtilFactory,
+      GitRepositoryManager repoManager,
+      ProjectCache projectCache,
+      ChangeNotes.Factory notesFactory,
+      Provider<ReviewDb> dbProvider,
+      PatchSetUtil psUtil) {
+    this.mergeUtilFactory = mergeUtilFactory;
+    this.repoManager = repoManager;
+    this.projectCache = projectCache;
+    this.notesFactory = notesFactory;
+    this.dbProvider = dbProvider;
+    this.psUtil = psUtil;
+  }
+
+  public PureRevertInfo get(ChangeNotes notes, @Nullable String claimedOriginal)
+      throws OrmException, IOException, BadRequestException, ResourceConflictException {
+    PatchSet currentPatchSet = psUtil.current(dbProvider.get(), notes);
+    if (currentPatchSet == null) {
+      throw new ResourceConflictException("current revision is missing");
+    }
+
+    if (claimedOriginal == null) {
+      if (notes.getChange().getRevertOf() == null) {
+        throw new BadRequestException("no ID was provided and change isn't a revert");
+      }
+      PatchSet ps =
+          psUtil.current(
+              dbProvider.get(),
+              notesFactory.createChecked(
+                  dbProvider.get(), notes.getProjectName(), notes.getChange().getRevertOf()));
+      claimedOriginal = ps.getRevision().get();
+    }
+
+    try (Repository repo = repoManager.openRepository(notes.getProjectName());
+        ObjectInserter oi = repo.newObjectInserter();
+        RevWalk rw = new RevWalk(repo)) {
+      RevCommit claimedOriginalCommit;
+      try {
+        claimedOriginalCommit = rw.parseCommit(ObjectId.fromString(claimedOriginal));
+      } catch (InvalidObjectIdException | MissingObjectException e) {
+        throw new BadRequestException("invalid object ID");
+      }
+      if (claimedOriginalCommit.getParentCount() == 0) {
+        throw new BadRequestException("can't check against initial commit");
+      }
+      RevCommit claimedRevertCommit =
+          rw.parseCommit(ObjectId.fromString(currentPatchSet.getRevision().get()));
+      if (claimedRevertCommit.getParentCount() == 0) {
+        throw new BadRequestException("claimed revert has no parents");
+      }
+      // Rebase claimed revert onto claimed original
+      ThreeWayMerger merger =
+          mergeUtilFactory
+              .create(projectCache.checkedGet(notes.getProjectName()))
+              .newThreeWayMerger(oi, repo.getConfig());
+      merger.setBase(claimedRevertCommit.getParent(0));
+      merger.merge(claimedRevertCommit, claimedOriginalCommit);
+      if (merger.getResultTreeId() == null) {
+        // Merge conflict during rebase
+        return new PureRevertInfo(false);
+      }
+
+      // Any differences between claimed original's parent and the rebase result indicate that the
+      // claimedRevert is not a pure revert but made content changes
+      try (DiffFormatter df = new DiffFormatter(new ByteArrayOutputStream())) {
+        df.setRepository(repo);
+        List<DiffEntry> entries =
+            df.scan(claimedOriginalCommit.getParent(0), merger.getResultTreeId());
+        return new PureRevertInfo(entries.isEmpty());
+      }
+    }
+  }
+}
diff --git a/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java b/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java
index 1c66e89..abdb517 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java
@@ -126,7 +126,7 @@
     this.accountCache = accountCache;
     this.serverIdent = serverIdent;
     this.serverId = serverId;
-    this.writeJson = config.getBoolean("notedb", "writeJson", false);
+    this.writeJson = config.getBoolean("notedb", "writeJson", true);
   }
 
   @VisibleForTesting
diff --git a/java/com/google/gerrit/server/query/change/ChangeData.java b/java/com/google/gerrit/server/query/change/ChangeData.java
index f07ae88..e73db1a 100644
--- a/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -59,8 +59,8 @@
 import com.google.gerrit.server.ReviewerStatusUpdate;
 import com.google.gerrit.server.StarredChangesUtil;
 import com.google.gerrit.server.StarredChangesUtil.StarRef;
-import com.google.gerrit.server.change.GetPureRevert;
 import com.google.gerrit.server.change.MergeabilityCache;
+import com.google.gerrit.server.change.PureRevert;
 import com.google.gerrit.server.config.AllUsersName;
 import com.google.gerrit.server.config.TrackingFooters;
 import com.google.gerrit.server.git.GitRepositoryManager;
@@ -346,7 +346,7 @@
   private final PatchSetUtil psUtil;
   private final ProjectCache projectCache;
   private final TrackingFooters trackingFooters;
-  private final GetPureRevert pureRevert;
+  private final PureRevert pureRevert;
   private final SubmitRuleEvaluator.Factory submitRuleEvaluatorFactory;
 
   // Required assisted injected fields.
@@ -415,7 +415,7 @@
       PatchSetUtil psUtil,
       ProjectCache projectCache,
       TrackingFooters trackingFooters,
-      GetPureRevert pureRevert,
+      PureRevert pureRevert,
       SubmitRuleEvaluator.Factory submitRuleEvaluatorFactory,
       @Assisted ReviewDb db,
       @Assisted Project.NameKey project,
@@ -1178,7 +1178,7 @@
       return null;
     }
     try {
-      return pureRevert.getPureRevert(notes()).isPureRevert;
+      return pureRevert.get(notes(), null).isPureRevert;
     } catch (IOException | BadRequestException | ResourceConflictException e) {
       throw new OrmException("could not compute pure revert", e);
     }
diff --git a/java/com/google/gerrit/server/schema/BUILD b/java/com/google/gerrit/server/schema/BUILD
new file mode 100644
index 0000000..2292234
--- /dev/null
+++ b/java/com/google/gerrit/server/schema/BUILD
@@ -0,0 +1,27 @@
+java_library(
+    name = "schema",
+    srcs = glob(
+        ["**/*.java"],
+    ),
+    visibility = ["//visibility:public"],
+    deps = [
+        "//java/com/google/gerrit/common:annotations",
+        "//java/com/google/gerrit/common:server",
+        "//java/com/google/gerrit/extensions:api",
+        "//java/com/google/gerrit/lifecycle",
+        "//java/com/google/gerrit/metrics",
+        "//java/com/google/gerrit/reviewdb:server",
+        "//java/com/google/gerrit/server",
+        "//java/org/eclipse/jgit:server",
+        "//lib:guava",
+        "//lib:gwtorm",
+        "//lib/auto:auto-value",
+        "//lib/commons:dbcp",
+        "//lib/guice",
+        "//lib/jgit/org.eclipse.jgit.archive:jgit-archive",
+        "//lib/jgit/org.eclipse.jgit:jgit",
+        "//lib/log:api",
+        "//lib/log:jsonevent-layout",
+        "//lib/log:log4j",
+    ],
+)
diff --git a/java/com/google/gerrit/sshd/BUILD b/java/com/google/gerrit/sshd/BUILD
index 0c71dae..a33ce86 100644
--- a/java/com/google/gerrit/sshd/BUILD
+++ b/java/com/google/gerrit/sshd/BUILD
@@ -14,6 +14,7 @@
         "//java/com/google/gerrit/server/cache/h2",
         "//java/com/google/gerrit/server/git/receive",
         "//java/com/google/gerrit/server/ioutil",
+        "//java/com/google/gerrit/server/schema",
         "//java/com/google/gerrit/util/cli",
         "//java/org/eclipse/jgit:server",
         "//lib:args4j",
diff --git a/java/com/google/gerrit/testing/BUILD b/java/com/google/gerrit/testing/BUILD
index 8e13b61..2715c75 100644
--- a/java/com/google/gerrit/testing/BUILD
+++ b/java/com/google/gerrit/testing/BUILD
@@ -25,6 +25,7 @@
         "//java/com/google/gerrit/server:module",
         "//java/com/google/gerrit/server/api",
         "//java/com/google/gerrit/server/cache/h2",
+        "//java/com/google/gerrit/server/schema",
         "//lib:gwtorm",
         "//lib:h2",
         "//lib:truth",
diff --git a/java/com/google/gerrit/testing/InMemoryModule.java b/java/com/google/gerrit/testing/InMemoryModule.java
index 06a1476..c714d741 100644
--- a/java/com/google/gerrit/testing/InMemoryModule.java
+++ b/java/com/google/gerrit/testing/InMemoryModule.java
@@ -171,7 +171,8 @@
 
     bindScope(RequestScoped.class, PerThreadRequestScope.REQUEST);
 
-    // TODO(dborowitz): Use jimfs.
+    // TODO(dborowitz): Use Jimfs. The biggest blocker is that JGit does not support Path-based
+    // Configs, only FileBasedConfig.
     bind(Path.class).annotatedWith(SitePath.class).toInstance(Paths.get("."));
     bind(Config.class).annotatedWith(GerritServerConfig.class).toInstance(cfg);
     bind(GerritOptions.class).toInstance(new GerritOptions(cfg, false, false, false));
diff --git a/javatests/com/google/gerrit/acceptance/api/change/AbandonIT.java b/javatests/com/google/gerrit/acceptance/api/change/AbandonIT.java
index 637d5f9..05eca2a 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/AbandonIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/AbandonIT.java
@@ -66,8 +66,7 @@
     PushOneCommit.Result a = createChange();
     PushOneCommit.Result b = createChange();
     List<ChangeData> list = ImmutableList.of(a.getChange(), b.getChange());
-    changeAbandoner.batchAbandon(
-        batchUpdateFactory, a.getChange().project(), user, list, "deadbeef");
+    batchAbandon.batchAbandon(batchUpdateFactory, a.getChange().project(), user, list, "deadbeef");
 
     ChangeInfo info = get(a.getChangeId(), MESSAGES);
     assertThat(info.status).isEqualTo(ChangeStatus.ABANDONED);
@@ -96,7 +95,7 @@
     exception.expect(ResourceConflictException.class);
     exception.expectMessage(
         String.format("Project name \"%s\" doesn't match \"%s\"", project2Name, project1Name));
-    changeAbandoner.batchAbandon(batchUpdateFactory, new Project.NameKey(project1Name), user, list);
+    batchAbandon.batchAbandon(batchUpdateFactory, new Project.NameKey(project1Name), user, list);
   }
 
   @Test
diff --git a/javatests/com/google/gerrit/acceptance/pgm/BUILD b/javatests/com/google/gerrit/acceptance/pgm/BUILD
index a61ca4b..3550a99 100644
--- a/javatests/com/google/gerrit/acceptance/pgm/BUILD
+++ b/javatests/com/google/gerrit/acceptance/pgm/BUILD
@@ -5,7 +5,10 @@
     group = "pgm",
     labels = ["pgm"],
     vm_args = ["-Xmx512m"],
-    deps = [":util"],
+    deps = [
+        ":util",
+        "//java/com/google/gerrit/server/schema",
+    ],
 )
 
 java_library(
diff --git a/javatests/com/google/gerrit/acceptance/server/notedb/BUILD b/javatests/com/google/gerrit/acceptance/server/notedb/BUILD
index 57454bb..20c256f 100644
--- a/javatests/com/google/gerrit/acceptance/server/notedb/BUILD
+++ b/javatests/com/google/gerrit/acceptance/server/notedb/BUILD
@@ -7,4 +7,5 @@
         "notedb",
         "server",
     ],
+    deps = ["//java/com/google/gerrit/server/schema"],
 )
diff --git a/javatests/com/google/gerrit/gpg/BUILD b/javatests/com/google/gerrit/gpg/BUILD
index 0c82c09..9beb0ff 100644
--- a/javatests/com/google/gerrit/gpg/BUILD
+++ b/javatests/com/google/gerrit/gpg/BUILD
@@ -15,6 +15,7 @@
         "//java/com/google/gerrit/server",
         "//java/com/google/gerrit/server/cache/h2",
         "//java/com/google/gerrit/server/project/testing:project-test-util",
+        "//java/com/google/gerrit/server/schema",
         "//java/com/google/gerrit/testing:gerrit-test-util",
         "//lib:guava",
         "//lib:gwtorm",
diff --git a/javatests/com/google/gerrit/server/BUILD b/javatests/com/google/gerrit/server/BUILD
index 2eaf4f5..5bdfe39 100644
--- a/javatests/com/google/gerrit/server/BUILD
+++ b/javatests/com/google/gerrit/server/BUILD
@@ -42,6 +42,7 @@
         "//java/com/google/gerrit/reviewdb:server",
         "//java/com/google/gerrit/server",
         "//java/com/google/gerrit/server/project/testing:project-test-util",
+        "//java/com/google/gerrit/server/schema",
         "//java/com/google/gerrit/testing:gerrit-test-util",
         "//java/org/eclipse/jgit:server",
         "//lib:grappa",
diff --git a/javatests/com/google/gerrit/server/query/account/BUILD b/javatests/com/google/gerrit/server/query/account/BUILD
index f582bbd..0127fa5 100644
--- a/javatests/com/google/gerrit/server/query/account/BUILD
+++ b/javatests/com/google/gerrit/server/query/account/BUILD
@@ -13,6 +13,7 @@
         "//java/com/google/gerrit/lifecycle",
         "//java/com/google/gerrit/reviewdb:server",
         "//java/com/google/gerrit/server",
+        "//java/com/google/gerrit/server/schema",
         "//java/com/google/gerrit/testing:gerrit-test-util",
         "//lib:truth",
         "//lib/guice",
diff --git a/javatests/com/google/gerrit/server/query/change/BUILD b/javatests/com/google/gerrit/server/query/change/BUILD
index 35ba46b..5ade4ef 100644
--- a/javatests/com/google/gerrit/server/query/change/BUILD
+++ b/javatests/com/google/gerrit/server/query/change/BUILD
@@ -16,6 +16,7 @@
         "//java/com/google/gerrit/lifecycle",
         "//java/com/google/gerrit/reviewdb:server",
         "//java/com/google/gerrit/server",
+        "//java/com/google/gerrit/server/schema",
         "//java/com/google/gerrit/testing:gerrit-test-util",
         "//lib:gwtorm",
         "//lib:truth",
diff --git a/javatests/com/google/gerrit/server/query/group/BUILD b/javatests/com/google/gerrit/server/query/group/BUILD
index 3173d45..e9e206e 100644
--- a/javatests/com/google/gerrit/server/query/group/BUILD
+++ b/javatests/com/google/gerrit/server/query/group/BUILD
@@ -13,6 +13,7 @@
         "//java/com/google/gerrit/lifecycle",
         "//java/com/google/gerrit/reviewdb:server",
         "//java/com/google/gerrit/server",
+        "//java/com/google/gerrit/server/schema",
         "//java/com/google/gerrit/testing:gerrit-test-util",
         "//lib:truth",
         "//lib/guice",
diff --git a/javatests/com/google/gerrit/server/query/project/BUILD b/javatests/com/google/gerrit/server/query/project/BUILD
index aac64c6..760aa36 100644
--- a/javatests/com/google/gerrit/server/query/project/BUILD
+++ b/javatests/com/google/gerrit/server/query/project/BUILD
@@ -12,6 +12,7 @@
         "//java/com/google/gerrit/lifecycle",
         "//java/com/google/gerrit/reviewdb:server",
         "//java/com/google/gerrit/server",
+        "//java/com/google/gerrit/server/schema",
         "//java/com/google/gerrit/testing:gerrit-test-util",
         "//lib:truth",
         "//lib/guice",
diff --git a/javatests/com/google/gerrit/server/update/BUILD b/javatests/com/google/gerrit/server/update/BUILD
index 80c2d72..0cd3234 100644
--- a/javatests/com/google/gerrit/server/update/BUILD
+++ b/javatests/com/google/gerrit/server/update/BUILD
@@ -29,6 +29,7 @@
         "//java/com/google/gerrit/lifecycle",
         "//java/com/google/gerrit/reviewdb:server",
         "//java/com/google/gerrit/server",
+        "//java/com/google/gerrit/server/schema",
         "//java/com/google/gerrit/testing:gerrit-test-util",
         "//lib:guava",
         "//lib:gwtorm",
diff --git a/plugins/codemirror-editor b/plugins/codemirror-editor
index 0a741c0..f72905b 160000
--- a/plugins/codemirror-editor
+++ b/plugins/codemirror-editor
@@ -1 +1 @@
-Subproject commit 0a741c07f336ca7ed883967424012756835acecc
+Subproject commit f72905bb83cb9d97781da110bd3ad88c2193ed7e
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.js b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.js
index 572695b..262d1dc 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.js
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.js
@@ -201,8 +201,8 @@
      * Maps an index local to a particular section to the absolute index
      * across all the changes on the page.
      *
-     * @param sectionIndex {number} index of section
-     * @param localIndex {number} index of row within section
+     * @param {number} sectionIndex index of section
+     * @param {number} localIndex index of row within section
      * @return {number} absolute index of row in the aggregate dashboard
      */
     _computeItemAbsoluteIndex(sectionIndex, localIndex) {
diff --git a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.html b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.html
index 319c92d..63021db 100644
--- a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.html
+++ b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.html
@@ -60,7 +60,7 @@
           box-shadow: none;
           padding: .2em .85em;
         }
-        --gr-button-background: var(--button-background-color, #d9d9d9);
+        --gr-button-background: var(--button-background-color, #f5f5f5);
         --gr-button-color: black;
       }
       iron-selector > gr-button.iron-selected.max {