Merge "Move ServerInfo into api/ folder (expose to plugins)"
diff --git a/java/com/google/gerrit/acceptance/testsuite/change/ChangeKindCreator.java b/java/com/google/gerrit/acceptance/testsuite/change/ChangeKindCreator.java
index cb987da..1038a14 100644
--- a/java/com/google/gerrit/acceptance/testsuite/change/ChangeKindCreator.java
+++ b/java/com/google/gerrit/acceptance/testsuite/change/ChangeKindCreator.java
@@ -88,10 +88,10 @@
       throws Exception {
     switch (changeKind) {
       case NO_CODE_CHANGE:
-        noCodeChange(changeId, testRepo, user, project);
+        noCodeChange(changeId, testRepo, user);
         return;
       case REWORK:
-        rework(changeId, testRepo, user, project);
+        rework(changeId, testRepo, user);
         return;
       case TRIVIAL_REBASE:
         trivialRebase(changeId, testRepo, user, project);
@@ -100,7 +100,7 @@
         updateFirstParent(changeId, testRepo, user);
         return;
       case NO_CHANGE:
-        noChange(changeId, testRepo, user, project);
+        noChange(changeId, testRepo, user);
         return;
       default:
         assertWithMessage("unexpected change kind: " + changeKind).fail();
@@ -218,10 +218,7 @@
   }
 
   private void noCodeChange(
-      String changeId,
-      TestRepository<InMemoryRepository> testRepo,
-      TestAccount user,
-      Project.NameKey project)
+      String changeId, TestRepository<InMemoryRepository> testRepo, TestAccount user)
       throws Exception {
     TestRepository<?>.CommitBuilder commitBuilder =
         testRepo.amendRef("HEAD").insertChangeId(changeId.substring(1));
@@ -235,10 +232,7 @@
   }
 
   private void noChange(
-      String changeId,
-      TestRepository<InMemoryRepository> testRepo,
-      TestAccount user,
-      Project.NameKey project)
+      String changeId, TestRepository<InMemoryRepository> testRepo, TestAccount user)
       throws Exception {
     ChangeInfo change = gApi.changes().id(changeId).get();
     String commitMessage = change.revisions.get(change.currentRevision).commit.message;
@@ -255,10 +249,7 @@
   }
 
   private void rework(
-      String changeId,
-      TestRepository<InMemoryRepository> testRepo,
-      TestAccount user,
-      Project.NameKey project)
+      String changeId, TestRepository<InMemoryRepository> testRepo, TestAccount user)
       throws Exception {
     PushOneCommit push =
         pushFactory.create(
diff --git a/java/com/google/gerrit/entities/SubmitRequirementResult.java b/java/com/google/gerrit/entities/SubmitRequirementResult.java
index 7b4d609..e28c86f 100644
--- a/java/com/google/gerrit/entities/SubmitRequirementResult.java
+++ b/java/com/google/gerrit/entities/SubmitRequirementResult.java
@@ -16,7 +16,6 @@
 
 import com.google.auto.value.AutoValue;
 import com.google.auto.value.extension.memoized.Memoized;
-import com.google.gerrit.entities.SubmitRequirementExpressionResult.Status;
 import java.util.Optional;
 
 /** Result of evaluating a {@link SubmitRequirement} on a given Change. */
diff --git a/java/com/google/gerrit/extensions/common/ChangeInfoDiffer.java b/java/com/google/gerrit/extensions/common/ChangeInfoDiffer.java
index 0fff0ba..0447e80 100644
--- a/java/com/google/gerrit/extensions/common/ChangeInfoDiffer.java
+++ b/java/com/google/gerrit/extensions/common/ChangeInfoDiffer.java
@@ -71,12 +71,12 @@
   @SuppressWarnings("unchecked") // reflection is used to construct instances of T
   private static <T> T getAdded(T oldValue, T newValue) {
     if (newValue instanceof Collection) {
-      List result = getAddedForCollection((Collection<?>) oldValue, (Collection<?>) newValue);
+      List<?> result = getAddedForCollection((Collection<?>) oldValue, (Collection<?>) newValue);
       return (T) result;
     }
 
     if (newValue instanceof Map) {
-      Map result = getAddedForMap((Map<?, ?>) oldValue, (Map<?, ?>) newValue);
+      Map<?, ?> result = getAddedForMap((Map<?, ?>) oldValue, (Map<?, ?>) newValue);
       return (T) result;
     }
 
diff --git a/java/com/google/gerrit/index/query/IndexPredicate.java b/java/com/google/gerrit/index/query/IndexPredicate.java
index b255833..18d7fbc 100644
--- a/java/com/google/gerrit/index/query/IndexPredicate.java
+++ b/java/com/google/gerrit/index/query/IndexPredicate.java
@@ -62,29 +62,28 @@
    * integer, long) , the matching logic is consistent across this method and all known index
    * implementations. For text fields (i.e. prefix and full-text) the semantics vary between this
    * implementation and known index implementations:
-   * <li>Prefix: Lucene as well as {@link #match(I)} matches terms as true prefixes (prefix:foo ->
-   *     `foo bar` matches, but `baz foo bar` does not match). The index implementation at Google
+   * <li>Prefix: Lucene as well as {@link #match(Object)} matches terms as true prefixes (prefix:foo
+   *     -> `foo bar` matches, but `baz foo bar` does not match). The index implementation at Google
    *     tokenizes both the query and the indexed text and matches tokens individually (prefix:fo ba
    *     -> `baz foo bar` matches).
    * <li>Full text: Lucene uses a {@code PhraseQuery} to search for terms in full text fields
-   *     in-order. The index implementation at Google as well as {@link #match(I)} tokenizes both
-   *     the query and the indexed text and matches tokens individually.
+   *     in-order. The index implementation at Google as well as {@link #match(Object)} tokenizes
+   *     both the query and the indexed text and matches tokens individually.
    *
-   * @return true if the predicate matches the provided {@link I}.
+   * @return true if the predicate matches the provided {@code I}.
    */
   @Override
   public boolean match(I doc) {
     if (getField().isRepeatable()) {
-      Iterable<Object> values = (Iterable<Object>) getField().get(doc);
+      Iterable<?> values = (Iterable<?>) getField().get(doc);
       for (Object v : values) {
         if (matchesSingleObject(v)) {
           return true;
         }
       }
       return false;
-    } else {
-      return matchesSingleObject(getField().get(doc));
     }
+    return matchesSingleObject(getField().get(doc));
   }
 
   @Override
diff --git a/java/com/google/gerrit/server/account/AccountResolver.java b/java/com/google/gerrit/server/account/AccountResolver.java
index 75a2b38..103013c 100644
--- a/java/com/google/gerrit/server/account/AccountResolver.java
+++ b/java/com/google/gerrit/server/account/AccountResolver.java
@@ -535,10 +535,10 @@
    * As opposed to {@link #resolve}, the returned result includes all inactive accounts for the
    * input search.
    *
-   * <p>This can be used to resolve Gerrit Account from email to its {@link Account.Id}, to make
-   * sure that if {@link Account} with such email exists in Gerrit (even inactive), user data (email
-   * address) won't be recorded as it is, but instead will be stored as a link to the corresponding
-   * Gerrit Account.
+   * <p>This can be used to resolve Gerrit Account from email to its {@link
+   * com.google.gerrit.entities.Account.Id}, to make sure that if {@link Account} with such email
+   * exists in Gerrit (even inactive), user data (email address) won't be recorded as it is, but
+   * instead will be stored as a link to the corresponding Gerrit Account.
    */
   public Result resolveIncludeInactive(String input) throws ConfigInvalidException, IOException {
     return searchImpl(input, searchers, visibilitySupplierCanSee(), all());
diff --git a/java/com/google/gerrit/server/account/AccountsUpdate.java b/java/com/google/gerrit/server/account/AccountsUpdate.java
index 5a74047..7bd2c53 100644
--- a/java/com/google/gerrit/server/account/AccountsUpdate.java
+++ b/java/com/google/gerrit/server/account/AccountsUpdate.java
@@ -512,6 +512,7 @@
 
     BatchRefUpdate batchRefUpdate = allUsersRepo.getRefDatabase().newBatchUpdate();
 
+    String externalIdUpdateMessage = "Batch update for " + updatedAccounts.size() + " accounts";
     for (UpdatedAccount updatedAccount : updatedAccounts) {
       // These updates are all for different refs (because batches never update the same account
       // more than once), so there can be multiple commits in the same batch, all with the same base
@@ -528,7 +529,7 @@
 
       // These update the same ref, so they need to be stacked on top of one another using the same
       // ExternalIdNotes instance.
-      commitExternalIdUpdates(updatedAccount.message, allUsersRepo, batchRefUpdate);
+      commitExternalIdUpdates(externalIdUpdateMessage, allUsersRepo, batchRefUpdate);
     }
 
     RefUpdateUtil.executeChecked(batchRefUpdate, allUsersRepo);
diff --git a/java/com/google/gerrit/server/account/GroupCache.java b/java/com/google/gerrit/server/account/GroupCache.java
index aaae95a..d8cac71 100644
--- a/java/com/google/gerrit/server/account/GroupCache.java
+++ b/java/com/google/gerrit/server/account/GroupCache.java
@@ -103,6 +103,6 @@
    */
   void evict(AccountGroup.UUID groupUuid);
 
-  /** @see #evict(AccountGroup.UUID); */
+  /** @see #evict(AccountGroup.UUID) */
   void evict(Collection<AccountGroup.UUID> groupUuid);
 }
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalId.java b/java/com/google/gerrit/server/account/externalids/ExternalId.java
index 2d501ad..ac4017a 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalId.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalId.java
@@ -321,6 +321,8 @@
    */
   public static ExternalId parse(String noteId, byte[] raw, ObjectId blobId)
       throws ConfigInvalidException {
+    requireNonNull(blobId);
+
     Config externalIdConfig = new Config();
     try {
       externalIdConfig.fromText(new String(raw, UTF_8));
@@ -328,13 +330,6 @@
       throw invalidConfig(noteId, e.getMessage());
     }
 
-    return parse(noteId, externalIdConfig, blobId);
-  }
-
-  public static ExternalId parse(String noteId, Config externalIdConfig, ObjectId blobId)
-      throws ConfigInvalidException {
-    requireNonNull(blobId);
-
     Set<String> externalIdKeys = externalIdConfig.getSubsections(EXTERNAL_ID_SECTION);
     if (externalIdKeys.size() != 1) {
       throw invalidConfig(
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalIdNotes.java b/java/com/google/gerrit/server/account/externalids/ExternalIdNotes.java
index e403a5b..50a2f69 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalIdNotes.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalIdNotes.java
@@ -17,7 +17,6 @@
 import static com.google.common.base.Preconditions.checkState;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.Objects.requireNonNull;
-import static java.util.stream.Collectors.joining;
 import static java.util.stream.Collectors.toSet;
 import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
 
@@ -86,7 +85,8 @@
  *
  * <p>After committing the external IDs a cache update can be requested which also reindexes the
  * accounts for which external IDs have been updated (see {@link
- * ExternalIdNotesLoader#updateExternalIdCacheAndMaybeReindexAccounts)}).
+ * ExternalIdNotesLoader#updateExternalIdCacheAndMaybeReindexAccounts(ExternalIdNotes,
+ * Collection)}).
  */
 public class ExternalIdNotes extends VersionedMetaData {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -487,9 +487,9 @@
 
     Set<ExternalId> newExtIds = new HashSet<>();
     noteMapUpdates.add(
-        (rw, n, f) -> {
+        (rw, n) -> {
           for (ExternalId extId : extIds) {
-            ExternalId insertedExtId = upsert(rw, inserter, noteMap, f, extId);
+            ExternalId insertedExtId = upsert(rw, inserter, noteMap, extId);
             newExtIds.add(insertedExtId);
           }
         });
@@ -516,9 +516,9 @@
     Set<ExternalId> removedExtIds = get(ExternalId.Key.from(extIds));
     Set<ExternalId> updatedExtIds = new HashSet<>();
     noteMapUpdates.add(
-        (rw, n, f) -> {
+        (rw, n) -> {
           for (ExternalId extId : extIds) {
-            ExternalId updatedExtId = upsert(rw, inserter, noteMap, f, extId);
+            ExternalId updatedExtId = upsert(rw, inserter, noteMap, extId);
             updatedExtIds.add(updatedExtId);
           }
         });
@@ -547,9 +547,9 @@
     checkLoaded();
     Set<ExternalId> removedExtIds = new HashSet<>();
     noteMapUpdates.add(
-        (rw, n, f) -> {
+        (rw, n) -> {
           for (ExternalId extId : extIds) {
-            remove(rw, noteMap, f, extId);
+            remove(rw, noteMap, extId);
             removedExtIds.add(extId);
           }
         });
@@ -576,9 +576,9 @@
     checkLoaded();
     Set<ExternalId> removedExtIds = new HashSet<>();
     noteMapUpdates.add(
-        (rw, n, f) -> {
+        (rw, n) -> {
           for (ExternalId.Key extIdKey : extIdKeys) {
-            ExternalId removedExtId = remove(rw, noteMap, f, extIdKey, accountId);
+            ExternalId removedExtId = remove(rw, noteMap, extIdKey, accountId);
             removedExtIds.add(removedExtId);
           }
         });
@@ -594,9 +594,9 @@
     checkLoaded();
     Set<ExternalId> removedExtIds = new HashSet<>();
     noteMapUpdates.add(
-        (rw, n, f) -> {
+        (rw, n) -> {
           for (ExternalId.Key extIdKey : extIdKeys) {
-            ExternalId extId = remove(rw, noteMap, f, extIdKey, null);
+            ExternalId extId = remove(rw, noteMap, extIdKey, null);
             removedExtIds.add(extId);
           }
         });
@@ -624,16 +624,16 @@
     Set<ExternalId> removedExtIds = new HashSet<>();
     Set<ExternalId> updatedExtIds = new HashSet<>();
     noteMapUpdates.add(
-        (rw, n, f) -> {
+        (rw, n) -> {
           for (ExternalId.Key extIdKey : toDelete) {
-            ExternalId removedExtId = remove(rw, noteMap, f, extIdKey, accountId);
+            ExternalId removedExtId = remove(rw, noteMap, extIdKey, accountId);
             if (removedExtId != null) {
               removedExtIds.add(removedExtId);
             }
           }
 
           for (ExternalId extId : toAdd) {
-            ExternalId insertedExtId = upsert(rw, inserter, noteMap, f, extId);
+            ExternalId insertedExtId = upsert(rw, inserter, noteMap, extId);
             updatedExtIds.add(insertedExtId);
           }
         });
@@ -659,14 +659,14 @@
     Set<ExternalId> removedExtIds = new HashSet<>();
     Set<ExternalId> updatedExtIds = new HashSet<>();
     noteMapUpdates.add(
-        (rw, n, f) -> {
+        (rw, n) -> {
           for (ExternalId.Key extIdKey : toDelete) {
-            ExternalId removedExtId = remove(rw, noteMap, f, extIdKey, null);
+            ExternalId removedExtId = remove(rw, noteMap, extIdKey, null);
             removedExtIds.add(removedExtId);
           }
 
           for (ExternalId extId : toAdd) {
-            ExternalId insertedExtId = upsert(rw, inserter, noteMap, f, extId);
+            ExternalId insertedExtId = upsert(rw, inserter, noteMap, extId);
             updatedExtIds.add(insertedExtId);
           }
         });
@@ -745,21 +745,14 @@
     }
 
     try (RevWalk rw = new RevWalk(reader)) {
-      Set<String> footers = new HashSet<>();
       for (NoteMapUpdate noteMapUpdate : noteMapUpdates) {
         try {
-          noteMapUpdate.execute(rw, noteMap, footers);
+          noteMapUpdate.execute(rw, noteMap);
         } catch (DuplicateExternalIdKeyException e) {
           throw new IOException(e);
         }
       }
       noteMapUpdates.clear();
-      if (!footers.isEmpty()) {
-        commit.setMessage(
-            footers.stream()
-                .sorted()
-                .collect(joining("\n", commit.getMessage().trim() + "\n\n", "")));
-      }
 
       RevTree oldTree = revision != null ? rw.parseTree(revision) : null;
       ObjectId newTreeId = noteMap.writeTree(inserter);
@@ -821,17 +814,15 @@
    * <p>If the external ID already exists it is overwritten.
    */
   private static ExternalId upsert(
-      RevWalk rw, ObjectInserter ins, NoteMap noteMap, Set<String> footers, ExternalId extId)
+      RevWalk rw, ObjectInserter ins, NoteMap noteMap, ExternalId extId)
       throws IOException, ConfigInvalidException {
     ObjectId noteId = extId.key().sha1();
     Config c = new Config();
-    if (noteMap.contains(extId.key().sha1())) {
+    if (noteMap.contains(noteId)) {
       ObjectId noteDataId = noteMap.get(noteId);
       byte[] raw = readNoteData(rw, noteDataId);
       try {
         c = new BlobBasedConfig(null, raw);
-        ExternalId oldExtId = ExternalId.parse(noteId.name(), c, noteDataId);
-        addFooters(footers, oldExtId);
       } catch (ConfigInvalidException e) {
         throw new ConfigInvalidException(
             String.format("Invalid external id config for note %s: %s", noteId, e.getMessage()));
@@ -841,9 +832,7 @@
     byte[] raw = c.toText().getBytes(UTF_8);
     ObjectId noteData = ins.insert(OBJ_BLOB, raw);
     noteMap.set(noteId, noteData);
-    ExternalId newExtId = ExternalId.create(extId, noteData);
-    addFooters(footers, newExtId);
-    return newExtId;
+    return ExternalId.create(extId, noteData);
   }
 
   /**
@@ -852,7 +841,7 @@
    * @throws IllegalStateException is thrown if there is an existing external ID that has the same
    *     key, but otherwise doesn't match the specified external ID.
    */
-  private static void remove(RevWalk rw, NoteMap noteMap, Set<String> footers, ExternalId extId)
+  private static void remove(RevWalk rw, NoteMap noteMap, ExternalId extId)
       throws IOException, ConfigInvalidException {
     ObjectId noteId = extId.key().sha1();
     if (!noteMap.contains(noteId)) {
@@ -868,7 +857,6 @@
         extId.toString(),
         actualExtId.toString());
     noteMap.remove(noteId);
-    addFooters(footers, actualExtId);
   }
 
   /**
@@ -880,11 +868,7 @@
    *     exists
    */
   private static ExternalId remove(
-      RevWalk rw,
-      NoteMap noteMap,
-      Set<String> footers,
-      ExternalId.Key extIdKey,
-      Account.Id expectedAccountId)
+      RevWalk rw, NoteMap noteMap, ExternalId.Key extIdKey, Account.Id expectedAccountId)
       throws IOException, ConfigInvalidException {
     ObjectId noteId = extIdKey.sha1();
     if (!noteMap.contains(noteId)) {
@@ -904,17 +888,9 @@
           extId.accountId().get());
     }
     noteMap.remove(noteId);
-    addFooters(footers, extId);
     return extId;
   }
 
-  private static void addFooters(Set<String> footers, ExternalId extId) {
-    footers.add("Account: " + extId.accountId().get());
-    if (extId.email() != null) {
-      footers.add("Email: " + extId.email());
-    }
-  }
-
   private void checkExternalIdsDontExist(Collection<ExternalId> extIds)
       throws DuplicateExternalIdKeyException, IOException {
     checkExternalIdKeysDontExist(ExternalId.Key.from(extIds));
@@ -943,7 +919,7 @@
 
   @FunctionalInterface
   private interface NoteMapUpdate {
-    void execute(RevWalk rw, NoteMap noteMap, Set<String> footers)
+    void execute(RevWalk rw, NoteMap noteMap)
         throws IOException, ConfigInvalidException, DuplicateExternalIdKeyException;
   }
 
diff --git a/java/com/google/gerrit/server/change/DeleteReviewersUtil.java b/java/com/google/gerrit/server/change/DeleteReviewersUtil.java
index 3212c8d..79ed043 100644
--- a/java/com/google/gerrit/server/change/DeleteReviewersUtil.java
+++ b/java/com/google/gerrit/server/change/DeleteReviewersUtil.java
@@ -62,9 +62,8 @@
             changeNotes.getChangeId(),
             deleteReviewerOpFactory.create(result.asUnique().account(), deleteReviewerInput));
         return;
-      } else {
-        return;
       }
+      return;
     } catch (AccountResolver.UnresolvableAccountException e) {
       if (e.isSelf()) {
         throw new AuthException(e.getMessage(), e);
diff --git a/java/com/google/gerrit/server/change/IncludedInResolver.java b/java/com/google/gerrit/server/change/IncludedInResolver.java
index 9216964..3e1b69b 100644
--- a/java/com/google/gerrit/server/change/IncludedInResolver.java
+++ b/java/com/google/gerrit/server/change/IncludedInResolver.java
@@ -27,7 +27,6 @@
 import com.google.common.flogger.FluentLogger;
 import java.io.IOException;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -115,33 +114,6 @@
   }
 
   /**
-   * Partition the reference tips into two sets:
-   *
-   * <ul>
-   *   <li>before = commits with time < target.getCommitTime()
-   *   <li>after = commits with time >= target.getCommitTime()
-   * </ul>
-   *
-   * Each of the before/after lists is sorted by the commit time.
-   *
-   * @param before
-   * @param after
-   */
-  private void partition(List<RevCommit> before, List<RevCommit> after) {
-    int insertionPoint =
-        Collections.binarySearch(tipsByCommitTime, target, comparing(RevCommit::getCommitTime));
-    if (insertionPoint < 0) {
-      insertionPoint = -(insertionPoint + 1);
-    }
-    if (0 < insertionPoint) {
-      before.addAll(tipsByCommitTime.subList(0, insertionPoint));
-    }
-    if (insertionPoint < tipsByCommitTime.size()) {
-      after.addAll(tipsByCommitTime.subList(insertionPoint, tipsByCommitTime.size()));
-    }
-  }
-
-  /**
    * Returns the short names of refs which are as well in the matchingRefs list as well as in the
    * allRef list.
    */
diff --git a/java/com/google/gerrit/server/comment/CommentContextCacheImpl.java b/java/com/google/gerrit/server/comment/CommentContextCacheImpl.java
index c40b3d7..5be41d4 100644
--- a/java/com/google/gerrit/server/comment/CommentContextCacheImpl.java
+++ b/java/com/google/gerrit/server/comment/CommentContextCacheImpl.java
@@ -119,12 +119,7 @@
 
       for (CommentContextKey inputKey : inputKeys) {
         CommentContextKey cacheKey = inputKeysToCacheKeys.get(inputKey);
-        CommentContext commentContext = allContext.get(cacheKey);
-        if (commentContext == null) {
-          logger.atSevere().log("comment context for cache key %s is missing", cacheKey);
-          continue;
-        }
-        result.put(inputKey, commentContext);
+        result.put(inputKey, allContext.get(cacheKey));
       }
       return result.build();
     } catch (ExecutionException e) {
@@ -240,13 +235,7 @@
           result.putAll(context);
         }
       }
-      Map<CommentContextKey, CommentContext> allContexts = result.build();
-      if (allContexts.size() < Iterables.size(keys)) {
-        logger.atSevere().log(
-            "incomplete result: requested contexts for %s, got contexts for %s",
-            Iterables.toString(keys), allContexts.keySet());
-      }
-      return allContexts;
+      return result.build();
     }
 
     /**
@@ -267,21 +256,17 @@
       List<HumanComment> allComments =
           Streams.concat(humanComments.stream(), drafts.stream()).collect(Collectors.toList());
       CommentContextLoader loader = factory.create(project);
-      Map<ContextInput, CommentContextKey> commentsToKeys = new HashMap<>();
+      Map<CommentContextKey, ContextInput> keysToComments = new HashMap<>();
       for (CommentContextKey key : keys) {
         Comment comment = getCommentForKey(allComments, key);
-        commentsToKeys.put(ContextInput.fromComment(comment, key.contextPadding()), key);
+        keysToComments.put(key, ContextInput.fromComment(comment, key.contextPadding()));
       }
-      Map<ContextInput, CommentContext> allContext = loader.getContext(commentsToKeys.keySet());
-      Map<CommentContextKey, CommentContext> result =
-          allContext.entrySet().stream()
-              .collect(Collectors.toMap(e -> commentsToKeys.get(e.getKey()), Map.Entry::getValue));
-      if (result.size() < keys.size()) {
-        logger.atSevere().log(
-            "incomplete result: requested contexts for %s for change %s of project %s, got contexts for %s",
-            keys, changeId, project, result.keySet());
-      }
-      return result;
+      Map<ContextInput, CommentContext> allContext =
+          loader.getContext(
+              keysToComments.values().stream().distinct().collect(Collectors.toList()));
+      return keys.stream()
+          .collect(
+              Collectors.toMap(Function.identity(), k -> allContext.get(keysToComments.get(k))));
     }
 
     /**
diff --git a/java/com/google/gerrit/server/patch/AutoMerger.java b/java/com/google/gerrit/server/patch/AutoMerger.java
index 8d1e0ff..97910400 100644
--- a/java/com/google/gerrit/server/patch/AutoMerger.java
+++ b/java/com/google/gerrit/server/patch/AutoMerger.java
@@ -142,7 +142,7 @@
     }
     counter.increment(OperationType.IN_MEMORY_WRITE);
     logger.atInfo().log("Computing in-memory AutoMerge for " + merge.name());
-    try (Timer1.Context ignored = latency.start(OperationType.IN_MEMORY_WRITE)) {
+    try (Timer1.Context<OperationType> ignored = latency.start(OperationType.IN_MEMORY_WRITE)) {
       return rw.parseCommit(createAutoMergeCommit(repo.getConfig(), rw, ins, merge, mergeStrategy));
     }
   }
@@ -171,7 +171,7 @@
     }
 
     ObjectId autoMerge;
-    try (Timer1.Context ignored = latency.start(OperationType.ON_DISK_WRITE)) {
+    try (Timer1.Context<OperationType> ignored = latency.start(OperationType.ON_DISK_WRITE)) {
       autoMerge =
           createAutoMergeCommit(
               repoView.getConfig(), rw, ins, maybeMergeCommit, configuredMergeStrategy);
diff --git a/java/com/google/gerrit/server/patch/PatchScriptFactory.java b/java/com/google/gerrit/server/patch/PatchScriptFactory.java
index 885459a..0c648b5 100644
--- a/java/com/google/gerrit/server/patch/PatchScriptFactory.java
+++ b/java/com/google/gerrit/server/patch/PatchScriptFactory.java
@@ -265,9 +265,8 @@
           // the results while rolling out the new diff cache.
           runOldDiffCacheAsyncAndExportMetrics(git, aId, bId, patchScript);
           return patchScript;
-        } else {
-          return getPatchScriptWithOldDiffCache(git, aId, bId);
         }
+        return getPatchScriptWithOldDiffCache(git, aId, bId);
       } catch (PatchListNotAvailableException e) {
         throw new NoSuchChangeException(changeId, e);
       } catch (DiffNotAvailableException e) {
diff --git a/java/com/google/gerrit/server/query/change/ChangeData.java b/java/com/google/gerrit/server/query/change/ChangeData.java
index ac28342..f912250 100644
--- a/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -327,12 +327,16 @@
   private SubmitTypeRecord submitTypeRecord;
   private Boolean mergeable;
   private Set<String> hashtags;
-  /** Map from {@link Account.Id} to the tip of the edit ref for this change and a given user. */
+  /**
+   * Map from {@link com.google.gerrit.entities.Account.Id} to the tip of the edit ref for this
+   * change and a given user.
+   */
   private Table<Account.Id, PatchSet.Id, ObjectId> editsByUser;
 
   private Set<Account.Id> reviewedBy;
   /**
-   * Map from {@link Account.Id} to the tip of the draft comments ref for this change and the user.
+   * Map from {@link com.google.gerrit.entities.Account.Id} to the tip of the draft comments ref for
+   * this change and the user.
    */
   private Map<Account.Id, ObjectId> draftsByUser;
 
diff --git a/java/com/google/gerrit/server/query/change/ChangePredicates.java b/java/com/google/gerrit/server/query/change/ChangePredicates.java
index 617002d..044d276 100644
--- a/java/com/google/gerrit/server/query/change/ChangePredicates.java
+++ b/java/com/google/gerrit/server/query/change/ChangePredicates.java
@@ -33,22 +33,24 @@
   private ChangePredicates() {}
 
   /**
-   * Returns a predicate that matches changes where the provided {@link Account.Id} is in the
-   * attention set.
+   * Returns a predicate that matches changes where the provided {@link
+   * com.google.gerrit.entities.Account.Id} is in the attention set.
    */
   public static Predicate<ChangeData> attentionSet(Account.Id id) {
     return new ChangeIndexPredicate(ChangeField.ATTENTION_SET_USERS, id.toString());
   }
 
   /**
-   * Returns a predicate that matches changes that are assigned to the provided {@link Account.Id}.
+   * Returns a predicate that matches changes that are assigned to the provided {@link
+   * com.google.gerrit.entities.Account.Id}.
    */
   public static Predicate<ChangeData> assignee(Account.Id id) {
     return new ChangeIndexPredicate(ChangeField.ASSIGNEE, id.toString());
   }
 
   /**
-   * Returns a predicate that matches changes that are a revert of the provided {@link Change.Id}.
+   * Returns a predicate that matches changes that are a revert of the provided {@link
+   * com.google.gerrit.entities.Change.Id}.
    */
   public static Predicate<ChangeData> revertOf(Change.Id revertOf) {
     return new ChangeIndexPredicate(ChangeField.REVERT_OF, revertOf.toString());
@@ -56,23 +58,23 @@
 
   /**
    * Returns a predicate that matches changes that have a comment authored by the provided {@link
-   * Account.Id}.
+   * com.google.gerrit.entities.Account.Id}.
    */
   public static Predicate<ChangeData> commentBy(Account.Id id) {
     return new ChangeIndexPredicate(ChangeField.COMMENTBY, id.toString());
   }
 
   /**
-   * Returns a predicate that matches changes where the provided {@link Account.Id} has a pending
-   * change edit.
+   * Returns a predicate that matches changes where the provided {@link
+   * com.google.gerrit.entities.Account.Id} has a pending change edit.
    */
   public static Predicate<ChangeData> editBy(Account.Id id) {
     return new ChangeIndexPredicate(ChangeField.EDITBY, id.toString());
   }
 
   /**
-   * Returns a predicate that matches changes where the provided {@link Account.Id} has a pending
-   * draft comment.
+   * Returns a predicate that matches changes where the provided {@link
+   * com.google.gerrit.entities.Account.Id} has a pending draft comment.
    */
   public static Predicate<ChangeData> draftBy(Account.Id id) {
     return new ChangeIndexPredicate(ChangeField.DRAFTBY, id.toString());
@@ -80,7 +82,7 @@
 
   /**
    * Returns a predicate that matches changes that were reviewed by any of the provided {@link
-   * Account.Id}.
+   * com.google.gerrit.entities.Account.Id}.
    */
   public static Predicate<ChangeData> reviewedBy(Collection<Account.Id> ids) {
     List<Predicate<ChangeData>> predicates = new ArrayList<>(ids.size());
@@ -96,26 +98,35 @@
         new ChangeIndexPredicate(ChangeField.REVIEWEDBY, ChangeField.NOT_REVIEWED.toString()));
   }
 
-  /** Returns a predicate that matches the change with the provided {@link Change.Id}. */
+  /**
+   * Returns a predicate that matches the change with the provided {@link
+   * com.google.gerrit.entities.Change.Id}.
+   */
   public static Predicate<ChangeData> id(Change.Id id) {
     return new ChangeIndexPredicate(
         ChangeField.LEGACY_ID, ChangeQueryBuilder.FIELD_CHANGE, id.toString());
   }
 
-  /** Returns a predicate that matches the change with the provided {@link Change.Id}. */
+  /**
+   * Returns a predicate that matches the change with the provided {@link
+   * com.google.gerrit.entities.Change.Id}.
+   */
   public static Predicate<ChangeData> idStr(Change.Id id) {
     return new ChangeIndexPredicate(
         ChangeField.LEGACY_ID_STR, ChangeQueryBuilder.FIELD_CHANGE, id.toString());
   }
 
-  /** Returns a predicate that matches changes owned by the provided {@link Account.Id}. */
+  /**
+   * Returns a predicate that matches changes owned by the provided {@link
+   * com.google.gerrit.entities.Account.Id}.
+   */
   public static Predicate<ChangeData> owner(Account.Id id) {
     return new ChangeIndexPredicate(ChangeField.OWNER, id.toString());
   }
 
   /**
    * Returns a predicate that matches changes that are a cherry pick of the provided {@link
-   * Change.Id}.
+   * com.google.gerrit.entities.Change.Id}.
    */
   public static Predicate<ChangeData> cherryPickOf(Change.Id id) {
     return new ChangeIndexPredicate(ChangeField.CHERRY_PICK_OF_CHANGE, id.toString());
@@ -123,7 +134,7 @@
 
   /**
    * Returns a predicate that matches changes that are a cherry pick of the provided {@link
-   * PatchSet.Id}.
+   * com.google.gerrit.entities.PatchSet.Id}.
    */
   public static Predicate<ChangeData> cherryPickOf(PatchSet.Id psId) {
     return Predicate.and(
@@ -131,7 +142,10 @@
         new ChangeIndexPredicate(ChangeField.CHERRY_PICK_OF_PATCHSET, String.valueOf(psId.get())));
   }
 
-  /** Returns a predicate that matches changes in the provided {@link Project.NameKey}. */
+  /**
+   * Returns a predicate that matches changes in the provided {@link
+   * com.google.gerrit.entities.Project.NameKey}.
+   */
   public static Predicate<ChangeData> project(Project.NameKey id) {
     return new ChangeIndexPredicate(ChangeField.PROJECT, id.get());
   }
diff --git a/java/com/google/gerrit/server/restapi/change/Abandon.java b/java/com/google/gerrit/server/restapi/change/Abandon.java
index 826c89d..2cfc3f5 100644
--- a/java/com/google/gerrit/server/restapi/change/Abandon.java
+++ b/java/com/google/gerrit/server/restapi/change/Abandon.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.server.restapi.change;
 
-import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.entities.Change;
 import com.google.gerrit.extensions.api.changes.AbandonInput;
 import com.google.gerrit.extensions.api.changes.NotifyHandling;
@@ -44,8 +43,6 @@
 @Singleton
 public class Abandon
     implements RestModifyView<ChangeResource, AbandonInput>, UiAction<ChangeResource> {
-  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
   private final BatchUpdate.Factory updateFactory;
   private final ChangeJson.Factory json;
   private final AbandonOp.Factory abandonOpFactory;
diff --git a/java/com/google/gerrit/server/restapi/change/DeleteVote.java b/java/com/google/gerrit/server/restapi/change/DeleteVote.java
index 35cadb7e..84424a8 100644
--- a/java/com/google/gerrit/server/restapi/change/DeleteVote.java
+++ b/java/com/google/gerrit/server/restapi/change/DeleteVote.java
@@ -89,7 +89,6 @@
       ApprovalsUtil approvalsUtil,
       PatchSetUtil psUtil,
       ChangeMessagesUtil cmUtil,
-      IdentifiedUser.GenericFactory userFactory,
       VoteDeleted voteDeleted,
       DeleteVoteSender.Factory deleteVoteSenderFactory,
       NotifyResolver notifyResolver,
diff --git a/java/com/google/gerrit/server/restapi/change/Move.java b/java/com/google/gerrit/server/restapi/change/Move.java
index 50b7516..9263971 100644
--- a/java/com/google/gerrit/server/restapi/change/Move.java
+++ b/java/com/google/gerrit/server/restapi/change/Move.java
@@ -21,7 +21,6 @@
 import static com.google.gerrit.server.query.change.ChangeData.asChanges;
 
 import com.google.common.base.Strings;
-import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.entities.BranchNameKey;
 import com.google.gerrit.entities.Change;
@@ -73,8 +72,6 @@
 
 @Singleton
 public class Move implements RestModifyView<ChangeResource, MoveInput>, UiAction<ChangeResource> {
-  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
   private final PermissionBackend permissionBackend;
   private final BatchUpdate.Factory updateFactory;
   private final ChangeJson.Factory json;
diff --git a/java/com/google/gerrit/server/restapi/change/Rebase.java b/java/com/google/gerrit/server/restapi/change/Rebase.java
index 3e1f033..2077fb8 100644
--- a/java/com/google/gerrit/server/restapi/change/Rebase.java
+++ b/java/com/google/gerrit/server/restapi/change/Rebase.java
@@ -18,7 +18,6 @@
 
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
-import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.entities.BranchNameKey;
 import com.google.gerrit.entities.Change;
 import com.google.gerrit.entities.PatchSet;
@@ -65,8 +64,6 @@
 @Singleton
 public class Rebase
     implements RestModifyView<RevisionResource, RebaseInput>, UiAction<RevisionResource> {
-  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
   private static final ImmutableSet<ListChangesOption> OPTIONS =
       Sets.immutableEnumSet(ListChangesOption.CURRENT_REVISION, ListChangesOption.CURRENT_COMMIT);
 
diff --git a/java/com/google/gerrit/server/restapi/change/Revert.java b/java/com/google/gerrit/server/restapi/change/Revert.java
index 7bb43d2..8d48c88 100644
--- a/java/com/google/gerrit/server/restapi/change/Revert.java
+++ b/java/com/google/gerrit/server/restapi/change/Revert.java
@@ -19,7 +19,6 @@
 import static com.google.gerrit.server.permissions.RefPermission.CREATE_CHANGE;
 import static com.google.gerrit.server.project.ProjectCache.illegalState;
 
-import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.entities.Change;
 import com.google.gerrit.entities.PatchSet;
 import com.google.gerrit.extensions.api.changes.RevertInput;
@@ -54,8 +53,6 @@
 @Singleton
 public class Revert
     implements RestModifyView<ChangeResource, RevertInput>, UiAction<ChangeResource> {
-  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
   private final PermissionBackend permissionBackend;
   private final PatchSetUtil psUtil;
   private final ChangeJson.Factory json;
diff --git a/java/com/google/gerrit/server/rules/DefaultSubmitRule.java b/java/com/google/gerrit/server/rules/DefaultSubmitRule.java
index f5709e4..fe429dd 100644
--- a/java/com/google/gerrit/server/rules/DefaultSubmitRule.java
+++ b/java/com/google/gerrit/server/rules/DefaultSubmitRule.java
@@ -17,16 +17,13 @@
 import static com.google.common.base.Preconditions.checkState;
 import static com.google.common.collect.ImmutableList.toImmutableList;
 
-import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.entities.LabelFunction;
 import com.google.gerrit.entities.LabelType;
 import com.google.gerrit.entities.PatchSetApproval;
 import com.google.gerrit.entities.SubmitRecord;
 import com.google.gerrit.extensions.annotations.Exports;
 import com.google.gerrit.extensions.config.FactoryModule;
-import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.query.change.ChangeData;
-import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -42,8 +39,6 @@
  */
 @Singleton
 public final class DefaultSubmitRule implements SubmitRule {
-  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
   public static class Module extends FactoryModule {
     @Override
     public void configure() {
@@ -53,13 +48,6 @@
     }
   }
 
-  private final ProjectCache projectCache;
-
-  @Inject
-  DefaultSubmitRule(ProjectCache projectCache) {
-    this.projectCache = projectCache;
-  }
-
   @Override
   public Optional<SubmitRecord> evaluate(ChangeData cd) {
     SubmitRecord submitRecord = new SubmitRecord();
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
index 1d50e82..d091ae1 100644
--- a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
@@ -2974,6 +2974,30 @@
     assertThat(e).hasMessageThat().contains("foo:bar");
   }
 
+  @Test
+  public void externalIdBatchUpdates_commitMsg() throws Exception {
+    ExternalId extId1 =
+        ExternalId.createWithEmail(ExternalId.Key.parse("foo:bar"), admin.id(), "1@foo.com");
+    ExternalId extId2 =
+        ExternalId.createWithEmail(ExternalId.Key.parse("foo:baz"), user.id(), "2@foo.com");
+
+    AccountsUpdate.UpdateArguments ua1 =
+        new AccountsUpdate.UpdateArguments(
+            "first message", admin.id(), (a, u) -> u.addExternalId(extId1));
+    AccountsUpdate.UpdateArguments ua2 =
+        new AccountsUpdate.UpdateArguments(
+            "second message", user.id(), (a, u) -> u.addExternalId(extId2));
+    accountsUpdateProvider.get().updateBatch(ImmutableList.of(ua1, ua2));
+
+    try (Repository allUsersRepo = repoManager.openRepository(allUsers);
+        RevWalk rw = new RevWalk(allUsersRepo)) {
+      RevCommit commit =
+          rw.parseCommit(allUsersRepo.exactRef(RefNames.REFS_EXTERNAL_IDS).getObjectId());
+
+      assertThat(commit.getFullMessage()).isEqualTo("Batch update for 2 accounts\n");
+    }
+  }
+
   private void createDraft(PushOneCommit.Result r, String path, String message) throws Exception {
     DraftInput in = new DraftInput();
     in.path = path;
diff --git a/javatests/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java b/javatests/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java
index b6033d4..20b378b 100644
--- a/javatests/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java
@@ -37,7 +37,6 @@
 import com.google.common.collect.Iterables;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.RestResponse;
-import com.google.gerrit.acceptance.TestAccount;
 import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
 import com.google.gerrit.common.data.GlobalCapability;
@@ -79,8 +78,6 @@
 import org.eclipse.jgit.junit.TestRepository;
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.FooterLine;
-import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.transport.PushResult;
 import org.eclipse.jgit.transport.RemoteRefUpdate;
@@ -774,97 +771,6 @@
     }
   }
 
-  @Test
-  public void footers() throws Exception {
-    // Insert external ID for different accounts
-    TestAccount user1 = accountCreator.create("user1");
-    TestAccount user2 = accountCreator.create("user2");
-    ExternalId extId1 = ExternalId.create("foo", "1", user1.id());
-    ExternalId extId2 = ExternalId.create("foo", "2", user1.id());
-    ExternalId extId3 = ExternalId.create("foo", "3", user2.id());
-    try (Repository allUsersRepo = repoManager.openRepository(allUsers);
-        MetaDataUpdate md = metaDataUpdateFactory.create(allUsers)) {
-      ExternalIdNotes extIdNotes = externalIdNotesFactory.load(allUsersRepo);
-      extIdNotes.insert(ImmutableSet.of(extId1, extId2, extId3));
-      RevCommit c = extIdNotes.commit(md);
-      assertThat(getFooters(c))
-          .containsExactly("Account: " + user1.id(), "Account: " + user2.id())
-          .inOrder();
-    }
-
-    // Insert external ID with different emails
-    ExternalId extId4 = ExternalId.createWithEmail("foo", "4", user1.id(), "foo4@example.com");
-    ExternalId extId5 = ExternalId.createWithEmail("foo", "5", user2.id(), "foo5@example.com");
-    try (Repository allUsersRepo = repoManager.openRepository(allUsers);
-        MetaDataUpdate md = metaDataUpdateFactory.create(allUsers)) {
-      ExternalIdNotes extIdNotes = externalIdNotesFactory.load(allUsersRepo);
-      extIdNotes.insert(ImmutableSet.of(extId4, extId5));
-      RevCommit c = extIdNotes.commit(md);
-      assertThat(getFooters(c))
-          .containsExactly(
-              "Account: " + user1.id(),
-              "Account: " + user2.id(),
-              "Email: foo4@example.com",
-              "Email: foo5@example.com")
-          .inOrder();
-    }
-
-    // Update external ID - Add Email
-    ExternalId extId1a = ExternalId.createWithEmail("foo", "1", user1.id(), "foo1@example.com");
-    try (Repository allUsersRepo = repoManager.openRepository(allUsers);
-        MetaDataUpdate md = metaDataUpdateFactory.create(allUsers)) {
-      ExternalIdNotes extIdNotes = externalIdNotesFactory.load(allUsersRepo);
-      extIdNotes.upsert(extId1a);
-      RevCommit c = extIdNotes.commit(md);
-      assertThat(getFooters(c))
-          .containsExactly("Account: " + user1.id(), "Email: foo1@example.com")
-          .inOrder();
-    }
-
-    // Update external ID - Remove Email
-    try (Repository allUsersRepo = repoManager.openRepository(allUsers);
-        MetaDataUpdate md = metaDataUpdateFactory.create(allUsers)) {
-      ExternalIdNotes extIdNotes = externalIdNotesFactory.load(allUsersRepo);
-      extIdNotes.upsert(extId1);
-      RevCommit c = extIdNotes.commit(md);
-      assertThat(getFooters(c))
-          .containsExactly("Account: " + user1.id(), "Email: foo1@example.com")
-          .inOrder();
-    }
-
-    // Delete external IDs
-    try (Repository allUsersRepo = repoManager.openRepository(allUsers);
-        MetaDataUpdate md = metaDataUpdateFactory.create(allUsers)) {
-      ExternalIdNotes extIdNotes = externalIdNotesFactory.load(allUsersRepo);
-      extIdNotes.delete(ImmutableSet.of(extId1, extId5));
-      RevCommit c = extIdNotes.commit(md);
-      assertThat(getFooters(c))
-          .containsExactly(
-              "Account: " + user1.id(), "Account: " + user2.id(), "Email: foo5@example.com")
-          .inOrder();
-    }
-
-    // Delete external ID by key without email
-    try (Repository allUsersRepo = repoManager.openRepository(allUsers);
-        MetaDataUpdate md = metaDataUpdateFactory.create(allUsers)) {
-      ExternalIdNotes extIdNotes = externalIdNotesFactory.load(allUsersRepo);
-      extIdNotes.delete(extId2.accountId(), extId2.key());
-      RevCommit c = extIdNotes.commit(md);
-      assertThat(getFooters(c)).containsExactly("Account: " + user1.id()).inOrder();
-    }
-
-    // Delete external ID by key with email
-    try (Repository allUsersRepo = repoManager.openRepository(allUsers);
-        MetaDataUpdate md = metaDataUpdateFactory.create(allUsers)) {
-      ExternalIdNotes extIdNotes = externalIdNotesFactory.load(allUsersRepo);
-      extIdNotes.delete(extId4.accountId(), extId4.key());
-      RevCommit c = extIdNotes.commit(md);
-      assertThat(getFooters(c))
-          .containsExactly("Account: " + user1.id(), "Email: foo4@example.com")
-          .inOrder();
-    }
-  }
-
   private boolean isPartialCacheReloadingEnabled() {
     return cfg.getBoolean("cache", "external_ids_map", "enablePartialReloads", true);
   }
@@ -915,10 +821,6 @@
     }
   }
 
-  private List<String> getFooters(RevCommit c) {
-    return c.getFooterLines().stream().map(FooterLine::toString).collect(toList());
-  }
-
   private List<AccountExternalIdInfo> toExternalIdInfos(Collection<ExternalId> extIds) {
     return extIds.stream().map(this::toExternalIdInfo).collect(toList());
   }
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java b/javatests/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java
index d93d3f7..e1a6f99 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java
@@ -312,11 +312,7 @@
     assertThat(info.message).isEqualTo(expectedMessage);
     List<ChangeMessageInfo> messagesAfterDeletion = gApi.changes().id(changeNum).messages();
     assertMessagesAfterDeletion(
-        messagesBeforeDeletion,
-        messagesAfterDeletion,
-        deletedMessageIndex,
-        deletedBy,
-        expectedMessage);
+        messagesBeforeDeletion, messagesAfterDeletion, deletedMessageIndex, expectedMessage);
     assertCommentsAfterDeletion(changeNum, commentsBefore);
 
     // Verify change index is updated after deletion.
@@ -331,7 +327,6 @@
       List<ChangeMessageInfo> messagesBeforeDeletion,
       List<ChangeMessageInfo> messagesAfterDeletion,
       int deletedMessageIndex,
-      TestAccount deletedBy,
       String expectedDeleteMessage) {
     assertWithMessage("after: %s; before: %s", messagesAfterDeletion, messagesBeforeDeletion)
         .that(messagesAfterDeletion)
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java b/javatests/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
index d788149..ad06226 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
@@ -103,8 +103,8 @@
 
   @Before
   public void addNonCommitHead() throws Exception {
-    try (Repository repo = repoManager.openRepository(project)) {
-      ObjectInserter ins = repo.newObjectInserter();
+    try (Repository repo = repoManager.openRepository(project);
+        ObjectInserter ins = repo.newObjectInserter()) {
       ObjectId answer = ins.insert(Constants.OBJ_BLOB, new byte[] {42});
       ins.flush();
       ins.close();
diff --git a/javatests/com/google/gerrit/acceptance/server/change/CommentContextIT.java b/javatests/com/google/gerrit/acceptance/server/change/CommentContextIT.java
index 9bd8e9c..29058ef 100644
--- a/javatests/com/google/gerrit/acceptance/server/change/CommentContextIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/change/CommentContextIT.java
@@ -445,6 +445,52 @@
     assertThat(comments.get(0).sourceContentType).isEqualTo("text/x-c++src");
   }
 
+  @Test
+  public void listChangeCommentsWithContextEnabled_twoRangeCommentsWithTheSameContext()
+      throws Exception {
+    PushOneCommit.Result r1 = createChange();
+
+    ImmutableList.Builder<String> content = ImmutableList.builder();
+    for (int i = 1; i <= 10; i++) {
+      content.add("line_" + i);
+    }
+
+    PushOneCommit.Result r2 =
+        pushFactory
+            .create(
+                admin.newIdent(),
+                testRepo,
+                PushOneCommit.SUBJECT,
+                FILE_NAME,
+                content.build().stream().collect(Collectors.joining("\n")),
+                r1.getChangeId())
+            .to("refs/for/master");
+
+    CommentsUtil.addCommentOnRange(gApi, r2, "looks good", createCommentRange(2, 5));
+    CommentsUtil.addCommentOnRange(gApi, r2, "are you sure?", createCommentRange(2, 5));
+
+    List<CommentInfo> comments =
+        gApi.changes().id(r2.getChangeId()).commentsRequest().withContext(true).getAsList();
+
+    assertThat(comments).hasSize(2);
+
+    assertThat(
+            comments.stream()
+                .filter(c -> c.message.equals("looks good"))
+                .collect(MoreCollectors.onlyElement())
+                .contextLines)
+        .containsExactlyElementsIn(
+            createContextLines("2", "line_2", "3", "line_3", "4", "line_4", "5", "line_5"));
+
+    assertThat(
+            comments.stream()
+                .filter(c -> c.message.equals("are you sure?"))
+                .collect(MoreCollectors.onlyElement())
+                .contextLines)
+        .containsExactlyElementsIn(
+            createContextLines("2", "line_2", "3", "line_3", "4", "line_4", "5", "line_5"));
+  }
+
   private String createChangeWithContent(String fileName, String fileContent, int line)
       throws Exception {
     PushOneCommit.Result result =
diff --git a/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java b/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java
index cbf8438..89074b7 100644
--- a/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java
@@ -1386,11 +1386,10 @@
             r1.getCommit().getName(),
             CommentsUtil.newDraft(FILE_NAME, Side.REVISION, createLineRange(4, 10), "comment 1"));
 
-    CommentInfo draftPs2 =
-        addDraft(
-            r1.getChangeId(),
-            r2.getCommit().getName(),
-            CommentsUtil.newDraft(FILE_NAME, Side.REVISION, createLineRange(3, 12), "comment 3"));
+    addDraft(
+        r1.getChangeId(),
+        r2.getCommit().getName(),
+        CommentsUtil.newDraft(FILE_NAME, Side.REVISION, createLineRange(3, 12), "comment 3"));
 
     ReviewInput reviewInput =
         createReviewInput(
diff --git a/javatests/com/google/gerrit/acceptance/server/mail/ChangeNotificationsIT.java b/javatests/com/google/gerrit/acceptance/server/mail/ChangeNotificationsIT.java
index 0f50797..65cb97a 100644
--- a/javatests/com/google/gerrit/acceptance/server/mail/ChangeNotificationsIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/mail/ChangeNotificationsIT.java
@@ -1718,10 +1718,6 @@
     gApi.changes().id(changeId).current().submit(in);
   }
 
-  private void merge(String changeId, TestAccount by, TestAccount onBehalfOf) throws Exception {
-    merge(changeId, by, onBehalfOf, /*notify=*/ null);
-  }
-
   private void merge(
       String changeId, TestAccount by, TestAccount onBehalfOf, @Nullable NotifyHandling notify)
       throws Exception {
diff --git a/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java b/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java
index fa37704..858a9bb 100644
--- a/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java
+++ b/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java
@@ -56,7 +56,6 @@
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.ReviewerSet;
-import com.google.gerrit.server.config.GerritServerId;
 import com.google.gerrit.server.notedb.ChangeNotesCommit.ChangeNotesRevWalk;
 import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.server.validators.ValidationException;
@@ -81,8 +80,6 @@
 
   @Inject private ChangeNoteJson changeNoteJson;
 
-  @Inject private @GerritServerId String serverId;
-
   @Test
   public void tagChangeMessage() throws Exception {
     String tag = "jenkins";
diff --git a/polygerrit-ui/app/api/annotation.ts b/polygerrit-ui/app/api/annotation.ts
index ad0846d..3bda936 100644
--- a/polygerrit-ui/app/api/annotation.ts
+++ b/polygerrit-ui/app/api/annotation.ts
@@ -15,6 +15,7 @@
  * limitations under the License.
  */
 import {CoverageRange, Side} from './diff';
+import {ChangeInfo} from './rest-api';
 
 /**
  * This is the callback object that Gerrit calls once for each diff. Gerrit
@@ -26,14 +27,7 @@
   path: string,
   basePatchNum?: number,
   patchNum?: number,
-  /**
-   * This is a ChangeInfo object as defined here:
-   * https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#change-info
-   * At the moment we neither want to repeat it nor add a dependency on it here.
-   * TODO: Create a dedicated smaller object for exposing a change in the plugin
-   * API. Or allow the plugin API to depend on the entire rest API.
-   */
-  change?: unknown
+  change?: ChangeInfo
 ) => Promise<Array<CoverageRange>>;
 
 export declare interface AnnotationPluginApi {
diff --git a/polygerrit-ui/app/api/checks.ts b/polygerrit-ui/app/api/checks.ts
index 06f9509..aed6f8d 100644
--- a/polygerrit-ui/app/api/checks.ts
+++ b/polygerrit-ui/app/api/checks.ts
@@ -15,6 +15,7 @@
  * limitations under the License.
  */
 import {CommentRange} from './core';
+import {ChangeInfo} from './rest-api';
 
 export declare interface ChecksPluginApi {
   /**
@@ -62,8 +63,7 @@
   patchsetSha: string;
   repo: string;
   commmitMessage?: string;
-  /* TODO(brohlfs): Add dep to Rest API types and replace type by ChangeInfo. */
-  changeInfo: unknown;
+  changeInfo: ChangeInfo;
 }
 
 export declare interface ChecksProvider {
diff --git a/polygerrit-ui/app/api/gerrit.ts b/polygerrit-ui/app/api/gerrit.ts
index 275282e..b5a349f 100644
--- a/polygerrit-ui/app/api/gerrit.ts
+++ b/polygerrit-ui/app/api/gerrit.ts
@@ -20,6 +20,7 @@
   interface Window {
     Gerrit: Gerrit;
     VERSION_INFO?: string;
+    ENABLED_EXPERIMENTS?: string[];
   }
 }
 
diff --git a/polygerrit-ui/app/api/hook.ts b/polygerrit-ui/app/api/hook.ts
index 0ac6468..deb8566 100644
--- a/polygerrit-ui/app/api/hook.ts
+++ b/polygerrit-ui/app/api/hook.ts
@@ -14,25 +14,22 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+import {ChangeInfo, ConfigInfo, RevisionInfo} from './rest-api';
+
 interface GerritElementExtensions {
   content?: HTMLElement & {hidden?: boolean};
-  change?: unknown;
-  revision?: unknown;
+  change?: ChangeInfo;
+  revision?: RevisionInfo;
   token?: string;
   repoName?: string;
-  /**
-   * This is a ConfigInfo object as defined here:
-   * https://gerrit-review.googlesource.com/Documentation/rest-api-projects.html#config-info
-   * We neither want to repeat it nor add a dependency on it here.
-   */
-  config?: unknown;
+  config?: ConfigInfo;
 }
 
 export type HookCallback = (el: HTMLElement & GerritElementExtensions) => void;
 
 export declare interface RegisterOptions {
   slot?: string;
-  replace: unknown;
+  replace: boolean;
 }
 
 export declare interface HookApi {
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
index 835f77a..1185342 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.ts
@@ -67,7 +67,6 @@
   FileNameToFileInfoMap,
   NumericChangeId,
   PatchRange,
-  PreferencesInfo,
   UrlEncodedCommentId,
 } from '../../../types/common';
 import {DiffPreferencesInfo} from '../../../types/diff';
@@ -81,6 +80,9 @@
 import {ParsedChangeInfo, PatchSetFile} from '../../../types/types';
 import {Timing} from '../../../constants/reporting';
 import {RevisionInfo} from '../../shared/revision-info/revision-info';
+import {preferences$} from '../../../services/user/user-model';
+import {Subject} from 'rxjs';
+import {takeUntil} from 'rxjs/operators';
 
 export const DEFAULT_NUM_FILES_SHOWN = 200;
 
@@ -225,9 +227,6 @@
   @property({type: Object, notify: true, observer: '_updateDiffPreferences'})
   diffPrefs?: DiffPreferencesInfo;
 
-  @property({type: Object})
-  _userPrefs?: PreferencesInfo;
-
   @property({type: Boolean})
   _showInlineDiffs?: boolean;
 
@@ -270,7 +269,7 @@
   @property({type: Object, computed: '_computeSizeBarLayout(_shownFiles.*)'})
   _sizeBarLayout: SizeBarLayout = createDefaultSizeBarLayout();
 
-  @property({type: Boolean, computed: '_computeShowSizeBars(_userPrefs)'})
+  @property({type: Boolean})
   _showSizeBars = true;
 
   // For merge commits vs Auto Merge, an extra file row is shown detailing the
@@ -321,6 +320,8 @@
 
   private readonly restApiService = appContext.restApiService;
 
+  disconnected$ = new Subject();
+
   get keyBindings() {
     return {
       esc: '_handleEscKey',
@@ -411,6 +412,7 @@
 
   /** @override */
   disconnectedCallback() {
+    this.disconnected$.next();
     this.diffCursor.dispose();
     this.fileCursor.unsetCursor();
     this._cancelDiffs();
@@ -476,11 +478,9 @@
       })
     );
 
-    promises.push(
-      this._getPreferences().then(prefs => {
-        this._userPrefs = prefs;
-      })
-    );
+    preferences$.pipe(takeUntil(this.disconnected$)).subscribe(prefs => {
+      this._showSizeBars = !!prefs?.size_bar_in_change_table;
+    });
 
     return Promise.all(promises).then(() => {
       this._loading = false;
@@ -1723,10 +1723,6 @@
     return stats.deletionOffset;
   }
 
-  _computeShowSizeBars(userPrefs?: PreferencesInfo) {
-    return !!userPrefs?.size_bar_in_change_table;
-  }
-
   _computeSizeBarsClass(showSizeBars?: boolean, path?: string) {
     let hideClass = '';
     if (!showSizeBars) {
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.js b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.js
index 7016ff1..ee7d04e 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.js
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.js
@@ -95,7 +95,6 @@
 
   suite('basic tests', () => {
     setup(done => {
-      stubRestApi('getPreferences').returns(Promise.resolve({}));
       stubRestApi('getDiffComments').returns(Promise.resolve({}));
       stubRestApi('getDiffRobotComments').returns(Promise.resolve({}));
       stubRestApi('getDiffDrafts').returns(Promise.resolve({}));
diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_html.ts b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_html.ts
index e4ffe0f..5ab8449 100644
--- a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_html.ts
+++ b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_html.ts
@@ -59,12 +59,8 @@
   </span>
   <span is="dom-if" if="[[filesWeblinks.meta_a]]" class="filesWeblinks">
     <template is="dom-repeat" items="[[filesWeblinks.meta_a]]" as="weblink">
-      <a
-        target="_blank"
-        rel="noopener"
-        href$="[[weblink.url]]"
-        aria-label$="Browse with [[weblink.name]]"
-        >Browse with [[weblink.name]]</a
+      <a target="_blank" rel="noopener" href$="[[weblink.url]]"
+        >[[weblink.name]]</a
       >
     </template>
   </span>
@@ -79,13 +75,7 @@
     </gr-dropdown-list>
     <span is="dom-if" if="[[filesWeblinks.meta_b]]" class="filesWeblinks">
       <template is="dom-repeat" items="[[filesWeblinks.meta_b]]" as="weblink">
-        <a
-          target="_blank"
-          rel="noopener"
-          href$="[[weblink.url]]"
-          aria-label$="Browse with [[weblink.name]]"
-          >Browse with [[weblink.name]]</a
-        >
+        <a target="_blank" href$="[[weblink.url]]">[[weblink.name]]</a>
       </template>
     </span>
   </span>
diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.js b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.js
index 9fc19ff..9af7e05 100644
--- a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.js
@@ -349,13 +349,9 @@
     flush();
     const domApi = dom(element.root);
     assert.equal(
-        domApi.querySelector('a[href="f.oo"]').textContent,
-        'Browse with foo'
-    );
+        domApi.querySelector('a[href="f.oo"]').textContent, 'foo');
     assert.equal(
-        domApi.querySelector('a[href="ba.r"]').textContent,
-        'Browse with bar'
-    );
+        domApi.querySelector('a[href="ba.r"]').textContent, 'bar');
   });
 
   test('_computePatchSetCommentsString', () => {
diff --git a/polygerrit-ui/app/services/checks/checks-service.ts b/polygerrit-ui/app/services/checks/checks-service.ts
index 7844fa6..ca6c6e7 100644
--- a/polygerrit-ui/app/services/checks/checks-service.ts
+++ b/polygerrit-ui/app/services/checks/checks-service.ts
@@ -54,7 +54,7 @@
   Subject,
   timer,
 } from 'rxjs';
-import {PatchSetNumber} from '../../types/common';
+import {ChangeInfo, PatchSetNumber} from '../../types/common';
 import {getCurrentRevision} from '../../utils/change-util';
 import {getShaByPatchNum} from '../../utils/patch-set-util';
 import {assertIsDefined} from '../../utils/common-util';
@@ -172,7 +172,7 @@
               patchsetSha,
               repo: change.project,
               commmitMessage: getCurrentRevision(change)?.commit?.message,
-              changeInfo: change,
+              changeInfo: change as ChangeInfo,
             };
             return this.fetchResults(pluginName, data, patchset);
           }
diff --git a/polygerrit-ui/app/services/flags/flags_impl.ts b/polygerrit-ui/app/services/flags/flags_impl.ts
index fbfa833..18e225b 100644
--- a/polygerrit-ui/app/services/flags/flags_impl.ts
+++ b/polygerrit-ui/app/services/flags/flags_impl.ts
@@ -18,7 +18,7 @@
 
 declare global {
   interface Window {
-    ENABLED_EXPERIMENTS: string[];
+    ENABLED_EXPERIMENTS?: string[];
   }
 }
 
@@ -40,7 +40,7 @@
   }
 
   _loadExperiments(): Set<string> {
-    return new Set(window.ENABLED_EXPERIMENTS);
+    return new Set(window.ENABLED_EXPERIMENTS ?? []);
   }
 
   get enabledExperiments() {
diff --git a/polygerrit-ui/app/services/flags/flags_test.ts b/polygerrit-ui/app/services/flags/flags_test.ts
index 8827368..4ae11bf 100644
--- a/polygerrit-ui/app/services/flags/flags_test.ts
+++ b/polygerrit-ui/app/services/flags/flags_test.ts
@@ -19,7 +19,7 @@
 import {FlagsServiceImplementation} from './flags_impl';
 
 suite('flags tests', () => {
-  let originalEnabledExperiments: string[];
+  let originalEnabledExperiments: string[] | undefined;
   let flags: FlagsServiceImplementation;
 
   suiteSetup(() => {