Merge "Update Getting Started to Gerrit 3.7.8" into stable-3.7
diff --git a/.bazelversion b/.bazelversion
index 6abaeb2..91e4a9f 100644
--- a/.bazelversion
+++ b/.bazelversion
@@ -1 +1 @@
-6.2.0
+6.3.2
diff --git a/Documentation/cmd-index-changes.txt b/Documentation/cmd-index-changes.txt
index 0ee7aab..1d4cbe34 100644
--- a/Documentation/cmd-index-changes.txt
+++ b/Documentation/cmd-index-changes.txt
@@ -16,8 +16,7 @@
 supported by the REST API.
 
 == ACCESS
-Caller must have the 'Maintain Server' capability, or be the owner of the change
-to be indexed.
+Caller must have the 'Maintain Server' capability.
 
 == SCRIPTING
 This command is intended to be used in scripts.
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index b9de6ab..720ac5f 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -6730,6 +6730,12 @@
 |`_number`            ||
 The change number. (The underscore is just a relict of a prior
 attempt to deprecate the change number.)
+|`virtual_id_number`  ||
+The virtual id number is globally unique. For local changes, it is equal to the
+`_number` attribute. For imported changes, the original `_number` is processed
+through a function designed to prevent conflicts with local change numbers.
+Note that its usage is intended solely for Gerrit's internals and UI, and
+adoption outside these scenarios is not advised.
 |`owner`              ||
 The owner of the change as an link:rest-api-accounts.html#account-info[
 AccountInfo] entity.
diff --git a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index 01c4942..92c3e92 100644
--- a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -1543,6 +1543,7 @@
     configLabel(project, label, func, ImmutableList.of(), value);
   }
 
+  @SuppressWarnings("deprecation")
   private void configLabel(
       Project.NameKey project,
       String label,
diff --git a/java/com/google/gerrit/acceptance/GerritServer.java b/java/com/google/gerrit/acceptance/GerritServer.java
index a9ad3f2..3bbc74e 100644
--- a/java/com/google/gerrit/acceptance/GerritServer.java
+++ b/java/com/google/gerrit/acceptance/GerritServer.java
@@ -332,7 +332,7 @@
     String configuredIndexBackend = cfg.getString("index", null, "type");
     if (configuredIndexBackend == null) {
       // Propagate index type to pgms that run off of the gerrit.config file on local disk.
-      IndexType indexType = IndexType.fromEnvironment().orElse(new IndexType("fake"));
+      IndexType indexType = IndexType.fromEnvironment().orElseGet(() -> new IndexType("fake"));
       gerritConfig.setString("index", null, "type", indexType.isLucene() ? "lucene" : "fake");
     }
     gerritConfig.save();
@@ -509,7 +509,7 @@
     IndexType indexType =
         (configuredIndexBackend != null)
             ? new IndexType(configuredIndexBackend)
-            : IndexType.fromEnvironment().orElse(new IndexType("fake"));
+            : IndexType.fromEnvironment().orElseGet(() -> new IndexType("fake"));
     daemon.setIndexModule(createIndexModule(indexType, baseConfig, testIndexModule));
 
     daemon.setEnableHttpd(desc.httpd());
diff --git a/java/com/google/gerrit/acceptance/testsuite/group/GroupOperationsImpl.java b/java/com/google/gerrit/acceptance/testsuite/group/GroupOperationsImpl.java
index dcf1158..a37c2ba 100644
--- a/java/com/google/gerrit/acceptance/testsuite/group/GroupOperationsImpl.java
+++ b/java/com/google/gerrit/acceptance/testsuite/group/GroupOperationsImpl.java
@@ -78,7 +78,7 @@
 
   private InternalGroupCreation toInternalGroupCreation(TestGroupCreation groupCreation) {
     AccountGroup.Id groupId = AccountGroup.id(seq.nextGroupId());
-    String groupName = groupCreation.name().orElse("group-with-id-" + groupId.get());
+    String groupName = groupCreation.name().orElseGet(() -> "group-with-id-" + groupId.get());
     AccountGroup.UUID groupUuid = GroupUuid.make(groupName, serverIdent);
     AccountGroup.NameKey nameKey = AccountGroup.nameKey(groupName);
     return InternalGroupCreation.builder()
diff --git a/java/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImpl.java b/java/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImpl.java
index deeb843..1014a0f 100644
--- a/java/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImpl.java
+++ b/java/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImpl.java
@@ -85,7 +85,7 @@
   }
 
   private Project.NameKey createNewProject(TestProjectCreation projectCreation) throws Exception {
-    String name = projectCreation.name().orElse(RandomStringUtils.randomAlphabetic(8));
+    String name = projectCreation.name().orElseGet(() -> RandomStringUtils.randomAlphabetic(8));
 
     CreateProjectArgs args = new CreateProjectArgs();
     args.setProjectName(name);
diff --git a/java/com/google/gerrit/extensions/common/ChangeInfo.java b/java/com/google/gerrit/extensions/common/ChangeInfo.java
index d381b68..75b3ef5 100644
--- a/java/com/google/gerrit/extensions/common/ChangeInfo.java
+++ b/java/com/google/gerrit/extensions/common/ChangeInfo.java
@@ -99,7 +99,7 @@
   public Boolean containsGitConflicts;
 
   public Integer _number;
-  public Integer _virtualIdNumber;
+  public Integer virtualIdNumber;
 
   public AccountInfo owner;
 
diff --git a/java/com/google/gerrit/httpd/raw/StaticModule.java b/java/com/google/gerrit/httpd/raw/StaticModule.java
index 15dcf42..1e76f21 100644
--- a/java/com/google/gerrit/httpd/raw/StaticModule.java
+++ b/java/com/google/gerrit/httpd/raw/StaticModule.java
@@ -242,7 +242,7 @@
         @GerritServerConfig Config cfg,
         GerritApi gerritApi,
         ExperimentFeatures experimentFeatures) {
-      String cdnPath = options.devCdn().orElse(cfg.getString("gerrit", null, "cdnPath"));
+      String cdnPath = options.devCdn().orElseGet(() -> cfg.getString("gerrit", null, "cdnPath"));
       String faviconPath = cfg.getString("gerrit", null, "faviconPath");
       return new IndexServlet(canonicalUrl, cdnPath, faviconPath, gerritApi, experimentFeatures);
     }
diff --git a/java/com/google/gerrit/index/QueryOptions.java b/java/com/google/gerrit/index/QueryOptions.java
index 29ab6d0..40677a18 100644
--- a/java/com/google/gerrit/index/QueryOptions.java
+++ b/java/com/google/gerrit/index/QueryOptions.java
@@ -128,4 +128,8 @@
         limit(),
         filter.apply(this));
   }
+
+  public int getLimitBasedOnPaginationType() {
+    return PaginationType.NONE == config().paginationType() ? limit() : pageSize();
+  }
 }
diff --git a/java/com/google/gerrit/index/query/IndexedQuery.java b/java/com/google/gerrit/index/query/IndexedQuery.java
index ee25ef9..cb98c06 100644
--- a/java/com/google/gerrit/index/query/IndexedQuery.java
+++ b/java/com/google/gerrit/index/query/IndexedQuery.java
@@ -87,6 +87,12 @@
   }
 
   @Override
