Merge changes from topic "FixJavaLangClash"

* changes:
  Enable error level for the JavaLangClash bug pattern in ErrorProne
  Remove unused FakeSubmitRule.Module class
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 84e68ed..acf65a5 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -4343,7 +4343,7 @@
 before it is focefully cancelled.
 +
 The receive timeout cannot be overriden by setting a higher
-link:user-upload#deadline[deadline] on the git push request.
+link:user-upload.html#deadline[deadline] on the git push request.
 +
 Default is 4 minutes. If no unit is specified, milliseconds
 is assumed.
diff --git a/Documentation/user-request-cancellation-and-deadlines.txt b/Documentation/user-request-cancellation-and-deadlines.txt
index b368d6a..7c39f16 100644
--- a/Documentation/user-request-cancellation-and-deadlines.txt
+++ b/Documentation/user-request-cancellation-and-deadlines.txt
@@ -24,16 +24,16 @@
 [[server-side-deadlines]]
 == Server-side deadlines
 
-To limit the maximal execution time for requests, administrators can [configure
-server-side deadlines](config-gerrit.html#deadline.id). If a server-side
-deadline is exceeded by a matching request, the request is automatically
-aborted. In this case the client gets a proper error message informing the user
-about the exceeded deadline.
+To limit the maximal execution time for requests, administrators can
+link:config-gerrit.html#deadline.id[configure server-side deadlines]. If a
+server-side deadline is exceeded by a matching request, the request is
+automatically aborted. In this case the client gets a proper error message
+informing the user about the exceeded deadline.
 
 Clients may override server-side deadlines by setting a
-[deadline](#client-provided-deadline) on the request. This means, if a request
-fails due to an exceeded server-side deadline, the client may repeat the request
-with a higher deadline or no deadline (deadline = 0) to get unblocked.
+link:#client-provided-deadlines[deadline] on the request. This means, if a
+request fails due to an exceeded server-side deadline, the client may repeat the
+request with a higher deadline or no deadline (deadline = 0) to get unblocked.
 
 Server-side deadlines are meant to protect the Gerrit service against resource
 exhaustion due to performence issues with a particular request. E.g. imagine a
@@ -49,15 +49,15 @@
 
 Finally server-side deadlines can help ops engineers to detect performance
 issues more reliably and more quicky. For this alerts may be setup that are
-based on the [cancellation metrics](metrics.html#cancellations).
+based on the link:metrics.html#cancellations[cancellation metrics].
 
 [[receive-timeout]]
 === Receive Timeout
 
-For git pushes it is possible to configure a [hard
-timeout](config-gerrit.html#receive.timeout). In contrast to server-side
-deadlines, this timeout is not overridable by [client-provided
-deadlines](#client-provided-deadlines).
+For git pushes it is possible to configure a
+link:config-gerrit.html#receive.timeout[hard timeout]. In contrast to
+server-side deadlines, this timeout is not overridable by
+link:#client-provided-deadlines[client-provided deadlines].
 
 [[client-provided-deadlines]]
 == Client-provided deadlines
@@ -72,30 +72,32 @@
 [options="header",cols="1,6"]
 |=======================
 |Request Type   |How to set a deadline?
-|REST over HTTP |Set the [X-Gerrit-Deadline header](rest-api.html#deadline).
-|SSH command    |Set the [deadline option](cmd-index.html#deadline).
-|git push       |Set the [deadline push option](user-upload.html#deadline).
+|REST over HTTP |Set the link:rest-api.html#deadline[X-Gerrit-Deadline header].
+|SSH command    |Set the link:cmd-index.html#deadline[deadline option].
+|git push       |Set the link:user-upload.html#deadline[deadline push option].
 |git clone/fetch|Not supported.
 |=======================
 
 [[override-server-side-deadline]]
 === Override server-side deadline
 
-By setting a deadline on a request it is possible to override any [server-side
-deadline](#server-side-deadline), e.g. in order to increase it. Setting the
-deadline to `0` disables any server-side deadline. This allows clients to get
-unblocked if a request has previously failed due to an exceeded deadline.
+By setting a deadline on a request it is possible to override any
+link:#server-side-deadlines[server-side deadline], e.g. in order to increase it.
+Setting the deadline to `0` disables any server-side deadline. This allows
+clients to get unblocked if a request has previously failed due to an exceeded
+deadline.
 
 [NOTE]
-It is stronly discouraged for clients to permanently override [server-side
-deadlines](#server-side-deadlines] with a higher deadline or to permanently
-disable them by always setting the deadline to `0`. If this becomes necessary
-the caller should get in touch with the Gerrit administrators to increase the
-server-side deadlines or resolve the performance issue in another way.
+It is stronly discouraged for clients to permanently override
+link:#server-side-deadlines[server-side deadlines] with a higher deadline or to
+permanently disable them by always setting the deadline to `0`. If this becomes
+necessary the caller should get in touch with the Gerrit administrators to
+increase the server-side deadlines or resolve the performance issue in another
+way.
 
 [NOTE]
-It's not possible for clients to override the [receive
-timeout](#receive-timeout) that is enforced on git push.
+It's not possible for clients to override the link:#receive-timeout[receive
+timeout] that is enforced on git push.
 
 [[faqs]]
 == FAQs
@@ -105,7 +107,7 @@
 
 To get unblocked, you may repeat the request with deadlines disabled. To do this
 set the deadline to `0` on the request as explained
-[above](#override-server-side-deadline).
+link:#override-server-side-deadline[above].
 
 If doing this becomes required frequently, please get in touch with the Gerrit
 administrators in order to investigate the performance issue and increase the
@@ -119,7 +121,7 @@
 [[push-fails-due-to-exceeded-deadline-but-cannot-be-overridden]]
 === My git push fails due to an exceeded deadline and I cannot override the deadline, what can I do?
 
-As explained [above](#receive-timeout) a configured receive timeout cannot be
+As explained link:#receive-timeout[above] a configured receive timeout cannot be
 overridden by clients. If pushes fail due to this timeout, get in touch with the
 Gerrit administrators in order to investigate the performance issue and increase
 the receive timeout if necessary.
@@ -138,7 +140,7 @@
 Technically the check whether a request should be aborted is done whenever the
 execution time of an operation or sub-step is captured, either by a timer
 metric or a `TraceTimer` ('TraceTimer` is the class that logs the execution time
-when the request is being [traced](user-request-tracing.html)).
+when the request is being link:user-request-tracing.html[traced]).
 
 [[how-are-requests-aborted]]
 === How does Gerrit abort requests?
@@ -165,8 +167,8 @@
 request has been aborted.
 
 Errors due to aborted requests are usually not counted as internal server errors,
-but the [cancellation metrics](metrics.html#cancellations) may be used to setup
-alerting for performance issues.
+but the link:metrics.html#cancellations[cancellation metrics] may be used to
+setup alerting for performance issues.
 
 [NOTE]
 During a request, cancellations can occur at any time. This means for non-atomic
diff --git a/java/com/google/gerrit/server/notedb/CommitRewriter.java b/java/com/google/gerrit/server/notedb/CommitRewriter.java
index e940b1e..eabee65 100644
--- a/java/com/google/gerrit/server/notedb/CommitRewriter.java
+++ b/java/com/google/gerrit/server/notedb/CommitRewriter.java
@@ -30,6 +30,7 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.UsedAt;
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.entities.Change;
@@ -117,6 +118,16 @@
     public boolean verifyCommits = true;
     /** Whether to compute and output the diff of the commit history for the backfilled refs. */
     public boolean outputDiff = true;
+
+    /** Max number of refs to update in a single {@link BatchRefUpdate}. */
+    public int maxRefsInBatch = 10000;
+    /**
+     * Max number of refs to fix by a single {@link RefsUpdate#backfillProject} run. Since second
+     * run on the same set of refs is a no-op, running with this option in a loop will eventually
+     * fix all refs. Number of executed {@link BatchRefUpdate} depends on {@link #maxRefsInBatch}
+     * option.
+     */
+    public int maxRefsToUpdate = 50000;
   }
 
   /** Result of the backfill run for a project. */
@@ -239,15 +250,24 @@
    */
   public BackfillResult backfillProject(
       Project.NameKey project, Repository repo, RunOptions options) {
+
+    checkState(
+        options.maxRefsInBatch > 0 && options.maxRefsToUpdate > 0,
+        "Expected maxRefsInBatch>0 && <= maxRefsToUpdate>0");
+    checkState(
+        options.maxRefsInBatch <= options.maxRefsToUpdate,
+        "Expected maxRefsInBatch(%s) <= maxRefsToUpdate(%s)",
+        options.maxRefsInBatch,
+        options.maxRefsToUpdate);
     BackfillResult result = new BackfillResult();
     result.ok = true;
-    try (RevWalk revWalk = new RevWalk(repo);
-        ObjectInserter ins = newPackInserter(repo)) {
-      BatchRefUpdate bru = repo.getRefDatabase().newBatchUpdate();
-      bru.setForceRefLog(true);
-      bru.setRefLogMessage(CommitRewriter.class.getName(), false);
-      bru.setAllowNonFastForwards(true);
+    int refsInUpdate = 0;
+    RefsUpdate refsUpdate = null;
+    try {
       for (Ref ref : repo.getRefDatabase().getRefsByPrefix(RefNames.REFS_CHANGES)) {
+        if (result.fixedRefDiff.size() >= options.maxRefsToUpdate) {
+          return result;
+        }
         Change.Id changeId = Change.Id.fromRef(ref.getName());
         if (changeId == null || !ref.getName().equals(RefNames.changeMetaRef(changeId))) {
           continue;
@@ -262,14 +282,26 @@
               logger.atWarning().withCause(e).log("Failed to run verification on ref %s", ref);
             }
           }
+          if (refsUpdate == null) {
+            refsUpdate = RefsUpdate.create(repo);
+          }
           ChangeFixProgress changeFixProgress =
-              backfillChange(revWalk, ins, ref, accountsInChange, options);
+              backfillChange(refsUpdate, ref, accountsInChange, options);
           if (changeFixProgress.anyFixesApplied) {
-            bru.addCommand(
-                new ReceiveCommand(ref.getObjectId(), changeFixProgress.newTipId, ref.getName()));
+            refsInUpdate++;
+            refsUpdate
+                .batchRefUpdate()
+                .addCommand(
+                    new ReceiveCommand(
+                        ref.getObjectId(), changeFixProgress.newTipId, ref.getName()));
             result.fixedRefDiff.put(ref.getName(), changeFixProgress.commitDiffs);
           }
-
+          if (refsInUpdate >= options.maxRefsInBatch
+              || result.fixedRefDiff.size() >= options.maxRefsToUpdate) {
+            processUpdate(options, refsUpdate);
+            refsUpdate = null;
+            refsInUpdate = 0;
+          }
           if (!changeFixProgress.isValidAfterFix) {
             result.refsStillInvalidAfterFix.add(ref.getName());
           }
@@ -278,21 +310,34 @@
           result.refsFailedToFix.add(ref.getName());
         }
       }
-
-      if (!bru.getCommands().isEmpty()) {
-        if (!options.dryRun) {
-          ins.flush();
-          RefUpdateUtil.executeChecked(bru, revWalk);
-        }
-      }
+      processUpdate(options, refsUpdate);
     } catch (IOException e) {
-      logger.atWarning().withCause(e).log("Failed to fix project %s", project.get());
+      logger.atWarning().log("Failed to fix project %s. Reason: %s", project.get(), e.getMessage());
       result.ok = false;
+    } finally {
+      if (refsUpdate != null) {
+        refsUpdate.close();
+      }
     }
 
     return result;
   }
 
+  /** Executes a single {@link RefsUpdate#batchRefUpdate}. */
+  private void processUpdate(RunOptions options, @Nullable RefsUpdate refsUpdate)
+      throws IOException {
+    if (refsUpdate == null) {
+      return;
+    }
+    if (!refsUpdate.batchRefUpdate().getCommands().isEmpty()) {
+      if (!options.dryRun) {
+        refsUpdate.inserter().flush();
+        RefUpdateUtil.executeChecked(refsUpdate.batchRefUpdate(), refsUpdate.revWalk());
+      }
+    }
+    refsUpdate.close();
+  }
+
   /**
    * Retrieves accounts, that are associated with a change (e.g. reviewers, commenters, etc.). These
    * accounts are used to verify that commits do not contain user data. See {@link #verifyCommit}
@@ -376,8 +421,7 @@
    * ChangeFixProgress#newTipId}.
    */
   public ChangeFixProgress backfillChange(
-      RevWalk revWalk,
-      ObjectInserter inserter,
+      RefsUpdate refsUpdate,
       Ref ref,
       ImmutableSet<AccountState> accountsInChange,
       RunOptions options)
@@ -385,17 +429,17 @@
 
     ObjectId oldTip = ref.getObjectId();
     // Walk from the first commit of the branch.
-    revWalk.reset();
-    revWalk.markStart(revWalk.parseCommit(oldTip));
-    revWalk.sort(RevSort.TOPO);
+    refsUpdate.revWalk().reset();
+    refsUpdate.revWalk().markStart(refsUpdate.revWalk().parseCommit(oldTip));
+    refsUpdate.revWalk().sort(RevSort.TOPO);
 
-    revWalk.sort(RevSort.REVERSE);
+    refsUpdate.revWalk().sort(RevSort.REVERSE);
 
     RevCommit originalCommit;
 
     boolean rewriteStarted = false;
     ChangeFixProgress changeFixProgress = new ChangeFixProgress(ref.getName());
-    while ((originalCommit = revWalk.next()) != null) {
+    while ((originalCommit = refsUpdate.revWalk().next()) != null) {
 
       changeFixProgress.updateAuthorId =
           parseIdent(changeFixProgress, originalCommit.getAuthorIdent());
@@ -453,7 +497,8 @@
       cb.setEncoding(originalCommit.getEncoding());
       byte[] newCommitContent = cb.build();
       checkCommitModification(originalCommit, newCommitContent);
-      changeFixProgress.newTipId = inserter.insert(Constants.OBJ_COMMIT, newCommitContent);
+      changeFixProgress.newTipId =
+          refsUpdate.inserter().insert(Constants.OBJ_COMMIT, newCommitContent);
       // Only compute diff if the content of the commit was actually changed.
       if (options.outputDiff && needsFix) {
         String diff = computeDiff(originalCommit.getRawBuffer(), newCommitContent);
@@ -1283,4 +1328,33 @@
 
     abstract Optional<String> email();
   }
+
+  /**
+   * Objects, needed to fix Refs in a single {@link BatchRefUpdate}. Number of changes in a batch
+   * are limited by {@link RunOptions#maxRefsInBatch}.
+   */
+  @AutoValue
+  abstract static class RefsUpdate implements AutoCloseable {
+    static RefsUpdate create(Repository repo) {
+      RevWalk revWalk = new RevWalk(repo);
+      ObjectInserter inserter = newPackInserter(repo);
+      BatchRefUpdate bru = repo.getRefDatabase().newBatchUpdate();
+      bru.setForceRefLog(true);
+      bru.setRefLogMessage(CommitRewriter.class.getName(), false);
+      bru.setAllowNonFastForwards(true);
+      return new AutoValue_CommitRewriter_RefsUpdate(bru, revWalk, inserter);
+    }
+
+    @Override
+    public void close() {
+      inserter().close();
+      revWalk().close();
+    }
+
+    abstract BatchRefUpdate batchRefUpdate();
+
+    abstract RevWalk revWalk();
+
+    abstract ObjectInserter inserter();
+  }
 }
diff --git a/javatests/com/google/gerrit/server/notedb/CommitRewriterTest.java b/javatests/com/google/gerrit/server/notedb/CommitRewriterTest.java
index 056c7dc..98721fd 100644
--- a/javatests/com/google/gerrit/server/notedb/CommitRewriterTest.java
+++ b/javatests/com/google/gerrit/server/notedb/CommitRewriterTest.java
@@ -24,6 +24,7 @@
 import static java.util.Objects.requireNonNull;
 
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.entities.AttentionSetUpdate;
 import com.google.gerrit.entities.AttentionSetUpdate.Operation;
@@ -33,6 +34,7 @@
 import com.google.gerrit.entities.PatchSetApproval;
 import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.entities.SubmitRecord;
+import com.google.gerrit.git.RefUpdateUtil;
 import com.google.gerrit.json.OutputFormat;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
@@ -48,7 +50,9 @@
 import java.sql.Timestamp;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 import java.util.stream.IntStream;
+import org.eclipse.jgit.lib.BatchRefUpdate;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.Ref;
@@ -56,6 +60,8 @@
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevSort;
 import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.ReceiveCommand;
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
@@ -70,6 +76,21 @@
   @Before
   public void setUp() throws Exception {}
 
+  @After
+  public void cleanUp() throws Exception {
+    BatchRefUpdate bru = repo.getRefDatabase().newBatchUpdate();
+    bru.setAllowNonFastForwards(true);
+    for (Ref ref : repo.getRefDatabase().getRefsByPrefix(RefNames.REFS_CHANGES)) {
+      Change.Id changeId = Change.Id.fromRef(ref.getName());
+      if (changeId == null || !ref.getName().equals(RefNames.changeMetaRef(changeId))) {
+        continue;
+      }
+      bru.addCommand(new ReceiveCommand(ref.getObjectId(), ObjectId.zeroId(), ref.getName()));
+    }
+
+    RefUpdateUtil.executeChecked(bru, repo);
+  }
+
   @Test
   public void validHistoryNoOp() throws Exception {
     String tag = "jenkins";
@@ -157,6 +178,138 @@
   }
 
   @Test
+  public void numRefs_greater_maxRefsToUpdate_allFixed() throws Exception {
+    int numberOfChanges = 12;
+    ImmutableMap.Builder<String, Ref> refsToOldMetaBuilder = new ImmutableMap.Builder<>();
+    for (int i = 0; i < numberOfChanges; i++) {
+      Change c = newChange();
+      ChangeUpdate update = newUpdate(c, changeOwner);
+      update.setChangeMessage("Change has been successfully merged by " + changeOwner.getName());
+      update.commit();
+      ChangeUpdate updateWithSubject = newUpdate(c, changeOwner);
+      updateWithSubject.setSubjectForCommit("Update with subject");
+      updateWithSubject.commit();
+      String refName = RefNames.changeMetaRef(c.getId());
+      Ref metaRefBeforeRewrite = repo.exactRef(refName);
+      refsToOldMetaBuilder.put(refName, metaRefBeforeRewrite);
+    }
+    ImmutableMap<String, Ref> refsToOldMeta = refsToOldMetaBuilder.build();
+
+    RunOptions options = new RunOptions();
+    options.dryRun = false;
+    options.outputDiff = false;
+    options.verifyCommits = false;
+    options.maxRefsInBatch = 10;
+    options.maxRefsToUpdate = 12;
+    BackfillResult backfillResult = rewriter.backfillProject(project, repo, options);
+    assertThat(backfillResult.fixedRefDiff.keySet()).isEqualTo(refsToOldMeta.keySet());
+    for (Map.Entry<String, Ref> refEntry : refsToOldMeta.entrySet()) {
+      Ref metaRefAfterRewrite = repo.exactRef(refEntry.getKey());
+      assertThat(refEntry.getValue()).isNotEqualTo(metaRefAfterRewrite);
+    }
+  }
+
+  @Test
+  public void maxRefsToUpdate_coversAllInvalid_inMultipleBatches() throws Exception {
+    testMaxRefsToUpdate(
+        /*numberOfInvalidChanges=*/ 11,
+        /*numberOfValidChanges=*/ 9,
+        /*maxRefsToUpdate=*/ 12,
+        /*maxRefsInBatch=*/ 2);
+  }
+
+  @Test
+  public void maxRefsToUpdate_coversAllInvalid_inSingleBatch() throws Exception {
+    testMaxRefsToUpdate(
+        /*numberOfInvalidChanges=*/ 11,
+        /*numberOfValidChanges=*/ 9,
+        /*maxRefsToUpdate=*/ 12,
+        /*maxRefsInBatch=*/ 12);
+  }
+
+  @Test
+  public void moreInvalidRefs_thenMaxRefsToUpdate_inMultipleBatches() throws Exception {
+    testMaxRefsToUpdate(
+        /*numberOfInvalidChanges=*/ 11,
+        /*numberOfValidChanges=*/ 9,
+        /*maxRefsToUpdate=*/ 10,
+        /*maxRefsInBatch=*/ 2);
+  }
+
+  @Test
+  public void moreInvalidRefs_thenMaxRefsToUpdate_inSingleBatch() throws Exception {
+    testMaxRefsToUpdate(
+        /*numberOfInvalidChanges=*/ 11,
+        /*numberOfValidChanges=*/ 9,
+        /*maxRefsToUpdate=*/ 10,
+        /*maxRefsInBatch=*/ 10);
+  }
+
+  private void testMaxRefsToUpdate(
+      int numberOfInvalidChanges, int numberOfValidChanges, int maxRefsToUpdate, int maxRefsInBatch)
+      throws Exception {
+    ImmutableMap.Builder<String, ObjectId> expectedFixedRefsToOldMetaBuilder =
+        new ImmutableMap.Builder<>();
+    ImmutableMap.Builder<String, ObjectId> expectedSkippedRefsToOldMetaBuilder =
+        new ImmutableMap.Builder<>();
+    for (int i = 0; i < numberOfValidChanges; i++) {
+      Change c = newChange();
+      ChangeUpdate updateWithSubject = newUpdate(c, changeOwner);
+      updateWithSubject.setSubjectForCommit("Update with subject");
+      updateWithSubject.commit();
+      String refName = RefNames.changeMetaRef(c.getId());
+      Ref metaRefBeforeRewrite = repo.exactRef(refName);
+      expectedSkippedRefsToOldMetaBuilder.put(refName, metaRefBeforeRewrite.getObjectId());
+    }
+    for (int i = 0; i < numberOfInvalidChanges; i++) {
+      Change c = newChange();
+      ChangeUpdate update = newUpdate(c, changeOwner);
+      update.setChangeMessage("Change has been successfully merged by " + changeOwner.getName());
+      update.commit();
+      ChangeUpdate updateWithSubject = newUpdate(c, changeOwner);
+      updateWithSubject.setSubjectForCommit("Update with subject");
+      updateWithSubject.commit();
+      String refName = RefNames.changeMetaRef(c.getId());
+      Ref metaRefBeforeRewrite = repo.exactRef(refName);
+      if (i < maxRefsToUpdate) {
+        expectedFixedRefsToOldMetaBuilder.put(refName, metaRefBeforeRewrite.getObjectId());
+      } else {
+        expectedSkippedRefsToOldMetaBuilder.put(refName, metaRefBeforeRewrite.getObjectId());
+      }
+    }
+    ImmutableMap<String, ObjectId> expectedFixedRefsToOldMeta =
+        expectedFixedRefsToOldMetaBuilder.build();
+    ImmutableMap<String, ObjectId> expectedSkippedRefsToOldMeta =
+        expectedSkippedRefsToOldMetaBuilder.build();
+    RunOptions options = new RunOptions();
+    options.dryRun = false;
+    options.outputDiff = false;
+    options.verifyCommits = false;
+    options.maxRefsInBatch = maxRefsInBatch;
+    options.maxRefsToUpdate = maxRefsToUpdate;
+    BackfillResult backfillResult = rewriter.backfillProject(project, repo, options);
+    assertThat(backfillResult.fixedRefDiff.keySet()).isEqualTo(expectedFixedRefsToOldMeta.keySet());
+    for (Map.Entry<String, ObjectId> refEntry : expectedFixedRefsToOldMeta.entrySet()) {
+      Ref metaRefAfterRewrite = repo.exactRef(refEntry.getKey());
+      assertThat(refEntry.getValue()).isNotEqualTo(metaRefAfterRewrite.getObjectId());
+    }
+    for (Map.Entry<String, ObjectId> refEntry : expectedSkippedRefsToOldMeta.entrySet()) {
+      Ref metaRefAfterRewrite = repo.exactRef(refEntry.getKey());
+      assertThat(refEntry.getValue()).isEqualTo(metaRefAfterRewrite.getObjectId());
+    }
+    RunOptions secondRunOptions = new RunOptions();
+    secondRunOptions.dryRun = false;
+    secondRunOptions.outputDiff = false;
+    secondRunOptions.verifyCommits = false;
+    secondRunOptions.maxRefsInBatch = maxRefsInBatch;
+    secondRunOptions.maxRefsToUpdate = numberOfInvalidChanges + numberOfValidChanges;
+    BackfillResult secondRunResult = rewriter.backfillProject(project, repo, options);
+    int expectedSecondRunResult =
+        numberOfInvalidChanges > maxRefsToUpdate ? numberOfInvalidChanges - maxRefsToUpdate : 0;
+    assertThat(secondRunResult.fixedRefDiff.keySet().size()).isEqualTo(expectedSecondRunResult);
+  }
+
+  @Test
   public void fixAuthorIdent() throws Exception {
     Change c = newChange();
     Timestamp when = TimeUtil.nowTs();
diff --git a/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements_html.ts b/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements_html.ts
index 8161592..6991c03 100644
--- a/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements_html.ts
@@ -154,6 +154,7 @@
           mutable="[[mutable]]"
           label="[[item.labelName]]"
           label-info="[[item.labelInfo]]"
+          showAlwaysOldUI
         ></gr-label-info>
       </div>
     </section>
@@ -204,6 +205,7 @@
           mutable="[[mutable]]"
           label="[[item.labelName]]"
           label-info="[[item.labelInfo]]"
+          showAlwaysOldUI
         ></gr-label-info>
       </div>
     </section>
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
index f8bdbef..c19be58e 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.ts
@@ -301,15 +301,6 @@
   @property({type: Boolean})
   disableEdit = false;
 
-  @property({type: Boolean})
-  disableDiffPrefs = false;
-
-  @property({
-    type: Boolean,
-    computed: '_computeDiffPrefsDisabled(disableDiffPrefs, _loggedIn)',
-  })
-  _diffPrefsDisabled?: boolean;
-
   @property({type: Array})
   _commentThreads?: CommentThread[];
 
@@ -1714,11 +1705,7 @@
     if (this.shortcuts.shouldSuppress(e) || this.shortcuts.modifierPressed(e)) {
       return;
     }
-
-    if (this._diffPrefsDisabled) {
-      return;
-    }
-
+    if (!this._loggedIn) return;
     e.preventDefault();
     this.$.fileList.openDiffPrefs();
   }
@@ -2602,10 +2589,6 @@
     return currentRevision && revisions && revisions[currentRevision];
   }
 
-  _computeDiffPrefsDisabled(disableDiffPrefs: boolean, loggedIn: boolean) {
-    return disableDiffPrefs || !loggedIn;
-  }
-
   /**
    * Wrapper for using in the element template and computed properties
    */
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
index d57aca8..0b77bc7 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
@@ -537,7 +537,7 @@
           patch-num="{{_patchRange.patchNum}}"
           base-patch-num="{{_patchRange.basePatchNum}}"
           files-expanded="[[_filesExpanded]]"
-          diff-prefs-disabled="[[_diffPrefsDisabled]]"
+          diff-prefs-disabled="[[!_loggedIn]]"
           on-open-diff-prefs="_handleOpenDiffPrefs"
           on-open-download-dialog="_handleOpenDownloadDialog"
           on-expand-diffs="_expandAllDiffs"
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts
index a82fceb..e508c63 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.ts
@@ -806,16 +806,11 @@
         'open'
       );
       element._loggedIn = false;
-      element.disableDiffPrefs = true;
       pressAndReleaseKeyOn(element, 188, null, ',');
       assert.isFalse(stub.called);
 
       element._loggedIn = true;
       pressAndReleaseKeyOn(element, 188, null, ',');
-      assert.isFalse(stub.called);
-
-      element.disableDiffPrefs = false;
-      pressAndReleaseKeyOn(element, 188, null, ',');
       assert.isTrue(stub.called);
     });
 
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts
index 8aef3c0..50bb665 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.ts
@@ -126,9 +126,6 @@
   @property({type: Object})
   diffPrefs?: DiffPreferencesInfo;
 
-  @property({type: Boolean})
-  diffPrefsDisabled?: boolean;
-
   @property({type: String, notify: true})
   diffViewMode?: DiffViewMode;
 
@@ -175,11 +172,8 @@
     return classes.join(' ');
   }
 
-  _computePrefsButtonHidden(
-    prefs: DiffPreferencesInfo,
-    diffPrefsDisabled: boolean
-  ) {
-    return diffPrefsDisabled || !prefs;
+  _computePrefsButtonHidden(prefs: DiffPreferencesInfo, loggedIn: boolean) {
+    return !loggedIn || !prefs;
   }
 
   _fileListActionsVisible(
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_html.ts b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_html.ts
index 5972393..73d0819 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_html.ts
@@ -170,12 +170,12 @@
         <gr-diff-mode-selector
           id="modeSelect"
           mode="{{diffViewMode}}"
-          save-on-change="[[!diffPrefsDisabled]]"
+          save-on-change="[[loggedIn]]"
         ></gr-diff-mode-selector>
         <span
           id="diffPrefsContainer"
           class="hideOnEdit"
-          hidden$="[[_computePrefsButtonHidden(diffPrefs, diffPrefsDisabled)]]"
+          hidden$="[[_computePrefsButtonHidden(diffPrefs, loggedIn)]]"
           hidden=""
         >
           <gr-tooltip-content has-tooltip title="Diff preferences">
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.js b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.js
index c90cfcc..479a9a1 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.js
+++ b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.js
@@ -38,21 +38,11 @@
     await flush();
   });
 
-  test('Diff preferences hidden when no prefs or diffPrefsDisabled', () => {
-    element.diffPrefsDisabled = true;
-    flush();
+  test('Diff preferences hidden when no prefs', () => {
     assert.isTrue(element.$.diffPrefsContainer.hidden);
 
-    element.diffPrefsDisabled = false;
-    flush();
-    assert.isTrue(element.$.diffPrefsContainer.hidden);
-
-    element.diffPrefsDisabled = true;
     element.diffPrefs = {font_size: '12'};
-    flush();
-    assert.isTrue(element.$.diffPrefsContainer.hidden);
-
-    element.diffPrefsDisabled = false;
+    element.loggedIn = true;
     flush();
     assert.isFalse(element.$.diffPrefsContainer.hidden);
   });
@@ -168,7 +158,7 @@
 
   suite('editMode behavior', () => {
     setup(() => {
-      element.diffPrefsDisabled = false;
+      element.loggedIn = true;
       element.diffPrefs = {};
     });
 
diff --git a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.ts b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.ts
index e3edb5e..de11b16 100644
--- a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.ts
+++ b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.ts
@@ -21,8 +21,6 @@
 import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
 import {PolymerElement} from '@polymer/polymer/polymer-element';
 import {htmlTemplate} from './gr-reviewer-list_html';
-import {isSelf, isServiceUser} from '../../../utils/account-util';
-import {hasAttention} from '../../../utils/attention-set-util';
 import {customElement, property, computed, observe} from '@polymer/decorators';
 import {
   ChangeInfo,
@@ -44,6 +42,7 @@
 import {appContext} from '../../../services/app-context';
 import {fireAlert} from '../../../utils/event-util';
 import {getApprovalInfo, getCodeReviewLabel} from '../../../utils/label-util';
+import {sortReviewers} from '../../../utils/attention-set-util';
 
 @customElement('gr-reviewer-list')
 export class GrReviewerList extends PolymerElement {
@@ -237,22 +236,7 @@
     }
     this._reviewers = result
       .filter(reviewer => reviewer._account_id !== owner._account_id)
-      // Sort order:
-      // 1. The user themselves
-      // 2. Human users in the attention set.
-      // 3. Other human users.
-      // 4. Service users.
-      .sort((r1, r2) => {
-        if (this.account) {
-          if (isSelf(r1, this.account)) return -1;
-          if (isSelf(r2, this.account)) return 1;
-        }
-        const a1 = hasAttention(r1, this.change!) ? 1 : 0;
-        const a2 = hasAttention(r2, this.change!) ? 1 : 0;
-        const s1 = isServiceUser(r1) ? -2 : 0;
-        const s2 = isServiceUser(r2) ? -2 : 0;
-        return a2 - a1 + s2 - s1;
-      });
+      .sort((r1, r2) => sortReviewers(r1, r2, this.change, this.account));
 
     if (this._reviewers.length > 8) {
       this._displayedReviewers = this._reviewers.slice(0, 6);
diff --git a/polygerrit-ui/app/elements/change/gr-submit-requirement-hovercard/gr-submit-requirement-hovercard.ts b/polygerrit-ui/app/elements/change/gr-submit-requirement-hovercard/gr-submit-requirement-hovercard.ts
index 5feb1ae..002ac56 100644
--- a/polygerrit-ui/app/elements/change/gr-submit-requirement-hovercard/gr-submit-requirement-hovercard.ts
+++ b/polygerrit-ui/app/elements/change/gr-submit-requirement-hovercard/gr-submit-requirement-hovercard.ts
@@ -119,9 +119,6 @@
           margin-top: var(--spacing-m);
           padding: var(--spacing-m) var(--spacing-xl) 0;
         }
-        .status-placeholder {
-          visibility: hidden;
-        }
       `,
     ];
   }
@@ -161,8 +158,6 @@
     return html` <div class="section">
       <div class="sectionIcon"></div>
       <div class="row">
-        <!-- Hidden placeholder to be aligned as Status line above -->
-        <div class="title status-placeholder">Status</div>
         <div>${labels.map(l => this.renderLabel(l, showLabelName))}</div>
       </div>
     </div>`;
diff --git a/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts b/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts
index 40d80bd..e302b43 100644
--- a/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts
+++ b/polygerrit-ui/app/elements/change/gr-submit-requirements/gr-submit-requirements.ts
@@ -81,7 +81,7 @@
           color: var(--success-foreground);
         }
         iron-icon.close {
-          color: var(--warning-foreground);
+          color: var(--error-foreground);
         }
         .requirements,
         section.trigger-votes {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
index 295e41f..084f9f6 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.ts
@@ -174,15 +174,6 @@
   @property({type: Object, notify: true, observer: '_changeViewStateChanged'})
   changeViewState: Partial<ChangeViewState> = {};
 
-  @property({type: Boolean})
-  disableDiffPrefs = false;
-
-  @property({
-    type: Boolean,
-    computed: '_computeDiffPrefsDisabled(disableDiffPrefs, _loggedIn)',
-  })
-  _diffPrefsDisabled?: boolean;
-
   @property({type: Object})
   _patchRange?: PatchRange;
 
@@ -805,7 +796,7 @@
   _handleCommaKey(e: IronKeyboardEvent) {
     if (this.shortcuts.shouldSuppress(e)) return;
     if (this.shortcuts.modifierPressed(e)) return;
-    if (this._diffPrefsDisabled) return;
+    if (!this._loggedIn) return;
 
     e.preventDefault();
     this.$.diffPreferencesDialog.open();
@@ -1409,11 +1400,8 @@
     return dropdownContent;
   }
 
-  _computePrefsButtonHidden(
-    prefs?: DiffPreferencesInfo,
-    prefsDisabled?: boolean
-  ) {
-    return prefsDisabled || !prefs;
+  _computePrefsButtonHidden(prefs?: DiffPreferencesInfo, loggedIn?: boolean) {
+    return !loggedIn || !prefs;
   }
 
   _handleFileChange(e: CustomEvent) {
@@ -1842,10 +1830,6 @@
     this.$.diffHost.toggleAllContext();
   }
 
-  _computeDiffPrefsDisabled(disableDiffPrefs?: boolean, loggedIn?: boolean) {
-    return disableDiffPrefs || !loggedIn;
-  }
-
   _handleNextUnreviewedFile(e: IronKeyboardEvent) {
     if (this.shortcuts.shouldSuppress(e)) return;
     this._setReviewed(true);
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_html.ts b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_html.ts
index b25be5a8..308c353 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_html.ts
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_html.ts
@@ -338,14 +338,14 @@
           <span>Diff view:</span>
           <gr-diff-mode-selector
             id="modeSelect"
-            save-on-change="[[!_diffPrefsDisabled]]"
+            save-on-change="[[_loggedIn]]"
             mode="{{changeViewState.diffMode}}"
             show-tooltip-below=""
           ></gr-diff-mode-selector>
         </div>
         <span
           id="diffPrefsContainer"
-          hidden$="[[_computePrefsButtonHidden(_prefs, _diffPrefsDisabled)]]"
+          hidden$="[[_computePrefsButtonHidden(_prefs, _loggedIn)]]"
           hidden=""
         >
           <span class="preferences desktop">
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.js b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.js
index 0c7abc8..cc35c3c 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.js
@@ -489,10 +489,6 @@
       MockInteractions.pressAndReleaseKeyOn(element, 188, null, ',');
       assert(showPrefsStub.calledOnce);
 
-      element.disableDiffPrefs = true;
-      MockInteractions.pressAndReleaseKeyOn(element, 188, null, ',');
-      assert(showPrefsStub.calledOnce);
-
       let scrollStub = sinon.stub(element.cursor, 'moveToNextChunk');
       MockInteractions.pressAndReleaseKeyOn(element, 78, null, 'n');
       assert(scrollStub.calledOnce);
@@ -988,8 +984,7 @@
     });
 
     suite('diff prefs hidden', () => {
-      test('when no prefs or logged out', () => {
-        element.disableDiffPrefs = false;
+      test('whenlogged out', () => {
         element._loggedIn = false;
         flush();
         assert.isTrue(element.$.diffPrefsContainer.hidden);
@@ -1004,21 +999,9 @@
         assert.isTrue(element.$.diffPrefsContainer.hidden);
 
         element._loggedIn = true;
-        flush();
-        assert.isFalse(element.$.diffPrefsContainer.hidden);
-      });
-
-      test('when disableDiffPrefs is set', () => {
-        element._loggedIn = true;
         element._prefs = {font_size: '12'};
-        element.disableDiffPrefs = false;
         flush();
-
         assert.isFalse(element.$.diffPrefsContainer.hidden);
-        element.disableDiffPrefs = true;
-        flush();
-
-        assert.isTrue(element.$.diffPrefsContainer.hidden);
       });
     });
 
diff --git a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.ts b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.ts
index 947ef3e..a65bb75 100644
--- a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.ts
+++ b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.ts
@@ -44,6 +44,7 @@
   getVotingRangeOrDefault,
   hasNeutralStatus,
   hasVoted,
+  valueString,
 } from '../../../utils/label-util';
 import {appContext} from '../../../services/app-context';
 import {ParsedChangeInfo} from '../../../types/types';
@@ -53,6 +54,7 @@
 import {ifDefined} from 'lit/directives/if-defined';
 import {fireReload} from '../../../utils/event-util';
 import {KnownExperimentId} from '../../../services/flags/flags';
+import {sortReviewers} from '../../../utils/attention-set-util';
 
 declare global {
   interface HTMLElementTagNameMap {
@@ -102,6 +104,10 @@
   @property({type: Boolean})
   showAllReviewers = true;
 
+  /** temporary until submit requirements are finished */
+  @property({type: Boolean})
+  showAlwaysOldUI = false;
+
   private readonly restApiService = appContext.restApiService;
 
   private readonly reporting = appContext.reportingService;
@@ -196,6 +202,11 @@
           color: var(--deemphasized-text-color);
           margin-left: var(--spacing-xs);
         }
+        gr-vote-chip {
+          --gr-vote-chip-width: 14px;
+          --gr-vote-chip-height: 14px;
+          margin-right: var(--spacing-s);
+        }
       `,
     ];
   }
@@ -203,7 +214,10 @@
   private readonly flagsService = appContext.flagsService;
 
   override render() {
-    if (this.flagsService.isEnabled(KnownExperimentId.SUBMIT_REQUIREMENTS_UI)) {
+    if (
+      this.flagsService.isEnabled(KnownExperimentId.SUBMIT_REQUIREMENTS_UI) &&
+      !this.showAlwaysOldUI
+    ) {
       return this.renderNewSubmitRequirements();
     } else {
       return this.renderOldSubmitRequirements();
@@ -213,11 +227,13 @@
   private renderNewSubmitRequirements() {
     const labelInfo = this.labelInfo;
     if (!labelInfo) return;
-    const reviewers = (this.change?.reviewers['REVIEWER'] ?? []).filter(
-      reviewer =>
-        (this.showAllReviewers && canVote(labelInfo, reviewer)) ||
-        (!this.showAllReviewers && hasVoted(labelInfo, reviewer))
-    );
+    const reviewers = (this.change?.reviewers['REVIEWER'] ?? [])
+      .filter(
+        reviewer =>
+          (this.showAllReviewers && canVote(labelInfo, reviewer)) ||
+          (!this.showAllReviewers && hasVoted(labelInfo, reviewer))
+      )
+      .sort((r1, r2) => sortReviewers(r1, r2, this.change, this.account));
     return html`<div>
       ${reviewers.map(reviewer => this.renderReviewerVote(reviewer))}
     </div>`;
@@ -255,7 +271,7 @@
         ></gr-vote-chip
       ></gr-account-chip>
       ${noVoteYet
-        ? html`<span class="no-votes">No votes</span>`
+        ? this.renderVoteAbility(reviewer)
         : html`${this.renderRemoveVote(reviewer)}`}
     </div>`;
   }
@@ -283,6 +299,19 @@
     </tr>`;
   }
 
+  private renderVoteAbility(reviewer: AccountInfo) {
+    if (this.labelInfo && isDetailedLabelInfo(this.labelInfo)) {
+      const approvalInfo = getApprovalInfo(this.labelInfo, reviewer);
+      if (approvalInfo?.permitted_voting_range) {
+        const {min, max} = approvalInfo?.permitted_voting_range;
+        return html`<span class="no-votes"
+          >Can vote ${valueString(min)}/${valueString(max)}</span
+        >`;
+      }
+    }
+    return html`<span class="no-votes">No votes</span>`;
+  }
+
   private renderRemoveVote(reviewer: AccountInfo) {
     return html`<gr-tooltip-content has-tooltip title="Remove vote">
       <gr-button
diff --git a/polygerrit-ui/app/utils/attention-set-util.ts b/polygerrit-ui/app/utils/attention-set-util.ts
index dcd2863..b0b7ef8 100644
--- a/polygerrit-ui/app/utils/attention-set-util.ts
+++ b/polygerrit-ui/app/utils/attention-set-util.ts
@@ -19,6 +19,7 @@
 import {ParsedChangeInfo} from '../types/types';
 import {
   getAccountTemplate,
+  isSelf,
   isServiceUser,
   replaceTemplates,
 } from './account-util';
@@ -92,3 +93,27 @@
   const entry = change!.attention_set![account!._account_id!];
   return entry?.last_update ? entry.last_update : '';
 }
+
+/**
+ *  Sort order:
+ * 1. The user themselves
+ * 2. Human users in the attention set.
+ * 3. Other human users.
+ * 4. Service users.
+ */
+export function sortReviewers(
+  r1: AccountInfo,
+  r2: AccountInfo,
+  change?: ChangeInfo | ParsedChangeInfo,
+  selfAccount?: AccountInfo
+) {
+  if (selfAccount) {
+    if (isSelf(r1, selfAccount)) return -1;
+    if (isSelf(r2, selfAccount)) return 1;
+  }
+  const a1 = hasAttention(r1, change) ? 1 : 0;
+  const a2 = hasAttention(r2, change) ? 1 : 0;
+  const s1 = isServiceUser(r1) ? -2 : 0;
+  const s2 = isServiceUser(r2) ? -2 : 0;
+  return a2 - a1 + s2 - s1;
+}
diff --git a/polygerrit-ui/app/utils/label-util.ts b/polygerrit-ui/app/utils/label-util.ts
index a8a2719..918d2ab 100644
--- a/polygerrit-ui/app/utils/label-util.ts
+++ b/polygerrit-ui/app/utils/label-util.ts
@@ -94,7 +94,7 @@
   label: DetailedLabelInfo,
   approvalInfo?: ApprovalInfo
 ) {
-  if (!approvalInfo) return true;
+  if (approvalInfo?.value === undefined) return true;
   return getLabelStatus(label, approvalInfo.value) === LabelStatus.NEUTRAL;
 }
 
diff --git a/polygerrit-ui/app/utils/label-util_test.ts b/polygerrit-ui/app/utils/label-util_test.ts
index 1004aac..9360688 100644
--- a/polygerrit-ui/app/utils/label-util_test.ts
+++ b/polygerrit-ui/app/utils/label-util_test.ts
@@ -24,6 +24,7 @@
   getRepresentativeValue,
   getVotingRange,
   getVotingRangeOrDefault,
+  hasNeutralStatus,
   labelCompare,
   LabelStatus,
 } from './label-util';
@@ -193,6 +194,15 @@
     assert.equal(getLabelStatus(labelInfo), LabelStatus.NEUTRAL);
   });
 
+  test('hasNeutralStatus', () => {
+    const labelInfo: DetailedLabelInfo = {all: [], values: VALUES_2};
+    assert.isTrue(hasNeutralStatus(labelInfo));
+    assert.isTrue(hasNeutralStatus(labelInfo, {}));
+    assert.isTrue(hasNeutralStatus(labelInfo, {value: 0}));
+    assert.isFalse(hasNeutralStatus(labelInfo, {value: -1}));
+    assert.isFalse(hasNeutralStatus(labelInfo, {value: 1}));
+  });
+
   test('getRepresentativeValue', () => {
     let labelInfo: DetailedLabelInfo = {all: []};
     assert.equal(getRepresentativeValue(labelInfo), 0);
diff --git a/resources/com/google/gerrit/server/mime/mime-types.properties b/resources/com/google/gerrit/server/mime/mime-types.properties
index 6ab682c..fd2280c 100644
--- a/resources/com/google/gerrit/server/mime/mime-types.properties
+++ b/resources/com/google/gerrit/server/mime/mime-types.properties
@@ -116,6 +116,7 @@
 jsx = text/jsx
 jsp = application/x-jsp
 kt = text/x-kotlin
+kts = text/x-kotlin
 less = text/x-less
 lhs = text/x-literate-haskell
 lisp = text/x-common-lisp