Merge "Rename test event"
diff --git a/Documentation/intro-user.txt b/Documentation/intro-user.txt
index 264ce73..7a042ba 100644
--- a/Documentation/intro-user.txt
+++ b/Documentation/intro-user.txt
@@ -1014,7 +1014,7 @@
 inline comment ("Yeah, I see why, let me try again.").
 
 [[security-fixes]]
--- Security Fixes
+== Security Fixes
 
 If a security vulnerability is discovered you normally want to have an
 embargo about it until fixed releases have been made available. This
diff --git a/Documentation/pg-plugin-endpoints.txt b/Documentation/pg-plugin-endpoints.txt
index 41f544d..dd82f27 100644
--- a/Documentation/pg-plugin-endpoints.txt
+++ b/Documentation/pg-plugin-endpoints.txt
@@ -132,6 +132,10 @@
 === settings-screen
 This endpoint is situated at the end of the body of the settings screen.
 
+=== profile
+This endpoint is situated at the top of the Profile section of the settings
+screen below the section description text.
+
 === reply-text
 This endpoint wraps the textarea in the reply dialog.
 
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 8992f86..65275bd 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -8253,6 +8253,7 @@
 ancestry, where the oldest ancestor is the first.
 |`contains_git_conflicts`  ||Whether any of the rebased changes has conflicts
 due to rebasing.
+|===========================
 
 [[related-change-and-commit-info]]
 === RelatedChangeAndCommitInfo
diff --git a/contrib/git-gc-preserve b/contrib/git-gc-preserve
index f3946ab..a886721 100755
--- a/contrib/git-gc-preserve
+++ b/contrib/git-gc-preserve
@@ -67,6 +67,7 @@
   test -f "$LOCKFILE" || touch "$LOCKFILE"
   exec 9> "$LOCKFILE"
   if flock -nx 9; then
+    echo -n "$$ $USERNAME@$HOSTNAME" >&9
     trap unlock EXIT
   else
     echo "$0 is already running"
diff --git a/java/com/google/gerrit/extensions/common/ChangeInfoDiffer.java b/java/com/google/gerrit/extensions/common/ChangeInfoDiffer.java
index 24182cc..51c35dc 100644
--- a/java/com/google/gerrit/extensions/common/ChangeInfoDiffer.java
+++ b/java/com/google/gerrit/extensions/common/ChangeInfoDiffer.java
@@ -150,16 +150,17 @@
   /** Returns {@code null} if nothing has been added to {@code oldCollection} */
   @Nullable
   private static ImmutableList<?> getAddedForCollection(
-      Collection<?> oldCollection, Collection<?> newCollection) {
-    ImmutableList<?> notInOldCollection = getAdditions(oldCollection, newCollection);
+      @Nullable Collection<?> oldCollection, Collection<?> newCollection) {
+    ImmutableList<?> notInOldCollection = getAdditionsForCollection(oldCollection, newCollection);
     return notInOldCollection.isEmpty() ? null : notInOldCollection;
   }
 
   @Nullable