+  public ResultSet<T> restart(int start) {
+    opts = opts.withStart(start);
+    return search();
+  }
+
+  @Override
   public ResultSet<T> restart(int start, int pageSize) {
     opts = opts.withStart(start).withPageSize(pageSize);
     return search();
diff --git a/java/com/google/gerrit/index/query/Paginated.java b/java/com/google/gerrit/index/query/Paginated.java
index 5521990..1065019 100644
--- a/java/com/google/gerrit/index/query/Paginated.java
+++ b/java/com/google/gerrit/index/query/Paginated.java
@@ -19,6 +19,8 @@
 public interface Paginated<T> {
   QueryOptions getOptions();
 
+  ResultSet<T> restart(int start);
+
   ResultSet<T> restart(int start, int pageSize);
 
   ResultSet<T> restart(Object searchAfter, int pageSize);
diff --git a/java/com/google/gerrit/index/query/PaginatingSource.java b/java/com/google/gerrit/index/query/PaginatingSource.java
index 03d749a..84572d7 100644
--- a/java/com/google/gerrit/index/query/PaginatingSource.java
+++ b/java/com/google/gerrit/index/query/PaginatingSource.java
@@ -63,42 +63,55 @@
             pageResultSize++;
           }
 
-          if (last != null
-              && source instanceof Paginated
-              // TODO: this fix is only for the stable branches and the real refactoring would be to
-              // restore the logic
-              // for the filtering in AndSource (L58 - 64) as per
-              // https://gerrit-review.googlesource.com/c/gerrit/+/345634/9
-              && !indexConfig.paginationType().equals(PaginationType.NONE)) {
+          if (last != null && source instanceof Paginated) {
             // Restart source and continue if we have not filled the
             // full limit the caller wants.
-            //
             @SuppressWarnings("unchecked")
             Paginated<T> p = (Paginated<T>) source;
             QueryOptions opts = p.getOptions();
             final int limit = opts.limit();
-            int pageSize = opts.pageSize();
-            int pageSizeMultiplier = opts.pageSizeMultiplier();
-            Object searchAfter = resultSet.searchAfter();
-            int nextStart = pageResultSize;
-            while (pageResultSize == pageSize && r.size() <= limit) { // get 1 more than the limit
-              pageSize = getNextPageSize(pageSize, pageSizeMultiplier);
-              ResultSet<T> next =
-                  indexConfig.paginationType().equals(PaginationType.SEARCH_AFTER)
-                      ? p.restart(searchAfter, pageSize)
-                      : p.restart(nextStart, pageSize);
-              pageResultSize = 0;
-              for (T data : buffer(next)) {
-                if (match(data)) {
-                  r.add(data);
+
+            // TODO: this fix is only for the stable branches and the real refactoring would be to
+            // restore the logic
+            // for the filtering in AndSource (L58 - 64) as per
+            // https://gerrit-review.googlesource.com/c/gerrit/+/345634/9
+            if (!indexConfig.paginationType().equals(PaginationType.NONE)) {
+              int pageSize = opts.pageSize();
+              int pageSizeMultiplier = opts.pageSizeMultiplier();
+              Object searchAfter = resultSet.searchAfter();
+              int nextStart = pageResultSize;
+              while (pageResultSize == pageSize && r.size() <= limit) { // get 1 more than the limit
+                pageSize = getNextPageSize(pageSize, pageSizeMultiplier);
+                ResultSet<T> next =
+                    indexConfig.paginationType().equals(PaginationType.SEARCH_AFTER)
+                        ? p.restart(searchAfter, pageSize)
+                        : p.restart(nextStart, pageSize);
+                pageResultSize = 0;
+                for (T data : buffer(next)) {
+                  if (match(data)) {
+                    r.add(data);
+                  }
+                  pageResultSize++;
+                  if (r.size() > limit) {
+                    break;
+                  }
                 }
-                pageResultSize++;
-                if (r.size() > limit) {
-                  break;
-                }
+                nextStart += pageResultSize;
+                searchAfter = next.searchAfter();
               }
-              nextStart += pageResultSize;
-              searchAfter = next.searchAfter();
+            } else {
+              int nextStart = pageResultSize;
+              while (pageResultSize == limit && r.size() < limit) {
+                ResultSet<T> next = p.restart(nextStart);
+                pageResultSize = 0;
+                for (T data : buffer(next)) {
+                  if (match(data)) {
+                    r.add(data);
+                  }
+                  pageResultSize++;
+                }
+                nextStart += pageResultSize;
+              }
             }
           }
 
diff --git a/java/com/google/gerrit/index/testing/AbstractFakeIndex.java b/java/com/google/gerrit/index/testing/AbstractFakeIndex.java
index c790fe4..b4b4e1c 100644
--- a/java/com/google/gerrit/index/testing/AbstractFakeIndex.java
+++ b/java/com/google/gerrit/index/testing/AbstractFakeIndex.java
@@ -149,10 +149,14 @@
                     .findFirst()
                     .orElse(-1)
                 + 1;
-        int toIndex = Math.min(fromIndex + opts.pageSize(), valueList.size());
+        int toIndex = Math.min(fromIndex + opts.getLimitBasedOnPaginationType(), valueList.size());
         results = valueList.subList(fromIndex, toIndex);
       } else {
-        results = valueStream.skip(opts.start()).limit(opts.pageSize()).collect(toImmutableList());
+        results =
+            valueStream
+                .skip(opts.start())
+                .limit(opts.getLimitBasedOnPaginationType())
+                .collect(toImmutableList());
       }
       queryCount++;
       resultsSizes.add(results.size());
diff --git a/java/com/google/gerrit/lucene/LuceneChangeIndex.java b/java/com/google/gerrit/lucene/LuceneChangeIndex.java
index a47b87e..7ee99ac 100644
--- a/java/com/google/gerrit/lucene/LuceneChangeIndex.java
+++ b/java/com/google/gerrit/lucene/LuceneChangeIndex.java
@@ -394,11 +394,11 @@
       IndexSearcher[] searchers = new IndexSearcher[indexes.size()];
       Map<ChangeSubIndex, ScoreDoc> searchAfterBySubIndex = new HashMap<>();
       try {
-        int realPageSize = opts.start() + opts.pageSize();
-        if (Integer.MAX_VALUE - opts.pageSize() < opts.start()) {
-          realPageSize = Integer.MAX_VALUE;
+        int pageLimit = AbstractLuceneIndex.getLimitBasedOnPaginationType(opts, opts.pageSize());
+        int queryLimit = opts.start() + pageLimit;
+        if (Integer.MAX_VALUE - pageLimit < opts.start()) {
+          queryLimit = Integer.MAX_VALUE;
         }
-        int queryLimit = AbstractLuceneIndex.getLimitBasedOnPaginationType(opts, realPageSize);
         List<TopFieldDocs> hits = new ArrayList<>();
         int searchAfterHitsCount = 0;
         for (int i = 0; i < indexes.size(); i++) {
@@ -406,7 +406,7 @@
           searchers[i] = subIndex.acquire();
           if (isSearchAfterPagination) {
             ScoreDoc searchAfter = getSearchAfter(subIndex);
-            int maxRemainingHits = realPageSize - searchAfterHitsCount;
+            int maxRemainingHits = queryLimit - searchAfterHitsCount;
             if (maxRemainingHits > 0) {
               TopFieldDocs subIndexHits =
                   searchers[i].searchAfter(
@@ -558,7 +558,7 @@
     }
 
     for (SchemaField<ChangeData, ?> field : getSchema().getSchemaFields().values()) {
-      if (fields.contains(field.getName()) && doc.get(field.getName()) != null) {
+      if (fields.contains(field.getName())) {
         field.setIfPossible(cd, new LuceneStoredValue(doc.get(field.getName())));
       }
     }
diff --git a/java/com/google/gerrit/server/DeadlineChecker.java b/java/com/google/gerrit/server/DeadlineChecker.java
index f41b1e3..9b7ffe6 100644
--- a/java/com/google/gerrit/server/DeadlineChecker.java
+++ b/java/com/google/gerrit/server/DeadlineChecker.java
@@ -180,12 +180,14 @@
     this.timeoutName =
         clientedProvidedTimeout
             .map(clientTimeout -> "client.timeout")
-            .orElse(
-                serverSideDeadline
-                    .map(serverDeadline -> serverDeadline.id() + ".timeout")
-                    .orElse("timeout"));
+            .orElseGet(
+                () ->
+                    serverSideDeadline
+                        .map(serverDeadline -> serverDeadline.id() + ".timeout")
+                        .orElse("timeout"));
     this.timeout =
-        clientedProvidedTimeout.orElse(serverSideDeadline.map(ServerDeadline::timeout).orElse(0L));
+        clientedProvidedTimeout.orElseGet(
+            () -> serverSideDeadline.map(ServerDeadline::timeout).orElse(0L));
     this.deadline = timeout > 0 ? Optional.of(start + timeout) : Optional.empty();
   }
 
diff --git a/java/com/google/gerrit/server/account/AccountCacheImpl.java b/java/com/google/gerrit/server/account/AccountCacheImpl.java
index 66a36f6..d306ad0 100644
--- a/java/com/google/gerrit/server/account/AccountCacheImpl.java
+++ b/java/com/google/gerrit/server/account/AccountCacheImpl.java
@@ -98,7 +98,7 @@
 
   @Override
   public AccountState getEvenIfMissing(Account.Id accountId) {
-    return get(accountId).orElse(missing(accountId));
+    return get(accountId).orElseGet(() -> missing(accountId));
   }
 
   @Override
diff --git a/java/com/google/gerrit/server/account/AccountManager.java b/java/com/google/gerrit/server/account/AccountManager.java
index 891a467..5edba08 100644
--- a/java/com/google/gerrit/server/account/AccountManager.java
+++ b/java/com/google/gerrit/server/account/AccountManager.java
@@ -195,7 +195,7 @@
           "Unable to deactivate account %s",
           authRequest
               .getUserName()
-              .orElse(" for external ID key " + authRequest.getExternalIdKey().get()));
+              .orElseGet(() -> " for external ID key " + authRequest.getExternalIdKey().get()));
     }
   }
 
diff --git a/java/com/google/gerrit/server/change/ChangeJson.java b/java/com/google/gerrit/server/change/ChangeJson.java
index 928c8f8..7248836 100644
--- a/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/java/com/google/gerrit/server/change/ChangeJson.java
@@ -773,7 +773,7 @@
               .collect(toList());
     }
 
-    out._virtualIdNumber = cd.virtualId().get();
+    out.virtualIdNumber = cd.virtualId().get();
 
     return out;
   }
@@ -965,7 +965,7 @@
     // repository only once
     try (Repository allUsersRepo = repoManager.openRepository(allUsers)) {
       List<Change.Id> changeIds =
-          changeInfos.stream().map(c -> Change.id(c._virtualIdNumber)).collect(Collectors.toList());
+          changeInfos.stream().map(c -> Change.id(c.virtualIdNumber)).collect(Collectors.toList());
       Set<Change.Id> starredChanges =
           starredChangesUtil.areStarred(
               allUsersRepo, changeIds, userProvider.get().asIdentifiedUser().getAccountId());
@@ -973,7 +973,7 @@
         return;
       }
       changeInfos.stream()
-          .forEach(c -> c.starred = starredChanges.contains(Change.id(c._virtualIdNumber)));
+          .forEach(c -> c.starred = starredChanges.contains(Change.id(c.virtualIdNumber)));
     } catch (IOException e) {
       logger.atWarning().withCause(e).log("Failed to open All-Users repo.");
     }
diff --git a/java/com/google/gerrit/server/change/DeleteChangeOp.java b/java/com/google/gerrit/server/change/DeleteChangeOp.java
index ac75165..4ac27c1 100644
--- a/java/com/google/gerrit/server/change/DeleteChangeOp.java
+++ b/java/com/google/gerrit/server/change/DeleteChangeOp.java
@@ -125,7 +125,7 @@
   }
 
   private void cleanUpReferences(ChangeData cd) throws IOException {
-    accountPatchReviewStore.run(s -> s.clearReviewed(cd.getId()));
+    accountPatchReviewStore.run(s -> s.clearReviewed(cd.virtualId()));
 
     // Non-atomic operation on All-Users refs; not much we can do to make it atomic.
     starredChangesUtil.unstarAllForChangeDeletion(cd.virtualId());
diff --git a/java/com/google/gerrit/server/change/DeleteReviewerOp.java b/java/com/google/gerrit/server/change/DeleteReviewerOp.java
index 1199be5..84ea730 100644
--- a/java/com/google/gerrit/server/change/DeleteReviewerOp.java
+++ b/java/com/google/gerrit/server/change/DeleteReviewerOp.java
@@ -214,7 +214,7 @@
             reviewerDeleted.fire(
                 ctx.getChangeData(currChange),
                 patchSet,
-                accountCache.get(reviewer.id()).orElse(AccountState.forAccount(reviewer)),
+                accountCache.get(reviewer.id()).orElseGet(() -> AccountState.forAccount(reviewer)),
                 ctx.getAccount(),
                 mailMessage,
                 newApprovals,
diff --git a/java/com/google/gerrit/server/git/DefaultChangeReportFormatter.java b/java/com/google/gerrit/server/git/DefaultChangeReportFormatter.java
index 4f6094e..58c3eb1 100644
--- a/java/com/google/gerrit/server/git/DefaultChangeReportFormatter.java
+++ b/java/com/google/gerrit/server/git/DefaultChangeReportFormatter.java
@@ -54,7 +54,7 @@
         urlFormatter
             .get()
             .getChangeViewUrl(c.getProject(), c.getId())
-            .orElse(c.getId().toString()));
+            .orElseGet(() -> c.getId().toString()));
   }
 
   protected String cropSubject(String subject) {
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
index 0f5e3bc..dbe0fa8 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
@@ -2019,10 +2019,10 @@
       magicBranch.dest = BranchNameKey.create(project.getNameKey(), ref);
       magicBranch.perm = permissions.ref(ref);
 
-      Optional<AuthException> err =
-          checkRefPermission(magicBranch.perm, RefPermission.READ)
-              .map(Optional::of)
-              .orElse(checkRefPermission(magicBranch.perm, RefPermission.CREATE_CHANGE));
+      Optional<AuthException> err = checkRefPermission(magicBranch.perm, RefPermission.READ);
+      if (err.isEmpty()) {
+        err = checkRefPermission(magicBranch.perm, RefPermission.CREATE_CHANGE);
+      }
       if (err.isPresent()) {
         rejectProhibited(cmd, err.get());
         return;
diff --git a/java/com/google/gerrit/server/group/db/AuditLogFormatter.java b/java/com/google/gerrit/server/group/db/AuditLogFormatter.java
index 235ca4f..3ba087e 100644
--- a/java/com/google/gerrit/server/group/db/AuditLogFormatter.java
+++ b/java/com/google/gerrit/server/group/db/AuditLogFormatter.java
@@ -167,7 +167,7 @@
         .map(Account::getName)
         // Historically, the database did not enforce relational integrity, so it is
         // possible for groups to have non-existing members.
-        .orElse("No Account for Id #" + accountId);
+        .orElseGet(() -> "No Account for Id #" + accountId);
   }
 
   private PersonIdent getParsableAuthorIdent(
diff --git a/java/com/google/gerrit/server/patch/PatchFile.java b/java/com/google/gerrit/server/patch/PatchFile.java
index 7a8180bd..c3a6807 100644
--- a/java/com/google/gerrit/server/patch/PatchFile.java
+++ b/java/com/google/gerrit/server/patch/PatchFile.java
@@ -61,7 +61,7 @@
             .filter(f -> f.getKey().equals(fileName))
             .map(Map.Entry::getValue)
             .findFirst()
-            .orElse(FileDiffOutput.empty(fileName, ObjectId.zeroId(), ObjectId.zeroId()));
+            .orElseGet(() -> FileDiffOutput.empty(fileName, ObjectId.zeroId(), ObjectId.zeroId()));
 
     if (Patch.PATCHSET_LEVEL.equals(fileName)) {
       aTree = null;
diff --git a/java/com/google/gerrit/server/project/Reachable.java b/java/com/google/gerrit/server/project/Reachable.java
index 342c2bc..c935adf 100644
--- a/java/com/google/gerrit/server/project/Reachable.java
+++ b/java/com/google/gerrit/server/project/Reachable.java
@@ -74,7 +74,7 @@
       Collection<Ref> filtered =
           optionalUserProvider
               .map(permissionBackend::user)
-              .orElse(permissionBackend.currentUser())
+              .orElseGet(() -> permissionBackend.currentUser())
               .project(project)
               .filter(refs, repo, RefFilterOptions.defaults());
       Collection<RevCommit> visible = new ArrayList<>();
diff --git a/java/com/google/gerrit/server/query/change/ChangeData.java b/java/com/google/gerrit/server/query/change/ChangeData.java
index a69d837..82f85e9 100644
--- a/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -1229,8 +1229,6 @@
         mergeable = true;
       } else if (c.isAbandoned()) {
         return null;
-      } else if (c.isWorkInProgress()) {
-        return null;
       } else {
         if (!lazyload()) {
           return null;
diff --git a/java/com/google/gerrit/server/query/change/PredicateArgs.java b/java/com/google/gerrit/server/query/change/PredicateArgs.java
index ebe4390..02d2ca6 100644
--- a/java/com/google/gerrit/server/query/change/PredicateArgs.java
+++ b/java/com/google/gerrit/server/query/change/PredicateArgs.java
@@ -71,7 +71,7 @@
    *
    * @param args arguments to be parsed
    */
-  PredicateArgs(String args) throws QueryParseException {
+  public PredicateArgs(String args) throws QueryParseException {
     positional = new ArrayList<>();
     keyValue = new HashMap<>();
 
diff --git a/java/com/google/gerrit/server/restapi/project/CreateLabel.java b/java/com/google/gerrit/server/restapi/project/CreateLabel.java
index ad32f4f..ad79a14 100644
--- a/java/com/google/gerrit/server/restapi/project/CreateLabel.java
+++ b/java/com/google/gerrit/server/restapi/project/CreateLabel.java
@@ -123,6 +123,7 @@
    * @throws BadRequestException if there was invalid data in the input
    * @throws ResourceConflictException if the label cannot be created due to a conflict
    */
+  @SuppressWarnings("deprecation")
   public LabelType createLabel(ProjectConfig config, String label, LabelDefinitionInput input)
       throws BadRequestException, ResourceConflictException {
     if (config.getLabelSections().containsKey(label)) {
diff --git a/java/com/google/gerrit/server/restapi/project/SetLabel.java b/java/com/google/gerrit/server/restapi/project/SetLabel.java
index 10589cc..6c4976d 100644
--- a/java/com/google/gerrit/server/restapi/project/SetLabel.java
+++ b/java/com/google/gerrit/server/restapi/project/SetLabel.java
@@ -115,6 +115,7 @@
    * @throws BadRequestException if there was invalid data in the input
    * @throws ResourceConflictException if the update cannot be applied due to a conflict
    */
+  @SuppressWarnings("deprecation")
   public boolean updateLabel(ProjectConfig config, LabelType labelType, LabelDefinitionInput input)
       throws BadRequestException, ResourceConflictException {
     boolean dirty = false;
diff --git a/java/com/google/gerrit/sshd/InvalidKeyAlgorithmException.java b/java/com/google/gerrit/sshd/InvalidKeyAlgorithmException.java
new file mode 100644
index 0000000..5f09658
--- /dev/null
+++ b/java/com/google/gerrit/sshd/InvalidKeyAlgorithmException.java
@@ -0,0 +1,44 @@
+// Copyright (C) 2024 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.sshd;
+
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
+
+public class InvalidKeyAlgorithmException extends InvalidKeySpecException {
+  private final String invalidKeyAlgo;
+  private final String expectedKeyAlgo;
+  private final PublicKey publicKey;
+
+  public InvalidKeyAlgorithmException(
+      String invalidKeyAlgo, String expectedKeyAlgo, PublicKey publicKey) {
+    super("Key algorithm mismatch: expected " + expectedKeyAlgo + " but got " + invalidKeyAlgo);
+    this.invalidKeyAlgo = invalidKeyAlgo;
+    this.expectedKeyAlgo = expectedKeyAlgo;
+    this.publicKey = publicKey;
+  }
+
+  public String getInvalidKeyAlgo() {
+    return invalidKeyAlgo;
+  }
+
+  public String getExpectedKeyAlgo() {
+    return expectedKeyAlgo;
+  }
+
+  public PublicKey getPublicKey() {
+    return publicKey;
+  }
+}
diff --git a/java/com/google/gerrit/sshd/SshKeyCacheImpl.java b/java/com/google/gerrit/sshd/SshKeyCacheImpl.java
index 628a050..58e331b 100644
--- a/java/com/google/gerrit/sshd/SshKeyCacheImpl.java
+++ b/java/com/google/gerrit/sshd/SshKeyCacheImpl.java
@@ -19,6 +19,7 @@
 import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
 import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.exceptions.InvalidSshKeyException;
 import com.google.gerrit.server.account.AccountSshKey;
 import com.google.gerrit.server.account.VersionedAuthorizedKeys;
 import com.google.gerrit.server.account.externalids.ExternalId;
@@ -144,7 +145,16 @@
         // to do with the key object, and instead we must abort this load.
         //
         throw e;
-      } catch (Exception e) {
+      } catch (InvalidKeyAlgorithmException e) {
+        logger.atWarning().withCause(e).log(
+            "SSH key %d of account %s has an invalid algorithm %s: fixing the algorithm to %s",
+            k.seq(), k.accountId(), e.getInvalidKeyAlgo(), e.getExpectedKeyAlgo());
+        if (fixKeyAlgorithm(k, e.getExpectedKeyAlgo())) {
+          kl.add(new SshKeyCacheEntry(k.accountId(), e.getPublicKey()));
+        } else {
+          markInvalid(k);
+        }
+      } catch (Throwable e) {
         markInvalid(k);
       }
     }
@@ -158,5 +168,20 @@
             "Failed to mark SSH key %d of account %s invalid", k.seq(), k.accountId());
       }
     }
+
+    private boolean fixKeyAlgorithm(AccountSshKey k, String keyAlgo) {
+      try {
+        logger.atInfo().log(
+            "Fixing SSH key %d of account %s algorithm to %s", k.seq(), k.accountId(), keyAlgo);
+        authorizedKeys.deleteKey(k.accountId(), k.seq());
+        String sshKey = k.sshPublicKey();
+        authorizedKeys.addKey(k.accountId(), keyAlgo + sshKey.substring(sshKey.indexOf(' ')));
+        return true;
+      } catch (IOException | ConfigInvalidException | InvalidSshKeyException e) {
+        logger.atSevere().withCause(e).log(
+            "Failed to fix SSH key %d of account %s with algo %s", k.seq(), k.accountId(), keyAlgo);
+        return false;
+      }
+    }
   }
 }
diff --git a/java/com/google/gerrit/sshd/SshUtil.java b/java/com/google/gerrit/sshd/SshUtil.java
index abbd81d..29d0e90 100644
--- a/java/com/google/gerrit/sshd/SshUtil.java
+++ b/java/com/google/gerrit/sshd/SshUtil.java
@@ -57,7 +57,12 @@
         throw new InvalidKeySpecException("No key string");
       }
       final byte[] bin = BaseEncoding.base64().decode(s);
-      return new ByteArrayBuffer(bin).getRawPublicKey();
+      String publicKeyAlgo = new ByteArrayBuffer(bin).getString();
+      PublicKey publicKey = new ByteArrayBuffer(bin).getRawPublicKey();
+      if (!key.algorithm().equals(publicKeyAlgo)) {
+        throw new InvalidKeyAlgorithmException(key.algorithm(), publicKeyAlgo, publicKey);
+      }
+      return publicKey;
     } catch (RuntimeException | SshException e) {
       throw new InvalidKeySpecException("Cannot parse key", e);
     }
diff --git a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
index c9c5c2c..ceceaac 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -4791,6 +4791,8 @@
   public void changeQueryReturnsMergeableWhenGerritIndexMergeable() throws Exception {
     String changeId = createChange().getChangeId();
     assertThat(gApi.changes().query(changeId).get().get(0).mergeable).isTrue();
+    gApi.changes().id(changeId).setWorkInProgress();
+    assertThat(gApi.changes().query(changeId).get().get(0).mergeable).isTrue();
   }
 
   @Test
diff --git a/javatests/com/google/gerrit/acceptance/api/change/StickyApprovalsIT.java b/javatests/com/google/gerrit/acceptance/api/change/StickyApprovalsIT.java
index 2668d1f..6ba1498 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/StickyApprovalsIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/StickyApprovalsIT.java
@@ -1232,6 +1232,7 @@
         .contains("Code-Review+1 by User");
   }
 
+  @SuppressWarnings("deprecation")
   @Test
   public void sticky_copiedToLatestPatchSetFromSubmitRecords() throws Exception {
     updateVerifiedLabel(b -> b.setFunction(LabelFunction.NO_BLOCK));
diff --git a/javatests/com/google/gerrit/acceptance/api/change/SubmitRequirementIT.java b/javatests/com/google/gerrit/acceptance/api/change/SubmitRequirementIT.java
index 242c278..c223562 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/SubmitRequirementIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/SubmitRequirementIT.java
@@ -1576,6 +1576,7 @@
 
       // Clear SRs for the project and update code-review label to be non-blocking.
       clearSubmitRequirements(project);
+      @SuppressWarnings("deprecation")
       LabelType cr =
           TestLabels.codeReview().toBuilder().setFunction(LabelFunction.NO_BLOCK).build();
       try (ProjectConfigUpdate u = updateProject(project)) {
@@ -1622,6 +1623,7 @@
 
       // Clear SRs for the project and update code-review label to be non-blocking.
       clearSubmitRequirements(project);
+      @SuppressWarnings("deprecation")
       LabelType cr =
           TestLabels.codeReview().toBuilder().setFunction(LabelFunction.NO_BLOCK).build();
       try (ProjectConfigUpdate u = updateProject(project)) {
@@ -2836,6 +2838,7 @@
             .setAllowOverrideInChildProjects(true)
             .build());
 
+    @SuppressWarnings("deprecation")
     LabelType cr = TestLabels.codeReview().toBuilder().setFunction(LabelFunction.NO_BLOCK).build();
     try (ProjectConfigUpdate u = updateProject(project)) {
       u.getConfig().upsertLabelType(cr);
diff --git a/javatests/com/google/gerrit/acceptance/api/change/SubmitRuleIT.java b/javatests/com/google/gerrit/acceptance/api/change/SubmitRuleIT.java
index af95e7e..e7efb62 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/SubmitRuleIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/SubmitRuleIT.java
@@ -98,6 +98,7 @@
     assertThat(recordLabels).contains(needCustomLabel);
   }
 
+  @SuppressWarnings("deprecation")
   private void setupCustomBlockingLabel() throws Exception {
     try (ProjectConfigUpdate u = updateProject(project)) {
       u.getConfig()
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java b/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
index 0e4f212..f561a5a 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
@@ -186,7 +186,7 @@
             .create(
                 admin.newIdent(),
                 testRepo,
-                "parent 2",
+                "parent 1",
                 ImmutableMap.of("foo", "foo-2", "bar", "bar-2"))
             .to("refs/heads/master");
 
@@ -205,7 +205,7 @@
             .create(
                 admin.newIdent(),
                 testRepo,
-                "parent 1",
+                "parent 2",
                 ImmutableMap.of("foo", "foo-1", "bar", "bar-1"))
             .to("refs/heads/stable");
 
@@ -564,6 +564,25 @@
   }
 
   @Test
+  public void submitParentIsWorkInProgressChange() throws Throwable {
+    PushOneCommit.Result parent = pushTo("refs/for/master%wip");
+    PushOneCommit.Result change = createChange();
+    Change.Id num = parent.getChange().getId();
+    if (getSubmitType() == SubmitType.CHERRY_PICK) {
+      submit(change.getChangeId());
+    } else {
+      submitWithConflict(
+          change.getChangeId(),
+          "Failed to submit 2 changes due to the following problems:\n"
+              + "Change "
+              + num
+              + ": Change "
+              + num
+              + " is work in progress");
+    }
+  }
+
+  @Test
   public void submitWithHiddenBranchInSameTopic() throws Throwable {
     assume().that(isSubmitWholeTopicEnabled()).isTrue();
     PushOneCommit.Result visible = createChange("refs/for/master%topic=" + name("topic"));
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/AttentionSetIT.java b/javatests/com/google/gerrit/acceptance/rest/change/AttentionSetIT.java
index ea52690..fe7f935 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/AttentionSetIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/AttentionSetIT.java
@@ -2566,8 +2566,8 @@
             String.format(
                 "Attention is currently required from: %s, %s.\n"
                     + "\n"
-                    + "%s has posted comments on this change.",
-                admin.fullName(), user.fullName(), approver.fullName()));
+                    + "%s has posted comments on this change by %s.",
+                admin.fullName(), user.fullName(), approver.fullName(), admin.fullName()));
     assertThat(message.body()).doesNotContain("\nPatch Set 2: Code-Review+2\n");
     assertThat(message.body())
         .contains("The change is no longer submittable: Code-Review is unsatisfied now.\n");
@@ -2652,8 +2652,8 @@
             String.format(
                 "Attention is currently required from: %s, %s.\n"
                     + "\n"
-                    + "%s has posted comments on this change.",
-                admin.fullName(), user.fullName(), approver.fullName()));
+                    + "%s has posted comments on this change by %s.",
+                admin.fullName(), user.fullName(), approver.fullName(), admin.fullName()));
     assertThat(message.body()).doesNotContain("\nPatch Set 2: Code-Review+2\n");
     assertThat(message.body()).contains("\nPatch Set 2: Code-Review+1\n");
     assertThat(message.body())