-  private static ImmutableList<Object> getAdditions(
-      Collection<?> oldCollection, Collection<?> newCollection) {
-    if (oldCollection == null)
-      return newCollection != null ? ImmutableList.copyOf(newCollection) : null;
+  private static ImmutableList<Object> getAdditionsForCollection(
+      @Nullable Collection<?> oldCollection, Collection<?> newCollection) {
+    if (oldCollection == null) {
+      return ImmutableList.copyOf(newCollection);
+    }
 
     Map<Object, List<Object>> duplicatesMap = newCollection.stream().collect(groupingBy(v -> v));
     oldCollection.forEach(
@@ -173,7 +174,18 @@
 
   /** Returns {@code null} if nothing has been added to {@code oldMap} */
   @Nullable
-  private static ImmutableMap<Object, Object> getAddedForMap(Map<?, ?> oldMap, Map<?, ?> newMap) {
+  private static ImmutableMap<Object, Object> getAddedForMap(
+      @Nullable Map<?, ?> oldMap, Map<?, ?> newMap) {
+    ImmutableMap<Object, Object> notInOldMap = getAdditionsForMap(oldMap, newMap);
+    return notInOldMap.isEmpty() ? null : notInOldMap;
+  }
+
+  @Nullable
+  private static ImmutableMap<Object, Object> getAdditionsForMap(
+      @Nullable Map<?, ?> oldMap, Map<?, ?> newMap) {
+    if (oldMap == null) {
+      return ImmutableMap.copyOf(newMap);
+    }
     ImmutableMap.Builder<Object, Object> additionsBuilder = ImmutableMap.builder();
     for (Map.Entry<?, ?> entry : newMap.entrySet()) {
       Object added = getAdded(oldMap.get(entry.getKey()), entry.getValue());
@@ -181,8 +193,7 @@
         additionsBuilder.put(entry.getKey(), added);
       }
     }
-    ImmutableMap<Object, Object> additions = additionsBuilder.build();
-    return additions.isEmpty() ? null : additions;
+    return additionsBuilder.build();
   }
 
   private static Object get(Field field, Object obj) {
diff --git a/java/com/google/gerrit/httpd/init/WebAppInitializer.java b/java/com/google/gerrit/httpd/init/WebAppInitializer.java
index 0073ec2..df2c5cb 100644
--- a/java/com/google/gerrit/httpd/init/WebAppInitializer.java
+++ b/java/com/google/gerrit/httpd/init/WebAppInitializer.java
@@ -55,6 +55,7 @@
 import com.google.gerrit.server.account.externalids.ExternalIdCaseSensitivityMigrator;
 import com.google.gerrit.server.api.GerritApiModule;
 import com.google.gerrit.server.api.PluginApiModule;
+import com.google.gerrit.server.api.projects.ProjectQueryBuilderModule;
 import com.google.gerrit.server.audit.AuditModule;
 import com.google.gerrit.server.cache.h2.H2CacheModule;
 import com.google.gerrit.server.cache.mem.DefaultMemoryCacheModule;
@@ -308,6 +309,7 @@
     modules.add(new MimeUtil2Module());
     modules.add(cfgInjector.getInstance(GerritGlobalModule.class));
     modules.add(new GerritApiModule());
+    modules.add(new ProjectQueryBuilderModule());
     modules.add(new PluginApiModule());
     modules.add(new SearchingChangeCacheImplModule());
     modules.add(new InternalAccountDirectoryModule());
diff --git a/java/com/google/gerrit/pgm/Daemon.java b/java/com/google/gerrit/pgm/Daemon.java
index 75891fe..0342fe5 100644
--- a/java/com/google/gerrit/pgm/Daemon.java
+++ b/java/com/google/gerrit/pgm/Daemon.java
@@ -64,6 +64,7 @@
 import com.google.gerrit.server.account.externalids.ExternalIdCaseSensitivityMigrator;
 import com.google.gerrit.server.api.GerritApiModule;
 import com.google.gerrit.server.api.PluginApiModule;
+import com.google.gerrit.server.api.projects.ProjectQueryBuilderModule;
 import com.google.gerrit.server.audit.AuditModule;
 import com.google.gerrit.server.cache.h2.H2CacheModule;
 import com.google.gerrit.server.cache.mem.DefaultMemoryCacheModule;
@@ -446,6 +447,7 @@
     modules.add(new MimeUtil2Module());
     modules.add(cfgInjector.getInstance(GerritGlobalModule.class));
     modules.add(new GerritApiModule());
+    modules.add(new ProjectQueryBuilderModule());
     modules.add(new PluginApiModule());
 
     modules.add(new SearchingChangeCacheImplModule(replica));
diff --git a/java/com/google/gerrit/server/StarredChangesUtil.java b/java/com/google/gerrit/server/StarredChangesUtil.java
index fd08fa8..8f413f9 100644
--- a/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/java/com/google/gerrit/server/StarredChangesUtil.java
@@ -23,7 +23,6 @@
 import com.google.common.base.CharMatcher;
 import com.google.common.base.Joiner;
 import com.google.common.base.Splitter;
-import com.google.common.collect.ImmutableListMultimap;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSortedSet;
@@ -39,21 +38,17 @@
 import com.google.gerrit.server.config.AllUsersName;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.index.change.ChangeField;
 import com.google.gerrit.server.logging.Metadata;
 import com.google.gerrit.server.logging.TraceContext;
 import com.google.gerrit.server.logging.TraceContext.TraceTimer;
-import com.google.gerrit.server.project.NoSuchChangeException;
-import com.google.gerrit.server.query.change.ChangeData;
-import com.google.gerrit.server.query.change.InternalChangeQuery;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
 import java.io.IOException;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.List;
 import java.util.NavigableSet;
+import java.util.Objects;
 import java.util.Set;
 import java.util.TreeSet;
 import org.eclipse.jgit.lib.BatchRefUpdate;
@@ -166,20 +161,17 @@
   private final GitReferenceUpdated gitRefUpdated;
   private final AllUsersName allUsers;
   private final Provider<PersonIdent> serverIdent;
-  private final Provider<InternalChangeQuery> queryProvider;
 
   @Inject
   StarredChangesUtil(
       GitRepositoryManager repoManager,
       GitReferenceUpdated gitRefUpdated,
       AllUsersName allUsers,
-      @GerritPersonIdent Provider<PersonIdent> serverIdent,
-      Provider<InternalChangeQuery> queryProvider) {
+      @GerritPersonIdent Provider<PersonIdent> serverIdent) {
     this.repoManager = repoManager;
     this.gitRefUpdated = gitRefUpdated;
     this.allUsers = allUsers;
     this.serverIdent = serverIdent;
-    this.queryProvider = queryProvider;
   }
 
   public NavigableSet<String> getLabels(Account.Id accountId, Change.Id changeId) {
@@ -238,7 +230,7 @@
       batchUpdate.setAllowNonFastForwards(true);
       batchUpdate.setRefLogIdent(serverIdent.get());
       batchUpdate.setRefLogMessage("Unstar change " + changeId.get(), true);
-      for (Account.Id accountId : byChangeFromIndex(changeId).keySet()) {
+      for (Account.Id accountId : getStars(repo, changeId)) {
         String refName = RefNames.refsStarredChanges(changeId, accountId);
         Ref ref = repo.getRefDatabase().exactRef(refName);
         if (ref != null) {
@@ -264,12 +256,7 @@
   public ImmutableMap<Account.Id, StarRef> byChange(Change.Id changeId) {
     try (Repository repo = repoManager.openRepository(allUsers)) {
       ImmutableMap.Builder<Account.Id, StarRef> builder = ImmutableMap.builder();
-      for (String refPart : getRefNames(repo, RefNames.refsStarredChangesPrefix(changeId))) {
-        Integer id = Ints.tryParse(refPart);
-        if (id == null) {
-          continue;
-        }
-        Account.Id accountId = Account.id(id);
+      for (Account.Id accountId : getStars(repo, changeId)) {
         builder.put(accountId, readLabels(repo, RefNames.refsStarredChanges(changeId, accountId)));
       }
       return builder.build();
@@ -308,22 +295,15 @@
     }
   }
 
-  public ImmutableListMultimap<Account.Id, String> byChangeFromIndex(Change.Id changeId) {
-    List<ChangeData> changeData =
-        queryProvider
-            .get()
-            .setRequestedFields(ChangeField.CHANGE_ID_SPEC, ChangeField.STAR_SPEC)
-            .byLegacyChangeId(changeId);
-    if (changeData.size() != 1) {
-      throw new NoSuchChangeException(changeId);
-    }
-    return changeData.get(0).stars();
-  }
-
-  private static Set<String> getRefNames(Repository repo, String prefix) throws IOException {
-    RefDatabase refDb = repo.getRefDatabase();
+  private static Set<Account.Id> getStars(Repository allUsers, Change.Id changeId)
+      throws IOException {
+    String prefix = RefNames.refsStarredChangesPrefix(changeId);
+    RefDatabase refDb = allUsers.getRefDatabase();
     return refDb.getRefsByPrefix(prefix).stream()
         .map(r -> r.getName().substring(prefix.length()))
+        .map(refPart -> Ints.tryParse(refPart))
+        .filter(Objects::nonNull)
+        .map(id -> Account.id(id))
         .collect(toSet());
   }
 
diff --git a/java/com/google/gerrit/server/account/AccountsUpdate.java b/java/com/google/gerrit/server/account/AccountsUpdate.java
index 8d5fea4..d6ea294 100644
--- a/java/com/google/gerrit/server/account/AccountsUpdate.java
+++ b/java/com/google/gerrit/server/account/AccountsUpdate.java
@@ -202,7 +202,6 @@
   private ExternalIdNotes externalIdNotes;
 
   @AssistedInject
-  @SuppressWarnings("BindingAnnotationWithoutInject")
   AccountsUpdate(
       GitRepositoryManager repoManager,
       GitReferenceUpdated gitRefUpdated,
@@ -228,7 +227,6 @@
   }
 
   @AssistedInject
-  @SuppressWarnings("BindingAnnotationWithoutInject")
   AccountsUpdate(
       GitRepositoryManager repoManager,
       GitReferenceUpdated gitRefUpdated,
diff --git a/java/com/google/gerrit/server/api/projects/ProjectQueryBuilderModule.java b/java/com/google/gerrit/server/api/projects/ProjectQueryBuilderModule.java
new file mode 100644
index 0000000..8ed1175
--- /dev/null
+++ b/java/com/google/gerrit/server/api/projects/ProjectQueryBuilderModule.java
@@ -0,0 +1,26 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.api.projects;
+
+import com.google.gerrit.server.query.project.ProjectQueryBuilder;
+import com.google.gerrit.server.query.project.ProjectQueryBuilderImpl;
+import com.google.inject.AbstractModule;
+
+public class ProjectQueryBuilderModule extends AbstractModule {
+  @Override
+  protected void configure() {
+    bind(ProjectQueryBuilder.class).to(ProjectQueryBuilderImpl.class);
+  }
+}
diff --git a/java/com/google/gerrit/server/change/GetRelatedChangesUtil.java b/java/com/google/gerrit/server/change/GetRelatedChangesUtil.java
index 27eeae1..9a75469 100644
--- a/java/com/google/gerrit/server/change/GetRelatedChangesUtil.java
+++ b/java/com/google/gerrit/server/change/GetRelatedChangesUtil.java
@@ -91,8 +91,7 @@
   }
 
   private List<ChangeData> getUnsortedRelated(
-      ChangeData changeData, PatchSet basePs, boolean alwaysIncludeOriginalChange)
-      throws IOException, PermissionBackendException {
+      ChangeData changeData, PatchSet basePs, boolean alwaysIncludeOriginalChange) {
     Set<String> groups = getAllGroups(changeData.patchSets());
     logger.atFine().log("groups = %s", groups);
     if (groups.isEmpty()) {
diff --git a/java/com/google/gerrit/server/group/db/GroupsUpdate.java b/java/com/google/gerrit/server/group/db/GroupsUpdate.java
index c0c934b..87d8db1 100644
--- a/java/com/google/gerrit/server/group/db/GroupsUpdate.java
+++ b/java/com/google/gerrit/server/group/db/GroupsUpdate.java
@@ -115,7 +115,6 @@
   private final RetryHelper retryHelper;
 
   @AssistedInject
-  @SuppressWarnings("BindingAnnotationWithoutInject")
   GroupsUpdate(
       GitRepositoryManager repoManager,
       AllUsersName allUsersName,
@@ -150,7 +149,6 @@
   }
 
   @AssistedInject
-  @SuppressWarnings("BindingAnnotationWithoutInject")
   GroupsUpdate(
       GitRepositoryManager repoManager,
       AllUsersName allUsersName,
@@ -185,7 +183,6 @@
         Optional.of(currentUser));
   }
 
-  @SuppressWarnings("BindingAnnotationWithoutInject")
   private GroupsUpdate(
       GitRepositoryManager repoManager,
       AllUsersName allUsersName,
diff --git a/java/com/google/gerrit/server/index/change/ChangeField.java b/java/com/google/gerrit/server/index/change/ChangeField.java
index 3585d13..1c89566f 100644
--- a/java/com/google/gerrit/server/index/change/ChangeField.java
+++ b/java/com/google/gerrit/server/index/change/ChangeField.java
@@ -988,7 +988,7 @@
 
   /** Serialized change object, used for pre-populating results. */
   private static final TypeToken<Entities.Change> CHANGE_TYPE_TOKEN =
-      new TypeToken<Entities.Change>() {
+      new TypeToken<>() {
         private static final long serialVersionUID = 1L;
       };
 
@@ -1007,7 +1007,7 @@
 
   /** Serialized approvals for the current patch set, used for pre-populating results. */
   private static final TypeToken<Iterable<Entities.PatchSetApproval>> APPROVAL_TYPE_TOKEN =
-      new TypeToken<Iterable<Entities.PatchSetApproval>>() {
+      new TypeToken<>() {
         private static final long serialVersionUID = 1L;
       };
 
@@ -1300,7 +1300,7 @@
 
   /** Serialized patch set object, used for pre-populating results. */
   private static final TypeToken<Iterable<Entities.PatchSet>> PATCH_SET_TYPE_TOKEN =
-      new TypeToken<Iterable<Entities.PatchSet>>() {
+      new TypeToken<>() {
         private static final long serialVersionUID = 1L;
       };
 
@@ -1621,7 +1621,7 @@
   /** Serialized submit requirements, used for pre-populating results. */
   private static final TypeToken<Iterable<Cache.SubmitRequirementResultProto>>
       STORED_SUBMIT_REQUIREMENTS_TYPE_TOKEN =
-          new TypeToken<Iterable<Cache.SubmitRequirementResultProto>>() {
+          new TypeToken<>() {
             private static final long serialVersionUID = 1L;
           };
 
diff --git a/java/com/google/gerrit/server/index/change/IndexedChangeQuery.java b/java/com/google/gerrit/server/index/change/IndexedChangeQuery.java
index 26477a4..8f5e36e 100644
--- a/java/com/google/gerrit/server/index/change/IndexedChangeQuery.java
+++ b/java/com/google/gerrit/server/index/change/IndexedChangeQuery.java
@@ -16,6 +16,7 @@
 
 import static com.google.common.base.Preconditions.checkState;
 import static com.google.gerrit.server.index.change.ChangeField.CHANGE_SPEC;
+import static com.google.gerrit.server.index.change.ChangeField.NUMERIC_ID_STR_SPEC;
 import static com.google.gerrit.server.index.change.ChangeField.PROJECT_SPEC;
 
 import com.google.common.annotations.VisibleForTesting;
@@ -68,10 +69,13 @@
       int pageSizeMultiplier,
       int limit,
       Set<String> fields) {
-    // Always include project since it is needed to load the change from NoteDb.
-    if (!fields.contains(CHANGE_SPEC.getName()) && !fields.contains(PROJECT_SPEC.getName())) {
+    // Always include project and change id since both are needed to load the change from NoteDb.
+    if (!fields.contains(CHANGE_SPEC.getName())
+        && !(fields.contains(PROJECT_SPEC.getName())
+            && fields.contains(NUMERIC_ID_STR_SPEC.getName()))) {
       fields = new HashSet<>(fields);
       fields.add(PROJECT_SPEC.getName());
+      fields.add(NUMERIC_ID_STR_SPEC.getName());
     }
     return QueryOptions.create(config, start, pageSize, pageSizeMultiplier, limit, fields);
   }
diff --git a/java/com/google/gerrit/server/patch/gitfilediff/GitFileDiffCacheImpl.java b/java/com/google/gerrit/server/patch/gitfilediff/GitFileDiffCacheImpl.java
index 2b856fb..43a212c 100644
--- a/java/com/google/gerrit/server/patch/gitfilediff/GitFileDiffCacheImpl.java
+++ b/java/com/google/gerrit/server/patch/gitfilediff/GitFileDiffCacheImpl.java
@@ -28,6 +28,7 @@
 import com.google.common.collect.MultimapBuilder;
 import com.google.common.collect.Multimaps;
 import com.google.common.collect.Streams;
+import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.entities.Patch;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
@@ -66,6 +67,7 @@
 import org.eclipse.jgit.diff.DiffFormatter;
 import org.eclipse.jgit.diff.HistogramDiff;
 import org.eclipse.jgit.diff.RawTextComparator;
+import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.lib.AbbreviatedObjectId;
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.ObjectId;
@@ -76,6 +78,7 @@
 /** Implementation of the {@link GitFileDiffCache} */
 @Singleton
 public class GitFileDiffCacheImpl implements GitFileDiffCache {
+  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
   private static final String GIT_DIFF = "git_file_diff";
 
   public static Module module() {
@@ -340,8 +343,7 @@
         throws IOException {
       if (!key.useTimeout()) {
         try (CloseablePool<DiffFormatter>.Handle formatter = diffPool.get()) {
-          FileHeader fileHeader = formatter.get().toFileHeader(diffEntry);
-          return GitFileDiff.create(diffEntry, fileHeader);
+          return GitFileDiff.create(diffEntry, getFileHeader(formatter, diffEntry));
         }
       }
       // This submits the DiffFormatter to a different thread. The CloseablePool and our usage of it
@@ -353,7 +355,7 @@
           diffExecutor.submit(
               () -> {
                 try (CloseablePool<DiffFormatter>.Handle formatter = diffPool.get()) {
-                  return GitFileDiff.create(diffEntry, formatter.get().toFileHeader(diffEntry));
+                  return GitFileDiff.create(diffEntry, getFileHeader(formatter, diffEntry));
                 }
               });
       try {
@@ -385,6 +387,46 @@
           ? diffEntry.getOldPath()
           : diffEntry.getNewPath();
     }
+
+    private FileHeader getFileHeader(
+        CloseablePool<DiffFormatter>.Handle formatter, DiffEntry diffEntry) throws IOException {
+      logger.atFine().log("getting file header for %s", formatDiffEntryForLogging(diffEntry));
+      try {
+        return formatter.get().toFileHeader(diffEntry);
+      } catch (MissingObjectException e) {
+        throw new IOException(
+            String.format("Failed to get file header for %s", formatDiffEntryForLogging(diffEntry)),
+            e);
+      }
+    }
+
+    private String formatDiffEntryForLogging(DiffEntry diffEntry) {
+      StringBuilder buf = new StringBuilder();
+      buf.append("DiffEntry[");
+      buf.append(diffEntry.getChangeType());
+      buf.append(" ");
+      switch (diffEntry.getChangeType()) {
+        case ADD:
+          buf.append(String.format("%s (%s)", diffEntry.getNewPath(), diffEntry.getNewId().name()));
+          break;
+        case COPY:
+        case RENAME:
+          buf.append(
+              String.format(
+                  "%s (%s) -> %s (%s)",
+                  diffEntry.getOldPath(),
+                  diffEntry.getOldId().name(),
+                  diffEntry.getNewPath(),
+                  diffEntry.getNewId().name()));
+          break;
+        case DELETE:
+        case MODIFY:
+          buf.append(String.format("%s (%s)", diffEntry.getOldPath(), diffEntry.getOldId().name()));
+          break;
+      }
+      buf.append("]");
+      return buf.toString();
+    }
   }
 
   /**
diff --git a/java/com/google/gerrit/server/project/PeriodicProjectListCacheWarmer.java b/java/com/google/gerrit/server/project/PeriodicProjectListCacheWarmer.java
index 85a3ab9..df2e1cf 100644
--- a/java/com/google/gerrit/server/project/PeriodicProjectListCacheWarmer.java
+++ b/java/com/google/gerrit/server/project/PeriodicProjectListCacheWarmer.java
@@ -92,7 +92,6 @@
   }
 
   @Override
-  @SuppressWarnings("CheckReturnValue")
   public void run() {
     logger.atFine().log("Loading project_list cache");
     cache.refreshProjectList();
diff --git a/java/com/google/gerrit/server/project/ProjectCacheImpl.java b/java/com/google/gerrit/server/project/ProjectCacheImpl.java
index 7fdd113..6498d1b 100644
--- a/java/com/google/gerrit/server/project/ProjectCacheImpl.java
+++ b/java/com/google/gerrit/server/project/ProjectCacheImpl.java
@@ -300,17 +300,21 @@
   @Override
   public Set<AccountGroup.UUID> guessRelevantGroupUUIDs() {
     try (Timer0.Context ignored = guessRelevantGroupsLatency.start()) {
-      return Streams.concat(
-              Arrays.stream(config.getStringList("groups", /* subsection= */ null, "relevantGroup"))
-                  .map(AccountGroup::uuid),
-              all().stream()
-                  .map(n -> inMemoryProjectCache.getIfPresent(n))
-                  .filter(Objects::nonNull)
-                  .flatMap(p -> p.getAllGroupUUIDs().stream())
-                  // getAllGroupUUIDs shouldn't really return null UUIDs, but harden
-                  // against them just in case there is a bug or corner case.
-                  .filter(id -> id != null && id.get() != null))
-          .collect(toSet());
+      Set<AccountGroup.UUID> relevantGroupUuids =
+          Streams.concat(
+                  Arrays.stream(
+                          config.getStringList("groups", /* subsection= */ null, "relevantGroup"))
+                      .map(AccountGroup::uuid),
+                  all().stream()
+                      .map(n -> inMemoryProjectCache.getIfPresent(n))
+                      .filter(Objects::nonNull)
+                      .flatMap(p -> p.getAllGroupUUIDs().stream())
+                      // getAllGroupUUIDs shouldn't really return null UUIDs, but harden
+                      // against them just in case there is a bug or corner case.
+                      .filter(id -> id != null && id.get() != null))
+              .collect(toSet());
+      logger.atFine().log("relevant group UUIDs: %s", relevantGroupUuids);
+      return relevantGroupUuids;
     }
   }
 
diff --git a/java/com/google/gerrit/server/query/change/ChangeData.java b/java/com/google/gerrit/server/query/change/ChangeData.java
index ec1fcad..fc4df49 100644
--- a/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -334,7 +334,7 @@
    * 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 Table<Account.Id, PatchSet.Id, Ref> editRefsByUser;
 
   private Set<Account.Id> reviewedBy;
   /**
@@ -966,7 +966,7 @@
    * submit requirements are evaluated online.
    *
    * <p>For changes loaded from the index, the value will be set from index field {@link
-   * com.google.gerrit.server.index.change.ChangeField#STORED_SUBMIT_REQUIREMENTS}.
+   * com.google.gerrit.server.index.change.ChangeField#STORED_SUBMIT_REQUIREMENTS_FIELD}.
    */
   public Map<SubmitRequirement, SubmitRequirementResult> submitRequirements() {
     if (submitRequirements == null) {
@@ -1097,8 +1097,8 @@
     return editRefs().rowKeySet();
   }
 
-  public Table<Account.Id, PatchSet.Id, ObjectId> editRefs() {
-    if (editsByUser == null) {
+  public Table<Account.Id, PatchSet.Id, Ref> editRefs() {
+    if (editRefsByUser == null) {
       if (!lazyload()) {
         return HashBasedTable.create();
       }
@@ -1106,7 +1106,7 @@
       if (c == null) {
         return HashBasedTable.create();
       }
-      editsByUser = HashBasedTable.create();
+      editRefsByUser = HashBasedTable.create();
       Change.Id id = requireNonNull(change.getId());
       try (Repository repo = repoManager.openRepository(project())) {
         for (Ref ref : repo.getRefDatabase().getRefsByPrefix(RefNames.REFS_USERS)) {
@@ -1117,7 +1117,7 @@
           if (id.equals(ps.changeId())) {
             Account.Id accountId = Account.Id.fromRef(ref.getName());
             if (accountId != null) {
-              editsByUser.put(accountId, ps, ref.getObjectId());
+              editRefsByUser.put(accountId, ps, ref);
             }
           }
         }
@@ -1125,7 +1125,7 @@
         throw new StorageException(e);
       }
     }
-    return editsByUser;
+    return editRefsByUser;
   }
 
   public Set<Account.Id> draftsByUser() {
@@ -1273,13 +1273,13 @@
       }
 
       ImmutableSetMultimap.Builder<NameKey, RefState> result = ImmutableSetMultimap.builder();
-      for (Table.Cell<Account.Id, PatchSet.Id, ObjectId> edit : editRefs().cellSet()) {
+      for (Table.Cell<Account.Id, PatchSet.Id, Ref> edit : editRefs().cellSet()) {
         result.put(
             project,
             RefState.create(
                 RefNames.refsEdit(
                     edit.getRowKey(), edit.getColumnKey().changeId(), edit.getColumnKey()),
-                edit.getValue()));
+                edit.getValue().getObjectId()));
       }
 
       // TODO: instantiating the notes is too much. We don't want to parse NoteDb, we just want the
@@ -1309,21 +1309,6 @@
             .forEach(r -> draftsByUser.put(Account.Id.fromRef(r.ref()), r.id()));
       }
     }
-    if (editsByUser == null) {
-      // Recover edit refs as well. Edits are represented as refs in the repository.
-      // ChangeData exposes #editsByUser which just provides a Set of Account.Ids of users who
-      // have edits on this change. Recovering this list from RefStates makes it available even
-      // on ChangeData instances retrieved from the index.
-      editsByUser = HashBasedTable.create();
-      if (refStates.containsKey(project())) {
-        refStates.get(project()).stream()
-            .filter(r -> RefNames.isRefsEdit(r.ref()))
-            .forEach(
-                r ->
-                    editsByUser.put(
-                        Account.Id.fromRef(r.ref()), PatchSet.Id.fromEditRef(r.ref()), r.id()));
-      }
-    }
   }
 
   public ImmutableList<byte[]> getRefStatePatterns() {
diff --git a/java/com/google/gerrit/server/query/project/ProjectQueryBuilder.java b/java/com/google/gerrit/server/query/project/ProjectQueryBuilder.java
index d234546..edb12ec 100644
--- a/java/com/google/gerrit/server/query/project/ProjectQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/project/ProjectQueryBuilder.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2017 The Android Open Source Project
+// Copyright (C) 2023 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -14,93 +14,21 @@
 
 package com.google.gerrit.server.query.project;
 
-import com.google.common.base.Strings;
-import com.google.common.collect.Lists;
-import com.google.common.primitives.Ints;
-import com.google.gerrit.entities.Project;
-import com.google.gerrit.extensions.client.ProjectState;
 import com.google.gerrit.index.project.ProjectData;
-import com.google.gerrit.index.query.LimitPredicate;
 import com.google.gerrit.index.query.Predicate;
-import com.google.gerrit.index.query.QueryBuilder;
 import com.google.gerrit.index.query.QueryParseException;
-import com.google.inject.Inject;
 import java.util.List;
 
-/** Parses a query string meant to be applied to project objects. */
-public class ProjectQueryBuilder extends QueryBuilder<ProjectData, ProjectQueryBuilder> {
-  public static final String FIELD_LIMIT = "limit";
+/**
+ * Provides methods required for parsing projects queries.
+ *
+ * <p>Internally (at google), this interface has a different implementation, comparing to upstream.
+ */
+public interface ProjectQueryBuilder {
+  String FIELD_LIMIT = "limit";
 
-  private static final QueryBuilder.Definition<ProjectData, ProjectQueryBuilder> mydef =
-      new QueryBuilder.Definition<>(ProjectQueryBuilder.class);
-
-  @Inject
-  ProjectQueryBuilder() {
-    super(mydef, null);
-  }
-
-  @Operator
-  public Predicate<ProjectData> name(String name) {
-    return ProjectPredicates.name(Project.nameKey(name));
-  }
-
-  @Operator
-  public Predicate<ProjectData> parent(String parentName) {
-    return ProjectPredicates.parent(Project.nameKey(parentName));
-  }
-
-  @Operator
-  public Predicate<ProjectData> inname(String namePart) {
-    if (namePart.isEmpty()) {
-      return name(namePart);
-    }
-    return ProjectPredicates.inname(namePart);
-  }
-
-  @Operator
-  public Predicate<ProjectData> description(String description) throws QueryParseException {
-    if (Strings.isNullOrEmpty(description)) {
-      throw error("description operator requires a value");
-    }
-
-    return ProjectPredicates.description(description);
-  }
-
-  @Operator
-  public Predicate<ProjectData> state(String state) throws QueryParseException {
-    if (Strings.isNullOrEmpty(state)) {
-      throw error("state operator requires a value");
-    }
-    ProjectState parsedState;
-    try {
-      parsedState = ProjectState.valueOf(state.replace('-', '_').toUpperCase());
-    } catch (IllegalArgumentException e) {
-      throw error("state operator must be either 'active' or 'read-only'", e);
-    }
-    if (parsedState == ProjectState.HIDDEN) {
-      throw error("state operator must be either 'active' or 'read-only'");
-    }
-    return ProjectPredicates.state(parsedState);
-  }
-
-  @Override
-  protected Predicate<ProjectData> defaultField(String query) throws QueryParseException {
-    // Adapt the capacity of this list when adding more default predicates.
-    List<Predicate<ProjectData>> preds = Lists.newArrayListWithCapacity(3);
-    preds.add(name(query));
-    preds.add(inname(query));
-    if (!Strings.isNullOrEmpty(query)) {
-      preds.add(description(query));
-    }
-    return Predicate.or(preds);
-  }
-
-  @Operator
-  public Predicate<ProjectData> limit(String query) throws QueryParseException {
-    Integer limit = Ints.tryParse(query);
-    if (limit == null) {
-      throw error("Invalid limit: " + query);
-    }
-    return new LimitPredicate<>(FIELD_LIMIT, limit);
-  }
+  /** See {@link com.google.gerrit.index.query.QueryBuilder#parse(String)}. */
+  Predicate<ProjectData> parse(String query) throws QueryParseException;
+  /** See {@link com.google.gerrit.index.query.QueryBuilder#parse(List<String>)}. */
+  List<Predicate<ProjectData>> parse(List<String> queries) throws QueryParseException;
 }
diff --git a/java/com/google/gerrit/server/query/project/ProjectQueryBuilderImpl.java b/java/com/google/gerrit/server/query/project/ProjectQueryBuilderImpl.java
new file mode 100644
index 0000000..f7135982
--- /dev/null
+++ b/java/com/google/gerrit/server/query/project/ProjectQueryBuilderImpl.java
@@ -0,0 +1,105 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.query.project;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import com.google.common.primitives.Ints;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.extensions.client.ProjectState;
+import com.google.gerrit.index.project.ProjectData;
+import com.google.gerrit.index.query.LimitPredicate;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryBuilder;
+import com.google.gerrit.index.query.QueryParseException;
+import com.google.inject.Inject;
+import java.util.List;
+
+/** Parses a query string meant to be applied to project objects. */
+public class ProjectQueryBuilderImpl extends QueryBuilder<ProjectData, ProjectQueryBuilderImpl>
+    implements ProjectQueryBuilder {
+  private static final QueryBuilder.Definition<ProjectData, ProjectQueryBuilderImpl> mydef =
+      new QueryBuilder.Definition<>(ProjectQueryBuilderImpl.class);
+
+  @Inject
+  ProjectQueryBuilderImpl() {
+    super(mydef, null);
+  }
+
+  @Operator
+  public Predicate<ProjectData> name(String name) {
+    return ProjectPredicates.name(Project.nameKey(name));
+  }
+
+  @Operator
+  public Predicate<ProjectData> parent(String parentName) {
+    return ProjectPredicates.parent(Project.nameKey(parentName));
+  }
+
+  @Operator
+  public Predicate<ProjectData> inname(String namePart) {
+    if (namePart.isEmpty()) {
+      return name(namePart);
+    }
+    return ProjectPredicates.inname(namePart);
+  }
+
+  @Operator
+  public Predicate<ProjectData> description(String description) throws QueryParseException {
+    if (Strings.isNullOrEmpty(description)) {
+      throw error("description operator requires a value");
+    }
+
+    return ProjectPredicates.description(description);
+  }
+
+  @Operator
+  public Predicate<ProjectData> state(String state) throws QueryParseException {
+    if (Strings.isNullOrEmpty(state)) {
+      throw error("state operator requires a value");
+    }
+    ProjectState parsedState;
+    try {
+      parsedState = ProjectState.valueOf(state.replace('-', '_').toUpperCase());
+    } catch (IllegalArgumentException e) {
+      throw error("state operator must be either 'active' or 'read-only'", e);
+    }
+    if (parsedState == ProjectState.HIDDEN) {
+      throw error("state operator must be either 'active' or 'read-only'");
+    }
+    return ProjectPredicates.state(parsedState);
+  }
+
+  @Override
+  protected Predicate<ProjectData> defaultField(String query) throws QueryParseException {
+    // Adapt the capacity of this list when adding more default predicates.
+    List<Predicate<ProjectData>> preds = Lists.newArrayListWithCapacity(3);
+    preds.add(name(query));
+    preds.add(inname(query));
+    if (!Strings.isNullOrEmpty(query)) {
+      preds.add(description(query));
+    }
+    return Predicate.or(preds);
+  }
+
+  @Operator
+  public Predicate<ProjectData> limit(String query) throws QueryParseException {
+    Integer limit = Ints.tryParse(query);
+    if (limit == null) {
+      throw error("Invalid limit: " + query);
+    }
+    return new LimitPredicate<>(FIELD_LIMIT, limit);
+  }
+}
diff --git a/java/com/google/gerrit/testing/InMemoryModule.java b/java/com/google/gerrit/testing/InMemoryModule.java
index aadf6d4..b828037 100644
--- a/java/com/google/gerrit/testing/InMemoryModule.java
+++ b/java/com/google/gerrit/testing/InMemoryModule.java
@@ -44,6 +44,7 @@
 import com.google.gerrit.server.account.GroupBackend;
 import com.google.gerrit.server.api.GerritApiModule;
 import com.google.gerrit.server.api.PluginApiModule;
+import com.google.gerrit.server.api.projects.ProjectQueryBuilderModule;
 import com.google.gerrit.server.audit.AuditModule;
 import com.google.gerrit.server.cache.h2.H2CacheModule;
 import com.google.gerrit.server.cache.mem.DefaultMemoryCacheModule;
@@ -191,6 +192,7 @@
     AuthConfig authConfig = cfgInjector.getInstance(AuthConfig.class);
     install(new AuthModule(authConfig));
     install(new GerritApiModule());
+    install(new ProjectQueryBuilderModule());
     factory(PluginUser.Factory.class);
     install(new PluginApiModule());
     install(new DefaultPermissionBackendModule());
diff --git a/java/com/google/gerrit/testing/TestChanges.java b/java/com/google/gerrit/testing/TestChanges.java
index 4a97bc5..c8f89cf 100644
--- a/java/com/google/gerrit/testing/TestChanges.java
+++ b/java/com/google/gerrit/testing/TestChanges.java
@@ -32,6 +32,7 @@
 import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.inject.Injector;
 import java.time.ZoneId;
+import java.util.Optional;
 import java.util.concurrent.atomic.AtomicInteger;
 import org.eclipse.jgit.junit.TestRepository;
 import org.eclipse.jgit.lib.ObjectId;
@@ -77,15 +78,20 @@
   }
 
   public static ChangeUpdate newUpdate(
-      Injector injector, Change c, CurrentUser user, boolean shouldExist) throws Exception {
+      Injector injector, Change c, Optional<CurrentUser> user, boolean shouldExist)
+      throws Exception {
     injector =
         injector.createChildInjector(
             new FactoryModule() {
               @Override
               public void configure() {
-                bind(CurrentUser.class).toInstance(user);
+                if (user.isPresent()) {
+                  // user may be already bound in injector
+                  bind(CurrentUser.class).toInstance(user.get());
+                }
               }
             });
+    CurrentUser currentUser = injector.getProvider(CurrentUser.class).get();
     ChangeUpdate update =
         injector
             .getInstance(ChangeUpdate.Factory.class)
@@ -93,7 +99,7 @@
                 new ChangeNotes(
                         injector.getInstance(AbstractChangeNotes.Args.class), c, shouldExist, null)
                     .load(),
-                user,
+                currentUser,
                 TimeUtil.now(),
                 Ordering.natural());
 
@@ -109,7 +115,9 @@
     try (Repository repo = repoManager.openRepository(c.getProject());
         TestRepository<Repository> tr = new TestRepository<>(repo)) {
       PersonIdent ident =
-          user.asIdentifiedUser().newCommitterIdent(update.getWhen(), ZoneId.systemDefault());
+          currentUser
+              .asIdentifiedUser()
+              .newCommitterIdent(update.getWhen(), ZoneId.systemDefault());
       TestRepository<Repository>.CommitBuilder cb =
           tr.commit()
               .author(ident)
diff --git a/javatests/com/google/gerrit/acceptance/api/change/RebaseIT.java b/javatests/com/google/gerrit/acceptance/api/change/RebaseIT.java
index 4248ac5..74bd94e 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/RebaseIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/RebaseIT.java
@@ -844,9 +844,11 @@
     public void setUp() throws Exception {
       init(
           id -> {
+            @SuppressWarnings("unused")
             Object unused = gApi.changes().id(id).rebaseChain();
           },
           (id, in) -> {
+            @SuppressWarnings("unused")
             Object unused = gApi.changes().id(id).rebaseChain(in);
           });
     }
diff --git a/javatests/com/google/gerrit/extensions/common/ChangeInfoDifferTest.java b/javatests/com/google/gerrit/extensions/common/ChangeInfoDifferTest.java
index 7ed236a..f45d33b 100644
--- a/javatests/com/google/gerrit/extensions/common/ChangeInfoDifferTest.java
+++ b/javatests/com/google/gerrit/extensions/common/ChangeInfoDifferTest.java
@@ -48,6 +48,7 @@
     assertThat(diff.added().messages).isNull();
     assertThat(diff.added().reviewers).isNull();
     assertThat(diff.added().hashtags).isNull();
+    assertThat(diff.added().removableLabels).isNull();
     assertThat(diff.removed()._number).isNull();
     assertThat(diff.removed().branch).isNull();
     assertThat(diff.removed().project).isNull();
@@ -56,6 +57,7 @@
     assertThat(diff.removed().messages).isNull();
     assertThat(diff.removed().reviewers).isNull();
     assertThat(diff.removed().hashtags).isNull();
+    assertThat(diff.removed().removableLabels).isNull();
   }
 
   @Test
@@ -315,6 +317,295 @@
   }
 
   @Test
+  public void getDiff_removableLabelsEmpty_returnsNullRemovableLabels() {
+    ChangeInfo oldChangeInfo = new ChangeInfo();
+    ChangeInfo newChangeInfo = new ChangeInfo();
+    oldChangeInfo.removableLabels = ImmutableMap.of();
+    newChangeInfo.removableLabels = ImmutableMap.of();
+
+    ChangeInfoDifference diff = ChangeInfoDiffer.getDifference(oldChangeInfo, newChangeInfo);
+
+    assertThat(diff.added().removableLabels).isNull();
+    assertThat(diff.removed().removableLabels).isNull();
+  }
+
+  @Test
+  public void getDiff_removableLabelsNullAndEmpty_returnsEmptyRemovableLabels() {
+    ChangeInfo oldChangeInfo = new ChangeInfo();
+    ChangeInfo newChangeInfo = new ChangeInfo();
+    newChangeInfo.removableLabels = ImmutableMap.of();
+
+    ChangeInfoDifference diff = ChangeInfoDiffer.getDifference(oldChangeInfo, newChangeInfo);
+
+    assertThat(diff.added().removableLabels).isEmpty();
+    assertThat(diff.removed().removableLabels).isNull();
+  }
+
+  @Test
+  public void getDiff_removableLabelsEmptyAndNull_returnsEmptyRemovableLabels() {
+    ChangeInfo oldChangeInfo = new ChangeInfo();
+    ChangeInfo newChangeInfo = new ChangeInfo();
+    oldChangeInfo.removableLabels = ImmutableMap.of();
+
+    ChangeInfoDifference diff = ChangeInfoDiffer.getDifference(oldChangeInfo, newChangeInfo);
+
+    assertThat(diff.added().removableLabels).isNull();
+    assertThat(diff.removed().removableLabels).isEmpty();
+  }
+
+  @Test
+  public void getDiff_removableLabelsLabelAdded() {
+    ChangeInfo oldChangeInfo = new ChangeInfo();
+    ChangeInfo newChangeInfo = new ChangeInfo();
+    AccountInfo acc1 = new AccountInfo();
+    acc1.name = "Cow";
+    AccountInfo acc2 = new AccountInfo();
+    acc2.name = "Pig";
+    AccountInfo acc3 = new AccountInfo();
+    acc3.name = "Cat";
+    AccountInfo acc4 = new AccountInfo();
+    acc4.name = "Dog";
+
+    oldChangeInfo.removableLabels =
+        ImmutableMap.of(
+            "Code-Review",
+            ImmutableMap.of("+1", ImmutableList.of(acc1), "-1", ImmutableList.of(acc2, acc3)));
+    newChangeInfo.removableLabels =
+        ImmutableMap.of(
+            "Code-Review",
+            ImmutableMap.of("+1", ImmutableList.of(acc1), "-1", ImmutableList.of(acc2, acc3)),
+            "Verified",
+            ImmutableMap.of("-1", ImmutableList.of(acc4)));
+
+    ChangeInfoDifference diff = ChangeInfoDiffer.getDifference(oldChangeInfo, newChangeInfo);
+
+    assertThat(diff.added().removableLabels)
+        .containsExactlyEntriesIn(
+            ImmutableMap.of("Verified", ImmutableMap.of("-1", ImmutableList.of(acc4))));
+    assertThat(diff.removed().removableLabels).isNull();
+  }
+
+  @Test
+  public void getDiff_removableLabelsLabelRemoved() {
+    ChangeInfo oldChangeInfo = new ChangeInfo();
+    ChangeInfo newChangeInfo = new ChangeInfo();
+    AccountInfo acc1 = new AccountInfo();
+    acc1.name = "Cow";
+    AccountInfo acc2 = new AccountInfo();
+    acc2.name = "Pig";
+    AccountInfo acc3 = new AccountInfo();
+    acc3.name = "Cat";
+    AccountInfo acc4 = new AccountInfo();
+    acc4.name = "Dog";
+
+    oldChangeInfo.removableLabels =
+        ImmutableMap.of(
+            "Code-Review",
+            ImmutableMap.of("+1", ImmutableList.of(acc1), "-1", ImmutableList.of(acc2, acc3)),
+            "Verified",
+            ImmutableMap.of("-1", ImmutableList.of(acc4)));
+    newChangeInfo.removableLabels =
+        ImmutableMap.of(
+            "Code-Review",
+            ImmutableMap.of("+1", ImmutableList.of(acc1), "-1", ImmutableList.of(acc2, acc3)));
+
+    ChangeInfoDifference diff = ChangeInfoDiffer.getDifference(oldChangeInfo, newChangeInfo);
+
+    assertThat(diff.added().removableLabels).isNull();
+    assertThat(diff.removed().removableLabels)
+        .containsExactlyEntriesIn(
+            ImmutableMap.of("Verified", ImmutableMap.of("-1", ImmutableList.of(acc4))));
+  }
+
+  @Test
+  public void getDiff_removableLabelsVoteAdded() {
+    ChangeInfo oldChangeInfo = new ChangeInfo();
+    ChangeInfo newChangeInfo = new ChangeInfo();
+    AccountInfo acc1 = new AccountInfo();
+    acc1.name = "acc1";
+    AccountInfo acc2 = new AccountInfo();
+    acc2.name = "acc2";
+    AccountInfo acc3 = new AccountInfo();
+    acc3.name = "acc3";
+
+    oldChangeInfo.removableLabels =
+        ImmutableMap.of("Code-Review", ImmutableMap.of("+1", ImmutableList.of(acc1)));
+    newChangeInfo.removableLabels =
+        ImmutableMap.of(
+            "Code-Review",
+            ImmutableMap.of("+1", ImmutableList.of(acc1), "-1", ImmutableList.of(acc2, acc3)));
+
+    ChangeInfoDifference diff = ChangeInfoDiffer.getDifference(oldChangeInfo, newChangeInfo);
+
+    assertThat(diff.added().removableLabels)
+        .containsExactlyEntriesIn(
+            ImmutableMap.of("Code-Review", ImmutableMap.of("-1", ImmutableList.of(acc2, acc3))));
+    assertThat(diff.removed().removableLabels).isNull();
+  }
+
+  @Test
+  public void getDiff_removableLabelsVoteRemoved() {
+    ChangeInfo oldChangeInfo = new ChangeInfo();
+    ChangeInfo newChangeInfo = new ChangeInfo();
+    AccountInfo acc1 = new AccountInfo();
+    acc1.name = "acc1";
+    AccountInfo acc2 = new AccountInfo();
+    acc2.name = "acc2";
+    AccountInfo acc3 = new AccountInfo();
+    acc3.name = "acc3";
+
+    oldChangeInfo.removableLabels =
+        ImmutableMap.of(
+            "Code-Review",
+            ImmutableMap.of("+1", ImmutableList.of(acc1), "-1", ImmutableList.of(acc2, acc3)));
+    newChangeInfo.removableLabels =
+        ImmutableMap.of("Code-Review", ImmutableMap.of("+1", ImmutableList.of(acc1)));
+
+    ChangeInfoDifference diff = ChangeInfoDiffer.getDifference(oldChangeInfo, newChangeInfo);
+
+    assertThat(diff.added().removableLabels).isNull();
+    assertThat(diff.removed().removableLabels)
+        .containsExactlyEntriesIn(
+            ImmutableMap.of("Code-Review", ImmutableMap.of("-1", ImmutableList.of(acc2, acc3))));
+  }
+
+  @Test
+  public void getDiff_removableLabelsAccountAdded() {
+    ChangeInfo oldChangeInfo = new ChangeInfo();
+    ChangeInfo newChangeInfo = new ChangeInfo();
+    AccountInfo acc1 = new AccountInfo();
+    acc1.name = "acc1";
+    AccountInfo acc2 = new AccountInfo();
+    acc2.name = "acc2";
+
+    oldChangeInfo.removableLabels =
+        ImmutableMap.of("Code-Review", ImmutableMap.of("+1", ImmutableList.of(acc1)));
+    newChangeInfo.removableLabels =
+        ImmutableMap.of("Code-Review", ImmutableMap.of("+1", ImmutableList.of(acc1, acc2)));
+
+    ChangeInfoDifference diff = ChangeInfoDiffer.getDifference(oldChangeInfo, newChangeInfo);
+
+    assertThat(diff.added().removableLabels)
+        .containsExactlyEntriesIn(
+            ImmutableMap.of("Code-Review", ImmutableMap.of("+1", ImmutableList.of(acc2))));
+    assertThat(diff.removed().removableLabels).isNull();
+  }
+
+  @Test
+  public void getDiff_removableLabelsAccountRemoved() {
+    ChangeInfo oldChangeInfo = new ChangeInfo();
+    ChangeInfo newChangeInfo = new ChangeInfo();
+    AccountInfo acc1 = new AccountInfo();
+    acc1.name = "acc1";
+    AccountInfo acc2 = new AccountInfo();
+    acc2.name = "acc2";
+
+    oldChangeInfo.removableLabels =
+        ImmutableMap.of("Code-Review", ImmutableMap.of("+1", ImmutableList.of(acc1)));
+    newChangeInfo.removableLabels =
+        ImmutableMap.of("Code-Review", ImmutableMap.of("+1", ImmutableList.of(acc1, acc2)));
+
+    ChangeInfoDifference diff = ChangeInfoDiffer.getDifference(oldChangeInfo, newChangeInfo);
+
+    assertThat(diff.added().removableLabels)
+        .containsExactlyEntriesIn(
+            ImmutableMap.of("Code-Review", ImmutableMap.of("+1", ImmutableList.of(acc2))));
+    assertThat(diff.removed().removableLabels).isNull();
+  }
+
+  @Test
+  public void getDiff_removableLabelsAccountChanged() {
+    ChangeInfo oldChangeInfo = new ChangeInfo();
+    ChangeInfo newChangeInfo = new ChangeInfo();
+    AccountInfo acc1 = new AccountInfo();
+    acc1.name = "acc1";
+    AccountInfo acc2 = new AccountInfo();
+    acc2.name = "acc2";
+
+    oldChangeInfo.removableLabels =
+        ImmutableMap.of("Code-Review", ImmutableMap.of("+1", ImmutableList.of(acc1)));
+    newChangeInfo.removableLabels =
+        ImmutableMap.of("Code-Review", ImmutableMap.of("+1", ImmutableList.of(acc2)));
+
+    ChangeInfoDifference diff = ChangeInfoDiffer.getDifference(oldChangeInfo, newChangeInfo);
+
+    assertThat(diff.added().removableLabels)
+        .containsExactlyEntriesIn(
+            ImmutableMap.of("Code-Review", ImmutableMap.of("+1", ImmutableList.of(acc2))));
+    assertThat(diff.removed().removableLabels)
+        .containsExactlyEntriesIn(
+            ImmutableMap.of("Code-Review", ImmutableMap.of("+1", ImmutableList.of(acc1))));
+  }
+
+  @Test
+  public void getDiff_removableLabelsScoreChanged() {
+    ChangeInfo oldChangeInfo = new ChangeInfo();
+    ChangeInfo newChangeInfo = new ChangeInfo();
+    AccountInfo acc1 = new AccountInfo();
+    acc1.name = "acc1";
+
+    oldChangeInfo.removableLabels =
+        ImmutableMap.of("Code-Review", ImmutableMap.of("+1", ImmutableList.of(acc1)));
+    newChangeInfo.removableLabels =
+        ImmutableMap.of("Code-Review", ImmutableMap.of("-1", ImmutableList.of(acc1)));
+
+    ChangeInfoDifference diff = ChangeInfoDiffer.getDifference(oldChangeInfo, newChangeInfo);
+
+    assertThat(diff.added().removableLabels)
+        .containsExactlyEntriesIn(
+            ImmutableMap.of("Code-Review", ImmutableMap.of("-1", ImmutableList.of(acc1))));
+    assertThat(diff.removed().removableLabels)
+        .containsExactlyEntriesIn(
+            ImmutableMap.of("Code-Review", ImmutableMap.of("+1", ImmutableList.of(acc1))));
+  }
+
+  @Test
+  public void getDiff_removableLabelsLabelChanged() {
+    ChangeInfo oldChangeInfo = new ChangeInfo();
+    ChangeInfo newChangeInfo = new ChangeInfo();
+    AccountInfo acc1 = new AccountInfo();
+    acc1.name = "acc1";
+
+    oldChangeInfo.removableLabels =
+        ImmutableMap.of("Code-Review", ImmutableMap.of("+1", ImmutableList.of(acc1)));
+    newChangeInfo.removableLabels =
+        ImmutableMap.of("Verified", ImmutableMap.of("+1", ImmutableList.of(acc1)));
+
+    ChangeInfoDifference diff = ChangeInfoDiffer.getDifference(oldChangeInfo, newChangeInfo);
+
+    assertThat(diff.added().removableLabels)
+        .containsExactlyEntriesIn(
+            ImmutableMap.of("Verified", ImmutableMap.of("+1", ImmutableList.of(acc1))));
+    assertThat(diff.removed().removableLabels)
+        .containsExactlyEntriesIn(
+            ImmutableMap.of("Code-Review", ImmutableMap.of("+1", ImmutableList.of(acc1))));
+  }
+
+  @Test
+  public void getDiff_removableLabelsLabelScoreAndAccountChanged() {
+    ChangeInfo oldChangeInfo = new ChangeInfo();
+    ChangeInfo newChangeInfo = new ChangeInfo();
+    AccountInfo acc1 = new AccountInfo();
+    acc1.name = "acc1";
+    AccountInfo acc2 = new AccountInfo();
+    acc2.name = "acc2";
+
+    oldChangeInfo.removableLabels =
+        ImmutableMap.of("Code-Review", ImmutableMap.of("+1", ImmutableList.of(acc1)));
+    newChangeInfo.removableLabels =
+        ImmutableMap.of("Verified", ImmutableMap.of("-1", ImmutableList.of(acc2)));
+
+    ChangeInfoDifference diff = ChangeInfoDiffer.getDifference(oldChangeInfo, newChangeInfo);
+
+    assertThat(diff.added().removableLabels)
+        .containsExactlyEntriesIn(
+            ImmutableMap.of("Verified", ImmutableMap.of("-1", ImmutableList.of(acc2))));
+    assertThat(diff.removed().removableLabels)
+        .containsExactlyEntriesIn(
+            ImmutableMap.of("Code-Review", ImmutableMap.of("+1", ImmutableList.of(acc1))));
+  }
+
+  @Test
   public void getDiff_assertCanConstructAllChangeInfoReferences() throws Exception {
     buildObjectWithFullFields(ChangeInfo.class);
   }
diff --git a/javatests/com/google/gerrit/server/index/account/AccountFieldTest.java b/javatests/com/google/gerrit/server/index/account/AccountFieldTest.java
index 65eb3b8..a40afe8 100644
--- a/javatests/com/google/gerrit/server/index/account/AccountFieldTest.java
+++ b/javatests/com/google/gerrit/server/index/account/AccountFieldTest.java
@@ -40,8 +40,7 @@
     String metaId = "0e39795bb25dc914118224995c53c5c36923a461";
     account.setMetaId(metaId);
     Iterable<byte[]> refStates =
-        (Iterable<byte[]>)
-            AccountField.REF_STATE_SPEC.get(AccountState.forAccount(account.build()));
+        AccountField.REF_STATE_SPEC.get(AccountState.forAccount(account.build()));
     List<String> values = toStrings(refStates);
     String expectedValue =
         allUsersName.get() + ":" + RefNames.refsUsers(account.id()) + ":" + metaId;
diff --git a/javatests/com/google/gerrit/server/notedb/AbstractChangeNotesTest.java b/javatests/com/google/gerrit/server/notedb/AbstractChangeNotesTest.java
index 37d8468..be8f1f9 100644
--- a/javatests/com/google/gerrit/server/notedb/AbstractChangeNotesTest.java
+++ b/javatests/com/google/gerrit/server/notedb/AbstractChangeNotesTest.java
@@ -76,6 +76,7 @@
 import com.google.inject.TypeLiteral;
 import java.time.Instant;
 import java.time.ZoneId;
+import java.util.Optional;
 import java.util.concurrent.ExecutorService;
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
 import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
@@ -276,7 +277,7 @@
 
   protected ChangeUpdate newUpdate(
       Injector injector, Change c, CurrentUser user, boolean shouldExist) throws Exception {
-    ChangeUpdate update = TestChanges.newUpdate(injector, c, user, shouldExist);
+    ChangeUpdate update = TestChanges.newUpdate(injector, c, Optional.of(user), shouldExist);
     update.setPatchSetId(c.currentPatchSetId());
     update.setAllowWriteToNewRef(true);
     return update;
diff --git a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index b1a9a49..5d38c55 100644
--- a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -46,6 +46,7 @@
 import com.google.common.collect.Multimaps;
 import com.google.common.collect.Streams;
 import com.google.common.truth.ThrowableSubject;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.gerrit.acceptance.ExtensionRegistry;
 import com.google.gerrit.acceptance.ExtensionRegistry.Registration;
 import com.google.gerrit.acceptance.FakeSubmitRule;
@@ -109,12 +110,14 @@
 import com.google.gerrit.server.account.AuthRequest;
 import com.google.gerrit.server.account.ListGroupMembership;
 import com.google.gerrit.server.account.VersionedAccountQueries;
+import com.google.gerrit.server.account.externalids.ExternalId;
 import com.google.gerrit.server.account.externalids.ExternalIdFactory;
 import com.google.gerrit.server.change.ChangeInserter;
 import com.google.gerrit.server.change.ChangeTriplet;
 import com.google.gerrit.server.change.NotifyResolver;
 import com.google.gerrit.server.change.PatchSetInserter;
 import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.meta.MetaDataUpdate;
 import com.google.gerrit.server.group.testing.TestGroupBackend;
 import com.google.gerrit.server.index.change.ChangeField;
@@ -122,6 +125,7 @@
 import com.google.gerrit.server.index.change.ChangeIndexer;
 import com.google.gerrit.server.index.change.IndexedChangeQuery;
 import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.notedb.ChangeUpdate;
 import com.google.gerrit.server.notedb.Sequences;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectConfig;
@@ -133,8 +137,7 @@
 import com.google.gerrit.server.util.ThreadLocalRequestContext;
 import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.testing.GerritServerTests;
-import com.google.gerrit.testing.InMemoryRepositoryManager;
-import com.google.gerrit.testing.InMemoryRepositoryManager.Repo;
+import com.google.gerrit.testing.TestChanges;
 import com.google.gerrit.testing.TestTimeUtil;
 import com.google.inject.Inject;
 import com.google.inject.Injector;
@@ -158,7 +161,7 @@
 import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.util.SystemReader;
@@ -183,7 +186,7 @@
   @Inject protected ChangeIndexer indexer;
   @Inject protected ExtensionRegistry extensionRegistry;
   @Inject protected IndexConfig indexConfig;
-  @Inject protected InMemoryRepositoryManager repoManager;
+  @Inject protected GitRepositoryManager repoManager;
   @Inject protected Provider<AnonymousUser> anonymousUserProvider;
   @Inject protected Provider<InternalChangeQuery> queryProvider;
   @Inject protected ChangeNotes.Factory notesFactory;
@@ -206,11 +209,20 @@
 
   protected Injector injector;
   protected LifecycleManager lifecycle;
+
+  /**
+   * Index tests should not use username in query assert, since some backends do not use {@link
+   * ExternalId#SCHEME_USERNAME}
+   */
   protected Account.Id userId;
+
   protected CurrentUser user;
+  protected Account userAccount;
 
   private String systemTimeZone;
 
+  protected TestRepository<Repository> repo;
+
   protected abstract Injector createInjector();
 
   @Before
@@ -226,6 +238,10 @@
 
   @After
   public void cleanUp() {
+    if (repo != null) {
+      repo.close();
+      repo = null;
+    }
     lifecycle.stop();
   }
 
@@ -252,8 +268,9 @@
     return () -> requestUser;
   }
 
-  protected void resetUser() {
+  protected void resetUser() throws ConfigInvalidException, IOException {
     user = userFactory.create(userId);
+    userAccount = accounts.get(userId).get().account();
     requestContext.setContext(newRequestContext(userId));
   }
 
@@ -289,9 +306,9 @@
 
   @Test
   public void byId() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
-    Change change1 = insert(repo, newChange(repo));
-    Change change2 = insert(repo, newChange(repo));
+    repo = createAndOpenProject("repo");
+    Change change1 = insert("repo", newChange(repo));
+    Change change2 = insert("repo", newChange(repo));
 
     assertQuery("12345");
     assertQuery(change1.getId().get(), change1);
@@ -300,8 +317,8 @@
 
   @Test
   public void byKey() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
-    Change change = insert(repo, newChange(repo));
+    repo = createAndOpenProject("repo");
+    Change change = insert("repo", newChange(repo));
     String key = change.getKey().get();
 
     assertQuery("I0000000000000000000000000000000000000000");
@@ -313,8 +330,8 @@
 
   @Test
   public void byTriplet() throws Exception {
-    TestRepository<Repo> repo = createProject("iabcde");
-    Change change = insert(repo, newChangeForBranch(repo, "branch"));
+    repo = createAndOpenProject("iabcde");
+    Change change = insert("iabcde", newChangeForBranch(repo, "branch"));
     String k = change.getKey().get();
 
     assertQuery("iabcde~branch~" + k, change);
@@ -336,11 +353,11 @@
 
   @Test
   public void byStatus() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     ChangeInserter ins1 = newChangeWithStatus(repo, Change.Status.NEW);
-    Change change1 = insert(repo, ins1);
+    Change change1 = insert("repo", ins1);
     ChangeInserter ins2 = newChangeWithStatus(repo, Change.Status.MERGED);
-    Change change2 = insert(repo, ins2);
+    Change change2 = insert("repo", ins2);
 
     assertQuery("status:new", change1);
     assertQuery("status:NEW", change1);
@@ -355,11 +372,11 @@
 
   @Test
   public void byStatusOr() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     ChangeInserter ins1 = newChangeWithStatus(repo, Change.Status.NEW);
-    Change change1 = insert(repo, ins1);
+    Change change1 = insert("repo", ins1);
     ChangeInserter ins2 = newChangeWithStatus(repo, Change.Status.MERGED);
-    Change change2 = insert(repo, ins2);
+    Change change2 = insert("repo", ins2);
 
     assertQuery("status:new OR status:merged", change2, change1);
     assertQuery("status:new or status:merged", change2, change1);
@@ -367,10 +384,10 @@
 
   @Test
   public void byStatusOpen() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     ChangeInserter ins1 = newChangeWithStatus(repo, Change.Status.NEW);
-    Change change1 = insert(repo, ins1);
-    insert(repo, newChangeWithStatus(repo, Change.Status.MERGED));
+    Change change1 = insert("repo", ins1);
+    insert("repo", newChangeWithStatus(repo, Change.Status.MERGED));
 
     Change[] expected = new Change[] {change1};
     assertQuery("status:open", expected);
@@ -389,12 +406,12 @@
 
   @Test
   public void byStatusClosed() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     ChangeInserter ins1 = newChangeWithStatus(repo, Change.Status.MERGED);
-    Change change1 = insert(repo, ins1);
+    Change change1 = insert("repo", ins1);
     ChangeInserter ins2 = newChangeWithStatus(repo, Change.Status.ABANDONED);
-    Change change2 = insert(repo, ins2);
-    insert(repo, newChangeWithStatus(repo, Change.Status.NEW));
+    Change change2 = insert("repo", ins2);
+    insert("repo", newChangeWithStatus(repo, Change.Status.NEW));
 
     Change[] expected = new Change[] {change2, change1};
     assertQuery("status:closed", expected);
@@ -410,12 +427,12 @@
 
   @Test
   public void byStatusAbandoned() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     ChangeInserter ins1 = newChangeWithStatus(repo, Change.Status.MERGED);
-    insert(repo, ins1);
+    insert("repo", ins1);
     ChangeInserter ins2 = newChangeWithStatus(repo, Change.Status.ABANDONED);
-    Change change1 = insert(repo, ins2);
-    insert(repo, newChangeWithStatus(repo, Change.Status.NEW));
+    Change change1 = insert("repo", ins2);
+    insert("repo", newChangeWithStatus(repo, Change.Status.NEW));
 
     assertQuery("status:abandoned", change1);
     assertQuery("status:ABANDONED", change1);
@@ -424,10 +441,10 @@
 
   @Test
   public void byStatusPrefix() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     ChangeInserter ins1 = newChangeWithStatus(repo, Change.Status.NEW);
-    Change change1 = insert(repo, ins1);
-    insert(repo, newChangeWithStatus(repo, Change.Status.MERGED));
+    Change change1 = insert("repo", ins1);
+    Change change2 = insert("repo", newChangeWithStatus(repo, Change.Status.MERGED));
 
     assertQuery("status:n", change1);
     assertQuery("status:ne", change1);
@@ -435,6 +452,7 @@
     assertQuery("status:N", change1);
     assertQuery("status:nE", change1);
     assertQuery("status:neW", change1);
+    assertQuery("status:m", change2);
     Exception thrown = assertThrows(BadRequestException.class, () -> assertQuery("status:newx"));
     assertThat(thrown).hasMessageThat().isEqualTo("Unrecognized value: newx");
     thrown = assertThrows(BadRequestException.class, () -> assertQuery("status:nx"));
@@ -443,11 +461,11 @@
 
   @Test
   public void byPrivate() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
-    Change change1 = insert(repo, newChange(repo), userId);
+    repo = createAndOpenProject("repo");
+    Change change1 = insert("repo", newChange(repo), userId);
     Account.Id user2 =
         accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
-    Change change2 = insert(repo, newChange(repo), user2);
+    Change change2 = insert("repo", newChange(repo), user2);
 
     // No private changes.
     assertQuery("is:open", change2, change1);
@@ -467,8 +485,8 @@
 
   @Test
   public void byWip() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
-    Change change1 = insert(repo, newChange(repo), userId);
+    repo = createAndOpenProject("repo");
+    Change change1 = insert("repo", newChange(repo), userId);
 
     assertQuery("is:open", change1);
     assertQuery("is:wip");
@@ -485,8 +503,8 @@
   @Test
   public void excludeWipChangeFromReviewersDashboards() throws Exception {
     Account.Id user1 = createAccount("user1");
-    TestRepository<Repo> repo = createProject("repo");
-    Change change1 = insert(repo, newChangeWorkInProgress(repo), userId);
+    repo = createAndOpenProject("repo");
+    Change change1 = insert("repo", newChangeWorkInProgress(repo), userId);
 
     assertQuery("is:wip", change1);
     assertQuery("reviewer:" + user1);
@@ -502,8 +520,8 @@
 
   @Test
   public void byStarted() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
-    Change change1 = insert(repo, newChangeWorkInProgress(repo));
+    repo = createAndOpenProject("repo");
+    Change change1 = insert("repo", newChangeWorkInProgress(repo));
 
     assertQuery("is:started");
 
@@ -538,11 +556,11 @@
   @Test
   public void restorePendingReviewers() throws Exception {
     Project.NameKey project = Project.nameKey("repo");
-    TestRepository<Repo> repo = createProject(project.get());
+    repo = createAndOpenProject(project.get());
     ConfigInput conf = new ConfigInput();
     conf.enableReviewerByEmail = InheritableBoolean.TRUE;
     gApi.projects().name(project.get()).config(conf);
-    Change change1 = insert(repo, newChangeWorkInProgress(repo));
+    Change change1 = insert("repo", newChangeWorkInProgress(repo));
     Account.Id user1 = createAccount("user1");
     Account.Id user2 = createAccount("user2");
     String email1 = "email1@example.com";
@@ -595,9 +613,9 @@
 
   @Test
   public void byCommit() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     ChangeInserter ins = newChange(repo);
-    Change change = insert(repo, ins);
+    Change change = insert("repo", ins);
     String sha = ins.getCommitId().name();
 
     assertQuery("0000000000000000000000000000000000000000");
@@ -611,11 +629,11 @@
 
   @Test
   public void byOwner() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
-    Change change1 = insert(repo, newChange(repo), userId);
+    repo = createAndOpenProject("repo");
+    Change change1 = insert("repo", newChange(repo), userId);
     Account.Id user2 =
         accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
-    Change change2 = insert(repo, newChange(repo), user2);
+    Change change2 = insert("repo", newChange(repo), user2);
 
     assertQuery("is:owner", change1);
     assertQuery("owner:" + userId.get(), change1);
@@ -628,15 +646,16 @@
   @Test
   public void byUploader() throws Exception {
     assume().that(getSchema().hasField(ChangeField.UPLOADER_SPEC)).isTrue();
-    Account.Id user2 =
-        accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
-    CurrentUser user2CurrentUser = userFactory.create(user2);
 
-    TestRepository<Repo> repo = createProject("repo");
-    Change change1 = insert(repo, newChange(repo), userId);
+    repo = createAndOpenProject("repo");
+    Change change1 = insert("repo", newChange(repo), userId);
     assertQuery("is:uploader", change1);
     assertQuery("uploader:" + userId.get(), change1);
-    change1 = newPatchSet(repo, change1, user2CurrentUser, /* message= */ Optional.empty());
+
+    Account.Id user2 = createAccount("anotheruser");
+    CurrentUser user2CurrentUser = userFactory.create(user2);
+
+    change1 = newPatchSet("repo", change1, user2CurrentUser, /* message= */ Optional.empty());
     // Uploader has changed
     assertQuery("uploader:" + userId.get());
     assertQuery("uploader:" + user2.get(), change1);
@@ -669,7 +688,7 @@
   }
 
   private void byAuthorOrCommitterExact(String searchOperator) throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
+    createProject("repo");
     PersonIdent johnDoe = new PersonIdent("John Doe", "john.doe@example.com");
     PersonIdent john = new PersonIdent("John", "john@example.com");
     PersonIdent doeSmith = new PersonIdent("Doe Smith", "doe_smith@example.com");
@@ -677,10 +696,10 @@
     PersonIdent myself = new PersonIdent("I Am", ua.preferredEmail());
     PersonIdent selfName = new PersonIdent("My Self", "my.self@example.com");
 
-    Change change1 = createChange(repo, johnDoe);
-    Change change2 = createChange(repo, john);
-    Change change3 = createChange(repo, doeSmith);
-    createChange(repo, selfName);
+    Change change1 = createChange("repo", johnDoe);
+    Change change2 = createChange("repo", john);
+    Change change3 = createChange("repo", doeSmith);
+    createChange("repo", selfName);
 
     // Only email address.
     assertQuery(searchOperator + "john.doe@example.com", change1);
@@ -705,19 +724,19 @@
     assertQuery(searchOperator + "self");
 
     // ':self' matches a change created with the current user's email address
-    Change change5 = createChange(repo, myself);
+    Change change5 = createChange("repo", myself);
     assertQuery(searchOperator + "me", change5);
     assertQuery(searchOperator + "self", change5);
   }
 
   private void byAuthorOrCommitterFullText(String searchOperator) throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
+    createProject("repo");
     PersonIdent johnDoe = new PersonIdent("John Doe", "john.doe@example.com");
     PersonIdent john = new PersonIdent("John", "john@example.com");
     PersonIdent doeSmith = new PersonIdent("Doe Smith", "doe_smith@example.com");
-    Change change1 = createChange(repo, johnDoe);
-    Change change2 = createChange(repo, john);
-    Change change3 = createChange(repo, doeSmith);
+    Change change1 = createChange("repo", johnDoe);
+    Change change2 = createChange("repo", john);
+    Change change3 = createChange("repo", doeSmith);
 
     // By exact name.
     assertQuery(searchOperator + "\"John Doe\"", change1);
@@ -738,20 +757,25 @@
     assertThat(thrown).hasMessageThat().contains("invalid value");
   }
 
-  protected Change createChange(TestRepository<Repo> repo, PersonIdent person) throws Exception {
-    RevCommit commit =
-        repo.parseBody(repo.commit().message("message").author(person).committer(person).create());
-    return insert(repo, newChangeForCommit(repo, commit), null);
+  @CanIgnoreReturnValue
+  protected Change createChange(String repoName, PersonIdent person) throws Exception {
+    try (TestRepository<Repository> repo =
+        new TestRepository<>(repoManager.openRepository(Project.nameKey(repoName)))) {
+      RevCommit commit =
+          repo.parseBody(
+              repo.commit().message("message").author(person).committer(person).create());
+      return insert("repo", newChangeForCommit(repo, commit), null);
+    }
   }
 
   @Test
   public void byOwnerIn() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
-    Change change1 = insert(repo, newChange(repo), userId);
+    repo = createAndOpenProject("repo");
+    Change change1 = insert("repo", newChange(repo), userId);
     Account.Id user2 =
         accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
-    Change change2 = insert(repo, newChange(repo), user2);
-    Change change3 = insert(repo, newChange(repo), user2);
+    Change change2 = insert("repo", newChange(repo), user2);
+    Change change3 = insert("repo", newChange(repo), user2);
     gApi.changes().id(change3.getId().get()).current().review(ReviewInput.approve());
     gApi.changes().id(change3.getId().get()).current().submit();
 
@@ -764,24 +788,25 @@
   @Test
   public void byUploaderIn() throws Exception {
     assume().that(getSchema().hasField(ChangeField.UPLOADER_SPEC)).isTrue();
-    TestRepository<Repo> repo = createProject("repo");
-    Change change1 = insert(repo, newChange(repo), userId);
+    repo = createAndOpenProject("repo");
+    Change change1 = insert("repo", newChange(repo), userId);
+
     assertQuery("uploaderin:Administrators", change1);
 
-    Account.Id user2 =
-        accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
+    Account.Id user2 = createAccount("anotheruser");
     CurrentUser user2CurrentUser = userFactory.create(user2);
-    change1 = newPatchSet(repo, change1, user2CurrentUser, /* message= */ Optional.empty());
+    change1 = newPatchSet("repo", change1, user2CurrentUser, /* message= */ Optional.empty());
+
     assertQuery("uploaderin:Administrators");
     assertQuery("uploaderin:\"Registered Users\"", change1);
   }
 
   @Test
   public void byProject() throws Exception {
-    TestRepository<Repo> repo1 = createProject("repo1");
-    TestRepository<Repo> repo2 = createProject("repo2");
-    Change change1 = insert(repo1, newChange(repo1));
-    Change change2 = insert(repo2, newChange(repo2));
+    createProject("repo1");
+    createProject("repo2");
+    Change change1 = insert("repo1", newChange("repo1"));
+    Change change2 = insert("repo2", newChange("repo2"));
 
     assertQuery("project:foo");
     assertQuery("project:repo");
@@ -791,16 +816,16 @@
 
   @Test
   public void byProjectWithHidden() throws Exception {
-    TestRepository<Repo> hiddenProject = createProject("hiddenProject");
-    insert(hiddenProject, newChange(hiddenProject));
+    createProject("hiddenProject");
+    insert("hiddenProject", newChange("hiddenProject"));
     projectOperations
         .project(Project.nameKey("hiddenProject"))
         .forUpdate()
         .add(block(Permission.READ).ref("refs/*").group(REGISTERED_USERS))
         .update();
 
-    TestRepository<Repo> visibleProject = createProject("visibleProject");
-    Change visibleChange = insert(visibleProject, newChange(visibleProject));
+    createProject("visibleProject");
+    Change visibleChange = insert("visibleProject", newChange("visibleProject"));
     assertQuery("project:visibleProject", visibleChange);
     assertQuery("project:hiddenProject");
     assertQuery("project:visibleProject OR project:hiddenProject", visibleChange);
@@ -808,13 +833,13 @@
 
   @Test
   public void byParentOf() throws Exception {
-    TestRepository<Repo> repo1 = createProject("repo1");
-    RevCommit commit1 = repo1.parseBody(repo1.commit().message("message").create());
-    Change change1 = insert(repo1, newChangeForCommit(repo1, commit1));
-    RevCommit commit2 = repo1.parseBody(repo1.commit(commit1));
-    Change change2 = insert(repo1, newChangeForCommit(repo1, commit2));
-    RevCommit commit3 = repo1.parseBody(repo1.commit(commit1, commit2));
-    Change change3 = insert(repo1, newChangeForCommit(repo1, commit3));
+    repo = createAndOpenProject("repo1");
+    RevCommit commit1 = repo.parseBody(repo.commit().message("message").create());
+    Change change1 = insert("repo1", newChangeForCommit(repo, commit1));
+    RevCommit commit2 = repo.parseBody(repo.commit(commit1));
+    Change change2 = insert("repo1", newChangeForCommit(repo, commit2));
+    RevCommit commit3 = repo.parseBody(repo.commit(commit1, commit2));
+    Change change3 = insert("repo1", newChangeForCommit(repo, commit3));
 
     assertQuery("parentof:" + change1.getId().get());
     assertQuery("parentof:" + change1.getKey().get());
@@ -826,10 +851,10 @@
 
   @Test
   public void byParentProject() throws Exception {
-    TestRepository<Repo> repo1 = createProject("repo1");
-    TestRepository<Repo> repo2 = createProject("repo2", "repo1");
-    Change change1 = insert(repo1, newChange(repo1));
-    Change change2 = insert(repo2, newChange(repo2));
+    createProject("repo1");
+    createProject("repo2", "repo1");
+    Change change1 = insert("repo1", newChange("repo1"));
+    Change change2 = insert("repo2", newChange("repo2"));
 
     assertQuery("parentproject:repo1", change2, change1);
     assertQuery("parentproject:repo2", change2);
@@ -837,10 +862,10 @@
 
   @Test
   public void byProjectPrefix() throws Exception {
-    TestRepository<Repo> repo1 = createProject("repo1");
-    TestRepository<Repo> repo2 = createProject("repo2");
-    Change change1 = insert(repo1, newChange(repo1));
-    Change change2 = insert(repo2, newChange(repo2));
+    createProject("repo1");
+    createProject("repo2");
+    Change change1 = insert("repo1", newChange("repo1"));
+    Change change2 = insert("repo2", newChange("repo2"));
 
     assertQuery("projects:foo");
     assertQuery("projects:repo1", change1);
@@ -850,10 +875,10 @@
 
   @Test
   public void byRepository() throws Exception {
-    TestRepository<Repo> repo1 = createProject("repo1");
-    TestRepository<Repo> repo2 = createProject("repo2");
-    Change change1 = insert(repo1, newChange(repo1));
-    Change change2 = insert(repo2, newChange(repo2));
+    createProject("repo1");
+    createProject("repo2");
+    Change change1 = insert("repo1", newChange("repo1"));
+    Change change2 = insert("repo2", newChange("repo2"));
 
     assertQuery("repository:foo");
     assertQuery("repository:repo");
@@ -863,10 +888,10 @@
 
   @Test
   public void byParentRepository() throws Exception {
-    TestRepository<Repo> repo1 = createProject("repo1");
-    TestRepository<Repo> repo2 = createProject("repo2", "repo1");
-    Change change1 = insert(repo1, newChange(repo1));
-    Change change2 = insert(repo2, newChange(repo2));
+    createProject("repo1");
+    createProject("repo2", "repo1");
+    Change change1 = insert("repo1", newChange("repo1"));
+    Change change2 = insert("repo2", newChange("repo2"));
 
     assertQuery("parentrepository:repo1", change2, change1);
     assertQuery("parentrepository:repo2", change2);
@@ -874,10 +899,10 @@
 
   @Test
   public void byRepositoryPrefix() throws Exception {
-    TestRepository<Repo> repo1 = createProject("repo1");
-    TestRepository<Repo> repo2 = createProject("repo2");
-    Change change1 = insert(repo1, newChange(repo1));
-    Change change2 = insert(repo2, newChange(repo2));
+    createProject("repo1");
+    createProject("repo2");
+    Change change1 = insert("repo1", newChange("repo1"));
+    Change change2 = insert("repo2", newChange("repo2"));
 
     assertQuery("repositories:foo");
     assertQuery("repositories:repo1", change1);
@@ -887,10 +912,10 @@
 
   @Test
   public void byRepo() throws Exception {
-    TestRepository<Repo> repo1 = createProject("repo1");
-    TestRepository<Repo> repo2 = createProject("repo2");
-    Change change1 = insert(repo1, newChange(repo1));
-    Change change2 = insert(repo2, newChange(repo2));
+    createProject("repo1");
+    createProject("repo2");
+    Change change1 = insert("repo1", newChange("repo1"));
+    Change change2 = insert("repo2", newChange("repo2"));
 
     assertQuery("repo:foo");
     assertQuery("repo:repo");
@@ -900,10 +925,10 @@
 
   @Test
   public void byParentRepo() throws Exception {
-    TestRepository<Repo> repo1 = createProject("repo1");
-    TestRepository<Repo> repo2 = createProject("repo2", "repo1");
-    Change change1 = insert(repo1, newChange(repo1));
-    Change change2 = insert(repo2, newChange(repo2));
+    createProject("repo1");
+    createProject("repo2", "repo1");
+    Change change1 = insert("repo1", newChange("repo1"));
+    Change change2 = insert("repo2", newChange("repo2"));
 
     assertQuery("parentrepo:repo1", change2, change1);
     assertQuery("parentrepo:repo2", change2);
@@ -911,10 +936,10 @@
 
   @Test
   public void byRepoPrefix() throws Exception {
-    TestRepository<Repo> repo1 = createProject("repo1");
-    TestRepository<Repo> repo2 = createProject("repo2");
-    Change change1 = insert(repo1, newChange(repo1));
-    Change change2 = insert(repo2, newChange(repo2));
+    createProject("repo1");
+    createProject("repo2");
+    Change change1 = insert("repo1", newChange("repo1"));
+    Change change2 = insert("repo2", newChange("repo2"));
 
     assertQuery("repos:foo");
     assertQuery("repos:repo1", change1);
@@ -924,9 +949,9 @@
 
   @Test
   public void byBranchAndRef() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
-    Change change1 = insert(repo, newChangeForBranch(repo, "master"));
-    Change change2 = insert(repo, newChangeForBranch(repo, "branch"));
+    repo = createAndOpenProject("repo");
+    Change change1 = insert("repo", newChangeForBranch(repo, "master"));
+    Change change2 = insert("repo", newChangeForBranch(repo, "branch"));
 
     assertQuery("branch:foo");
     assertQuery("branch:master", change1);
@@ -943,26 +968,26 @@
   @Test
   public void byTopic() throws Exception {
 
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     ChangeInserter ins1 = newChangeWithTopic(repo, "feature1");
-    Change change1 = insert(repo, ins1);
+    Change change1 = insert("repo", ins1);
 
     ChangeInserter ins2 = newChangeWithTopic(repo, "feature2");
-    Change change2 = insert(repo, ins2);
+    Change change2 = insert("repo", ins2);
 
     ChangeInserter ins3 = newChangeWithTopic(repo, "Cherrypick-feature2");
-    Change change3 = insert(repo, ins3);
+    Change change3 = insert("repo", ins3);
 
     ChangeInserter ins4 = newChangeWithTopic(repo, "feature2-fixup");
-    Change change4 = insert(repo, ins4);
+    Change change4 = insert("repo", ins4);
 
     ChangeInserter ins5 = newChangeWithTopic(repo, "https://gerrit.local");
-    Change change5 = insert(repo, ins5);
+    Change change5 = insert("repo", ins5);
 
     ChangeInserter ins6 = newChangeWithTopic(repo, "git_gerrit_training");
-    Change change6 = insert(repo, ins6);
+    Change change6 = insert("repo", ins6);
 
-    Change change_no_topic = insert(repo, newChange(repo));
+    Change changeNoTopic = insert("repo", newChange(repo));
 
     assertQuery("intopic:foo");
     assertQuery("intopic:feature1", change1);
@@ -971,8 +996,8 @@
     assertQuery("intopic:feature2", change4, change3, change2);
     assertQuery("intopic:fixup", change4);
     assertQuery("intopic:gerrit", change6, change5);
-    assertQuery("topic:\"\"", change_no_topic);
-    assertQuery("intopic:\"\"", change_no_topic);
+    assertQuery("topic:\"\"", changeNoTopic);
+    assertQuery("intopic:\"\"", changeNoTopic);
 
     assume().that(getSchema().hasField(ChangeField.PREFIX_TOPIC)).isTrue();
     assertQuery("prefixtopic:feature", change4, change2, change1);
@@ -982,16 +1007,16 @@
 
   @Test
   public void byTopicRegex() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
 
     ChangeInserter ins1 = newChangeWithTopic(repo, "feature1");
-    Change change1 = insert(repo, ins1);
+    Change change1 = insert("repo", ins1);
 
     ChangeInserter ins2 = newChangeWithTopic(repo, "Cherrypick-feature1");
-    Change change2 = insert(repo, ins2);
+    Change change2 = insert("repo", ins2);
 
     ChangeInserter ins3 = newChangeWithTopic(repo, "feature1-fixup");
-    Change change3 = insert(repo, ins3);
+    Change change3 = insert("repo", ins3);
 
     assertQuery("intopic:^feature1.*", change3, change1);
     assertQuery("intopic:{^.*feature1$}", change2, change1);
@@ -999,13 +1024,13 @@
 
   @Test
   public void byMessageExact() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     RevCommit commit1 = repo.parseBody(repo.commit().message("one").create());
-    Change change1 = insert(repo, newChangeForCommit(repo, commit1));
+    Change change1 = insert("repo", newChangeForCommit(repo, commit1));
     RevCommit commit2 = repo.parseBody(repo.commit().message("two").create());
-    Change change2 = insert(repo, newChangeForCommit(repo, commit2));
+    Change change2 = insert("repo", newChangeForCommit(repo, commit2));
     RevCommit commit3 = repo.parseBody(repo.commit().message("A great \"fix\" to my bug").create());
-    Change change3 = insert(repo, newChangeForCommit(repo, commit3));
+    Change change3 = insert("repo", newChangeForCommit(repo, commit3));
 
     assertQuery("message:foo");
     assertQuery("message:one", change1);
@@ -1016,16 +1041,16 @@
   @Test
   public void byMessageRegEx() throws Exception {
     assume().that(getSchema().hasField(ChangeField.COMMIT_MESSAGE_EXACT)).isTrue();
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     RevCommit commit1 = repo.parseBody(repo.commit().message("aaaabcc").create());
-    Change change1 = insert(repo, newChangeForCommit(repo, commit1));
+    Change change1 = insert("repo", newChangeForCommit(repo, commit1));
     RevCommit commit2 = repo.parseBody(repo.commit().message("aaaacc").create());
-    Change change2 = insert(repo, newChangeForCommit(repo, commit2));
+    Change change2 = insert("repo", newChangeForCommit(repo, commit2));
     RevCommit commit3 = repo.parseBody(repo.commit().message("Title\n\nHELLO WORLD").create());
-    Change change3 = insert(repo, newChangeForCommit(repo, commit3));
+    Change change3 = insert("repo", newChangeForCommit(repo, commit3));
     RevCommit commit4 =
         repo.parseBody(repo.commit().message("Title\n\nfoobar hello WORLD").create());
-    Change change4 = insert(repo, newChangeForCommit(repo, commit4));
+    Change change4 = insert("repo", newChangeForCommit(repo, commit4));
 
     assertQuery("message:\"^aaaa(b|c)*\"", change2, change1);
     assertQuery("message:\"^aaaa(c)*c.*\"", change2);
@@ -1037,7 +1062,7 @@
   @Test
   public void bySubject() throws Exception {
     assume().that(getSchema().hasField(ChangeField.SUBJECT_SPEC)).isTrue();
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     RevCommit commit1 =
         repo.parseBody(
             repo.commit()
@@ -1046,7 +1071,7 @@
                         + "Message body\n\n"
                         + "Change-Id: I986c6a013dd5b3a2e8a0271c04deac2c9752b920")
                 .create());
-    Change change1 = insert(repo, newChangeForCommit(repo, commit1));
+    Change change1 = insert("repo", newChangeForCommit(repo, commit1));
     RevCommit commit2 =
         repo.parseBody(
             repo.commit()
@@ -1055,7 +1080,7 @@
                         + "Message body for another commit\n\n"
                         + "Change-Id: I986c6a013dd5b3a2e8a0271c04deac2c9752b921")
                 .create());
-    Change change2 = insert(repo, newChangeForCommit(repo, commit2));
+    Change change2 = insert("repo", newChangeForCommit(repo, commit2));
     RevCommit commit3 =
         repo.parseBody(
             repo.commit()
@@ -1064,7 +1089,7 @@
                         + "Last message body\n\n"
                         + "Change-Id: I986c6a013dd5b3a2e8a0271c04deac2c9752b921")
                 .create());
-    Change change3 = insert(repo, newChangeForCommit(repo, commit3));
+    Change change3 = insert("repo", newChangeForCommit(repo, commit3));
 
     assertQuery("subject:First", change1);
     assertQuery("subject:Second", change2);
@@ -1074,7 +1099,7 @@
     assertQuery("subject:body");
     change1 =
         newPatchSet(
-            repo,
+            "repo",
             change1,
             user,
             Optional.of("Rework of commit with test subject\n\n" + "Message body\n\n"));
@@ -1086,7 +1111,7 @@
   @Test
   public void bySubjectPrefix() throws Exception {
     assume().that(getSchema().hasField(ChangeField.PREFIX_SUBJECT_SPEC)).isTrue();
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     RevCommit commit1 =
         repo.parseBody(
             repo.commit()
@@ -1095,7 +1120,7 @@
                         + "Message body\n\n"
                         + "Change-Id: I986c6a013dd5b3a2e8a0271c04deac2c9752b920")
                 .create());
-    Change change1 = insert(repo, newChangeForCommit(repo, commit1));
+    Change change1 = insert("repo", newChangeForCommit(repo, commit1));
     RevCommit commit2 =
         repo.parseBody(
             repo.commit()
@@ -1104,7 +1129,7 @@
                         + "Message body for another commit\n\n"
                         + "Change-Id: I986c6a013dd5b3a2e8a0271c04deac2c9752b921")
                 .create());
-    Change change2 = insert(repo, newChangeForCommit(repo, commit2));
+    Change change2 = insert("repo", newChangeForCommit(repo, commit2));
     RevCommit commit3 =
         repo.parseBody(
             repo.commit()
@@ -1113,7 +1138,7 @@
                         + "Last message body\n\n"
                         + "Change-Id: I986c6a013dd5b3a2e8a0271c04deac2c9752b921")
                 .create());
-    Change change3 = insert(repo, newChangeForCommit(repo, commit3));
+    Change change3 = insert("repo", newChangeForCommit(repo, commit3));
 
     assertQuery("prefixsubject:\"[FOO\"", change3, change1);
     assertQuery("prefixsubject:\"[BAR\"", change2);
@@ -1123,7 +1148,7 @@
     assertQuery("prefixsubject:FOO");
     change1 =
         newPatchSet(
-            repo,
+            "repo",
             change1,
             user,
             Optional.of("[BAR123] Rework of commit with test subject\n\n" + "Message body\n\n"));
@@ -1133,11 +1158,11 @@
 
   @Test
   public void fullTextWithNumbers() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     RevCommit commit1 = repo.parseBody(repo.commit().message("12345 67890").create());
-    Change change1 = insert(repo, newChangeForCommit(repo, commit1));
+    Change change1 = insert("repo", newChangeForCommit(repo, commit1));
     RevCommit commit2 = repo.parseBody(repo.commit().message("12346 67891").create());
-    Change change2 = insert(repo, newChangeForCommit(repo, commit2));
+    Change change2 = insert("repo", newChangeForCommit(repo, commit2));
 
     assertQuery("message:1234");
     assertQuery("message:12345", change1);
@@ -1146,13 +1171,13 @@
 
   @Test
   public void fullTextMultipleTerms() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     RevCommit commit1 = repo.parseBody(repo.commit().message("Signed-off: owner").create());
-    Change change1 = insert(repo, newChangeForCommit(repo, commit1));
+    Change change1 = insert("repo", newChangeForCommit(repo, commit1));
     RevCommit commit2 = repo.parseBody(repo.commit().message("Signed by owner").create());
-    Change change2 = insert(repo, newChangeForCommit(repo, commit2));
+    Change change2 = insert("repo", newChangeForCommit(repo, commit2));
     RevCommit commit3 = repo.parseBody(repo.commit().message("This change is off").create());
-    Change change3 = insert(repo, newChangeForCommit(repo, commit3));
+    Change change3 = insert("repo", newChangeForCommit(repo, commit3));
 
     assertQuery("message:\"Signed-off: owner\"", change1);
     assertQuery("message:\"Signed\"", change2, change1);
@@ -1161,11 +1186,11 @@
 
   @Test
   public void byMessageMixedCase() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     RevCommit commit1 = repo.parseBody(repo.commit().message("Hello gerrit").create());
-    Change change1 = insert(repo, newChangeForCommit(repo, commit1));
+    Change change1 = insert("repo", newChangeForCommit(repo, commit1));
     RevCommit commit2 = repo.parseBody(repo.commit().message("Hello Gerrit").create());
-    Change change2 = insert(repo, newChangeForCommit(repo, commit2));
+    Change change2 = insert("repo", newChangeForCommit(repo, commit2));
 
     assertQuery("message:gerrit", change2, change1);
     assertQuery("message:Gerrit", change2, change1);
@@ -1173,16 +1198,16 @@
 
   @Test
   public void byMessageSubstring() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     RevCommit commit1 = repo.parseBody(repo.commit().message("https://gerrit.local").create());
-    Change change1 = insert(repo, newChangeForCommit(repo, commit1));
+    Change change1 = insert("repo", newChangeForCommit(repo, commit1));
     assertQuery("message:gerrit", change1);
   }
 
   @Test
   public void byLabel() throws Exception {
-    accountManager.authenticate(authRequestFactory.createForUser("anotheruser"));
-    TestRepository<Repo> repo = createProject("repo");
+    Account.Id anotherUser = createAccount("anotheruser");
+    repo = createAndOpenProject("repo");
     ChangeInserter ins = newChange(repo);
     ChangeInserter ins2 = newChange(repo);
     ChangeInserter ins3 = newChange(repo);
@@ -1190,24 +1215,24 @@
     ChangeInserter ins5 = newChange(repo);
     ChangeInserter ins6 = newChange(repo);
 
-    Change reviewMinus2Change = insert(repo, ins);
+    Change reviewMinus2Change = insert("repo", ins);
     gApi.changes().id(reviewMinus2Change.getId().get()).current().review(ReviewInput.reject());
 
-    Change reviewMinus1Change = insert(repo, ins2);
+    Change reviewMinus1Change = insert("repo", ins2);
     gApi.changes().id(reviewMinus1Change.getId().get()).current().review(ReviewInput.dislike());
 
-    Change noLabelChange = insert(repo, ins3);
+    Change noLabelChange = insert("repo", ins3);
 
-    Change reviewPlus1Change = insert(repo, ins4);
+    Change reviewPlus1Change = insert("repo", ins4);
     gApi.changes().id(reviewPlus1Change.getId().get()).current().review(ReviewInput.recommend());
 
-    Change reviewTwoPlus1Change = insert(repo, ins5);
+    Change reviewTwoPlus1Change = insert("repo", ins5);
     gApi.changes().id(reviewTwoPlus1Change.getId().get()).current().review(ReviewInput.recommend());
     requestContext.setContext(newRequestContext(createAccount("user1")));
     gApi.changes().id(reviewTwoPlus1Change.getId().get()).current().review(ReviewInput.recommend());
     requestContext.setContext(newRequestContext(userId));
 
-    Change reviewPlus2Change = insert(repo, ins6);
+    Change reviewPlus2Change = insert("repo", ins6);
     gApi.changes().id(reviewPlus2Change.getId().get()).current().review(ReviewInput.approve());
 
     Map<String, Short> m =
@@ -1271,9 +1296,15 @@
     assertQuery("label:Code-Review<=-2", reviewMinus2Change);
     assertQuery("label:Code-Review<-2");
 
-    assertQuery("label:Code-Review=+1,anotheruser");
-    assertQuery("label:Code-Review=+1,user", reviewTwoPlus1Change, reviewPlus1Change);
-    assertQuery("label:Code-Review=+1,user=user", reviewTwoPlus1Change, reviewPlus1Change);
+    assertQuery("label:Code-Review=+1," + anotherUser);
+    assertQuery(
+        String.format("label:Code-Review=+1,%s", userAccount.preferredEmail()),
+        reviewTwoPlus1Change,
+        reviewPlus1Change);
+    assertQuery(
+        String.format("label:Code-Review=+1,user=%s", userAccount.preferredEmail()),
+        reviewTwoPlus1Change,
+        reviewPlus1Change);
     assertQuery("label:Code-Review=+1,Administrators", reviewTwoPlus1Change, reviewPlus1Change);
     assertQuery(
         "label:Code-Review=+1,group=Administrators", reviewTwoPlus1Change, reviewPlus1Change);
@@ -1340,9 +1371,8 @@
 
   @Test
   public void byLabelMulti() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
-    Project.NameKey project =
-        Project.nameKey(repo.getRepository().getDescription().getRepositoryName());
+    Project.NameKey project = Project.nameKey("repo");
+    repo = createAndOpenProject(project.get());
 
     LabelType verified =
         label(LabelId.VERIFIED, value(1, "Passes"), value(0, "No score"), value(-1, "Failed"));
@@ -1369,25 +1399,25 @@
     ChangeInserter ins5 = newChange(repo);
 
     // CR+1
-    Change reviewCRplus1 = insert(repo, ins);
+    Change reviewCRplus1 = insert(project.get(), ins);
     gApi.changes().id(reviewCRplus1.getId().get()).current().review(ReviewInput.recommend());
 
     // CR+2
-    Change reviewCRplus2 = insert(repo, ins2);
+    Change reviewCRplus2 = insert(project.get(), ins2);
     gApi.changes().id(reviewCRplus2.getId().get()).current().review(ReviewInput.approve());
 
     // CR+1 VR+1
-    Change reviewCRplus1VRplus1 = insert(repo, ins3);
+    Change reviewCRplus1VRplus1 = insert(project.get(), ins3);
     gApi.changes().id(reviewCRplus1VRplus1.getId().get()).current().review(ReviewInput.recommend());
     gApi.changes().id(reviewCRplus1VRplus1.getId().get()).current().review(reviewVerified);
 
     // CR+2 VR+1
-    Change reviewCRplus2VRplus1 = insert(repo, ins4);
+    Change reviewCRplus2VRplus1 = insert(project.get(), ins4);
     gApi.changes().id(reviewCRplus2VRplus1.getId().get()).current().review(ReviewInput.approve());
     gApi.changes().id(reviewCRplus2VRplus1.getId().get()).current().review(reviewVerified);
 
     // VR+1
-    Change reviewVRplus1 = insert(repo, ins5);
+    Change reviewVRplus1 = insert(project.get(), ins5);
     gApi.changes().id(reviewVRplus1.getId().get()).current().review(reviewVerified);
 
     assertQuery("label:Code-Review=+1", reviewCRplus1VRplus1, reviewCRplus1);
@@ -1406,28 +1436,28 @@
 
   @Test
   public void byLabelNotOwner() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     ChangeInserter ins = newChange(repo);
     Account.Id user1 = createAccount("user1");
 
-    Change reviewPlus1Change = insert(repo, ins);
+    Change reviewPlus1Change = insert("repo", ins);
 
     // post a review with user1
     requestContext.setContext(newRequestContext(user1));
     gApi.changes().id(reviewPlus1Change.getId().get()).current().review(ReviewInput.recommend());
 
-    assertQuery("label:Code-Review=+1,user=user1", reviewPlus1Change);
+    assertQuery("label:Code-Review=+1,user=" + user1, reviewPlus1Change);
     assertQuery("label:Code-Review=+1,owner");
   }
 
   @Test
   public void byLabelNonUploader() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     ChangeInserter ins = newChange(repo);
     Account.Id user1 = createAccount("user1");
 
     // create a change with "user"
-    Change reviewPlus1Change = insert(repo, ins);
+    Change reviewPlus1Change = insert("repo", ins);
 
     // add a +1 vote with "user". Query doesn't match since voter is the uploader.
     gApi.changes().id(reviewPlus1Change.getId().get()).current().review(ReviewInput.recommend());
@@ -1466,8 +1496,8 @@
   @Test
   public void byLabelGroup() throws Exception {
     Account.Id user1 = createAccount("user1");
-    createAccount("user2");
-    TestRepository<Repo> repo = createProject("repo");
+    Account.Id user2 = createAccount("user2");
+    repo = createAndOpenProject("repo");
 
     // create group and add users
     String g1 = createGroup("group1", "Administrators");
@@ -1476,7 +1506,7 @@
     gApi.groups().id(g2).addMembers("user2");
 
     // create a change
-    Change change1 = insert(repo, newChange(repo), user1);
+    Change change1 = insert("repo", newChange(repo), user1);
 
     // post a review with user1
     requestContext.setContext(newRequestContext(user1));
@@ -1489,8 +1519,8 @@
     requestContext.setContext(newRequestContext(userId));
     assertQuery("label:Code-Review=+1,group1", change1);
     assertQuery("label:Code-Review=+1,group=group1", change1);
-    assertQuery("label:Code-Review=+1,user=user1", change1);
-    assertQuery("label:Code-Review=+1,user=user2");
+    assertQuery("label:Code-Review=+1,user=" + user1, change1);
+    assertQuery("label:Code-Review=+1,user=" + user2);
     assertQuery("label:Code-Review=+1,group=group2");
   }
 
@@ -1498,7 +1528,7 @@
   public void byLabelExternalGroup() throws Exception {
     Account.Id user1 = createAccount("user1");
     Account.Id user2 = createAccount("user2");
-    TestRepository<InMemoryRepositoryManager.Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
 
     // create group and add users
     AccountGroup.UUID external_group1 = AccountGroup.uuid("testbackend:group1");
@@ -1510,8 +1540,8 @@
     testGroupBackend.setMembershipsOf(
         user2, new ListGroupMembership(ImmutableList.of(external_group2)));
 
-    Change change1 = insert(repo, newChange(repo), user1);
-    Change change2 = insert(repo, newChange(repo), user1);
+    Change change1 = insert("repo", newChange(repo), user1);
+    Change change2 = insert("repo", newChange(repo), user1);
 
     // post a review with user1 and other_user
     requestContext.setContext(newRequestContext(user1));
@@ -1527,18 +1557,18 @@
 
     assertQuery("label:Code-Review=+1," + external_group1.get(), change1);
     assertQuery("label:Code-Review=+1,group=" + external_group1.get(), change1);
-    assertQuery("label:Code-Review=+1,user=user1", change1);
-    assertQuery("label:Code-Review=+1,user=user2");
+    assertQuery("label:Code-Review=+1,user=" + user1, change1);
+    assertQuery("label:Code-Review=+1,user=" + user2);
     assertQuery("label:Code-Review=+1,group=" + external_group2.get());
   }
 
   @Test
   public void limit() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     Change last = null;
     int n = 5;
     for (int i = 0; i < n; i++) {
-      last = insert(repo, newChange(repo));
+      last = insert("repo", newChange(repo));
     }
 
     for (int i = 1; i <= n + 2; i++) {
@@ -1563,10 +1593,10 @@
 
   @Test
   public void start() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     List<Change> changes = new ArrayList<>();
     for (int i = 0; i < 2; i++) {
-      changes.add(insert(repo, newChange(repo)));
+      changes.add(insert("repo", newChange(repo)));
     }
 
     assertQuery("status:new", changes.get(1), changes.get(0));
@@ -1583,10 +1613,10 @@
 
   @Test
   public void startWithLimit() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     List<Change> changes = new ArrayList<>();
     for (int i = 0; i < 3; i++) {
-      changes.add(insert(repo, newChange(repo)));
+      changes.add(insert("repo", newChange(repo)));
     }
 
     assertQuery("status:new limit:2", changes.get(2), changes.get(1));
@@ -1597,8 +1627,8 @@
 
   @Test
   public void maxPages() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
-    Change change = insert(repo, newChange(repo));
+    repo = createAndOpenProject("repo");
+    Change change = insert("repo", newChange(repo));
 
     QueryRequest query = newQuery("status:new").withLimit(10);
     assertQuery(query, change);
@@ -1613,12 +1643,12 @@
   @Test
   public void updateOrder() throws Exception {
     resetTimeWithClockStep(2, MINUTES);
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     List<ChangeInserter> inserters = new ArrayList<>();
     List<Change> changes = new ArrayList<>();
     for (int i = 0; i < 5; i++) {
       inserters.add(newChange(repo));
-      changes.add(insert(repo, inserters.get(i)));
+      changes.add(insert("repo", inserters.get(i)));
     }
 
     for (int i : ImmutableList.of(2, 0, 1, 4, 3)) {
@@ -1640,10 +1670,10 @@
   @Test
   public void updatedOrder() throws Exception {
     resetTimeWithClockStep(1, SECONDS);
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     ChangeInserter ins1 = newChange(repo);
-    Change change1 = insert(repo, ins1);
-    Change change2 = insert(repo, newChange(repo));
+    Change change1 = insert("repo", ins1);
+    Change change2 = insert("repo", newChange(repo));
 
     assertThat(lastUpdatedMs(change1)).isLessThan(lastUpdatedMs(change2));
     assertQuery("status:new", change2, change1);
@@ -1661,12 +1691,12 @@
 
   @Test
   public void filterOutMoreThanOnePageOfResults() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
-    Change change = insert(repo, newChange(repo), userId);
+    repo = createAndOpenProject("repo");
+    Change change = insert("repo", newChange(repo), userId);
     Account.Id user2 =
         accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
     for (int i = 0; i < 5; i++) {
-      insert(repo, newChange(repo), user2);
+      insert("repo", newChange(repo), user2);
     }
 
     assertQuery("status:new ownerin:Administrators", change);
@@ -1675,11 +1705,11 @@
 
   @Test
   public void filterOutAllResults() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     Account.Id user2 =
         accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
     for (int i = 0; i < 5; i++) {
-      insert(repo, newChange(repo), user2);
+      insert("repo", newChange(repo), user2);
     }
 
     assertQuery("status:new ownerin:Administrators");
@@ -1688,8 +1718,8 @@
 
   @Test
   public void byFileExact() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
-    Change change = insert(repo, newChangeWithFiles(repo, "dir/file1", "dir/file2"));
+    repo = createAndOpenProject("repo");
+    Change change = insert("repo", newChangeWithFiles(repo, "dir/file1", "dir/file2"));
 
     assertQuery("file:file");
     assertQuery("file:dir", change);
@@ -1701,8 +1731,8 @@
 
   @Test
   public void byFileRegex() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
-    Change change = insert(repo, newChangeWithFiles(repo, "dir/file1", "dir/file2"));
+    repo = createAndOpenProject("repo");
+    Change change = insert("repo", newChangeWithFiles(repo, "dir/file1", "dir/file2"));
 
     assertQuery("file:.*file.*");
     assertQuery("file:^file.*"); // Whole path only.
@@ -1711,8 +1741,8 @@
 
   @Test
   public void byPathExact() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
-    Change change = insert(repo, newChangeWithFiles(repo, "dir/file1", "dir/file2"));
+    repo = createAndOpenProject("repo");
+    Change change = insert("repo", newChangeWithFiles(repo, "dir/file1", "dir/file2"));
 
     assertQuery("path:file");
     assertQuery("path:dir");
@@ -1724,8 +1754,8 @@
 
   @Test
   public void byPathRegex() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
-    Change change = insert(repo, newChangeWithFiles(repo, "dir/file1", "dir/file2"));
+    repo = createAndOpenProject("repo");
+    Change change = insert("repo", newChangeWithFiles(repo, "dir/file1", "dir/file2"));
 
     assertQuery("path:.*file.*");
     assertQuery("path:^dir.file.*", change);
@@ -1733,12 +1763,12 @@
 
   @Test
   public void byExtension() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
-    Change change1 = insert(repo, newChangeWithFiles(repo, "foo.h", "foo.cc"));
-    Change change2 = insert(repo, newChangeWithFiles(repo, "bar.H", "bar.CC"));
-    Change change3 = insert(repo, newChangeWithFiles(repo, "dir/baz.h", "dir/baz.cc"));
-    Change change4 = insert(repo, newChangeWithFiles(repo, "Quux.java", "foo"));
-    Change change5 = insert(repo, newChangeWithFiles(repo, "foo"));
+    repo = createAndOpenProject("repo");
+    Change change1 = insert("repo", newChangeWithFiles(repo, "foo.h", "foo.cc"));
+    Change change2 = insert("repo", newChangeWithFiles(repo, "bar.H", "bar.CC"));
+    Change change3 = insert("repo", newChangeWithFiles(repo, "dir/baz.h", "dir/baz.cc"));
+    Change change4 = insert("repo", newChangeWithFiles(repo, "Quux.java", "foo"));
+    Change change5 = insert("repo", newChangeWithFiles(repo, "foo"));
 
     assertQuery("extension:java", change4);
     assertQuery("ext:java", change4);
@@ -1754,14 +1784,14 @@
 
   @Test
   public void byOnlyExtensions() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
-    Change change1 = insert(repo, newChangeWithFiles(repo, "foo.h", "foo.cc", "bar.cc"));
-    Change change2 = insert(repo, newChangeWithFiles(repo, "bar.H", "bar.CC", "foo.H"));
-    Change change3 = insert(repo, newChangeWithFiles(repo, "foo.CC", "bar.cc"));
-    Change change4 = insert(repo, newChangeWithFiles(repo, "dir/baz.h", "dir/baz.cc"));
-    Change change5 = insert(repo, newChangeWithFiles(repo, "Quux.java"));
-    Change change6 = insert(repo, newChangeWithFiles(repo, "foo.txt", "foo"));
-    Change change7 = insert(repo, newChangeWithFiles(repo, "foo"));
+    repo = createAndOpenProject("repo");
+    Change change1 = insert("repo", newChangeWithFiles(repo, "foo.h", "foo.cc", "bar.cc"));
+    Change change2 = insert("repo", newChangeWithFiles(repo, "bar.H", "bar.CC", "foo.H"));
+    Change change3 = insert("repo", newChangeWithFiles(repo, "foo.CC", "bar.cc"));
+    Change change4 = insert("repo", newChangeWithFiles(repo, "dir/baz.h", "dir/baz.cc"));
+    Change change5 = insert("repo", newChangeWithFiles(repo, "Quux.java"));
+    Change change6 = insert("repo", newChangeWithFiles(repo, "foo.txt", "foo"));
+    Change change7 = insert("repo", newChangeWithFiles(repo, "foo"));
 
     // case doesn't matter
     assertQuery("onlyextensions:cc,h", change4, change2, change1);
@@ -1801,23 +1831,23 @@
 
   @Test
   public void byFooter() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     RevCommit commit1 = repo.parseBody(repo.commit().message("Test\n\nfoo: bar").create());
-    Change change1 = insert(repo, newChangeForCommit(repo, commit1));
+    Change change1 = insert("repo", newChangeForCommit(repo, commit1));
     RevCommit commit2 = repo.parseBody(repo.commit().message("Test\n\nfoo: baz").create());
-    Change change2 = insert(repo, newChangeForCommit(repo, commit2));
+    Change change2 = insert("repo", newChangeForCommit(repo, commit2));
     RevCommit commit3 = repo.parseBody(repo.commit().message("Test\n\nfoo: bar\nfoo:baz").create());
-    Change change3 = insert(repo, newChangeForCommit(repo, commit3));
+    Change change3 = insert("repo", newChangeForCommit(repo, commit3));
     RevCommit commit4 = repo.parseBody(repo.commit().message("Test\n\nfoo: bar=baz").create());
-    Change change4 = insert(repo, newChangeForCommit(repo, commit4));
+    Change change4 = insert("repo", newChangeForCommit(repo, commit4));
 
     // create a changes with lines that look like footers, but which are not
     RevCommit commit5 =
         repo.parseBody(
             repo.commit().message("Test\n\nfoo: bar\n\nfoo=bar").insertChangeId().create());
-    Change change5 = insert(repo, newChangeForCommit(repo, commit5));
+    Change change5 = insert("repo", newChangeForCommit(repo, commit5));
     RevCommit commit6 = repo.parseBody(repo.commit().message("Test\n\na=b: c").create());
-    insert(repo, newChangeForCommit(repo, commit6));
+    insert("repo", newChangeForCommit(repo, commit6));
 
     // matching by 'key=value' works
     assertQuery("footer:foo=bar", change3, change1);
@@ -1851,15 +1881,15 @@
   @Test
   public void byFooterName() throws Exception {
     assume().that(getSchema().hasField(ChangeField.FOOTER_NAME)).isTrue();
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     RevCommit commit1 = repo.parseBody(repo.commit().message("Test\n\nfoo: bar").create());
-    Change change1 = insert(repo, newChangeForCommit(repo, commit1));
+    Change change1 = insert("repo", newChangeForCommit(repo, commit1));
     RevCommit commit2 = repo.parseBody(repo.commit().message("Test\n\nBaR: baz").create());
-    Change change2 = insert(repo, newChangeForCommit(repo, commit2));
+    Change change2 = insert("repo", newChangeForCommit(repo, commit2));
 
     // create a changes with lines that look like footers, but which are not
     RevCommit commit6 = repo.parseBody(repo.commit().message("Test\n\na=b: c").create());
-    insert(repo, newChangeForCommit(repo, commit6));
+    insert("repo", newChangeForCommit(repo, commit6));
 
     // matching by 'key=value' works
     assertQuery("hasfooter:foo", change1);
@@ -1871,14 +1901,14 @@
 
   @Test
   public void byDirectory() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
-    Change change1 = insert(repo, newChangeWithFiles(repo, "src/foo.h", "src/foo.cc"));
-    Change change2 = insert(repo, newChangeWithFiles(repo, "src/java/foo.java", "src/js/bar.js"));
+    repo = createAndOpenProject("repo");
+    Change change1 = insert("repo", newChangeWithFiles(repo, "src/foo.h", "src/foo.cc"));
+    Change change2 = insert("repo", newChangeWithFiles(repo, "src/java/foo.java", "src/js/bar.js"));
     Change change3 =
-        insert(repo, newChangeWithFiles(repo, "documentation/training/slides/README.txt"));
-    Change change4 = insert(repo, newChangeWithFiles(repo, "a.txt"));
-    Change change5 = insert(repo, newChangeWithFiles(repo, "a/b/c/d/e/foo.txt"));
-    Change change6 = insert(repo, newChangeWithFiles(repo, "all/caps/DIRECTORY/file.txt"));
+        insert("repo", newChangeWithFiles(repo, "documentation/training/slides/README.txt"));
+    Change change4 = insert("repo", newChangeWithFiles(repo, "a.txt"));
+    Change change5 = insert("repo", newChangeWithFiles(repo, "a/b/c/d/e/foo.txt"));
+    Change change6 = insert("repo", newChangeWithFiles(repo, "all/caps/DIRECTORY/file.txt"));
 
     // matching by directory prefix works
     assertQuery("directory:src", change2, change1);
@@ -1939,10 +1969,10 @@
 
   @Test
   public void byDirectoryRegex() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
-    Change change1 = insert(repo, newChangeWithFiles(repo, "src/java/foo.java", "src/js/bar.js"));
+    repo = createAndOpenProject("repo");
+    Change change1 = insert("repo", newChangeWithFiles(repo, "src/java/foo.java", "src/js/bar.js"));
     Change change2 =
-        insert(repo, newChangeWithFiles(repo, "documentation/training/slides/README.txt"));
+        insert("repo", newChangeWithFiles(repo, "documentation/training/slides/README.txt"));
 
     // match by regexp
     assertQuery("directory:^.*va.*", change1);
@@ -1952,9 +1982,9 @@
 
   @Test
   public void byComment() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     ChangeInserter ins = newChange(repo);
-    Change change = insert(repo, ins);
+    Change change = insert("repo", ins);
 
     ReviewInput input = new ReviewInput();
     input.message = "toplevel";
@@ -1982,11 +2012,11 @@
   public void byAge() throws Exception {
     long thirtyHoursInMs = MILLISECONDS.convert(30, HOURS);
     resetTimeWithClockStep(thirtyHoursInMs, MILLISECONDS);
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     long startMs = TestTimeUtil.START.toEpochMilli();
-    Change change1 = insert(repo, newChange(repo), null, Instant.ofEpochMilli(startMs));
+    Change change1 = insert("repo", newChange(repo), null, Instant.ofEpochMilli(startMs));
     Change change2 =
-        insert(repo, newChange(repo), null, Instant.ofEpochMilli(startMs + thirtyHoursInMs));
+        insert("repo", newChange(repo), null, Instant.ofEpochMilli(startMs + thirtyHoursInMs));
 
     // Stop time so age queries use the same endpoint.
     TestTimeUtil.setClockStep(0, MILLISECONDS);
@@ -2023,11 +2053,11 @@
   public void byBeforeUntil() throws Exception {
     long thirtyHoursInMs = MILLISECONDS.convert(30, HOURS);
     resetTimeWithClockStep(thirtyHoursInMs, MILLISECONDS);
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     long startMs = TestTimeUtil.START.toEpochMilli();
-    Change change1 = insert(repo, newChange(repo), null, Instant.ofEpochMilli(startMs));
+    Change change1 = insert("repo", newChange(repo), null, Instant.ofEpochMilli(startMs));
     Change change2 =
-        insert(repo, newChange(repo), null, Instant.ofEpochMilli(startMs + thirtyHoursInMs));
+        insert("repo", newChange(repo), null, Instant.ofEpochMilli(startMs + thirtyHoursInMs));
     TestTimeUtil.setClockStep(0, MILLISECONDS);
 
     // Change1 was last updated on 2009-09-30 21:00:00 -0000
@@ -2075,11 +2105,11 @@
   public void byAfterSince() throws Exception {
     long thirtyHoursInMs = MILLISECONDS.convert(30, HOURS);
     resetTimeWithClockStep(thirtyHoursInMs, MILLISECONDS);
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     long startMs = TestTimeUtil.START.toEpochMilli();
-    Change change1 = insert(repo, newChange(repo), null, Instant.ofEpochMilli(startMs));
+    Change change1 = insert("repo", newChange(repo), null, Instant.ofEpochMilli(startMs));
     Change change2 =
-        insert(repo, newChange(repo), null, Instant.ofEpochMilli(startMs + thirtyHoursInMs));
+        insert("repo", newChange(repo), null, Instant.ofEpochMilli(startMs + thirtyHoursInMs));
     TestTimeUtil.setClockStep(0, MILLISECONDS);
 
     // Change1 was last updated on 2009-09-30 21:00:00 -0000
@@ -2119,12 +2149,12 @@
 
     // Stop the clock, will set time to specific test values.
     resetTimeWithClockStep(0, MILLISECONDS);
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     long startMs = TestTimeUtil.START.toEpochMilli();
     TestTimeUtil.setClock(new Timestamp(startMs));
-    Change change1 = insert(repo, newChange(repo));
-    Change change2 = insert(repo, newChange(repo));
-    Change change3 = insert(repo, newChange(repo));
+    Change change1 = insert("repo", newChange(repo));
+    Change change2 = insert("repo", newChange(repo));
+    Change change3 = insert("repo", newChange(repo));
 
     TestTimeUtil.setClock(new Timestamp(startMs + thirtyHoursInMs));
     submit(change3);
@@ -2179,12 +2209,12 @@
 
     // Stop the clock, will set time to specific test values.
     resetTimeWithClockStep(0, MILLISECONDS);
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     long startMs = TestTimeUtil.START.toEpochMilli();
     TestTimeUtil.setClock(new Timestamp(startMs));
-    Change change1 = insert(repo, newChange(repo));
-    Change change2 = insert(repo, newChange(repo));
-    Change change3 = insert(repo, newChange(repo));
+    Change change1 = insert("repo", newChange(repo));
+    Change change2 = insert("repo", newChange(repo));
+    Change change3 = insert("repo", newChange(repo));
     assertThat(TimeUtil.nowMs()).isEqualTo(startMs);
 
     TestTimeUtil.setClock(new Timestamp(startMs + thirtyHoursInMs));
@@ -2249,13 +2279,13 @@
 
     // Stop the clock, will set time to specific test values.
     resetTimeWithClockStep(0, MILLISECONDS);
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     long startMs = TestTimeUtil.START.toEpochMilli();
     TestTimeUtil.setClock(new Timestamp(startMs));
 
-    Change change1 = insert(repo, newChange(repo));
-    Change change2 = insert(repo, newChange(repo));
-    Change change3 = insert(repo, newChange(repo));
+    Change change1 = insert("repo", newChange(repo));
+    Change change2 = insert("repo", newChange(repo));
+    Change change3 = insert("repo", newChange(repo));
 
     TestTimeUtil.setClock(new Timestamp(startMs + thirtyHoursInMs));
     submit(change2);
@@ -2282,15 +2312,15 @@
 
   @Test
   public void bySize() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
 
     // added = 3, deleted = 0, delta = 3
     RevCommit commit1 = repo.parseBody(repo.commit().add("file1", "foo\n\foo\nfoo").create());
     // added = 0, deleted = 2, delta = 2
     RevCommit commit2 = repo.parseBody(repo.commit().parent(commit1).add("file1", "foo").create());
 
-    Change change1 = insert(repo, newChangeForCommit(repo, commit1));
-    Change change2 = insert(repo, newChangeForCommit(repo, commit2));
+    Change change1 = insert("repo", newChangeForCommit(repo, commit1));
+    Change change2 = insert("repo", newChangeForCommit(repo, commit2));
 
     assertQuery("added:>4");
     assertQuery("-added:<=4");
@@ -2338,9 +2368,9 @@
   }
 
   private List<Change> setUpHashtagChanges() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
-    Change change1 = insert(repo, newChange(repo));
-    Change change2 = insert(repo, newChange(repo));
+    repo = createAndOpenProject("repo");
+    Change change1 = insert("repo", newChange(repo));
+    Change change2 = insert("repo", newChange(repo));
 
     addHashtags(change1.getId(), "foo", "aaa-bbb-ccc");
     addHashtags(change2.getId(), "foo", "bar", "a tag", "ACamelCaseTag");
@@ -2387,10 +2417,10 @@
 
   @Test
   public void byHashtagRegex() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
-    Change change1 = insert(repo, newChange(repo));
-    Change change2 = insert(repo, newChange(repo));
-    Change change3 = insert(repo, newChange(repo));
+    repo = createAndOpenProject("repo");
+    Change change1 = insert("repo", newChange(repo));
+    Change change2 = insert("repo", newChange(repo));
+    Change change3 = insert("repo", newChange(repo));
     addHashtags(change1.getId(), "feature1");
     addHashtags(change1.getId(), "trending");
     addHashtags(change2.getId(), "Cherrypick-feature1");
@@ -2403,27 +2433,27 @@
 
   @Test
   public void byDefault() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
 
-    Change change1 = insert(repo, newChange(repo));
+    Change change1 = insert("repo", newChange(repo));
 
     RevCommit commit2 = repo.parseBody(repo.commit().message("foosubject").create());
-    Change change2 = insert(repo, newChangeForCommit(repo, commit2));
+    Change change2 = insert("repo", newChangeForCommit(repo, commit2));
 
     RevCommit commit3 = repo.parseBody(repo.commit().add("Foo.java", "foo contents").create());
-    Change change3 = insert(repo, newChangeForCommit(repo, commit3));
+    Change change3 = insert("repo", newChangeForCommit(repo, commit3));
 
     ChangeInserter ins4 = newChange(repo);
-    Change change4 = insert(repo, ins4);
+    Change change4 = insert("repo", ins4);
     ReviewInput ri4 = new ReviewInput();
     ri4.message = "toplevel";
     ri4.labels = ImmutableMap.of("Code-Review", (short) 1);
     gApi.changes().id(change4.getId().get()).current().review(ri4);
 
     ChangeInserter ins5 = newChangeWithTopic(repo, "feature5");
-    Change change5 = insert(repo, ins5);
+    Change change5 = insert("repo", ins5);
 
-    Change change6 = insert(repo, newChangeForBranch(repo, "branch6"));
+    Change change6 = insert("repo", newChangeForBranch(repo, "branch6"));
 
     assertQuery(change1.getId().get(), change1);
     assertQuery(ChangeTriplet.format(change1), change1);
@@ -2444,18 +2474,18 @@
 
   @Test
   public void byDefaultWithCommitPrefix() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     RevCommit commit = repo.parseBody(repo.commit().message("message").create());
-    Change change = insert(repo, newChangeForCommit(repo, commit));
+    Change change = insert("repo", newChangeForCommit(repo, commit));
 
     assertQuery(commit.getId().getName().substring(0, 6), change);
   }
 
   @Test
   public void visible() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
-    Change change1 = insert(repo, newChange(repo));
-    Change change2 = insert(repo, newChangePrivate(repo));
+    repo = createAndOpenProject("repo");
+    Change change1 = insert("repo", newChange(repo));
+    Change change2 = insert("repo", newChangePrivate(repo));
 
     String q = "project:repo";
 
@@ -2509,8 +2539,8 @@
 
     // Switch to user3
     requestContext.setContext(newRequestContext(user3));
-    Change change3 = insert(repo, newChange(repo), user3);
-    Change change4 = insert(repo, newChangePrivate(repo), user3);
+    Change change3 = insert("repo", newChange(repo), user3);
+    Change change4 = insert("repo", newChangePrivate(repo), user3);
 
     // User3 can see both their changes and the first user's change
     assertQuery(q + " visibleto:" + user3.get(), change4, change3, change1);
@@ -2550,9 +2580,9 @@
 
   @Test
   public void visibleToSelf() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
-    Change change1 = insert(repo, newChange(repo));
-    Change change2 = insert(repo, newChange(repo));
+    repo = createAndOpenProject("repo");
+    Change change1 = insert("repo", newChange(repo));
+    Change change2 = insert("repo", newChange(repo));
 
     gApi.changes().id(change2.getChangeId()).setPrivate(true, "private");
 
@@ -2568,16 +2598,12 @@
 
   @Test
   public void byCommentBy() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
-    Change change1 = insert(repo, newChange(repo));
-    Change change2 = insert(repo, newChange(repo));
 
-    int user2 =
-        accountManager
-            .authenticate(authRequestFactory.createForUser("anotheruser"))
-            .getAccountId()
-            .get();
+    repo = createAndOpenProject("repo");
+    Change change1 = insert("repo", newChange(repo));
+    Change change2 = insert("repo", newChange(repo));
 
+    Account.Id user2 = createAccount("anotheruser");
     ReviewInput input = new ReviewInput();
     input.message = "toplevel";
     ReviewInput.CommentInput comment = new ReviewInput.CommentInput();
@@ -2598,8 +2624,8 @@
   public void bySubmitRuleResult() throws Exception {
     try (Registration registration =
         extensionRegistry.newRegistration().add(new FakeSubmitRule())) {
-      TestRepository<Repo> repo = createProject("repo");
-      Change change = insert(repo, newChange(repo));
+      repo = createAndOpenProject("repo");
+      Change change = insert("repo", newChange(repo));
       // The fake submit rule exports its ruleName as "FakeSubmitRule"
       assertQuery("rule:FakeSubmitRule");
 
@@ -2618,17 +2644,17 @@
   public void byNonExistingSubmitRule_returnsEmpty() throws Exception {
     try (Registration registration =
         extensionRegistry.newRegistration().add(new FakeSubmitRule())) {
-      TestRepository<Repo> repo = createProject("repo");
-      insert(repo, newChange(repo));
+      repo = createAndOpenProject("repo");
+      insert("repo", newChange(repo));
       assertQuery("rule:non-existent-rule");
     }
   }
 
   @Test
   public void byHasDraft() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
-    Change change1 = insert(repo, newChange(repo));
-    Change change2 = insert(repo, newChange(repo));
+    repo = createAndOpenProject("repo");
+    Change change1 = insert("repo", newChange(repo));
+    Change change2 = insert("repo", newChange(repo));
 
     assertQuery("has:draft");
 
@@ -2660,8 +2686,8 @@
    */
   public void byHasDraftExcludesZombieDrafts() throws Exception {
     Project.NameKey project = Project.nameKey("repo");
-    TestRepository<Repo> repo = createProject(project.get());
-    Change change = insert(repo, newChange(repo));
+    repo = createAndOpenProject(project.get());
+    Change change = insert("repo", newChange(repo));
     Change.Id id = change.getId();
 
     DraftInput in = new DraftInput();
@@ -2673,7 +2699,7 @@
     assertQuery("has:draft", change);
     assertQuery("commentby:" + userId);
 
-    try (TestRepository<Repo> allUsers =
+    try (TestRepository<Repository> allUsers =
         new TestRepository<>(repoManager.openRepository(allUsersName))) {
       Ref draftsRef = allUsers.getRepository().exactRef(RefNames.refsDraftComments(id, userId));
       assertThat(draftsRef).isNotNull();
@@ -2697,15 +2723,15 @@
 
   @Test
   public void byHasDraftWithManyDrafts() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     Change[] changesWithDrafts = new Change[30];
 
     // unrelated change not shown in the result.
-    insert(repo, newChange(repo));
+    insert("repo", newChange(repo));
 
     for (int i = 0; i < changesWithDrafts.length; i++) {
       // put the changes in reverse order since this is the order we receive them from the index.
-      changesWithDrafts[changesWithDrafts.length - 1 - i] = insert(repo, newChange(repo));
+      changesWithDrafts[changesWithDrafts.length - 1 - i] = insert("repo", newChange(repo));
       DraftInput in = new DraftInput();
       in.line = 1;
       in.message = "nit: trailing whitespace";
@@ -2725,10 +2751,10 @@
 
   @Test
   public void byStarredBy() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
-    Change change1 = insert(repo, newChange(repo));
-    Change change2 = insert(repo, newChange(repo));
-    insert(repo, newChange(repo));
+    repo = createAndOpenProject("repo");
+    Change change1 = insert("repo", newChange(repo));
+    Change change2 = insert("repo", newChange(repo));
+    insert("repo", newChange(repo));
 
     gApi.accounts().self().starChange(change1.getId().toString());
     gApi.accounts().self().starChange(change2.getId().toString());
@@ -2744,8 +2770,8 @@
 
   @Test
   public void byStar() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
-    Change change1 = insert(repo, newChangeWithStatus(repo, Change.Status.MERGED));
+    repo = createAndOpenProject("repo");
+    Change change1 = insert("repo", newChangeWithStatus(repo, Change.Status.MERGED));
 
     Account.Id user2 =
         accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
@@ -2760,11 +2786,11 @@
 
   @Test
   public void byStarWithManyStars() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     Change[] changesWithDrafts = new Change[30];
     for (int i = 0; i < changesWithDrafts.length; i++) {
       // put the changes in reverse order since this is the order we receive them from the index.
-      changesWithDrafts[changesWithDrafts.length - 1 - i] = insert(repo, newChange(repo));
+      changesWithDrafts[changesWithDrafts.length - 1 - i] = insert("repo", newChange(repo));
 
       // star the change
       gApi.accounts()
@@ -2778,12 +2804,12 @@
 
   @Test
   public void byFrom() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
-    Change change1 = insert(repo, newChange(repo));
+    repo = createAndOpenProject("repo");
+    Change change1 = insert("repo", newChange(repo));
 
     Account.Id user2 =
         accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
-    Change change2 = insert(repo, newChange(repo), user2);
+    Change change2 = insert("repo", newChange(repo), user2);
 
     ReviewInput input = new ReviewInput();
     input.message = "toplevel";
@@ -2799,7 +2825,7 @@
 
   @Test
   public void conflicts() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     RevCommit commit1 =
         repo.parseBody(
             repo.commit()
@@ -2811,10 +2837,10 @@
     RevCommit commit3 =
         repo.parseBody(repo.commit().add("dir/file2", "contents2 different").create());
     RevCommit commit4 = repo.parseBody(repo.commit().add("file4", "contents4").create());
-    Change change1 = insert(repo, newChangeForCommit(repo, commit1));
-    Change change2 = insert(repo, newChangeForCommit(repo, commit2));
-    Change change3 = insert(repo, newChangeForCommit(repo, commit3));
-    Change change4 = insert(repo, newChangeForCommit(repo, commit4));
+    Change change1 = insert("repo", newChangeForCommit(repo, commit1));
+    Change change2 = insert("repo", newChangeForCommit(repo, commit2));
+    Change change3 = insert("repo", newChangeForCommit(repo, commit3));
+    Change change4 = insert("repo", newChangeForCommit(repo, commit4));
 
     assertQuery("conflicts:" + change1.getId().get(), change3);
     assertQuery("conflicts:" + change2.getId().get());
@@ -2827,11 +2853,12 @@
       name = "change.mergeabilityComputationBehavior",
       value = "API_REF_UPDATED_AND_CHANGE_REINDEX")
   public void mergeable() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
+    assume().that(getSchema().hasField(ChangeField.MERGEABLE_SPEC)).isTrue();
+    repo = createAndOpenProject("repo");
     RevCommit commit1 = repo.parseBody(repo.commit().add("file1", "contents1").create());
     RevCommit commit2 = repo.parseBody(repo.commit().add("file1", "contents2").create());
-    Change change1 = insert(repo, newChangeForCommit(repo, commit1));
-    Change change2 = insert(repo, newChangeForCommit(repo, commit2));
+    Change change1 = insert("repo", newChangeForCommit(repo, commit1));
+    Change change2 = insert("repo", newChangeForCommit(repo, commit2));
 
     assertQuery("conflicts:" + change1.getId().get(), change2);
     assertQuery("conflicts:" + change2.getId().get(), change1);
@@ -2855,9 +2882,9 @@
   @Test
   public void cherrypick() throws Exception {
     assume().that(getSchema().hasField(ChangeField.CHERRY_PICK_SPEC)).isTrue();
-    TestRepository<Repo> repo = createProject("repo");
-    Change change1 = insert(repo, newChange(repo));
-    Change change2 = insert(repo, newCherryPickChange(repo, "foo", change1.currentPatchSetId()));
+    repo = createAndOpenProject("repo");
+    Change change1 = insert("repo", newChange(repo));
+    Change change2 = insert("repo", newCherryPickChange(repo, "foo", change1.currentPatchSetId()));
 
     assertQuery("is:cherrypick", change2);
     assertQuery("-is:cherrypick", change1);
@@ -2866,14 +2893,14 @@
   @Test
   public void merge() throws Exception {
     assume().that(getSchema().hasField(ChangeField.MERGE_SPEC)).isTrue();
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     RevCommit commit1 = repo.parseBody(repo.commit().add("file1", "contents1").create());
     RevCommit commit2 = repo.parseBody(repo.commit().add("file1", "contents2").create());
     RevCommit commit3 =
         repo.parseBody(repo.commit().parent(commit2).add("file1", "contents3").create());
-    Change change1 = insert(repo, newChangeForCommit(repo, commit1));
-    Change change2 = insert(repo, newChangeForCommit(repo, commit2));
-    Change change3 = insert(repo, newChangeForCommit(repo, commit3));
+    Change change1 = insert("repo", newChangeForCommit(repo, commit1));
+    Change change2 = insert("repo", newChangeForCommit(repo, commit2));
+    Change change3 = insert("repo", newChangeForCommit(repo, commit3));
     RevCommit mergeCommit =
         repo.branch("master")
             .commit()
@@ -2882,7 +2909,7 @@
             .parent(commit3)
             .insertChangeId()
             .create();
-    Change mergeChange = insert(repo, newChangeForCommit(repo, mergeCommit));
+    Change mergeChange = insert("repo", newChangeForCommit(repo, mergeCommit));
 
     assertQuery("status:open is:merge", mergeChange);
     assertQuery("status:open -is:merge", change3, change2, change1);
@@ -2892,10 +2919,10 @@
   @Test
   public void reviewedBy() throws Exception {
     resetTimeWithClockStep(2, MINUTES);
-    TestRepository<Repo> repo = createProject("repo");
-    Change change1 = insert(repo, newChange(repo));
-    Change change2 = insert(repo, newChange(repo));
-    Change change3 = insert(repo, newChange(repo));
+    repo = createAndOpenProject("repo");
+    Change change1 = insert("repo", newChange(repo));
+    Change change2 = insert("repo", newChange(repo));
+    Change change3 = insert("repo", newChange(repo));
 
     gApi.changes().id(change1.getId().get()).current().review(new ReviewInput().message("comment"));
 
@@ -2906,7 +2933,7 @@
     gApi.changes().id(change2.getId().get()).current().review(new ReviewInput().message("comment"));
 
     PatchSet.Id ps3_1 = change3.currentPatchSetId();
-    change3 = newPatchSet(repo, change3, user, /* message= */ Optional.empty());
+    change3 = newPatchSet("repo", change3, user, /* message= */ Optional.empty());
     assertThat(change3.currentPatchSetId()).isNotEqualTo(ps3_1);
     // Response to previous patch set still counts as reviewing.
     gApi.changes()
@@ -2933,11 +2960,11 @@
   @Test
   public void reviewerAndCc() throws Exception {
     Account.Id user1 = createAccount("user1");
-    TestRepository<Repo> repo = createProject("repo");
-    Change change1 = insert(repo, newChange(repo));
-    Change change2 = insert(repo, newChange(repo));
-    Change change3 = insert(repo, newChange(repo));
-    insert(repo, newChange(repo));
+    repo = createAndOpenProject("repo");
+    Change change1 = insert("repo", newChange(repo));
+    Change change2 = insert("repo", newChange(repo));
+    Change change3 = insert("repo", newChange(repo));
+    insert("repo", newChange(repo));
 
     ReviewerInput rin = new ReviewerInput();
     rin.reviewer = user1.toString();
@@ -2964,11 +2991,11 @@
 
   @Test
   public void byReviewed() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     Account.Id otherUser =
         accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
-    Change change1 = insert(repo, newChange(repo));
-    Change change2 = insert(repo, newChange(repo));
+    Change change1 = insert("repo", newChange(repo));
+    Change change2 = insert("repo", newChange(repo));
 
     assertQuery("is:reviewed");
     assertQuery("status:reviewed");
@@ -2992,11 +3019,11 @@
         accountManager.authenticate(authRequestFactory.createForUser("user2")).getAccountId();
     Account.Id user3 =
         accountManager.authenticate(authRequestFactory.createForUser("user3")).getAccountId();
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
 
-    Change change1 = insert(repo, newChange(repo));
-    Change change2 = insert(repo, newChange(repo));
-    Change change3 = insert(repo, newChange(repo));
+    Change change1 = insert("repo", newChange(repo));
+    Change change2 = insert("repo", newChange(repo));
+    Change change3 = insert("repo", newChange(repo));
 
     ReviewerInput rin = new ReviewerInput();
     rin.reviewer = user1.toString();
@@ -3036,7 +3063,7 @@
   @Test
   public void reviewerAndCcByEmail() throws Exception {
     Project.NameKey project = Project.nameKey("repo");
-    TestRepository<Repo> repo = createProject(project.get());
+    repo = createAndOpenProject(project.get());
     ConfigInput conf = new ConfigInput();
     conf.enableReviewerByEmail = InheritableBoolean.TRUE;
     gApi.projects().name(project.get()).config(conf);
@@ -3044,9 +3071,9 @@
     String userByEmail = "un.registered@reviewer.com";
     String userByEmailWithName = "John Doe <" + userByEmail + ">";
 
-    Change change1 = insert(repo, newChange(repo));
-    Change change2 = insert(repo, newChange(repo));
-    insert(repo, newChange(repo));
+    Change change1 = insert("repo", newChange(repo));
+    Change change2 = insert("repo", newChange(repo));
+    insert("repo", newChange(repo));
 
     ReviewerInput rin = new ReviewerInput();
     rin.reviewer = userByEmailWithName;
@@ -3069,16 +3096,16 @@
   @Test
   public void reviewerAndCcByEmailWithQueryForDifferentUser() throws Exception {
     Project.NameKey project = Project.nameKey("repo");
-    TestRepository<Repo> repo = createProject(project.get());
+    repo = createAndOpenProject(project.get());
     ConfigInput conf = new ConfigInput();
     conf.enableReviewerByEmail = InheritableBoolean.TRUE;
     gApi.projects().name(project.get()).config(conf);
 
     String userByEmail = "John Doe <un.registered@reviewer.com>";
 
-    Change change1 = insert(repo, newChange(repo));
-    Change change2 = insert(repo, newChange(repo));
-    insert(repo, newChange(repo));
+    Change change1 = insert("repo", newChange(repo));
+    Change change2 = insert("repo", newChange(repo));
+    insert("repo", newChange(repo));
 
     ReviewerInput rin = new ReviewerInput();
     rin.reviewer = userByEmail;
@@ -3097,9 +3124,9 @@
   @Test
   public void submitRecords() throws Exception {
     Account.Id user1 = createAccount("user1");
-    TestRepository<Repo> repo = createProject("repo");
-    Change change1 = insert(repo, newChange(repo));
-    Change change2 = insert(repo, newChange(repo));
+    repo = createAndOpenProject("repo");
+    Change change1 = insert("repo", newChange(repo));
+    Change change2 = insert("repo", newChange(repo));
 
     gApi.changes().id(change1.getId().get()).current().review(ReviewInput.approve());
     requestContext.setContext(newRequestContext(user1));
@@ -3110,7 +3137,7 @@
     assertQuery("-is:submittable", change2);
 
     assertQuery("label:CodE-RevieW=ok", change1);
-    assertQuery("label:CodE-RevieW=ok,user=user", change1);
+    assertQuery("label:CodE-RevieW=ok,user=" + userAccount.preferredEmail(), change1);
     assertQuery("label:CodE-RevieW=ok,Administrators", change1);
     assertQuery("label:CodE-RevieW=ok,group=Administrators", change1);
     assertQuery("label:CodE-RevieW=ok,owner", change1);
@@ -3129,10 +3156,10 @@
   public void hasEdit() throws Exception {
     Account.Id user1 = createAccount("user1");
     Account.Id user2 = createAccount("user2");
-    TestRepository<Repo> repo = createProject("repo");
-    Change change1 = insert(repo, newChange(repo));
+    repo = createAndOpenProject("repo");
+    Change change1 = insert("repo", newChange(repo));
     String changeId1 = change1.getKey().get();
-    Change change2 = insert(repo, newChange(repo));
+    Change change2 = insert("repo", newChange(repo));
     String changeId2 = change2.getKey().get();
 
     requestContext.setContext(newRequestContext(user1));
@@ -3153,10 +3180,10 @@
 
   @Test
   public void byUnresolved() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
-    Change change1 = insert(repo, newChange(repo));
-    Change change2 = insert(repo, newChange(repo));
-    Change change3 = insert(repo, newChange(repo));
+    repo = createAndOpenProject("repo");
+    Change change1 = insert("repo", newChange(repo));
+    Change change2 = insert("repo", newChange(repo));
+    Change change3 = insert("repo", newChange(repo));
 
     // Change1 has one resolved comment (unresolvedcount = 0)
     // Change2 has one unresolved comment (unresolvedcount = 1)
@@ -3184,13 +3211,13 @@
 
   @Test
   public void byCommitsOnBranchNotMerged() throws Exception {
-    TestRepository<Repo> tr = createProject("repo");
-    testByCommitsOnBranchNotMerged(tr, ImmutableSet.of());
+    createProject("repo");
+    testByCommitsOnBranchNotMerged("repo", ImmutableSet.of());
   }
 
   @Test
   public void byCommitsOnBranchNotMergedSkipsMissingChanges() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     ObjectId missing =
         repo.branch(PatchSet.id(Change.id(987654), 1).toRefName())
             .commit()
@@ -3198,72 +3225,75 @@
             .insertChangeId()
             .create()
             .copy();
-    testByCommitsOnBranchNotMerged(repo, ImmutableSet.of(missing));
+    testByCommitsOnBranchNotMerged("repo", ImmutableSet.of(missing));
   }
 
-  private void testByCommitsOnBranchNotMerged(TestRepository<Repo> repo, Collection<ObjectId> extra)
+  private void testByCommitsOnBranchNotMerged(String repo, Collection<ObjectId> extra)
       throws Exception {
     int n = 10;
     List<String> shas = new ArrayList<>(n + extra.size());
     extra.forEach(i -> shas.add(i.name()));
     List<Integer> expectedIds = new ArrayList<>(n);
     BranchNameKey dest = null;
-    for (int i = 0; i < n; i++) {
-      ChangeInserter ins = newChange(repo);
-      insert(repo, ins);
-      if (dest == null) {
-        dest = ins.getChange().getDest();
+    try (TestRepository<Repository> repository =
+        new TestRepository<>(repoManager.openRepository(Project.nameKey(repo)))) {
+      for (int i = 0; i < n; i++) {
+        ChangeInserter ins = newChange(repository);
+        insert("repo", ins);
+        if (dest == null) {
+          dest = ins.getChange().getDest();
+        }
+        shas.add(ins.getCommitId().name());
+        expectedIds.add(ins.getChange().getId().get());
       }
-      shas.add(ins.getCommitId().name());
-      expectedIds.add(ins.getChange().getId().get());
     }
-
-    for (int i = 1; i <= 11; i++) {
-      Iterable<ChangeData> cds =
-          queryProvider.get().byCommitsOnBranchNotMerged(repo.getRepository(), dest, shas, i);
-      Iterable<Integer> ids = FluentIterable.from(cds).transform(in -> in.getId().get());
-      String name = "limit " + i;
-      assertWithMessage(name).that(ids).hasSize(n);
-      assertWithMessage(name).that(ids).containsExactlyElementsIn(expectedIds);
+    try (Repository repository = repoManager.openRepository(Project.nameKey(repo))) {
+      for (int i = 1; i <= 11; i++) {
+        Iterable<ChangeData> cds =
+            queryProvider.get().byCommitsOnBranchNotMerged(repository, dest, shas, i);
+        Iterable<Integer> ids = FluentIterable.from(cds).transform(in -> in.getId().get());
+        String name = "limit " + i;
+        assertWithMessage(name).that(ids).hasSize(n);
+        assertWithMessage(name).that(ids).containsExactlyElementsIn(expectedIds);
+      }
     }
   }
 
   @Test
   public void reindexIfStale() throws Exception {
-    Account.Id user = createAccount("user");
     Project.NameKey project = Project.nameKey("repo");
-    TestRepository<Repo> repo = createProject(project.get());
-    Change change = insert(repo, newChange(repo));
+    repo = createAndOpenProject(project.get());
+    Change change = insert("repo", newChange(repo));
     String changeId = change.getKey().get();
-    ChangeNotes notes = notesFactory.create(change.getProject(), change.getId());
-    PatchSet ps = psUtil.get(notes, change.currentPatchSetId());
 
-    requestContext.setContext(newRequestContext(user));
-    gApi.changes().id(changeId).edit().create();
-    assertQuery("has:edit", change);
+    Account.Id anotherUser = createAccount("another-user");
+    requestContext.setContext(newRequestContext(anotherUser));
+    gApi.changes().id(changeId).addReviewer(anotherUser.toString());
+
+    assertQuery("reviewer:self", change);
     assertThat(indexer.reindexIfStale(project, change.getId()).get()).isFalse();
 
-    // Delete edit ref behind index's back.
-    RefUpdate ru = repo.getRepository().updateRef(RefNames.refsEdit(user, change.getId(), ps.id()));
-    ru.setForceUpdate(true);
-    assertThat(ru.delete()).isEqualTo(RefUpdate.Result.FORCED);
+    // Remove reviewer behind index's back.
+    ChangeUpdate update = newUpdate(change);
+    update.removeReviewer(anotherUser);
+    update.commit();
 
     // Index is stale.
-    assertQuery("has:edit", change);
+    assertQuery("reviewer:self", change);
     assertThat(indexer.reindexIfStale(project, change.getId()).get()).isTrue();
-    assertQuery("has:edit");
+    assertQuery("reviewer:self");
   }
 
   @Test
   public void watched() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
-    ChangeInserter ins1 = newChangeWithStatus(repo, Change.Status.NEW);
-    Change change1 = insert(repo, ins1);
+    createProject("repo");
+    ChangeInserter ins1 = newChangeWithStatus("repo", Change.Status.NEW);
+    Change change1 = insert("repo", ins1);
 
-    TestRepository<Repo> repo2 = createProject("repo2");
+    createProject("repo2");
 
-    ChangeInserter ins2 = newChangeWithStatus(repo2, Change.Status.NEW);
-    insert(repo2, ins2);
+    ChangeInserter ins2 = newChangeWithStatus("repo2", Change.Status.NEW);
+    insert("repo2", ins2);
 
     assertQuery("is:watched");
 
@@ -3283,17 +3313,17 @@
 
   @Test
   public void trackingid() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     RevCommit commit1 =
         repo.parseBody(repo.commit().message("Change one\n\nBug:QUERY123").create());
-    Change change1 = insert(repo, newChangeForCommit(repo, commit1));
+    Change change1 = insert("repo", newChangeForCommit(repo, commit1));
     RevCommit commit2 =
         repo.parseBody(repo.commit().message("Change two\n\nIssue: Issue 16038\n").create());
-    Change change2 = insert(repo, newChangeForCommit(repo, commit2));
+    Change change2 = insert("repo", newChangeForCommit(repo, commit2));
 
     RevCommit commit3 =
         repo.parseBody(repo.commit().message("Change two\n\nGoogle-Bug-Id: b/16039\n").create());
-    Change change3 = insert(repo, newChangeForCommit(repo, commit3));
+    Change change3 = insert("repo", newChangeForCommit(repo, commit3));
 
     assertQuery("tr:QUERY123", change1);
     assertQuery("bug:QUERY123", change1);
@@ -3319,9 +3349,9 @@
 
   @Test
   public void revertOf() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     // Create two commits and revert second commit (initial commit can't be reverted)
-    Change initial = insert(repo, newChange(repo));
+    Change initial = insert("repo", newChange(repo));
     gApi.changes().id(initial.getChangeId()).current().review(ReviewInput.approve());
     gApi.changes().id(initial.getChangeId()).current().submit();
 
@@ -3336,10 +3366,10 @@
 
   @Test
   public void submissionId() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
-    Change change = insert(repo, newChange(repo));
+    repo = createAndOpenProject("repo");
+    Change change = insert("repo", newChange(repo));
     // create irrelevant change
-    insert(repo, newChange(repo));
+    insert("repo", newChange(repo));
     gApi.changes().id(change.getChangeId()).current().review(ReviewInput.approve());
     gApi.changes().id(change.getChangeId()).current().submit();
     String submissionId = gApi.changes().id(change.getChangeId()).get().submissionId;
@@ -3409,9 +3439,9 @@
       return this;
     }
 
-    DashboardChangeState create(TestRepository<Repo> repo) throws Exception {
+    DashboardChangeState create(TestRepository<Repository> repo) throws Exception {
       requestContext.setContext(newRequestContext(ownerId));
-      Change change = insert(repo, newChange(repo), ownerId);
+      Change change = insert("repo", newChange(repo), ownerId);
       id = change.getId();
       ChangeApi cApi = gApi.changes().id(change.getChangeId());
       if (assigneeId != null) {
@@ -3479,7 +3509,7 @@
 
   @Test
   public void dashboardHasUnpublishedDrafts() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     Account.Id otherAccountId = createAccount("other");
     DashboardChangeState hasUnpublishedDraft =
         new DashboardChangeState(otherAccountId).draftCommentBy(user.getAccountId()).create(repo);
@@ -3497,7 +3527,7 @@
 
   @Test
   public void dashboardAssignedReviews() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     Account.Id otherAccountId = createAccount("other");
     DashboardChangeState otherOpenWip =
         new DashboardChangeState(otherAccountId).wip().assignTo(user.getAccountId()).create(repo);
@@ -3519,12 +3549,12 @@
     // Viewing another user's dashboard.
     requestContext.setContext(newRequestContext(otherAccountId));
     assertDashboardQuery(
-        user.getUserName().get(), IndexPreloadingUtil.DASHBOARD_ASSIGNED_QUERY, otherOpenWip);
+        userId.toString(), IndexPreloadingUtil.DASHBOARD_ASSIGNED_QUERY, otherOpenWip);
   }
 
   @Test
   public void dashboardWorkInProgressReviews() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     DashboardChangeState ownedOpenWip =
         new DashboardChangeState(user.getAccountId()).wip().create(repo);
 
@@ -3539,7 +3569,7 @@
 
   @Test
   public void dashboardOutgoingReviews() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     Account.Id otherAccountId = createAccount("other");
     DashboardChangeState ownedOpenReviewable =
         new DashboardChangeState(user.getAccountId()).create(repo);
@@ -3554,14 +3584,12 @@
     // Viewing another user's dashboard.
     requestContext.setContext(newRequestContext(otherAccountId));
     assertDashboardQuery(
-        user.getUserName().get(),
-        IndexPreloadingUtil.DASHBOARD_OUTGOING_QUERY,
-        ownedOpenReviewable);
+        userId.toString(), IndexPreloadingUtil.DASHBOARD_OUTGOING_QUERY, ownedOpenReviewable);
   }
 
   @Test
   public void dashboardIncomingReviews() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     Account.Id otherAccountId = createAccount("other");
     DashboardChangeState reviewingReviewable =
         new DashboardChangeState(otherAccountId).addReviewer(user.getAccountId()).create(repo);
@@ -3587,7 +3615,7 @@
     // Viewing another user's dashboard.
     requestContext.setContext(newRequestContext(otherAccountId));
     assertDashboardQuery(
-        user.getUserName().get(),
+        userId.toString(),
         IndexPreloadingUtil.DASHBOARD_INCOMING_QUERY,
         assignedReviewable,
         reviewingReviewable);
@@ -3595,7 +3623,7 @@
 
   @Test
   public void dashboardRecentlyClosedReviews() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     Account.Id otherAccountId = createAccount("other");
     DashboardChangeState mergedOwned =
         new DashboardChangeState(user.getAccountId()).mergeBy(user.getAccountId()).create(repo);
@@ -3658,7 +3686,7 @@
     // Viewing another user's dashboard.
     requestContext.setContext(newRequestContext(otherAccountId));
     assertDashboardQuery(
-        user.getUserName().get(),
+        userId.toString(),
         IndexPreloadingUtil.DASHBOARD_RECENTLY_CLOSED_QUERY,
         abandonedAssignedWip,
         abandonedAssigned,
@@ -3674,9 +3702,9 @@
   public void attentionSetIndexed() throws Exception {
     assume().that(getSchema().hasField(ChangeField.ATTENTION_SET_USERS)).isTrue();
     assume().that(getSchema().hasField(ChangeField.ATTENTION_SET_USERS_COUNT)).isTrue();
-    TestRepository<Repo> repo = createProject("repo");
-    Change change1 = insert(repo, newChange(repo));
-    Change change2 = insert(repo, newChange(repo));
+    repo = createAndOpenProject("repo");
+    Change change1 = insert("repo", newChange(repo));
+    Change change2 = insert("repo", newChange(repo));
 
     AttentionSetInput input = new AttentionSetInput(userId.toString(), "some reason");
     gApi.changes().id(change1.getChangeId()).addToAttentionSet(input);
@@ -3685,7 +3713,7 @@
     assertQuery("-is:attention", change2);
     assertQuery("has:attention", change1);
     assertQuery("-has:attention", change2);
-    assertQuery("attention:" + user.getUserName().get(), change1);
+    assertQuery("attention:" + userAccount.preferredEmail(), change1);
     assertQuery("-attention:" + userId.toString(), change2);
 
     gApi.changes()
@@ -3698,8 +3726,8 @@
   @Test
   public void attentionSetStored() throws Exception {
     assume().that(getSchema().hasField(ChangeField.ATTENTION_SET_USERS)).isTrue();
-    TestRepository<Repo> repo = createProject("repo");
-    Change change = insert(repo, newChange(repo));
+    repo = createAndOpenProject("repo");
+    Change change = insert("repo", newChange(repo));
 
     AttentionSetInput input = new AttentionSetInput(userId.toString(), "reason 1");
     gApi.changes().id(change.getChangeId()).addToAttentionSet(input);
@@ -3727,29 +3755,29 @@
 
   @Test
   public void assignee() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
-    Change change1 = insert(repo, newChange(repo));
-    Change change2 = insert(repo, newChange(repo));
+    repo = createAndOpenProject("repo");
+    Change change1 = insert("repo", newChange(repo));
+    Change change2 = insert("repo", newChange(repo));
 
     AssigneeInput input = new AssigneeInput();
-    input.assignee = user.getUserName().get();
+    input.assignee = user.getAccountId().toString();
     gApi.changes().id(change1.getChangeId()).setAssignee(input);
 
     assertQuery("is:assigned", change1);
     assertQuery("-is:assigned", change2);
     assertQuery("is:unassigned", change2);
     assertQuery("-is:unassigned", change1);
-    assertQuery("assignee:" + user.getUserName().get(), change1);
-    assertQuery("-assignee:" + user.getUserName().get(), change2);
+    assertQuery("assignee:" + user.getAccountId(), change1);
+    assertQuery("-assignee:" + user.getAccountId(), change2);
   }
 
   @GerritConfig(name = "accounts.visibility", value = "NONE")
   @Test
   public void userDestination() throws Exception {
-    TestRepository<Repo> repo1 = createProject("repo1");
-    Change change1 = insert(repo1, newChange(repo1));
-    TestRepository<Repo> repo2 = createProject("repo2");
-    Change change2 = insert(repo2, newChange(repo2));
+    createProject("repo1");
+    Change change1 = insert("repo1", newChange("repo1"));
+    createProject("repo2");
+    Change change2 = insert("repo2", newChange("repo2"));
 
     assertThatQueryException("destination:foo")
         .hasMessageThat()
@@ -3763,7 +3791,7 @@
     String destination4 = "refs/heads/master\trepo3";
     String destination5 = "refs/heads/other\trepo1";
 
-    try (TestRepository<Repo> allUsers =
+    try (TestRepository<Repository> allUsers =
         new TestRepository<>(repoManager.openRepository(allUsersName))) {
       String refsUsers = RefNames.refsUsers(userId);
       allUsers.branch(refsUsers).commit().add("destinations/destination1", destination1).create();
@@ -3814,26 +3842,25 @@
     assertThatQueryException("destination:destination3,user=" + anotherUserId)
         .hasMessageThat()
         .isEqualTo("Unknown named destination: destination3");
-    assertThatQueryException("destination:destination3,user=test")
+    assertThatQueryException("destination:destination3,user=non-existent")
         .hasMessageThat()
-        .isEqualTo("Account 'test' not found");
+        .isEqualTo("Account 'non-existent' not found");
 
     requestContext.setContext(newRequestContext(anotherUserId));
-    // account 1000000 is not visible to 'anotheruser' as they are not an admin
+    // account userId is not visible to 'anotheruser' as they are not an admin
     assertThatQueryException("destination:destination3,user=" + userId)
         .hasMessageThat()
-        .isEqualTo("Account '1000000' not found");
+        .isEqualTo(String.format("Account '%s' not found", userId));
   }
 
   @GerritConfig(name = "accounts.visibility", value = "NONE")
   @Test
   public void userQuery() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
-    Change change1 = insert(repo, newChange(repo));
-    Change change2 = insert(repo, newChangeForBranch(repo, "stable"));
+    repo = createAndOpenProject("repo");
+    Change change1 = insert("repo", newChange(repo));
+    Change change2 = insert("repo", newChangeForBranch(repo, "stable"));
 
-    Account.Id anotherUserId =
-        accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
+    Account.Id anotherUserId = createAccount("anotheruser");
     String queryListText =
         "query1\tproject:repo\n"
             + "query2\tproject:repo status:open\n"
@@ -3845,7 +3872,7 @@
             + "query7\tproject:repo branch:stable\n"
             + "query8\tproject:repo branch:other";
 
-    try (TestRepository<Repo> allUsers =
+    try (TestRepository<Repository> allUsers =
             new TestRepository<>(repoManager.openRepository(allUsersName));
         MetaDataUpdate md = metaDataUpdateFactory.create(allUsersName);
         MetaDataUpdate anotherMd = metaDataUpdateFactory.create(allUsersName)) {
@@ -3859,19 +3886,20 @@
       anotherQueries.commit(anotherMd);
     }
 
+    assertThat(gApi.accounts().self().get()._accountId).isEqualTo(userId.get());
     assertThatQueryException("query:foo").hasMessageThat().isEqualTo("Unknown named query: foo");
     assertThatQueryException("query:query1,user=" + anotherUserId)
         .hasMessageThat()
         .isEqualTo("Unknown named query: query1");
-    assertThatQueryException("query:query1,user=test")
+    assertThatQueryException("query:query1,user=non-existent")
         .hasMessageThat()
-        .isEqualTo("Account 'test' not found");
+        .isEqualTo("Account 'non-existent' not found");
 
     requestContext.setContext(newRequestContext(anotherUserId));
     // account 1000000 is not visible to 'anotheruser' as they are not an admin
     assertThatQueryException("query:query1,user=" + userId)
         .hasMessageThat()
-        .isEqualTo("Account '1000000' not found");
+        .isEqualTo(String.format("Account '%s' not found", userId));
     requestContext.setContext(newRequestContext(userId));
 
     assertQuery("query:query1", change2, change1);
@@ -3890,16 +3918,16 @@
 
   @Test
   public void byOwnerInvalidQuery() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
-    insert(repo, newChange(repo), userId);
+    repo = createAndOpenProject("repo");
+    insert("repo", newChange(repo), userId);
     String nameEmail = user.asIdentifiedUser().getNameEmail();
     assertQuery("owner: \"" + nameEmail + "\"\\");
   }
 
   @Test
   public void byDeletedChange() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
-    Change change = insert(repo, newChange(repo));
+    repo = createAndOpenProject("repo");
+    Change change = insert("repo", newChange(repo));
 
     String query = "change:" + change.getId();
     assertQuery(query, change);
@@ -3910,8 +3938,8 @@
 
   @Test
   public void byUrlEncodedProject() throws Exception {
-    TestRepository<Repo> repo = createProject("repo+foo");
-    Change change = insert(repo, newChange(repo));
+    repo = createAndOpenProject("repo+foo");
+    Change change = insert("repo+foo", newChange(repo));
     assertQuery("project:repo+foo", change);
   }
 
@@ -3944,9 +3972,9 @@
   @Test
   public void isPureRevert() throws Exception {
     assume().that(getSchema().hasField(ChangeField.IS_PURE_REVERT_SPEC)).isTrue();
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     // Create two commits and revert second commit (initial commit can't be reverted)
-    Change initial = insert(repo, newChange(repo));
+    Change initial = insert("repo", newChange(repo));
     gApi.changes().id(initial.getChangeId()).current().review(ReviewInput.approve());
     gApi.changes().id(initial.getChangeId()).current().submit();
 
@@ -3990,8 +4018,8 @@
     Account.Id user2 =
         accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
 
-    TestRepository<Repo> repo = createProject("repo");
-    Change change = insert(repo, newChange(repo));
+    repo = createAndOpenProject("repo");
+    Change change = insert("repo", newChange(repo));
     gApi.changes().id(change.getId().get()).addReviewer(user2.toString());
 
     RequestContext adminContext = requestContext.setContext(newRequestContext(user2));
@@ -4006,8 +4034,8 @@
 
   @Test
   public void none() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
-    Change change = insert(repo, newChange(repo));
+    repo = createAndOpenProject("repo");
+    Change change = insert("repo", newChange(repo));
 
     assertQuery(ChangeIndexPredicate.none());
 
@@ -4027,9 +4055,10 @@
   @Test
   @GerritConfig(name = "change.mergeabilityComputationBehavior", value = "NEVER")
   public void mergeableFailsWhenNotIndexed() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
+    assume().that(getSchema().hasField(ChangeField.MERGE_SPEC)).isTrue();
+    repo = createAndOpenProject("repo");
     RevCommit commit1 = repo.parseBody(repo.commit().add("file1", "contents1").create());
-    insert(repo, newChangeForCommit(repo, commit1));
+    insert("repo", newChangeForCommit(repo, commit1));
 
     Throwable thrown = assertThrows(Throwable.class, () -> assertQuery("status:open is:mergeable"));
     assertThat(thrown.getCause()).isInstanceOf(QueryParseException.class);
@@ -4038,16 +4067,12 @@
         .contains("'is:mergeable' operator is not supported by server");
   }
 
-  protected ChangeInserter newChange(TestRepository<Repo> repo) throws Exception {
-    return newChange(repo, null, null, null, null, null, false, false);
-  }
-
-  protected ChangeInserter newChangeForCommit(TestRepository<Repo> repo, RevCommit commit)
+  protected ChangeInserter newChangeForCommit(TestRepository<Repository> repo, RevCommit commit)
       throws Exception {
     return newChange(repo, commit, null, null, null, null, false, false);
   }
 
-  protected ChangeInserter newChangeWithFiles(TestRepository<Repo> repo, String... paths)
+  protected ChangeInserter newChangeWithFiles(TestRepository<Repository> repo, String... paths)
       throws Exception {
     TestRepository<?>.CommitBuilder b = repo.commit().message("Change with files");
     for (String path : paths) {
@@ -4056,36 +4081,67 @@
     return newChangeForCommit(repo, repo.parseBody(b.create()));
   }
 
-  protected ChangeInserter newChangeForBranch(TestRepository<Repo> repo, String branch)
+  protected ChangeInserter newChangeForBranch(TestRepository<Repository> repo, String branch)
       throws Exception {
     return newChange(repo, null, branch, null, null, null, false, false);
   }
 
-  protected ChangeInserter newChangeWithStatus(TestRepository<Repo> repo, Change.Status status)
-      throws Exception {
+  protected ChangeInserter newChangeWithStatus(
+      TestRepository<Repository> repo, Change.Status status) throws Exception {
     return newChange(repo, null, null, status, null, null, false, false);
   }
 
-  protected ChangeInserter newChangeWithTopic(TestRepository<Repo> repo, String topic)
+  protected ChangeInserter newChangeWithStatus(String repoName, Change.Status status)
+      throws Exception {
+    return newChange(repoName, null, null, status, null, null, false, false);
+  }
+
+  protected ChangeInserter newChangeWithTopic(TestRepository<Repository> repo, String topic)
       throws Exception {
     return newChange(repo, null, null, null, topic, null, false, false);
   }
 
-  protected ChangeInserter newChangeWorkInProgress(TestRepository<Repo> repo) throws Exception {
+  protected ChangeInserter newChangeWorkInProgress(TestRepository<Repository> repo)
+      throws Exception {
     return newChange(repo, null, null, null, null, null, true, false);
   }
 
-  protected ChangeInserter newChangePrivate(TestRepository<Repo> repo) throws Exception {
+  protected ChangeInserter newChangePrivate(TestRepository<Repository> repo) throws Exception {
     return newChange(repo, null, null, null, null, null, false, true);
   }
 
   protected ChangeInserter newCherryPickChange(
-      TestRepository<Repo> repo, String branch, PatchSet.Id cherryPickOf) throws Exception {
+      TestRepository<Repository> repo, String branch, PatchSet.Id cherryPickOf) throws Exception {
     return newChange(repo, null, branch, null, null, cherryPickOf, false, true);
   }
 
+  protected ChangeInserter newChange(String repoName) throws Exception {
+    return newChange(repoName, null, null, null, null, null, false, false);
+  }
+
   protected ChangeInserter newChange(
-      TestRepository<Repo> repo,
+      String repoName,
+      @Nullable RevCommit commit,
+      @Nullable String branch,
+      @Nullable Change.Status status,
+      @Nullable String topic,
+      @Nullable PatchSet.Id cherryPickOf,
+      boolean workInProgress,
+      boolean isPrivate)
+      throws Exception {
+    try (TestRepository<Repository> repo =
+        new TestRepository<>(repoManager.openRepository(Project.nameKey(repoName)))) {
+      return newChange(
+          repo, commit, branch, status, topic, cherryPickOf, workInProgress, isPrivate);
+    }
+  }
+
+  protected ChangeInserter newChange(TestRepository<Repository> repo) throws Exception {
+    return newChange(repo, null, null, null, null, null, false, false);
+  }
+
+  protected ChangeInserter newChange(
+      TestRepository<Repository> repo,
       @Nullable RevCommit commit,
       @Nullable String branch,
       @Nullable Change.Status status,
@@ -4095,7 +4151,7 @@
       boolean isPrivate)
       throws Exception {
     if (commit == null) {
-      commit = repo.parseBody(repo.commit().message("message").create());
+      commit = repo.parseBody(repo.commit().message("initial message").create());
     }
 
     branch = MoreObjects.firstNonNull(branch, "refs/heads/master");
@@ -4104,32 +4160,32 @@
     }
 
     Change.Id id = Change.id(seq.nextChangeId());
-    ChangeInserter ins =
-        changeFactory
-            .create(id, commit, branch)
-            .setValidate(false)
-            .setStatus(status)
-            .setTopic(topic)
-            .setWorkInProgress(workInProgress)
-            .setPrivate(isPrivate)
-            .setCherryPickOf(cherryPickOf);
-    return ins;
+    return changeFactory
+        .create(id, commit, branch)
+        .setValidate(false)
+        .setStatus(status)
+        .setTopic(topic)
+        .setWorkInProgress(workInProgress)
+        .setPrivate(isPrivate)
+        .setCherryPickOf(cherryPickOf);
   }
 
-  protected Change insert(TestRepository<Repo> repo, ChangeInserter ins) throws Exception {
-    return insert(repo, ins, null, TimeUtil.now());
-  }
-
-  protected Change insert(TestRepository<Repo> repo, ChangeInserter ins, @Nullable Account.Id owner)
+  @CanIgnoreReturnValue
+  protected Change insert(String repoName, ChangeInserter ins, @Nullable Account.Id owner)
       throws Exception {
-    return insert(repo, ins, owner, TimeUtil.now());
+    return insert(repoName, ins, owner, TimeUtil.now());
   }
 
+  @CanIgnoreReturnValue
+  protected Change insert(String repoName, ChangeInserter ins) throws Exception {
+    return insert(repoName, ins, null, TimeUtil.now());
+  }
+
+  @CanIgnoreReturnValue
   protected Change insert(
-      TestRepository<Repo> repo, ChangeInserter ins, @Nullable Account.Id owner, Instant createdOn)
+      String repoName, ChangeInserter ins, @Nullable Account.Id owner, Instant createdOn)
       throws Exception {
-    Project.NameKey project =
-        Project.nameKey(repo.getRepository().getDescription().getRepositoryName());
+    Project.NameKey project = Project.nameKey(repoName);
     Account.Id ownerId = owner != null ? owner : userId;
     IdentifiedUser user = userFactory.create(ownerId);
     try (BatchUpdate bu = updateFactory.create(project, user, createdOn)) {
@@ -4140,34 +4196,36 @@
   }
 
   protected Change newPatchSet(
-      TestRepository<Repo> repo, Change c, CurrentUser user, Optional<String> message)
-      throws Exception {
-    // Add a new file so the patch set is not a trivial rebase, to avoid default
-    // Code-Review label copying.
-    int n = c.currentPatchSetId().get() + 1;
-    RevCommit commit =
-        repo.parseBody(
-            repo.commit()
-                .message(message.orElse("message"))
-                .add("file" + n, "contents " + n)
-                .create());
+      String repoName, Change c, CurrentUser user, Optional<String> message) throws Exception {
+    try (TestRepository<Repository> repo =
+        new TestRepository<>(repoManager.openRepository(Project.nameKey(repoName)))) {
+      // Add a new file so the patch set is not a trivial rebase, to avoid default
+      // Code-Review label copying.
+      int n = c.currentPatchSetId().get() + 1;
+      RevCommit commit =
+          repo.parseBody(
+              repo.commit()
+                  .message(message.orElse("updated message"))
+                  .add("file" + n, "contents " + n)
+                  .create());
 
-    PatchSetInserter inserter =
-        patchSetFactory
-            .create(changeNotesFactory.createChecked(c), PatchSet.id(c.getId(), n), commit)
-            .setFireRevisionCreated(false)
-            .setValidate(false);
-    try (BatchUpdate bu = updateFactory.create(c.getProject(), user, TimeUtil.now());
-        ObjectInserter oi = repo.getRepository().newObjectInserter();
-        ObjectReader reader = oi.newReader();
-        RevWalk rw = new RevWalk(reader)) {
-      bu.setRepository(repo.getRepository(), rw, oi);
-      bu.setNotify(NotifyResolver.Result.none());
-      bu.addOp(c.getId(), inserter);
-      bu.execute();
+      PatchSetInserter inserter =
+          patchSetFactory
+              .create(changeNotesFactory.createChecked(c), PatchSet.id(c.getId(), n), commit)
+              .setFireRevisionCreated(false)
+              .setValidate(false);
+      try (BatchUpdate bu = updateFactory.create(c.getProject(), user, TimeUtil.now());
+          ObjectInserter oi = repo.getRepository().newObjectInserter();
+          ObjectReader reader = oi.newReader();
+          RevWalk rw = new RevWalk(reader)) {
+        bu.setRepository(repo.getRepository(), rw, oi);
+        bu.setNotify(NotifyResolver.Result.none());
+        bu.addOp(c.getId(), inserter);
+        bu.execute();
+      }
+
+      return inserter.getChange();
     }
-
-    return inserter.getChange();
   }
 
   protected ThrowableSubject assertThatQueryException(Object query) throws Exception {
@@ -4192,17 +4250,27 @@
     }
   }
 
-  protected TestRepository<Repo> createProject(String name) throws Exception {
-    gApi.projects().create(name).get();
+  @CanIgnoreReturnValue
+  protected TestRepository<Repository> createAndOpenProject(String name) throws Exception {
+    createProject(name);
     return new TestRepository<>(repoManager.openRepository(Project.nameKey(name)));
   }
 
-  protected TestRepository<Repo> createProject(String name, String parent) throws Exception {
+  protected TestRepository<Repository> createAndOpenProject(String name, String parent)
+      throws Exception {
+    createProject(name, parent);
+    return new TestRepository<>(repoManager.openRepository(Project.nameKey(name)));
+  }
+
+  protected void createProject(String name) throws Exception {
+    gApi.projects().create(name).get();
+  }
+
+  protected void createProject(String name, String parent) throws Exception {
     ProjectInput input = new ProjectInput();
     input.name = name;
     input.parent = parent;
     gApi.projects().create(input).get();
-    return new TestRepository<>(repoManager.openRepository(Project.nameKey(name)));
   }
 
   protected QueryRequest newQuery(Object query) {
@@ -4413,4 +4481,12 @@
   protected Schema<ChangeData> getSchema() {
     return indexes.getSearchIndex().getSchema();
   }
+
+  protected ChangeUpdate newUpdate(Change c) throws Exception {
+    ChangeUpdate update =
+        TestChanges.newUpdate(injector, c, Optional.empty(), /* shouldExist= */ true);
+    update.setPatchSetId(c.currentPatchSetId());
+    update.setAllowWriteToNewRef(true);
+    return update;
+  }
 }
diff --git a/javatests/com/google/gerrit/server/query/change/FakeQueryChangesTest.java b/javatests/com/google/gerrit/server/query/change/FakeQueryChangesTest.java
index fe60119..6b17bb6 100644
--- a/javatests/com/google/gerrit/server/query/change/FakeQueryChangesTest.java
+++ b/javatests/com/google/gerrit/server/query/change/FakeQueryChangesTest.java
@@ -27,14 +27,13 @@
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.index.change.ChangeIndexCollection;
 import com.google.gerrit.testing.InMemoryModule;
-import com.google.gerrit.testing.InMemoryRepositoryManager;
-import com.google.gerrit.testing.InMemoryRepositoryManager.Repo;
 import com.google.inject.Guice;
 import com.google.inject.Inject;
 import com.google.inject.Injector;
 import java.util.List;
 import org.eclipse.jgit.junit.TestRepository;
 import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Repository;
 import org.junit.Test;
 
 /**
@@ -58,19 +57,21 @@
   @UseClockStep
   public void stopQueryIfNoMoreResults() throws Exception {
     // create 2 visible changes
-    TestRepository<InMemoryRepositoryManager.Repo> testRepo = createProject("repo");
-    insert(testRepo, newChange(testRepo));
-    insert(testRepo, newChange(testRepo));
+    try (TestRepository<Repository> testRepo = createAndOpenProject("repo")) {
+      insert("repo", newChange(testRepo));
+      insert("repo", newChange(testRepo));
+    }
 
     // create 2 invisible changes
-    TestRepository<Repo> hiddenProject = createProject("hiddenProject");
-    insert(hiddenProject, newChange(hiddenProject));
-    insert(hiddenProject, newChange(hiddenProject));
-    projectOperations
-        .project(Project.nameKey("hiddenProject"))
-        .forUpdate()
-        .add(block(Permission.READ).ref("refs/*").group(REGISTERED_USERS))
-        .update();
+    try (TestRepository<Repository> hiddenProject = createAndOpenProject("hiddenProject")) {
+      insert("hiddenProject", newChange(hiddenProject));
+      insert("hiddenProject", newChange(hiddenProject));
+      projectOperations
+          .project(Project.nameKey("hiddenProject"))
+          .forUpdate()
+          .add(block(Permission.READ).ref("refs/*").group(REGISTERED_USERS))
+          .update();
+    }
 
     AbstractFakeIndex<?, ?, ?> idx =
         (AbstractFakeIndex<?, ?, ?>) changeIndexCollection.getSearchIndex();
@@ -83,12 +84,13 @@
   @Test
   @UseClockStep
   public void noLimitQueryPaginates() throws Exception {
-    TestRepository<InMemoryRepositoryManager.Repo> testRepo = createProject("repo");
-    // create 4 changes
-    insert(testRepo, newChange(testRepo));
-    insert(testRepo, newChange(testRepo));
-    insert(testRepo, newChange(testRepo));
-    insert(testRepo, newChange(testRepo));
+    try (TestRepository<Repository> testRepo = createAndOpenProject("repo")) {
+      // create 4 changes
+      insert("repo", newChange(testRepo));
+      insert("repo", newChange(testRepo));
+      insert("repo", newChange(testRepo));
+      insert("repo", newChange(testRepo));
+    }
 
     // Set queryLimit to 2
     projectOperations
@@ -111,11 +113,12 @@
   @UseClockStep
   public void internalQueriesPaginate() throws Exception {
     // create 4 changes
-    TestRepository<InMemoryRepositoryManager.Repo> testRepo = createProject("repo");
-    insert(testRepo, newChange(testRepo));
-    insert(testRepo, newChange(testRepo));
-    insert(testRepo, newChange(testRepo));
-    insert(testRepo, newChange(testRepo));
+    try (TestRepository<Repository> testRepo = createAndOpenProject("repo")) {
+      insert("repo", newChange(testRepo));
+      insert("repo", newChange(testRepo));
+      insert("repo", newChange(testRepo));
+      insert("repo", newChange(testRepo));
+    }
 
     // Set queryLimit to 2
     projectOperations
diff --git a/javatests/com/google/gerrit/server/query/change/LuceneQueryChangesTest.java b/javatests/com/google/gerrit/server/query/change/LuceneQueryChangesTest.java
index 9717bfb..5cae012 100644
--- a/javatests/com/google/gerrit/server/query/change/LuceneQueryChangesTest.java
+++ b/javatests/com/google/gerrit/server/query/change/LuceneQueryChangesTest.java
@@ -24,11 +24,9 @@
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.testing.InMemoryModule;
-import com.google.gerrit.testing.InMemoryRepositoryManager.Repo;
 import com.google.inject.Guice;
 import com.google.inject.Inject;
 import com.google.inject.Injector;
-import org.eclipse.jgit.junit.TestRepository;
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.junit.Test;
@@ -45,11 +43,11 @@
 
   @Test
   public void fullTextWithSpecialChars() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
     RevCommit commit1 = repo.parseBody(repo.commit().message("foo_bar_foo").create());
-    Change change1 = insert(repo, newChangeForCommit(repo, commit1));
+    Change change1 = insert("repo", newChangeForCommit(repo, commit1));
     RevCommit commit2 = repo.parseBody(repo.commit().message("one.two.three").create());
-    Change change2 = insert(repo, newChangeForCommit(repo, commit2));
+    Change change2 = insert("repo", newChangeForCommit(repo, commit2));
 
     assertQuery("message:foo_ba");
     assertQuery("message:bar", change1);
@@ -63,8 +61,8 @@
   @Test
   @Override
   public void byOwnerInvalidQuery() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
-    Change change1 = insert(repo, newChange(repo), userId);
+    repo = createAndOpenProject("repo");
+    Change change1 = insert("repo", newChange(repo), userId);
     String nameEmail = user.asIdentifiedUser().getNameEmail();
 
     BadRequestException thrown =
@@ -76,17 +74,17 @@
 
   @Test
   public void openAndClosedChanges() throws Exception {
-    TestRepository<Repo> repo = createProject("repo");
+    repo = createAndOpenProject("repo");
 
     // create 3 closed changes
-    Change change1 = insert(repo, newChangeWithStatus(repo, Change.Status.MERGED));
-    Change change2 = insert(repo, newChangeWithStatus(repo, Change.Status.MERGED));
-    Change change3 = insert(repo, newChangeWithStatus(repo, Change.Status.MERGED));
+    Change change1 = insert("repo", newChangeWithStatus(repo, Change.Status.MERGED));
+    Change change2 = insert("repo", newChangeWithStatus(repo, Change.Status.MERGED));
+    Change change3 = insert("repo", newChangeWithStatus(repo, Change.Status.MERGED));
 
     // create 3 new changes
-    Change change4 = insert(repo, newChangeWithStatus(repo, Change.Status.NEW));
-    Change change5 = insert(repo, newChangeWithStatus(repo, Change.Status.NEW));
-    Change change6 = insert(repo, newChangeWithStatus(repo, Change.Status.NEW));
+    Change change4 = insert("repo", newChangeWithStatus(repo, Change.Status.NEW));
+    Change change5 = insert("repo", newChangeWithStatus(repo, Change.Status.NEW));
+    Change change6 = insert("repo", newChangeWithStatus(repo, Change.Status.NEW));
 
     // Set queryLimit to 1
     projectOperations
diff --git a/modules/jgit b/modules/jgit
index e74f385..801a56b 160000
--- a/modules/jgit
+++ b/modules/jgit
@@ -1 +1 @@
-Subproject commit e74f3855ad9d54c986d60b0b2ea4c223d52b2cd1
+Subproject commit 801a56b48a7fe3c6e171073211cc62194184fe79
diff --git a/plugins/codemirror-editor b/plugins/codemirror-editor
index 867c6e6..c964b31 160000
--- a/plugins/codemirror-editor
+++ b/plugins/codemirror-editor
@@ -1 +1 @@
-Subproject commit 867c6e64789611a0dabc9f07f8d924b76a9102bc
+Subproject commit c964b31675de90cfafe43fff9ec357f4b97d1e08
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.ts b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.ts
index 27fe620..d3562f2 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.ts
+++ b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.ts
@@ -16,7 +16,11 @@
 import {LitElement, PropertyValues, css, html} from 'lit';
 import {customElement, query, property, state} from 'lit/decorators.js';
 import {assertIsDefined} from '../../../utils/common-util';
-import {AdminViewState} from '../../../models/views/admin';
+import {
+  AdminChildView,
+  AdminViewState,
+  createAdminUrl,
+} from '../../../models/views/admin';
 import {createGroupUrl} from '../../../models/views/group';
 import {whenVisible} from '../../../utils/dom-util';
 import {modalStyles} from '../../../styles/gr-modal-styles';
@@ -29,8 +33,6 @@
 
 @customElement('gr-admin-group-list')
 export class GrAdminGroupList extends LitElement {
-  readonly path = '/admin/groups';
-
   @query('#createModal') private createModal?: HTMLDialogElement;
 
   @query('#createNewModal') private createNewModal?: GrCreateGroupDialog;
@@ -87,7 +89,7 @@
         .itemsPerPage=${this.groupsPerPage}
         .loading=${this.loading}
         .offset=${this.offset}
-        .path=${this.path}
+        .path=${createAdminUrl({adminView: AdminChildView.GROUPS})}
         @create-clicked=${() => this.handleCreateClicked()}
       >
         <table id="list" class="genericList">
@@ -167,18 +169,14 @@
    *
    * private but used in test
    */
-  maybeOpenCreateModal(params?: AdminViewState) {
+  async maybeOpenCreateModal(params?: AdminViewState) {
     if (params?.openCreateModal) {
-      assertIsDefined(this.createModal, 'createModal');
-      this.createModal.showModal();
+      await this.updateComplete;
+      if (!this.createModal?.open) this.createModal?.showModal();
     }
   }
 
-  /**
-   * Generates groups link (/admin/groups/<uuid>)
-   *
-   * private but used in test
-   */
+  // private but used in test
   computeGroupUrl(encodedId: string) {
     const groupId = decodeURIComponent(encodedId) as GroupId;
     return createGroupUrl({groupId});
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list_test.ts b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list_test.ts
index 1b128aa..e9b7ea0 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list_test.ts
+++ b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list_test.ts
@@ -120,17 +120,17 @@
       assert.equal(element.groups.slice(0, SHOWN_ITEMS_COUNT).length, 25);
     });
 
-    test('maybeOpenCreateModal', () => {
+    test('maybeOpenCreateModal', async () => {
       const modalOpen = sinon.stub(
         queryAndAssert<HTMLDialogElement>(element, '#createModal'),
         'showModal'
       );
-      element.maybeOpenCreateModal();
+      await element.maybeOpenCreateModal();
       assert.isFalse(modalOpen.called);
-      element.maybeOpenCreateModal(undefined);
+      await element.maybeOpenCreateModal(undefined);
       assert.isFalse(modalOpen.called);
       value.openCreateModal = true;
-      element.maybeOpenCreateModal(value);
+      await element.maybeOpenCreateModal(value);
       assert.isTrue(modalOpen.called);
     });
   });
diff --git a/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog.ts b/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog.ts
index 4808d00..8d5689c 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog.ts
+++ b/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog.ts
@@ -6,9 +6,8 @@
 import '@polymer/iron-input/iron-input';
 import '../../../styles/gr-form-styles';
 import '../../../styles/shared-styles';
-import {encodeURL, getBaseUrl} from '../../../utils/url-util';
 import {page} from '../../../utils/page-wrapper-utils';
-import {GroupName} from '../../../types/common';
+import {GroupId, GroupName} from '../../../types/common';
 import {getAppContext} from '../../../services/app-context';
 import {formStyles} from '../../../styles/gr-form-styles';
 import {sharedStyles} from '../../../styles/shared-styles';
@@ -16,6 +15,7 @@
 import {customElement, query, property} from 'lit/decorators.js';
 import {BindValueChangeEvent} from '../../../types/events';
 import {fireEvent} from '../../../utils/event-util';
+import {createGroupUrl} from '../../../models/views/group';
 
 declare global {
   interface HTMLElementTagNameMap {
@@ -75,10 +75,6 @@
     fireEvent(this, 'has-new-group-name');
   }
 
-  private computeGroupUrl(groupId: string) {
-    return getBaseUrl() + '/admin/groups/' + encodeURL(groupId, true);
-  }
-
   override focus() {
     this.input.focus();
   }
@@ -89,7 +85,9 @@
       if (groupRegistered.status !== 201) return;
       return this.restApiService.getGroupConfig(name).then(group => {
         if (!group) return;
-        page.show(this.computeGroupUrl(String(group.group_id!)));
+        const groupId = String(group.group_id!) as GroupId;
+        // TODO: Use navigation service instead of `page.show()` directly.
+        page.show(createGroupUrl({groupId}));
       });
     });
   }
diff --git a/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.ts b/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.ts
index a90abbd..bb59ccc 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.ts
+++ b/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.ts
@@ -7,7 +7,6 @@
 import '../../shared/gr-autocomplete/gr-autocomplete';
 import '../../shared/gr-button/gr-button';
 import '../../shared/gr-select/gr-select';
-import {encodeURL, getBaseUrl} from '../../../utils/url-util';
 import {page} from '../../../utils/page-wrapper-utils';
 import {
   BranchName,
@@ -24,6 +23,7 @@
 import {customElement, query, property, state} from 'lit/decorators.js';
 import {fireEvent} from '../../../utils/event-util';
 import {throwingErrorCallback} from '../../shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper';
+import {createRepoUrl} from '../../../models/views/repo';
 
 declare global {
   interface HTMLElementTagNameMap {
@@ -183,10 +183,6 @@
     `;
   }
 
-  _computeRepoUrl(repoName: string) {
-    return getBaseUrl() + '/admin/repos/' + encodeURL(repoName, true);
-  }
-
   override focus() {
     this.input?.focus();
   }
@@ -199,7 +195,8 @@
     );
     if (repoRegistered.status === 201) {
       this.repoCreated = true;
-      page.show(this._computeRepoUrl(this.repoConfig.name));
+      // TODO: Use navigation service instead of `page.show()` directly.
+      page.show(createRepoUrl({repo: this.repoConfig.name}));
     }
     return repoRegistered;
   }
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.ts b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.ts
index 389e7c4..52e0b3f 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.ts
@@ -4,7 +4,7 @@
  * SPDX-License-Identifier: Apache-2.0
  */
 import '../gr-access-section/gr-access-section';
-import {encodeURL, getBaseUrl, singleDecodeURL} from '../../../utils/url-util';
+import {singleDecodeURL} from '../../../utils/url-util';
 import {navigationToken} from '../../core/gr-navigation/gr-navigation';
 import {toSortedPermissionsArray} from '../../../utils/access-util';
 import {
@@ -44,6 +44,7 @@
 import {resolve} from '../../../models/dependency';
 import {createChangeUrl} from '../../../models/views/change';
 import {throwingErrorCallback} from '../../shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper';
+import {createRepoUrl, RepoDetailView} from '../../../models/views/repo';
 
 const NOTHING_TO_SAVE = 'No changes to save.';
 
@@ -726,10 +727,10 @@
 
   computeParentHref() {
     if (!this.inheritsFrom?.name) return '';
-    return `${getBaseUrl()}/admin/repos/${encodeURL(
-      this.inheritsFrom.name,
-      true
-    )},access`;
+    return createRepoUrl({
+      repo: this.inheritsFrom.name,
+      detail: RepoDetailView.ACCESS,
+    });
   }
 
   private handleEditInheritFromTextChanged(e: ValueChangedEvent) {
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.ts b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.ts
index 3b193a6..632ec4c 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.ts
@@ -11,7 +11,6 @@
 import '../../shared/gr-list-view/gr-list-view';
 import '../gr-create-pointer-dialog/gr-create-pointer-dialog';
 import '../gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog';
-import {encodeURL} from '../../../utils/url-util';
 import {GrCreatePointerDialog} from '../gr-create-pointer-dialog/gr-create-pointer-dialog';
 import {
   BranchInfo,
@@ -29,12 +28,16 @@
 import {formStyles} from '../../../styles/gr-form-styles';
 import {tableStyles} from '../../../styles/gr-table-styles';
 import {sharedStyles} from '../../../styles/shared-styles';
-import {LitElement, PropertyValues, css, html} from 'lit';
+import {LitElement, PropertyValues, css, html, nothing} from 'lit';
 import {customElement, query, property, state} from 'lit/decorators.js';
 import {BindValueChangeEvent} from '../../../types/events';
 import {assertIsDefined} from '../../../utils/common-util';
 import {ifDefined} from 'lit/directives/if-defined.js';
-import {RepoDetailView, RepoViewState} from '../../../models/views/repo';
+import {
+  createRepoUrl,
+  RepoDetailView,
+  RepoViewState,
+} from '../../../models/views/repo';
 import {modalStyles} from '../../../styles/gr-modal-styles';
 
 const PGP_START = '-----BEGIN PGP SIGNATURE-----';
@@ -139,6 +142,7 @@
   }
 
   override render() {
+    if (!this.repo || !this.detailType) return nothing;
     return html`
       <gr-list-view
         .createNew=${this.loggedIn}
@@ -147,7 +151,7 @@
         .items=${this.items}
         .loading=${this.loading}
         .offset=${this.offset}
-        .path=${this.getPath(this.repo, this.detailType)}
+        .path=${createRepoUrl({repo: this.repo, detail: this.detailType})}
         @create-clicked=${() => {
           this.handleCreateClicked();
         }}
@@ -441,10 +445,6 @@
     return Promise.reject(new Error('unknown detail type'));
   }
 
-  private getPath(repo?: RepoName, detailType?: RepoDetailView) {
-    return `/admin/repos/${encodeURL(repo ?? '', false)},${detailType}`;
-  }
-
   private computeWeblink(repo: ProjectInfo | BranchInfo | TagInfo) {
     if (!repo.web_links) return [];
     const webLinks = repo.web_links;
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.ts b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.ts
index 28fc751..ec1bb82 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.ts
@@ -2434,6 +2434,18 @@
     });
 
     suite('create new', () => {
+      setup(async () => {
+        stubRestApi('getRepoBranches').resolves(createBranchesList(3));
+
+        element.params = {
+          view: GerritView.REPO,
+          repo: 'test' as RepoName,
+          detail: RepoDetailView.BRANCHES,
+        };
+        await element.paramsChanged();
+        await element.updateComplete;
+      });
+
       test('handleCreateClicked called when create-click fired', () => {
         const handleCreateClickedStub = sinon.stub(
           element,
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.ts b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.ts
index c6c6efd..2fd7e79 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.ts
+++ b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.ts
@@ -6,23 +6,23 @@
 import '../../shared/gr-dialog/gr-dialog';
 import '../../shared/gr-list-view/gr-list-view';
 import '../gr-create-repo-dialog/gr-create-repo-dialog';
-import {
-  RepoName,
-  ProjectInfoWithName,
-  WebLinkInfo,
-} from '../../../types/common';
+import {ProjectInfoWithName, WebLinkInfo} from '../../../types/common';
 import {GrCreateRepoDialog} from '../gr-create-repo-dialog/gr-create-repo-dialog';
 import {RepoState, SHOWN_ITEMS_COUNT} from '../../../constants/constants';
 import {fireTitleChange} from '../../../utils/event-util';
 import {getAppContext} from '../../../services/app-context';
-import {encodeURL, getBaseUrl} from '../../../utils/url-util';
 import {tableStyles} from '../../../styles/gr-table-styles';
 import {sharedStyles} from '../../../styles/shared-styles';
 import {LitElement, PropertyValues, css, html} from 'lit';
 import {customElement, property, query, state} from 'lit/decorators.js';
-import {AdminViewState} from '../../../models/views/admin';
+import {
+  AdminChildView,
+  AdminViewState,
+  createAdminUrl,
+} from '../../../models/views/admin';
 import {createSearchUrl} from '../../../models/views/search';
 import {modalStyles} from '../../../styles/gr-modal-styles';
+import {createRepoUrl} from '../../../models/views/repo';
 
 declare global {
   interface HTMLElementTagNameMap {
@@ -32,8 +32,6 @@
 
 @customElement('gr-repo-list')
 export class GrRepoList extends LitElement {
-  readonly path = '/admin/repos';
-
   @query('#createModal') private createModal?: HTMLDialogElement;
 
   @query('#createNewModal') private createNewModal?: GrCreateRepoDialog;
@@ -103,7 +101,7 @@
         .items=${this.repos}
         .loading=${this.loading}
         .offset=${this.offset}
-        .path=${this.path}
+        .path=${createAdminUrl({adminView: AdminChildView.REPOS})}
         @create-clicked=${() => this.handleCreateClicked()}
       >
         <table id="list" class="genericList">
@@ -157,11 +155,11 @@
     return html`
       <tr class="table">
         <td class="name">
-          <a href=${this.computeRepoUrl(item.name)}>${item.name}</a>
+          <a href=${createRepoUrl({repo: item.name})}>${item.name}</a>
         </td>
         <td class="repositoryBrowser">${this.renderWebLinks(item)}</td>
         <td class="changesLink">
-          <a href=${this.computeChangesLink(item.name)}>view all</a>
+          <a href=${createSearchUrl({repo: item.name})}>view all</a>
         </td>
         <td class="readOnly">
           ${item.state === RepoState.READ_ONLY ? 'Y' : ''}
@@ -210,14 +208,6 @@
     }
   }
 
-  private computeRepoUrl(name: string) {
-    return `${getBaseUrl()}${this.path}/${encodeURL(name, true)}`;
-  }
-
-  private computeChangesLink(name: string) {
-    return createSearchUrl({repo: name as RepoName});
-  }
-
   private async getCreateRepoCapability() {
     const account = await this.restApiService.getAccount();
 
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts
index 7abfce9..faaee0b 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.ts
@@ -282,12 +282,14 @@
   // private but used in test
   handleNextPage() {
     if (!this.nextArrow || !this.changesPerPage) return;
+    // TODO: Use navigation service instead of `page.show()` directly.
     page.show(this.computeNavLink(1));
   }
 
   // private but used in test
   handlePreviousPage() {
     if (!this.prevArrow || !this.changesPerPage) return;
+    // TODO: Use navigation service instead of `page.show()` directly.
     page.show(this.computeNavLink(-1));
   }
 
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 1c15a4f..c958c7e 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
@@ -144,7 +144,7 @@
   throttleWrap,
   until,
 } from '../../../utils/async-util';
-import {Interaction, Timing, Execution} from '../../../constants/reporting';
+import {Interaction, Timing} from '../../../constants/reporting';
 import {ChangeStates} from '../../shared/gr-change-status/gr-change-status';
 import {getRevertCreatedChangeIds} from '../../../utils/message-util';
 import {
@@ -2106,7 +2106,7 @@
 
     this.patchRange = {
       patchNum: this.viewState.patchNum,
-      basePatchNum: this.getBasePatchNum(),
+      basePatchNum: this.viewState.basePatchNum,
     };
     this.scrollCommentId = this.viewState.commentId;
 
@@ -2343,13 +2343,10 @@
       this.prefs &&
       this.prefs.default_base_for_merges === DefaultBase.FIRST_PARENT;
 
-    // TODO: I think checking `!patchRange.patchNum` here is a bug and means
-    // that the feature is actually broken at the moment. Looking at the
-    // `changeChanged` method, `patchRange.patchNum` is set before
-    // `getBasePatchNum` is called, so it is unlikely that this method will
-    // ever return -1.
+    // Verified via reportExecution that -1 is returned(1-5 times per day)
+    // changeChanged does set this.patchRange?.patchNum so it's still unclear
+    // how it is undefined.
     if (isMerge && preferFirst && !this.patchRange?.patchNum) {
-      this.reporting.reportExecution(Execution.PREFER_MERGE_FIRST_PARENT);
       return -1 as BasePatchSetNum;
     }
     return PARENT;
diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.ts b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.ts
index 41cf952..b1ff749 100644
--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.ts
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.ts
@@ -204,15 +204,18 @@
           text-decoration: underline;
         }
         .titleText::before {
+          --icon-width: var(--header-icon-width, var(--header-icon-size, 0));
+          --icon-height: var(--header-icon-height, var(--header-icon-size, 0));
           background-image: var(--header-icon);
-          background-size: var(--header-icon-size) var(--header-icon-size);
+          background-size: var(--icon-width) var(--icon-height);
           background-repeat: no-repeat;
           content: '';
           display: inline-block;
-          height: var(--header-icon-size);
-          margin-right: calc(var(--header-icon-size) / 4);
+          height: var(--icon-height);
+          /* If size or height are set, then use 'spacing-m', 0px otherwise. */
+          margin-right: clamp(0px, var(--icon-height), var(--spacing-m));
           vertical-align: text-bottom;
-          width: var(--header-icon-size);
+          width: var(--icon-width);
         }
         .titleText::after {
           content: var(--header-title-content);
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router.ts b/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
index 6caa000..1b914bf 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.ts
@@ -56,6 +56,7 @@
   RepoViewState,
 } from '../../../models/views/repo';
 import {
+  createGroupUrl,
   GroupDetailView,
   GroupViewModel,
   GroupViewState,
@@ -1067,7 +1068,8 @@
   }
 
   handleGroupInfoRoute(ctx: PageContext) {
-    this.redirect('/admin/groups/' + encodeURIComponent(ctx.params[0]));
+    const groupId = ctx.params[0] as GroupId;
+    this.redirect(createGroupUrl({groupId}));
   }
 
   handleGroupSelfRedirectRoute(_: PageContext) {
@@ -1151,6 +1153,8 @@
       }
     }
 
+    // TODO: Change the route pattern to match `repo` and `detailView`
+    // separately, and then use `createRepoUrl()` here.
     this.redirect(`/admin/repos/${params}`);
   }
 
diff --git a/polygerrit-ui/app/elements/gr-app-element.ts b/polygerrit-ui/app/elements/gr-app-element.ts
index 4860c87..e959e05 100644
--- a/polygerrit-ui/app/elements/gr-app-element.ts
+++ b/polygerrit-ui/app/elements/gr-app-element.ts
@@ -73,6 +73,7 @@
 import {createDashboardUrl} from '../models/views/dashboard';
 import {userModelToken} from '../models/user/user-model';
 import {modalStyles} from '../styles/gr-modal-styles';
+import {AdminChildView, createAdminUrl} from '../models/views/admin';
 
 interface ErrorInfo {
   text: string;
@@ -209,6 +210,16 @@
         createSearchUrl({query: 'is:watched is:open'})
       )
     );
+    this.shortcuts.addAbstract(Shortcut.GO_TO_REPOS, () =>
+      this.getNavigation().setUrl(
+        createAdminUrl({adminView: AdminChildView.REPOS})
+      )
+    );
+    this.shortcuts.addAbstract(Shortcut.GO_TO_GROUPS, () =>
+      this.getNavigation().setUrl(
+        createAdminUrl({adminView: AdminChildView.GROUPS})
+      )
+    );
 
     subscribe(
       this,
diff --git a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.ts b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.ts
index 1549da5..0e75abb 100644
--- a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.ts
+++ b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.ts
@@ -95,12 +95,26 @@
       .lengthCounter {
         font-weight: var(--font-weight-normal);
       }
+      p {
+        max-width: 65ch;
+        margin-bottom: var(--spacing-m);
+      }
     `,
   ];
 
   override render() {
     if (!this.account || this.loading) return nothing;
     return html`<div class="gr-form-styles">
+      <p>
+        All profile fields below may be publicly displayed to others, including
+        on changes you are associated with, as well as in search and
+        autocompletion.
+        <a
+          href="https://gerrit-review.googlesource.com/Documentation/user-privacy.html"
+          >Learn more</a
+        >
+      </p>
+      <gr-endpoint-decorator name="profile"></gr-endpoint-decorator>
       <section>
         <span class="title"></span>
         <span class="value">
diff --git a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.ts b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.ts
index e968b12..f954960 100644
--- a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.ts
+++ b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.ts
@@ -69,6 +69,16 @@
       element,
       /* HTML */ `
         <div class="gr-form-styles">
+          <p>
+            All profile fields below may be publicly displayed to others,
+            including on changes you are associated with, as well as in search
+            and autocompletion.
+            <a
+              href="https://gerrit-review.googlesource.com/Documentation/user-privacy.html"
+              >Learn more</a
+            >
+          </p>
+          <gr-endpoint-decorator name="profile"></gr-endpoint-decorator>
           <section>
             <span class="title"></span>
             <span class="value">
@@ -134,7 +144,8 @@
             </span>
           </section>
         </div>
-      `
+      `,
+      {ignoreChildren: ['p']}
     );
   });
 
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
index 2d6c52f..090dfef 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.ts
@@ -130,6 +130,9 @@
   @query('#confirmDeleteModal')
   confirmDeleteModal?: HTMLDialogElement;
 
+  @query('#confirmDeleteCommentDialog')
+  confirmDeleteDialog?: GrConfirmDeleteCommentDialog;
+
   @property({type: Object})
   comment?: Comment;
 
@@ -200,9 +203,6 @@
   unresolved = true;
 
   @property({type: Boolean})
-  showConfirmDeleteModal = false;
-
-  @property({type: Boolean})
   unableToSave = false;
 
   @property({type: Boolean, attribute: 'show-patchset'})
@@ -940,11 +940,10 @@
   }
 
   private renderConfirmDialog() {
-    if (!this.showConfirmDeleteModal) return;
     return html`
       <dialog id="confirmDeleteModal" tabindex="-1">
         <gr-confirm-delete-comment-dialog
-          id="confirmDeleteComment"
+          id="confirmDeleteCommentDialog"
           @confirm=${this.handleConfirmDeleteComment}
           @cancel=${this.closeDeleteCommentModal}
         >
@@ -1264,51 +1263,35 @@
     }
   }
 
-  private async openDeleteCommentModal() {
-    this.showConfirmDeleteModal = true;
-    await this.updateComplete;
-    await this.confirmDeleteModal?.showModal();
+  private openDeleteCommentModal() {
+    this.confirmDeleteModal?.showModal();
+    whenVisible(this.confirmDeleteDialog!, () => {
+      this.confirmDeleteDialog!.resetFocus();
+    });
   }
 
   private closeDeleteCommentModal() {
-    this.showConfirmDeleteModal = false;
-    this.confirmDeleteModal?.remove();
     this.confirmDeleteModal?.close();
   }
 
   /**
    * Deleting a *published* comment is an admin feature. It means more than just
    * discarding a draft.
-   *
-   * TODO: Also move this into the comments-service.
-   * TODO: Figure out a good reloading strategy when deleting was successful.
-   *       `this.comment = newComment` does not seem sufficient.
    */
   // private, but visible for testing
-  handleConfirmDeleteComment() {
-    const dialog = this.confirmDeleteModal?.querySelector(
-      '#confirmDeleteComment'
-    ) as GrConfirmDeleteCommentDialog | null;
-    if (!dialog || !dialog.message) {
+  async handleConfirmDeleteComment() {
+    if (!this.confirmDeleteDialog || !this.confirmDeleteDialog.message) {
       throw new Error('missing confirm delete dialog');
     }
     assertIsDefined(this.changeNum, 'changeNum');
     assertIsDefined(this.comment, 'comment');
-    assertIsDefined(this.comment.patch_set, 'comment.patch_set');
-    if (isDraftOrUnsaved(this.comment)) {
-      throw new Error('Admin deletion is only for published comments.');
-    }
-    this.restApiService
-      .deleteComment(
-        this.changeNum,
-        this.comment.patch_set,
-        this.comment.id,
-        dialog.message
-      )
-      .then(newComment => {
-        this.closeDeleteCommentModal();
-        this.comment = newComment;
-      });
+
+    await this.getCommentsModel().deleteComment(
+      this.changeNum,
+      this.comment,
+      this.confirmDeleteDialog.message
+    );
+    this.closeDeleteCommentModal();
   }
 }
 
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts
index 5b191c8..ec9c875 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.ts
@@ -127,6 +127,10 @@
               </div>
             </div>
           </gr-endpoint-decorator>
+          <dialog id="confirmDeleteModal" tabindex="-1">
+            <gr-confirm-delete-comment-dialog id="confirmDeleteCommentDialog">
+            </gr-confirm-delete-comment-dialog>
+          </dialog>
         `
       );
     });
@@ -166,6 +170,10 @@
               </div>
             </div>
           </gr-endpoint-decorator>
+          <dialog id="confirmDeleteModal" tabindex="-1">
+            <gr-confirm-delete-comment-dialog id="confirmDeleteCommentDialog">
+            </gr-confirm-delete-comment-dialog>
+          </dialog>
         `
       );
     });
@@ -238,6 +246,10 @@
               </div>
             </div>
           </gr-endpoint-decorator>
+          <dialog id="confirmDeleteModal" tabindex="-1">
+            <gr-confirm-delete-comment-dialog id="confirmDeleteCommentDialog">
+            </gr-confirm-delete-comment-dialog>
+          </dialog>
         `
       );
     });
@@ -336,6 +348,10 @@
               </div>
             </div>
           </gr-endpoint-decorator>
+          <dialog id="confirmDeleteModal" tabindex="-1">
+            <gr-confirm-delete-comment-dialog id="confirmDeleteCommentDialog">
+            </gr-confirm-delete-comment-dialog>
+          </dialog>
         `
       );
     });
@@ -421,6 +437,10 @@
               </div>
             </div>
           </gr-endpoint-decorator>
+          <dialog id="confirmDeleteModal" tabindex="-1">
+            <gr-confirm-delete-comment-dialog id="confirmDeleteCommentDialog">
+            </gr-confirm-delete-comment-dialog>
+          </dialog>
         `
       );
     });
@@ -503,7 +523,7 @@
     assertIsDefined(element.confirmDeleteModal, 'confirmDeleteModal');
     const dialog = queryAndAssert<GrConfirmDeleteCommentDialog>(
       element.confirmDeleteModal,
-      '#confirmDeleteComment'
+      '#confirmDeleteCommentDialog'
     );
     dialog.message = 'removal reason';
     await element.updateComplete;
diff --git a/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog.ts b/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog.ts
index b6512b3..285a41a 100644
--- a/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog.ts
+++ b/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog.ts
@@ -75,6 +75,7 @@
   override render() {
     return html` <gr-dialog
       confirm-label="Delete"
+      ?disabled=${this.message === ''}
       @confirm=${this.handleConfirmTap}
       @cancel=${this.handleCancelTap}
     >
diff --git a/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog_test.ts b/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog_test.ts
index bd84ac4..b7551c1 100644
--- a/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-confirm-delete-comment-dialog/gr-confirm-delete-comment-dialog_test.ts
@@ -7,6 +7,7 @@
 import {fixture, html, assert} from '@open-wc/testing';
 import {GrConfirmDeleteCommentDialog} from './gr-confirm-delete-comment-dialog';
 import './gr-confirm-delete-comment-dialog';
+import {GrDialog} from '../gr-dialog/gr-dialog';
 
 suite('gr-confirm-delete-comment-dialog tests', () => {
   let element: GrConfirmDeleteCommentDialog;
@@ -17,7 +18,10 @@
     );
   });
 
-  test('render', () => {
+  test('render', async () => {
+    element.message = 'Just cause';
+    await element.updateComplete;
+
     // prettier and shadowDom string disagree about wrapping in <p> tag.
     assert.shadowDom.equal(
       element,
@@ -43,4 +47,13 @@
     `
     );
   });
+
+  test('dialog is disabled when message is empty', async () => {
+    element.message = '';
+    await element.updateComplete;
+
+    assert.isTrue(
+      (element.shadowRoot!.querySelector('gr-dialog') as GrDialog).disabled
+    );
+  });
 });
diff --git a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts
index c8663e6..627ea27 100644
--- a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts
+++ b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.ts
@@ -198,7 +198,16 @@
     text = htmlEscape(text).toString();
     // Unescape block quotes '>'. This is slightly dangerous as '>' can be used
     // in HTML fragments, but it is insufficient on it's own.
-    text = text.replace(/(^|\n)&gt;/g, '$1>');
+    for (;;) {
+      const newText = text.replace(
+        /(^|\n)((?:\s{0,3}&gt;)*\s{0,3})&gt;/g,
+        '$1$2>'
+      );
+      if (newText === text) {
+        break;
+      }
+      text = newText;
+    }
 
     return text;
   }
diff --git a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.ts b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.ts
index 9acca0f..3881c62 100644
--- a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.ts
@@ -593,5 +593,27 @@
         `
       );
     });
+
+    test('renders nested block quotes', async () => {
+      element.content = '> > > block quote';
+      await element.updateComplete;
+
+      assert.shadowDom.equal(
+        element,
+        /* HTML */ `
+          <marked-element>
+            <div slot="markdown-html" class="markdown-html">
+              <blockquote>
+                <blockquote>
+                  <blockquote>
+                    <p>block quote</p>
+                  </blockquote>
+                </blockquote>
+              </blockquote>
+            </div>
+          </marked-element>
+        `
+      );
+    });
   });
 });
diff --git a/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.ts b/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.ts
index fd43869..449c041 100644
--- a/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.ts
+++ b/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view.ts
@@ -177,9 +177,11 @@
       () => {
         if (!this.isConnected || !this.path) return;
         if (filter) {
+          // TODO: Use navigation service instead of `page.show()` directly.
           page.show(`${this.path}/q/filter:${encodeURL(filter, false)}`);
           return;
         }
+        // TODO: Use navigation service instead of `page.show()` directly.
         page.show(this.path);
       },
       REQUEST_DEBOUNCE_INTERVAL_MS
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-section.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-section.ts
index 1c09372..28e83ae 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-section.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-section.ts
@@ -16,7 +16,11 @@
   DiffPreferencesInfo,
 } from '../../../api/diff';
 import {GrDiffGroup, GrDiffGroupType} from '../gr-diff/gr-diff-group';
-import {countLines, diffClasses} from '../gr-diff/gr-diff-utils';
+import {
+  countLines,
+  diffClasses,
+  getResponsiveMode,
+} from '../gr-diff/gr-diff-utils';
 import {GrDiffRow} from './gr-diff-row';
 import '../gr-context-controls/gr-context-controls-section';
 import '../gr-context-controls/gr-context-controls';
@@ -71,6 +75,7 @@
     if (this.group.ignoredWhitespaceOnly) extras.push('ignoredWhitespaceOnly');
 
     const pairs = this.getLinePairs();
+    const responsiveMode = getResponsiveMode(this.diffPrefs, this.renderPrefs);
     const body = html`
       <tbody class=${diffClasses(...extras)}>
         ${this.renderContextControls()} ${this.renderMoveControls()}
@@ -86,6 +91,7 @@
               .lineLength=${this.diffPrefs?.line_length ?? 80}
               .tabSize=${this.diffPrefs?.tab_size ?? 2}
               .unifiedDiff=${this.isUnifiedDiff()}
+              .responsiveMode=${responsiveMode}
             >
             </gr-diff-row>
           `;
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-text.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-text.ts
index f7cc7e5..c1b13ac 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-text.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-text.ts
@@ -115,7 +115,7 @@
     }
     const piece = html`<span
       class=${diffClasses('tab')}
-      style=${styleMap({'tab-size': `${this.tabSize}`})}
+      style=${styleMap({'tab-size': `${tabSize}`})}
       >${TAB}</span
     >`;
     this.pieces.push(piece);
diff --git a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-text_test.ts b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-text_test.ts
index 8f0503a..a0e7840 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-text_test.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff-builder/gr-diff-text_test.ts
@@ -75,18 +75,25 @@
 
     test('tab wrapper style', async () => {
       element.lineLimit = 100;
-      element.text = '\t';
-      await element.updateComplete;
-      for (const size of [1, 3, 8, 55]) {
-        element.tabSize = size;
-        await element.updateComplete;
-        await check(
-          '\t',
-          /* HTML */ `
-            <span class="gr-diff tab" style="tab-size: ${size};"> </span>
-          `
-        );
-      }
+      element.tabSize = 4;
+      await check(
+        '\t',
+        /* HTML */ '<span class="gr-diff tab" style="tab-size:4;"></span>'
+      );
+      await check(
+        'abc\t',
+        /* HTML */ 'abc<span class="gr-diff tab" style="tab-size:1;"></span>'
+      );
+
+      element.tabSize = 8;
+      await check(
+        '\t',
+        /* HTML */ '<span class="gr-diff tab" style="tab-size:8;"></span>'
+      );
+      await check(
+        'abc\t',
+        /* HTML */ 'abc<span class="gr-diff tab" style="tab-size:5;"></span>'
+      );
     });
 
     test('tab wrapper insertion', async () => {
diff --git a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils.ts b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils.ts
index f583c2e..3392951 100644
--- a/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils.ts
+++ b/polygerrit-ui/app/embed/diff/gr-diff/gr-diff-utils.ts
@@ -49,14 +49,14 @@
 }
 
 export function getResponsiveMode(
-  prefs: DiffPreferencesInfo,
+  prefs?: DiffPreferencesInfo,
   renderPrefs?: RenderPreferences
 ): DiffResponsiveMode {
   if (renderPrefs?.responsive_mode) {
     return renderPrefs.responsive_mode;
   }
   // Backwards compatibility to the line_wrapping param.
-  if (prefs.line_wrapping) {
+  if (prefs?.line_wrapping) {
     return 'FULL_RESPONSIVE';
   }
   return 'NONE';
diff --git a/polygerrit-ui/app/models/comments/comments-model.ts b/polygerrit-ui/app/models/comments/comments-model.ts
index a7b43ca..51b4591 100644
--- a/polygerrit-ui/app/models/comments/comments-model.ts
+++ b/polygerrit-ui/app/models/comments/comments-model.ts
@@ -18,8 +18,10 @@
 } from '../../types/common';
 import {
   addPath,
+  Comment,
   DraftInfo,
   isDraft,
+  isDraftOrUnsaved,
   isDraftThread,
   isUnsaved,
   reportingDetails,
@@ -111,6 +113,35 @@
   return nextState;
 }
 
+/** Updates a single comment in a state. */
+export function updateComment(
+  state: CommentState,
+  comment: CommentInfo
+): CommentState {
+  if (!comment.path || !state.comments) {
+    return state;
+  }
+  const newCommentsAtPath = [...state.comments[comment.path]];
+  for (let i = 0; i < newCommentsAtPath.length; ++i) {
+    if (newCommentsAtPath[i].id === comment.id) {
+      // TODO: In "delete comment" the returned comment is missing some of the
+      // fields (for example patch_set), which would throw errors when
+      // rendering. Remove merging with the old comment, once that is fixed in
+      // server code.
+      newCommentsAtPath[i] = {...newCommentsAtPath[i], ...comment};
+
+      return {
+        ...state,
+        comments: {
+          ...state.comments,
+          [comment.path]: newCommentsAtPath,
+        },
+      };
+    }
+  }
+  throw new Error('Comment to be updated does not exist');
+}
+
 // Private but used in tests.
 export function setRobotComments(
   state: CommentState,
@@ -621,6 +652,25 @@
     this.report(Interaction.COMMENT_DISCARDED, draft);
   }
 
+  async deleteComment(
+    changeNum: NumericChangeId,
+    comment: Comment,
+    reason: string
+  ) {
+    assertIsDefined(comment.patch_set, 'comment.patch_set');
+    if (isDraftOrUnsaved(comment)) {
+      throw new Error('Admin deletion is only for published comments.');
+    }
+
+    const newComment = await this.restApiService.deleteComment(
+      changeNum,
+      comment.patch_set,
+      comment.id,
+      reason
+    );
+    this.modifyState(s => updateComment(s, newComment));
+  }
+
   private report(interaction: Interaction, comment: CommentBasics) {
     const details = reportingDetails(comment);
     this.reporting.reportInteraction(interaction, details);
diff --git a/polygerrit-ui/app/models/comments/comments-model_test.ts b/polygerrit-ui/app/models/comments/comments-model_test.ts
index 4db5d57..cab36d2 100644
--- a/polygerrit-ui/app/models/comments/comments-model_test.ts
+++ b/polygerrit-ui/app/models/comments/comments-model_test.ts
@@ -11,6 +11,7 @@
 import {
   AccountInfo,
   EmailAddress,
+  NumericChangeId,
   Timestamp,
   UrlEncodedCommentId,
 } from '../../types/common';
@@ -29,6 +30,7 @@
 import {assert} from '@open-wc/testing';
 import {testResolver} from '../../test/common-test-setup';
 import {accountsModelToken} from '../accounts-model/accounts-model';
+import {ChangeComments} from '../../elements/diff/gr-comment-api/gr-comment-api';
 
 suite('comments model tests', () => {
   test('updateStateDeleteDraft', () => {
@@ -187,4 +189,36 @@
     });
     await waitUntil(() => mentionedUsers.length === 0);
   });
+
+  test('delete comment change is emitted', async () => {
+    const comment = createComment();
+    stubRestApi('deleteComment').returns(
+      Promise.resolve({
+        ...comment,
+        message: 'Comment is deleted',
+      })
+    );
+    const model = new CommentsModel(
+      testResolver(routerModelToken),
+      testResolver(changeModelToken),
+      testResolver(accountsModelToken),
+      getAppContext().restApiService,
+      getAppContext().reportingService
+    );
+
+    let changeComments: ChangeComments | undefined = undefined;
+    model.changeComments$.subscribe(x => (changeComments = x));
+    model.setState({
+      comments: {[comment.path!]: [comment]},
+      discardedDrafts: [],
+    });
+
+    model.deleteComment(123 as NumericChangeId, comment, 'Comment is deleted');
+
+    await waitUntil(
+      () =>
+        changeComments?.getAllCommentsForPath(comment.path!)[0].message ===
+        'Comment is deleted'
+    );
+  });
 });
diff --git a/polygerrit-ui/app/models/views/admin.ts b/polygerrit-ui/app/models/views/admin.ts
index 2ad95a2..3380637 100644
--- a/polygerrit-ui/app/models/views/admin.ts
+++ b/polygerrit-ui/app/models/views/admin.ts
@@ -4,6 +4,7 @@
  * SPDX-License-Identifier: Apache-2.0
  */
 import {GerritView} from '../../services/router/router-model';
+import {getBaseUrl} from '../../utils/url-util';
 import {define} from '../dependency';
 import {Model} from '../model';
 import {ViewState} from './base';
@@ -21,6 +22,17 @@
   offset?: number | string;
 }
 
+export function createAdminUrl(state: Omit<AdminViewState, 'view'>) {
+  switch (state.adminView) {
+    case AdminChildView.REPOS:
+      return `${getBaseUrl()}/admin/repos`;
+    case AdminChildView.GROUPS:
+      return `${getBaseUrl()}/admin/groups`;
+    case AdminChildView.PLUGINS:
+      return `${getBaseUrl()}/admin/plugins`;
+  }
+}
+
 export const adminViewModelToken = define<AdminViewModel>('admin-view-model');
 
 export class AdminViewModel extends Model<AdminViewState | undefined> {
diff --git a/polygerrit-ui/app/package.json b/polygerrit-ui/app/package.json
index 3df17c1..392b5a8 100644
--- a/polygerrit-ui/app/package.json
+++ b/polygerrit-ui/app/package.json
@@ -12,7 +12,6 @@
     "@polymer/iron-icon": "^3.0.1",
     "@polymer/iron-iconset-svg": "^3.0.1",
     "@polymer/iron-input": "^3.0.1",
-    "@polymer/iron-overlay-behavior": "^3.0.3",
     "@polymer/iron-selector": "^3.0.1",
     "@polymer/marked-element": "^3.0.1",
     "@polymer/paper-button": "^3.0.1",
diff --git a/polygerrit-ui/app/scripts/js/bundled-polymer-bridges.js b/polygerrit-ui/app/scripts/js/bundled-polymer-bridges.js
index 573b24a..494acd9 100644
--- a/polygerrit-ui/app/scripts/js/bundled-polymer-bridges.js
+++ b/polygerrit-ui/app/scripts/js/bundled-polymer-bridges.js
@@ -58,7 +58,3 @@
 import 'polymer-bridges/polymer/lib/elements/custom-style_bridge.js';
 import 'polymer-bridges/polymer/lib/legacy/mutable-data-behavior_bridge.js';
 import 'polymer-bridges/polymer/polymer-legacy_bridge.js';
-
-// This is needed due to the Polymer.IronFocusablesHelper in gr-overlay.ts
-import 'polymer-bridges/iron-overlay-behavior/iron-focusables-helper_bridge.js';
-
diff --git a/polygerrit-ui/app/services/shortcuts/shortcuts-config.ts b/polygerrit-ui/app/services/shortcuts/shortcuts-config.ts
index da61c41..9ca2213 100644
--- a/polygerrit-ui/app/services/shortcuts/shortcuts-config.ts
+++ b/polygerrit-ui/app/services/shortcuts/shortcuts-config.ts
@@ -35,6 +35,8 @@
   GO_TO_MERGED_CHANGES = 'GO_TO_MERGED_CHANGES',
   GO_TO_ABANDONED_CHANGES = 'GO_TO_ABANDONED_CHANGES',
   GO_TO_WATCHED_CHANGES = 'GO_TO_WATCHED_CHANGES',
+  GO_TO_REPOS = 'GO_TO_REPOS',
+  GO_TO_GROUPS = 'GO_TO_GROUPS',
 
   CURSOR_NEXT_CHANGE = 'CURSOR_NEXT_CHANGE',
   CURSOR_PREV_CHANGE = 'CURSOR_PREV_CHANGE',
@@ -167,6 +169,16 @@
     {key: 'w', combo: ComboKey.G}
   );
   describe(
+    Shortcut.GO_TO_REPOS,
+    ShortcutSection.EVERYWHERE,
+    'Go to Repositories',
+    {key: 'r', combo: ComboKey.G}
+  );
+  describe(Shortcut.GO_TO_GROUPS, ShortcutSection.EVERYWHERE, 'Go to Groups', {
+    key: 'g',
+    combo: ComboKey.G,
+  });
+  describe(
     Shortcut.TOGGLE_CHECKBOX,
     ShortcutSection.ACTIONS,
     'Toggle checkbox',
diff --git a/polygerrit-ui/app/styles/gr-menu-page-styles.ts b/polygerrit-ui/app/styles/gr-menu-page-styles.ts
index 7a44e79..17b7461 100644
--- a/polygerrit-ui/app/styles/gr-menu-page-styles.ts
+++ b/polygerrit-ui/app/styles/gr-menu-page-styles.ts
@@ -32,7 +32,7 @@
     color: var(--deemphasized-text-color);
     padding: var(--spacing-l);
   }
-  @media only screen and (max-width: 67em) {
+  @media only screen and (max-width: 70em) {
     .main {
       margin: var(--spacing-xxl) 0 var(--spacing-xxl) 15em;
     }
diff --git a/polygerrit-ui/app/styles/themes/app-theme.ts b/polygerrit-ui/app/styles/themes/app-theme.ts
index 86c6ba2..6dbda3e 100644
--- a/polygerrit-ui/app/styles/themes/app-theme.ts
+++ b/polygerrit-ui/app/styles/themes/app-theme.ts
@@ -480,10 +480,7 @@
     --border-radius: 4px;
     --line-length-indicator-color: #681da8;
 
-    /* paper and iron component overrides */
-    --iron-overlay-backdrop-background-color: black;
-    --iron-overlay-backdrop-opacity: 0.32;
-
+    /* paper component overrides */
     --paper-tooltip-delay-in: 200ms;
     --paper-tooltip-delay-out: 0;
     --paper-tooltip-duration-in: 0;
@@ -520,9 +517,6 @@
     --paper-tooltip: {
       font-size: var(--font-size-small);
     };
-    --iron-overlay-backdrop: {
-      transition: none;
-    };
   }
 `;
 
diff --git a/polygerrit-ui/app/styles/themes/dark-theme.ts b/polygerrit-ui/app/styles/themes/dark-theme.ts
index ecae845..c6884a6 100644
--- a/polygerrit-ui/app/styles/themes/dark-theme.ts
+++ b/polygerrit-ui/app/styles/themes/dark-theme.ts
@@ -281,9 +281,6 @@
     /* misc */
     --line-length-indicator-color: #d7aefb;
 
-    /* paper and iron component overrides */
-    --iron-overlay-backdrop-background-color: white;
-
     /* rules applied to html */
     background-color: var(--view-background-color);
   }
diff --git a/polygerrit-ui/app/test/common-test-setup.ts b/polygerrit-ui/app/test/common-test-setup.ts
index 33d2753..aed58d8 100644
--- a/polygerrit-ui/app/test/common-test-setup.ts
+++ b/polygerrit-ui/app/test/common-test-setup.ts
@@ -16,8 +16,6 @@
 import {
   cleanupTestUtils,
   getCleanupsCount,
-  addIronOverlayBackdropStyleEl,
-  removeIronOverlayBackdropStyleEl,
   removeThemeStyles,
 } from './test-utils';
 import {safeTypesBridge} from '../utils/safe-types-util';
@@ -99,7 +97,6 @@
 
 setup(() => {
   testSetupTimestampMs = new Date().getTime();
-  addIronOverlayBackdropStyleEl();
 
   // If the following asserts fails - then window.stub is
   // overwritten by some other code.
@@ -173,7 +170,6 @@
   fixtureCleanup();
   cleanupTestUtils();
   checkGlobalSpace();
-  removeIronOverlayBackdropStyleEl();
   removeThemeStyles();
   cancelAllTasks();
   cleanUpStorage();
diff --git a/polygerrit-ui/app/test/test-utils.ts b/polygerrit-ui/app/test/test-utils.ts
index 383c420..c400d9c 100644
--- a/polygerrit-ui/app/test/test-utils.ts
+++ b/polygerrit-ui/app/test/test-utils.ts
@@ -122,24 +122,6 @@
   ReturnType<F>
 >;
 
-/**
- * Forcing an opacity of 0 onto the ironOverlayBackdrop is required, because
- * otherwise the backdrop stays around in the DOM for too long waiting for
- * an animation to finish.
- */
-export function addIronOverlayBackdropStyleEl() {
-  const el = document.createElement('style');
-  el.setAttribute('id', 'backdrop-style');
-  document.head.appendChild(el);
-  el.sheet!.insertRule('body { --iron-overlay-backdrop-opacity: 0; }');
-}
-
-export function removeIronOverlayBackdropStyleEl() {
-  const el = document.getElementById('backdrop-style');
-  if (!el?.parentNode) throw new Error('Backdrop style element not found.');
-  el.parentNode?.removeChild(el);
-}
-
 export function removeThemeStyles() {
   // Do not remove the light theme, because it is only added once statically,
   // not once per gr-app instantiation.
diff --git a/polygerrit-ui/app/utils/admin-nav-util.ts b/polygerrit-ui/app/utils/admin-nav-util.ts
index 6f81d57..7916799 100644
--- a/polygerrit-ui/app/utils/admin-nav-util.ts
+++ b/polygerrit-ui/app/utils/admin-nav-util.ts
@@ -12,7 +12,7 @@
 import {hasOwnProperty} from './common-util';
 import {GerritView} from '../services/router/router-model';
 import {MenuLink} from '../api/admin';
-import {AdminChildView} from '../models/views/admin';
+import {AdminChildView, createAdminUrl} from '../models/views/admin';
 import {createGroupUrl, GroupDetailView} from '../models/views/group';
 import {createRepoUrl, RepoDetailView} from '../models/views/repo';
 
@@ -20,7 +20,7 @@
   {
     name: 'Repositories',
     noBaseUrl: true,
-    url: '/admin/repos',
+    url: createAdminUrl({adminView: AdminChildView.REPOS}),
     view: 'gr-repo-list' as GerritView,
     viewableToAll: true,
   },
@@ -28,7 +28,7 @@
     name: 'Groups',
     section: 'Groups',
     noBaseUrl: true,
-    url: '/admin/groups',
+    url: createAdminUrl({adminView: AdminChildView.GROUPS}),
     view: 'gr-admin-group-list' as GerritView,
   },
   {
@@ -36,7 +36,7 @@
     capability: 'viewPlugins',
     section: 'Plugins',
     noBaseUrl: true,
-    url: '/admin/plugins',
+    url: createAdminUrl({adminView: AdminChildView.PLUGINS}),
     view: 'gr-plugin-list' as GerritView,
   },
 ];
diff --git a/polygerrit-ui/app/yarn.lock b/polygerrit-ui/app/yarn.lock
index 2f7cf3c..5a91fc0 100644
--- a/polygerrit-ui/app/yarn.lock
+++ b/polygerrit-ui/app/yarn.lock
@@ -161,7 +161,7 @@
   dependencies:
     "@polymer/polymer" "^3.0.0"
 
-"@polymer/iron-overlay-behavior@^3.0.0-pre.27", "@polymer/iron-overlay-behavior@^3.0.3":
+"@polymer/iron-overlay-behavior@^3.0.0-pre.27":
   version "3.0.3"
   resolved "https://registry.yarnpkg.com/@polymer/iron-overlay-behavior/-/iron-overlay-behavior-3.0.3.tgz#29c198e19e05bb2bcf7d86d3c11848cb93301d00"
   integrity sha512-Q/Fp0+uOQQ145ebZ7T8Cxl4m1tUKYjyymkjcL2rXUm+aDQGb1wA1M1LYxUF5YBqd+9lipE0PTIiYwA2ZL/sznA==