@@ -2740,8 +2740,8 @@
             String.format(
                 "Attention is currently required from: %s, %s.\n"
                     + "\n"
-                    + "%s has posted comments on this change.",
-                admin.fullName(), user.fullName(), approver.fullName()));
+                    + "%s has posted comments on this change by %s.",
+                admin.fullName(), user.fullName(), approver.fullName(), admin.fullName()));
     assertThat(message.body()).contains("\nPatch Set 2: Code-Review-2\n");
     assertThat(message.body())
         .contains("The change is no longer submittable: Code-Review is unsatisfied now.\n");
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/ChangeNoLongerSubmittableIT.java b/javatests/com/google/gerrit/acceptance/rest/change/ChangeNoLongerSubmittableIT.java
index 1094a42..5bb6dc4 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/ChangeNoLongerSubmittableIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/ChangeNoLongerSubmittableIT.java
@@ -83,8 +83,8 @@
             String.format(
                 "Attention is currently required from: %s, %s.\n"
                     + "\n"
-                    + "%s has posted comments on this change.",
-                admin.fullName(), user.fullName(), approver.fullName()));
+                    + "%s has posted comments on this change by %s.",
+                admin.fullName(), user.fullName(), approver.fullName(), admin.fullName()));
     assertThat(message.body())
         .contains("The change is no longer submittable: Code-Review is unsatisfied now.\n");
     assertThat(message.htmlBody())
@@ -127,8 +127,8 @@
             String.format(
                 "Attention is currently required from: %s, %s.\n"
                     + "\n"
-                    + "%s has posted comments on this change.",
-                admin.fullName(), user.fullName(), approver.fullName()));
+                    + "%s has posted comments on this change by %s.",
+                admin.fullName(), user.fullName(), approver.fullName(), admin.fullName()));
     assertThat(message.body())
         .contains("The change is no longer submittable: Code-Review is unsatisfied now.\n");
     assertThat(message.htmlBody())
@@ -172,8 +172,8 @@
             String.format(
                 "Attention is currently required from: %s, %s.\n"
                     + "\n"
-                    + "%s has posted comments on this change.",
-                admin.fullName(), user.fullName(), approver.fullName()));
+                    + "%s has posted comments on this change by %s.",
+                admin.fullName(), user.fullName(), approver.fullName(), admin.fullName()));
     assertThat(message.body())
         .contains("The change is no longer submittable: Code-Review is unsatisfied now.\n");
     assertThat(message.htmlBody())
@@ -263,8 +263,8 @@
             String.format(
                 "Attention is currently required from: %s, %s.\n"
                     + "\n"
-                    + "%s has posted comments on this change.",
-                admin.fullName(), user.fullName(), approver.fullName()));
+                    + "%s has posted comments on this change by %s.",
+                admin.fullName(), user.fullName(), approver.fullName(), admin.fullName()));
     assertThat(message.body())
         .contains(
             "The change is no longer submittable:"
@@ -315,8 +315,8 @@
             String.format(
                 "Attention is currently required from: %s, %s.\n"
                     + "\n"
-                    + "%s has posted comments on this change.",
-                admin.fullName(), user.fullName(), approver.fullName()));
+                    + "%s has posted comments on this change by %s.",
+                admin.fullName(), user.fullName(), approver.fullName(), admin.fullName()));
     assertThat(message.body()).doesNotContain("The change is no longer submittable");
     assertThat(message.htmlBody()).doesNotContain("The change is no longer submittable");
   }
@@ -361,8 +361,8 @@
             String.format(
                 "Attention is currently required from: %s, %s.\n"
                     + "\n"
-                    + "%s has posted comments on this change.",
-                admin.fullName(), user.fullName(), approver.fullName()));
+                    + "%s has posted comments on this change by %s.",
+                admin.fullName(), user.fullName(), approver.fullName(), admin.fullName()));
     assertThat(message.body()).doesNotContain("The change is no longer submittable");
     assertThat(message.htmlBody()).doesNotContain("The change is no longer submittable");
   }
@@ -398,8 +398,8 @@
             String.format(
                 "Attention is currently required from: %s, %s.\n"
                     + "\n"
-                    + "%s has posted comments on this change.",
-                admin.fullName(), user.fullName(), approver.fullName()));
+                    + "%s has posted comments on this change by %s.",
+                admin.fullName(), user.fullName(), approver.fullName(), admin.fullName()));
     assertThat(message.body()).doesNotContain("The change is no longer submittable");
     assertThat(message.htmlBody()).doesNotContain("The change is no longer submittable");
   }
@@ -435,8 +435,8 @@
             String.format(
                 "Attention is currently required from: %s, %s.\n"
                     + "\n"
-                    + "%s has posted comments on this change.",
-                admin.fullName(), user.fullName(), approver.fullName()));
+                    + "%s has posted comments on this change by %s.",
+                admin.fullName(), user.fullName(), approver.fullName(), admin.fullName()));
     assertThat(message.body()).doesNotContain("The change is no longer submittable");
     assertThat(message.htmlBody()).doesNotContain("The change is no longer submittable");
   }
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/ChangeReviewersIT.java b/javatests/com/google/gerrit/acceptance/rest/change/ChangeReviewersIT.java
index 647e26b..985baba 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/ChangeReviewersIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/ChangeReviewersIT.java
@@ -402,7 +402,9 @@
 
     Message m = messages.get(0);
     assertThat(m.rcpt()).containsExactly(user.getNameEmail(), observer.getNameEmail());
-    assertThat(m.body()).contains(admin.fullName() + " has posted comments on this change.");
+    assertThat(m.body())
+        .contains(
+            admin.fullName() + " has posted comments on this change by " + admin.fullName() + ".");
     assertThat(m.body()).contains("Change subject: " + PushOneCommit.SUBJECT + "\n");
     assertThat(m.body()).contains("Patch Set 1: Code-Review+2");
 
diff --git a/javatests/com/google/gerrit/acceptance/server/project/CustomLabelIT.java b/javatests/com/google/gerrit/acceptance/server/project/CustomLabelIT.java
index 576c7d0..7ffc0a4 100644
--- a/javatests/com/google/gerrit/acceptance/server/project/CustomLabelIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/project/CustomLabelIT.java
@@ -76,6 +76,7 @@
         .update();
   }
 
+  @SuppressWarnings("deprecation")
   @Test
   public void customLabelNoOp_NegativeVoteNotBlock() throws Exception {
     saveLabelConfig(LABEL.toBuilder().setFunction(NO_OP));
@@ -91,6 +92,7 @@
     assertThat(q.blocking).isNull();
   }
 
+  @SuppressWarnings("deprecation")
   @Test
   public void customLabelNoBlock_NegativeVoteNotBlock() throws Exception {
     saveLabelConfig(LABEL.toBuilder().setFunction(NO_BLOCK));
@@ -106,6 +108,7 @@
     assertThat(q.blocking).isNull();
   }
 
+  @SuppressWarnings("deprecation")
   @Test
   public void customLabelMaxNoBlock_NegativeVoteNotBlock() throws Exception {
     saveLabelConfig(LABEL.toBuilder().setFunction(MAX_NO_BLOCK));
@@ -121,6 +124,7 @@
     assertThat(q.blocking).isNull();
   }
 
+  @SuppressWarnings("deprecation")
   @Test
   public void customLabelMaxNoBlock_MaxVoteSubmittable() throws Exception {
     saveLabelConfig(LABEL.toBuilder().setFunction(MAX_NO_BLOCK), P.toBuilder().setFunction(NO_OP));
@@ -139,6 +143,7 @@
     assertThat(q.blocking).isNull();
   }
 
+  @SuppressWarnings("deprecation")
   @Test
   public void customLabelAnyWithBlock_NegativeVoteBlock() throws Exception {
     saveLabelConfig(LABEL.toBuilder().setFunction(ANY_WITH_BLOCK));
@@ -163,6 +168,7 @@
     }
   }
 
+  @SuppressWarnings("deprecation")
   @Test
   public void customLabelAnyWithBlock_Addreviewer_ZeroVote() throws Exception {
     TestListener testListener = new TestListener();
@@ -190,6 +196,7 @@
     }
   }
 
+  @SuppressWarnings("deprecation")
   @Test
   public void customLabelMaxWithBlock_NegativeVoteBlock() throws Exception {
     saveLabelConfig(LABEL.toBuilder().setFunction(MAX_WITH_BLOCK));
@@ -205,6 +212,7 @@
     assertThat(q.blocking).isTrue();
   }
 
+  @SuppressWarnings("deprecation")
   @Test
   public void customLabelMaxWithBlock_DeletedVoteDoesNotTriggerNegativeBlock() throws Exception {
     saveLabelConfig(P.toBuilder().setFunction(MAX_WITH_BLOCK));
@@ -227,6 +235,7 @@
     assertThat(labelInfo.blocking).isNull(); // label is not blocking the change submission
   }
 
+  @SuppressWarnings("deprecation")
   @Test
   public void customLabelMaxWithBlock_MaxVoteSubmittable() throws Exception {
     saveLabelConfig(
@@ -246,6 +255,7 @@
     assertThat(q.blocking).isNull();
   }
 
+  @SuppressWarnings("deprecation")
   @Test
   public void customLabelMaxWithBlock_MaxVoteNegativeVoteBlock() throws Exception {
     saveLabelConfig(LABEL.toBuilder().setFunction(MAX_WITH_BLOCK));
@@ -262,6 +272,7 @@
     assertThat(q.blocking).isTrue();
   }
 
+  @SuppressWarnings("deprecation")
   @Test
   public void customLabel_DisallowPostSubmit() throws Exception {
     saveLabelConfig(
diff --git a/javatests/com/google/gerrit/server/group/db/AbstractGroupTest.java b/javatests/com/google/gerrit/server/group/db/AbstractGroupTest.java
index bea5eaa..a9ceddb 100644
--- a/javatests/com/google/gerrit/server/group/db/AbstractGroupTest.java
+++ b/javatests/com/google/gerrit/server/group/db/AbstractGroupTest.java
@@ -141,7 +141,7 @@
               return GroupConfig.loadForGroup(allUsersName, allUsersRepo, uuid)
                   .getLoadedGroup()
                   .map(InternalGroup::getName)
-                  .orElse("Group " + uuid);
+                  .orElseGet(() -> "Group " + uuid);
             } catch (IOException | ConfigInvalidException e) {
               return "Group " + uuid;
             }
diff --git a/javatests/com/google/gerrit/server/project/SubmitRequirementsAdapterTest.java b/javatests/com/google/gerrit/server/project/SubmitRequirementsAdapterTest.java
index b05f3c7..180dd92 100644
--- a/javatests/com/google/gerrit/server/project/SubmitRequirementsAdapterTest.java
+++ b/javatests/com/google/gerrit/server/project/SubmitRequirementsAdapterTest.java
@@ -40,6 +40,7 @@
 
   @Before
   public void setup() {
+    @SuppressWarnings("deprecation")
     LabelType codeReview =
         LabelType.builder(
                 "Code-Review",
@@ -50,6 +51,7 @@
             .setFunction(LabelFunction.MAX_WITH_BLOCK)
             .build();
 
+    @SuppressWarnings("deprecation")
     LabelType verified =
         LabelType.builder(
                 "Verified",
@@ -60,6 +62,7 @@
             .setFunction(LabelFunction.MAX_NO_BLOCK)
             .build();
 
+    @SuppressWarnings("deprecation")
     LabelType codeStyle =
         LabelType.builder(
                 "Code-Style",
@@ -70,6 +73,7 @@
             .setFunction(LabelFunction.ANY_WITH_BLOCK)
             .build();
 
+    @SuppressWarnings("deprecation")
     LabelType ignoreSelfApprovalLabel =
         LabelType.builder(
                 "ISA-Label",
diff --git a/javatests/com/google/gerrit/server/query/change/FakeQueryChangesTest.java b/javatests/com/google/gerrit/server/query/change/FakeQueryChangesTest.java
index b1faf03..e1f324f 100644
--- a/javatests/com/google/gerrit/server/query/change/FakeQueryChangesTest.java
+++ b/javatests/com/google/gerrit/server/query/change/FakeQueryChangesTest.java
@@ -25,6 +25,8 @@
 import static org.junit.Assume.assumeTrue;
 
 import com.google.gerrit.acceptance.UseClockStep;
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.entities.Change;
 import com.google.gerrit.entities.Permission;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.extensions.common.ChangeInfo;
@@ -83,12 +85,49 @@
 
     AbstractFakeIndex idx = (AbstractFakeIndex) changeIndexCollection.getSearchIndex();
     newQuery("status:new").withLimit(5).get();
+    // Since the limit of the query (i.e. 5) is more than the total number of changes (i.e. 4),
+    // only 1 index search is expected.
     assertThatSearchQueryWasNotPaginated(idx.getQueryCount());
   }
 
   @Test
   @UseClockStep
   @SuppressWarnings("unchecked")
+  public void queryRightNumberOfTimes() throws Exception {
+    TestRepository<Repo> repo = createProject("repo");
+    Account.Id user2 =
+        accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
+
+    // create 1 visible change
+    Change visibleChange1 = insert(repo, newChangeWithStatus(repo, Change.Status.NEW));
+
+    // create 4 private changes
+    Change invisibleChange2 = insert(repo, newChangeWithStatus(repo, Change.Status.NEW), user2);
+    Change invisibleChange3 = insert(repo, newChangeWithStatus(repo, Change.Status.NEW), user2);
+    Change invisibleChange4 = insert(repo, newChangeWithStatus(repo, Change.Status.NEW), user2);
+    Change invisibleChange5 = insert(repo, newChangeWithStatus(repo, Change.Status.NEW), user2);
+    gApi.changes().id(invisibleChange2.getChangeId()).setPrivate(true, null);
+    gApi.changes().id(invisibleChange3.getChangeId()).setPrivate(true, null);
+    gApi.changes().id(invisibleChange4.getChangeId()).setPrivate(true, null);
+    gApi.changes().id(invisibleChange5.getChangeId()).setPrivate(true, null);
+
+    AbstractFakeIndex idx = (AbstractFakeIndex) changeIndexCollection.getSearchIndex();
+    idx.resetQueryCount();
+    List<ChangeInfo> queryResult = newQuery("status:new").withLimit(2).get();
+    assertThat(queryResult).hasSize(1);
+    assertThat(queryResult.get(0).changeId).isEqualTo(visibleChange1.getKey().get());
+
+    // Since the limit of the query (i.e. 2), 2 index searches are expected in fact:
+    // 1: The first query will return invisibleChange5, invisibleChange4 and invisibleChange3,
+    // 2: Another query is needed to back-fill the limit requested by the user.
+    // even if one result in the second query is skipped because it is not visible,
+    // there are no more results to query.
+    assertThatSearchQueryWasPaginated(idx.getQueryCount(), 2);
+  }
+
+  @Test
+  @UseClockStep
+  @SuppressWarnings("unchecked")
   public void noLimitQueryPaginates() throws Exception {
     assumeFalse(PaginationType.NONE == getCurrentPaginationType());
 
@@ -123,37 +162,7 @@
 
   @Test
   @UseClockStep
-  public void invisibleChangesNotPaginatedWithNonePaginationType() throws Exception {
-    assumeTrue(PaginationType.NONE == getCurrentPaginationType());
-    AbstractFakeIndex idx = setupRepoWithFourChanges();
-    final int LIMIT = 3;
-
-    projectOperations
-        .project(allProjectsName)
-        .forUpdate()
-        .removeAllAccessSections()
-        .add(allow(Permission.READ).ref("refs/*").group(SystemGroupBackend.REGISTERED_USERS))
-        .update();
-
-    // Set queryLimit to 3
-    projectOperations
-        .project(allProjects)
-        .forUpdate()
-        .add(allowCapability(QUERY_LIMIT).group(ANONYMOUS_USERS).range(0, LIMIT))
-        .update();
-
-    requestContext.setContext(anonymousUserProvider::get);
-    List<ChangeInfo> result = newQuery("status:new").withLimit(LIMIT).get();
-    assertThat(result.size()).isEqualTo(0);
-    assertThatSearchQueryWasNotPaginated(idx.getQueryCount());
-    assertThat(idx.getResultsSizes().get(0)).isEqualTo(LIMIT + 1);
-  }
-
-  @Test
-  @UseClockStep
   public void invisibleChangesPaginatedWithPagination() throws Exception {
-    assumeFalse(PaginationType.NONE == getCurrentPaginationType());
-
     AbstractFakeIndex idx = setupRepoWithFourChanges();
     final int LIMIT = 3;
 
diff --git a/javatests/com/google/gerrit/server/query/change/LuceneQueryChangesTest.java b/javatests/com/google/gerrit/server/query/change/LuceneQueryChangesTest.java
index 9717bfb..54d0c87 100644
--- a/javatests/com/google/gerrit/server/query/change/LuceneQueryChangesTest.java
+++ b/javatests/com/google/gerrit/server/query/change/LuceneQueryChangesTest.java
@@ -20,6 +20,7 @@
 import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
 import static com.google.gerrit.testing.GerritJUnit.assertThrows;
 
+import com.google.gerrit.entities.Account;
 import com.google.gerrit.entities.Change;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.server.config.AllProjectsName;
@@ -98,4 +99,27 @@
     Change[] expected = new Change[] {change6, change5, change4, change3, change2, change1};
     assertQuery(newQuery("project:repo").withNoLimit(), expected);
   }
+
+  @Test
+  public void skipChangesNotVisible() throws Exception {
+    // create 1 new change on a repo
+    TestRepository<Repo> repo = createProject("repo");
+    Change visibleChange = insert(repo, newChangeWithStatus(repo, Change.Status.NEW));
+    Change[] expected = new Change[] {visibleChange};
+
+    // pagination does not need to restart the datasource, the request is fulfilled
+    assertQuery(newQuery("status:new").withLimit(1), expected);
+
+    // create 2 new private changes
+    Account.Id user2 =
+        accountManager.authenticate(authRequestFactory.createForUser("anotheruser")).getAccountId();
+
+    Change invisibleChange1 = insert(repo, newChangeWithStatus(repo, Change.Status.NEW), user2);
+    Change invisibleChange2 = insert(repo, newChangeWithStatus(repo, Change.Status.NEW), user2);
+    gApi.changes().id(invisibleChange1.getChangeId()).setPrivate(true, null);
+    gApi.changes().id(invisibleChange2.getChangeId()).setPrivate(true, null);
+
+    // pagination should back-fill when the results skipped because of the visibility
+    assertQuery(newQuery("status:new").withLimit(1), expected);
+  }
 }
diff --git a/javatests/com/google/gerrit/sshd/BUILD b/javatests/com/google/gerrit/sshd/BUILD
index 3e11ff2..44b9c62 100644
--- a/javatests/com/google/gerrit/sshd/BUILD
+++ b/javatests/com/google/gerrit/sshd/BUILD
@@ -4,8 +4,11 @@
     name = "sshd_tests",
     srcs = glob(["**/*.java"]),
     deps = [
+        "//java/com/google/gerrit/entities",
         "//java/com/google/gerrit/extensions:api",
+        "//java/com/google/gerrit/server",
         "//java/com/google/gerrit/sshd",
+        "//java/com/google/gerrit/testing:gerrit-test-util",
         "//lib/mina:sshd",
         "//lib/truth",
     ],
diff --git a/javatests/com/google/gerrit/sshd/SshUtilTest.java b/javatests/com/google/gerrit/sshd/SshUtilTest.java
new file mode 100644
index 0000000..1585bc3
--- /dev/null
+++ b/javatests/com/google/gerrit/sshd/SshUtilTest.java
@@ -0,0 +1,49 @@
+// Copyright (C) 2024 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.sshd;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.server.account.AccountSshKey;
+import java.security.spec.InvalidKeySpecException;
+import org.junit.Test;
+
+public class SshUtilTest {
+  private static final Account.Id TEST_ACCOUNT_ID = Account.id(1);
+  private static final int TEST_SSHKEY_SEQUENCE = 1;
+  private static final String INVALID_ALGO = "invalid-algo";
+  private static final String VALID_OPENSSH_RSA_KEY =
+      "AAAAB3NzaC1yc2EAAAABIwAAAIEA0R66EoZ7hFp81w9sAJqu34UFyE+w36H/mobUqnT5Lns7PcTOJh3sgMJAlswX2lFAWqvF2gd2PRMpMhbfEU4iq2SfY8x+RDCJ4ZQWESln/587T41BlQjOXzu3W1bqgmtHnRCte3DjyWDvM/fucnUMSwOgP+FVEZCLTrk3thLMWsU=";
+  private static final Object VALID_SSH_RSA_ALGO = "ssh-rsa";
+
+  @Test
+  public void shouldFailParsingOpenSshKeyWithInvalidAlgo() {
+    String sshKeyWithInvalidAlgo = String.format("%s %s", INVALID_ALGO, VALID_OPENSSH_RSA_KEY);
+    AccountSshKey sshKey =
+        AccountSshKey.create(TEST_ACCOUNT_ID, TEST_SSHKEY_SEQUENCE, sshKeyWithInvalidAlgo);
+    assertThrows(InvalidKeySpecException.class, () -> SshUtil.parse(sshKey));
+  }
+
+  @Test
+  public void shouldParseSshKeyWithAlgoMatchingKey() {
+    String sshKeyWithValidKeyAlgo =
+        String.format("%s %s", VALID_SSH_RSA_ALGO, VALID_OPENSSH_RSA_KEY);
+    AccountSshKey sshKey =
+        AccountSshKey.create(TEST_ACCOUNT_ID, TEST_SSHKEY_SEQUENCE, sshKeyWithValidKeyAlgo);
+    assertThat(sshKey).isNotNull();
+  }
+}
diff --git a/modules/jgit b/modules/jgit
index acf21c0..c0b415f 160000
--- a/modules/jgit
+++ b/modules/jgit
@@ -1 +1 @@
-Subproject commit acf21c0bc6a63a3d20fca92757b992a1f2d55f41
+Subproject commit c0b415fb028b4c1f29b6df749323bbb11599495d
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-results.ts b/polygerrit-ui/app/elements/checks/gr-checks-results.ts
index 848948f..e1604cf 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-results.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-results.ts
@@ -495,7 +495,7 @@
     return html`
       <div class="label ${status}">
         <span>${label} ${valueStr}</span>
-        <paper-tooltip offset="5" ?fitToVisibleBounds=${true}>
+        <paper-tooltip offset="5" .fitToVisibleBounds=${true}>
           The check result has (probably) influenced this label vote.
         </paper-tooltip>
       </div>
@@ -607,7 +607,7 @@
       @click=${(e: MouseEvent) => this.tagClick(e, tag.name)}
     >
       <span>${tag.name}</span>
-      <paper-tooltip offset="5" ?fitToVisibleBounds=${true}>
+      <paper-tooltip offset="5" .fitToVisibleBounds=${true}>
         ${tag.tooltip ??
         'A category tag for this check result. Click to filter.'}
       </paper-tooltip>
diff --git a/polygerrit-ui/app/elements/checks/gr-checks-results_test.ts b/polygerrit-ui/app/elements/checks/gr-checks-results_test.ts
index 934e958..15176b0 100644
--- a/polygerrit-ui/app/elements/checks/gr-checks-results_test.ts
+++ b/polygerrit-ui/app/elements/checks/gr-checks-results_test.ts
@@ -45,12 +45,7 @@
       /* HTML */ `
         <div class="approved label">
           <span> test-label +1 </span>
-          <paper-tooltip
-            fittovisiblebounds=""
-            offset="5"
-            role="tooltip"
-            tabindex="-1"
-          >
+          <paper-tooltip offset="5" role="tooltip" tabindex="-1">
             The check result has (probably) influenced this label vote.
           </paper-tooltip>
         </div>
@@ -92,7 +87,6 @@
             <button class="tag">
               <span> OBSOLETE </span>
               <paper-tooltip
-                fittovisiblebounds=""
                 offset="5"
                 role="tooltip"
                 tabindex="-1"
@@ -103,7 +97,6 @@
             <button class="tag">
               <span> E2E </span>
               <paper-tooltip
-                fittovisiblebounds=""
                 offset="5"
                 role="tooltip"
                 tabindex="-1"
diff --git a/resources/com/google/gerrit/server/mail/Comment.soy b/resources/com/google/gerrit/server/mail/Comment.soy
index 98ab4b2..6a67398 100644
--- a/resources/com/google/gerrit/server/mail/Comment.soy
+++ b/resources/com/google/gerrit/server/mail/Comment.soy
@@ -29,7 +29,7 @@
   {@param unsatisfiedSubmitRequirements: ?}
   {@param oldSubmitRequirements: ?}
   {@param newSubmitRequirements: ?}
-  {$fromName} has posted comments on this change.
+  {$fromName} has posted comments on this change by {$change.ownerName}.
   {if $email.changeUrl} ( {$email.changeUrl} ){/if}{\n}
   {if $unsatisfiedSubmitRequirements}
     {\n}
diff --git a/tools/deps.bzl b/tools/deps.bzl
index 9a17ca8..5ceae59 100644
--- a/tools/deps.bzl
+++ b/tools/deps.bzl
@@ -115,15 +115,15 @@
 
     maven_jar(
         name = "commons-codec",
-        artifact = "commons-codec:commons-codec:1.10",
-        sha1 = "4b95f4897fa13f2cd904aee711aeafc0c5295cd8",
+        artifact = "commons-codec:commons-codec:1.15",
+        sha1 = "49d94806b6e3dc933dacbd8acb0fdbab8ebd1e5d",
     )
 
     # When upgrading commons-compress, also upgrade tukaani-xz
     maven_jar(
         name = "commons-compress",
-        artifact = "org.apache.commons:commons-compress:1.22",
-        sha1 = "691a8b4e6cf4248c3bc72c8b719337d5cb7359fa",
+        artifact = "org.apache.commons:commons-compress:1.25.0",
+        sha1 = "9d35aec423da6c8a7f93d7e9e1c6b1d9fe14bb5e",
     )
 
     maven_jar(