Merge "Improve autocompletion when adding topic"
diff --git a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index eb77677..ec6c7f1 100644
--- a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -1226,7 +1226,7 @@
                 .submittedTogether(EnumSet.copyOf(submittedTogetherOptions))
                 .changes;
 
-    EnumSet enumSetIncludingNonVisibleChanges =
+    EnumSet<SubmittedTogetherOption> enumSetIncludingNonVisibleChanges =
         submittedTogetherOptions.isEmpty()
             ? EnumSet.of(NON_VISIBLE_CHANGES)
             : EnumSet.copyOf(submittedTogetherOptions);
diff --git a/java/com/google/gerrit/entities/Change.java b/java/com/google/gerrit/entities/Change.java
index 7ca235d..66e1a96 100644
--- a/java/com/google/gerrit/entities/Change.java
+++ b/java/com/google/gerrit/entities/Change.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.entities;
 
-import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.gerrit.entities.RefNames.REFS_CHANGES;
 
 import com.google.auto.value.AutoValue;
@@ -109,18 +108,6 @@
     /**
      * Parse a Change.Id out of a string representation.
      *
-     * @deprecated use {@link #tryParse(String)} instead.
-     */
-    @Deprecated
-    public static Id parse(String str) {
-      Integer id = Ints.tryParse(str);
-      checkArgument(id != null, "invalid change ID: %s", str);
-      return Change.id(id);
-    }
-
-    /**
-     * Parse a Change.Id out of a string representation.
-     *
      * @param str the string to parse
      * @return Optional containing the Change.Id, or {@code Optional.empty()} if str does not
      *     represent a valid Change.Id.
diff --git a/java/com/google/gerrit/httpd/raw/IndexServlet.java b/java/com/google/gerrit/httpd/raw/IndexServlet.java
index c7f239d..fcb821e 100644
--- a/java/com/google/gerrit/httpd/raw/IndexServlet.java
+++ b/java/com/google/gerrit/httpd/raw/IndexServlet.java
@@ -38,6 +38,8 @@
 
 public class IndexServlet extends HttpServlet {
   private static final long serialVersionUID = 1L;
+  private static final String POLY_GERRIT_INDEX_HTML_SOY =
+      "com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy";
 
   @Nullable private final String canonicalUrl;
   @Nullable private final String cdnPath;
@@ -60,7 +62,7 @@
     this.experimentFeatures = experimentFeatures;
     this.soySauce =
         SoyFileSet.builder()
-            .add(Resources.getResource("com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy"))
+            .add(Resources.getResource(POLY_GERRIT_INDEX_HTML_SOY), POLY_GERRIT_INDEX_HTML_SOY)
             .build()
             .compileTemplates();
     this.urlOrdainer =
diff --git a/java/com/google/gerrit/metrics/DisabledMetricMaker.java b/java/com/google/gerrit/metrics/DisabledMetricMaker.java
index 5b340fb..234378b 100644
--- a/java/com/google/gerrit/metrics/DisabledMetricMaker.java
+++ b/java/com/google/gerrit/metrics/DisabledMetricMaker.java
@@ -79,7 +79,7 @@
 
   @Override
   public <F1> Timer1<F1> newTimer(String name, Description desc, Field<F1> field1) {
-    return new Timer1<F1>(name, field1) {
+    return new Timer1<>(name, field1) {
       @Override
       protected void doRecord(F1 field1, long value, TimeUnit unit) {}
 
@@ -91,7 +91,7 @@
   @Override
   public <F1, F2> Timer2<F1, F2> newTimer(
       String name, Description desc, Field<F1> field1, Field<F2> field2) {
-    return new Timer2<F1, F2>(name, field1, field2) {
+    return new Timer2<>(name, field1, field2) {
       @Override
       protected void doRecord(F1 field1, F2 field2, long value, TimeUnit unit) {}
 
@@ -103,7 +103,7 @@
   @Override
   public <F1, F2, F3> Timer3<F1, F2, F3> newTimer(
       String name, Description desc, Field<F1> field1, Field<F2> field2, Field<F3> field3) {
-    return new Timer3<F1, F2, F3>(name, field1, field2, field3) {
+    return new Timer3<>(name, field1, field2, field3) {
       @Override
       protected void doRecord(F1 field1, F2 field2, F3 field3, long value, TimeUnit unit) {}
 
diff --git a/java/com/google/gerrit/metrics/dropwizard/TimerImpl1.java b/java/com/google/gerrit/metrics/dropwizard/TimerImpl1.java
index b7d535b..36b52e1 100644
--- a/java/com/google/gerrit/metrics/dropwizard/TimerImpl1.java
+++ b/java/com/google/gerrit/metrics/dropwizard/TimerImpl1.java
@@ -28,7 +28,7 @@
 
   @SuppressWarnings("unchecked")
   Timer1<F1> timer() {
-    return new Timer1<F1>(name, (Field<F1>) fields[0]) {
+    return new Timer1<>(name, (Field<F1>) fields[0]) {
       @Override
       protected void doRecord(F1 field1, long value, TimeUnit unit) {
         total.record(value, unit);
diff --git a/java/com/google/gerrit/metrics/dropwizard/TimerImplN.java b/java/com/google/gerrit/metrics/dropwizard/TimerImplN.java
index dee800e..77ce8cd 100644
--- a/java/com/google/gerrit/metrics/dropwizard/TimerImplN.java
+++ b/java/com/google/gerrit/metrics/dropwizard/TimerImplN.java
@@ -31,7 +31,7 @@
 
   @SuppressWarnings("unchecked")
   <F1, F2> Timer2<F1, F2> timer2() {
-    return new Timer2<F1, F2>(name, (Field<F1>) fields[0], (Field<F2>) fields[1]) {
+    return new Timer2<>(name, (Field<F1>) fields[0], (Field<F2>) fields[1]) {
       @Override
       protected void doRecord(F1 field1, F2 field2, long value, TimeUnit unit) {
         total.record(value, unit);
@@ -47,8 +47,7 @@
 
   @SuppressWarnings("unchecked")
   <F1, F2, F3> Timer3<F1, F2, F3> timer3() {
-    return new Timer3<F1, F2, F3>(
-        name, (Field<F1>) fields[0], (Field<F2>) fields[1], (Field<F3>) fields[2]) {
+    return new Timer3<>(name, (Field<F1>) fields[0], (Field<F2>) fields[1], (Field<F3>) fields[2]) {
       @Override
       protected void doRecord(F1 field1, F2 field2, F3 field3, long value, TimeUnit unit) {
         total.record(value, unit);
diff --git a/java/com/google/gerrit/server/account/AccountsUpdate.java b/java/com/google/gerrit/server/account/AccountsUpdate.java
index 0e643f8..3ee6365 100644
--- a/java/com/google/gerrit/server/account/AccountsUpdate.java
+++ b/java/com/google/gerrit/server/account/AccountsUpdate.java
@@ -583,7 +583,7 @@
     return metaDataUpdate;
   }
 
-  private static void doNothing() {};
+  private static void doNothing() {}
 
   @FunctionalInterface
   private interface ExecutableUpdate {
diff --git a/java/com/google/gerrit/server/account/Realm.java b/java/com/google/gerrit/server/account/Realm.java
index 51c5ecd..ffc95a3 100644
--- a/java/com/google/gerrit/server/account/Realm.java
+++ b/java/com/google/gerrit/server/account/Realm.java
@@ -56,7 +56,14 @@
    */
   Account.Id lookup(String accountName) throws IOException;
 
-  /** Returns true if the account is active. */
+  /**
+   * Returns true if the account is active.
+   *
+   * @throws LoginException thrown if login is required and fails
+   * @throws NamingException may be thrown if the name is invalid
+   * @throws AccountException may be thrown in case the username is ambiguous
+   * @throws IOException thrown in case of IO errors
+   */
   default boolean isActive(@SuppressWarnings("unused") String username)
       throws LoginException, NamingException, AccountException, IOException {
     return true;
@@ -64,7 +71,7 @@
 
   /** Returns true if the account is backed by the realm, false otherwise. */
   default boolean accountBelongsToRealm(
-      @SuppressWarnings("unused") Collection<ExternalId> externalIds) throws IOException {
+      @SuppressWarnings("unused") Collection<ExternalId> externalIds) {
     return false;
   }
 }
diff --git a/java/com/google/gerrit/server/approval/PatchSetApprovalUuidGenerator.java b/java/com/google/gerrit/server/approval/PatchSetApprovalUuidGenerator.java
index e752ec5..128bee4 100644
--- a/java/com/google/gerrit/server/approval/PatchSetApprovalUuidGenerator.java
+++ b/java/com/google/gerrit/server/approval/PatchSetApprovalUuidGenerator.java
@@ -22,17 +22,18 @@
 import java.time.Instant;
 
 /**
- * Generator for {@link PatchSetApproval.UUID}.
+ * Generator for {@link com.google.gerrit.entities.PatchSetApproval.UUID}.
  *
- * <p>Since {@link PatchSetApproval.UUID} must be unique for each granted {@link PatchSetApproval},
- * implementations must generate globally unique UUID for each {@link #get} invocation.
+ * <p>Since {@link com.google.gerrit.entities.PatchSetApproval.UUID} must be unique for each granted
+ * {@link PatchSetApproval}, implementations must generate globally unique UUID for each {@link
+ * #get} invocation.
  */
 @ImplementedBy(PatchSetApprovalUuidGeneratorImpl.class)
 public interface PatchSetApprovalUuidGenerator {
 
   /**
-   * Generates {@link PatchSetApproval.UUID} based on the properties of {@link PatchSetApproval}
-   * that is being granted.
+   * Generates {@link com.google.gerrit.entities.PatchSetApproval.UUID} based on the properties of
+   * {@link PatchSetApproval} that is being granted.
    */
   UUID get(
       PatchSet.Id patchSetId, Account.Id accountId, String label, short value, Instant granted);
diff --git a/java/com/google/gerrit/server/change/IncludedInRefs.java b/java/com/google/gerrit/server/change/IncludedInRefs.java
index f069251..17a0c9d 100644
--- a/java/com/google/gerrit/server/change/IncludedInRefs.java
+++ b/java/com/google/gerrit/server/change/IncludedInRefs.java
@@ -16,11 +16,8 @@
 
 import static java.util.stream.Collectors.toSet;
 
+import com.google.common.collect.ImmutableMap;
 import com.google.gerrit.entities.Project;
-import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.extensions.restapi.BadRequestException;
-import com.google.gerrit.extensions.restapi.ResourceConflictException;
-import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.permissions.PermissionBackend;
 import com.google.gerrit.server.permissions.PermissionBackend.RefFilterOptions;
@@ -28,7 +25,6 @@
 import com.google.inject.Inject;
 import java.io.IOException;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -55,8 +51,7 @@
 
   public Map<String, Set<String>> apply(
       Project.NameKey project, Set<String> commits, Set<String> refNames)
-      throws ResourceConflictException, BadRequestException, IOException,
-          PermissionBackendException, ResourceNotFoundException, AuthException {
+      throws IOException, PermissionBackendException {
     try (Repository repo = repoManager.openRepository(project)) {
       Set<Ref> visibleRefs = getVisibleRefs(repo, refNames, project);
 
@@ -72,7 +67,7 @@
         }
       }
     }
-    return Collections.EMPTY_MAP;
+    return ImmutableMap.of();
   }
 
   private Set<Ref> getVisibleRefs(Repository repo, Set<String> refNames, Project.NameKey project)
diff --git a/java/com/google/gerrit/server/change/RelatedChangesSorter.java b/java/com/google/gerrit/server/change/RelatedChangesSorter.java
index c993c42..719578f 100644
--- a/java/com/google/gerrit/server/change/RelatedChangesSorter.java
+++ b/java/com/google/gerrit/server/change/RelatedChangesSorter.java
@@ -29,7 +29,6 @@
 import com.google.gerrit.entities.PatchSet;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.server.change.RelatedChangesSorter.PatchSetData;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.permissions.ChangePermission;
 import com.google.gerrit.server.permissions.PermissionBackend;
diff --git a/java/com/google/gerrit/server/git/MultiProgressMonitor.java b/java/com/google/gerrit/server/git/MultiProgressMonitor.java
index a49260f..2a57d3d 100644
--- a/java/com/google/gerrit/server/git/MultiProgressMonitor.java
+++ b/java/com/google/gerrit/server/git/MultiProgressMonitor.java
@@ -309,7 +309,7 @@
    * calls {@link #end()}, the future has an additional {@code maxInterval} to finish before it is
    * forcefully cancelled and {@link ExecutionException} is thrown.
    *
-   * @see #waitForNonFinalTask(Future, long, TimeUnit)
+   * @see #waitForNonFinalTask(Future, long, TimeUnit, long, TimeUnit)
    * @param workerFuture a future that returns when worker threads are finished.
    * @param taskTimeoutTime overall timeout for the task; the future gets a cancellation signal
    *     after this timeout is exceeded; non-positive values indicate no timeout.
@@ -350,7 +350,7 @@
   /**
    * Wait for a non-final task managed by a {@link Future}, with no timeout.
    *
-   * @see #waitForNonFinalTask(Future, long, TimeUnit)
+   * @see #waitForNonFinalTask(Future, long, TimeUnit, long, TimeUnit)
    */
   public <T> T waitForNonFinalTask(Future<T> workerFuture) {
     try {
diff --git a/java/com/google/gerrit/server/git/receive/ReplaceOp.java b/java/com/google/gerrit/server/git/receive/ReplaceOp.java
index e843dc8..197183f 100644
--- a/java/com/google/gerrit/server/git/receive/ReplaceOp.java
+++ b/java/com/google/gerrit/server/git/receive/ReplaceOp.java
@@ -406,8 +406,7 @@
     return input;
   }
 
-  private String insertChangeMessage(ChangeUpdate update, ChangeContext ctx, String reviewMessage)
-      throws IOException {
+  private String insertChangeMessage(ChangeUpdate update, ChangeContext ctx, String reviewMessage) {
     String approvalMessage =
         ApprovalsUtil.renderMessageWithApprovals(
             patchSetId.get(), approvals, scanLabels(ctx, approvals));
@@ -451,8 +450,8 @@
     }
   }
 
-  private Map<String, PatchSetApproval> scanLabels(ChangeContext ctx, Map<String, Short> approvals)
-      throws IOException {
+  private Map<String, PatchSetApproval> scanLabels(
+      ChangeContext ctx, Map<String, Short> approvals) {
     Map<String, PatchSetApproval> current = new HashMap<>();
     // We optimize here and only retrieve current when approvals provided
     if (!approvals.isEmpty()) {
diff --git a/java/com/google/gerrit/server/mail/send/OutgoingEmail.java b/java/com/google/gerrit/server/mail/send/OutgoingEmail.java
index 32a8252..bfc1f5b 100644
--- a/java/com/google/gerrit/server/mail/send/OutgoingEmail.java
+++ b/java/com/google/gerrit/server/mail/send/OutgoingEmail.java
@@ -554,6 +554,8 @@
    * Returns whether this email is visible to the given account
    *
    * @param to account.
+   * @throws PermissionBackendException thrown if checking a permission fails due to an error in the
+   *     permission backend
    */
   protected boolean isVisibleTo(Account.Id to) throws PermissionBackendException {
     return true;
diff --git a/java/com/google/gerrit/server/notedb/ChangeNoteJson.java b/java/com/google/gerrit/server/notedb/ChangeNoteJson.java
index 0dfd494..44bb244 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNoteJson.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNoteJson.java
@@ -74,7 +74,7 @@
 
     @Override
     public Optional<Boolean> read(JsonReader in) throws IOException {
-      JsonElement parsed = new JsonParser().parse(in);
+      JsonElement parsed = JsonParser.parseReader(in);
       if (parsed == null) {
         return Optional.empty();
       }
@@ -101,7 +101,7 @@
 
     @Override
     public ObjectId read(JsonReader in) throws IOException {
-      JsonElement parsed = new JsonParser().parse(in);
+      JsonElement parsed = JsonParser.parseReader(in);
       if (parsed.isJsonObject() && isJGitFormat(parsed)) {
         // Some object IDs may have been serialized using the JGit format using the five integers
         // w1, w2, w3, w4, w5. Detect this case so that we can deserialize properly.
diff --git a/java/com/google/gerrit/server/notedb/ChangeNotesParser.java b/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
index 8c876b4..0d59542 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
@@ -1040,7 +1040,7 @@
   }
 
   /**
-   * Identifies the {@link Account.Id} that issued the vote.
+   * Identifies the {@link com.google.gerrit.entities.Account.Id} that issued the vote.
    *
    * <p>There are potentially 3 accounts involved here: 1. The account from the commit, which is the
    * effective IdentifiedUser that produced the update. 2. The account in the label footer itself,
diff --git a/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java b/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java
index 866535b..94e11c8 100644
--- a/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java
+++ b/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java
@@ -469,7 +469,8 @@
    * <p>2. NoteDb rewriters.
    *
    * <p>3. If any of the receive commands is of type {@link
-   * ReceiveCommand.Type#UPDATE_NONFASTFORWARD} (for example due to a force push).
+   * org.eclipse.jgit.transport.ReceiveCommand.Type#UPDATE_NONFASTFORWARD} (for example due to a
+   * force push).
    *
    * <p>Note that we don't need to explicitly allow non fast-forward updates for DELETE commands
    * since JGit forces the update implicitly in this case.
diff --git a/java/com/google/gerrit/server/project/SubmitRequirementsUtil.java b/java/com/google/gerrit/server/project/SubmitRequirementsUtil.java
index 4ea3bf5..e34ab1d 100644
--- a/java/com/google/gerrit/server/project/SubmitRequirementsUtil.java
+++ b/java/com/google/gerrit/server/project/SubmitRequirementsUtil.java
@@ -37,8 +37,8 @@
 
   @Singleton
   static class Metrics {
-    final Counter2 submitRequirementsMatchingWithLegacy;
-    final Counter2 submitRequirementsMismatchingWithLegacy;
+    final Counter2<String, String> submitRequirementsMatchingWithLegacy;
+    final Counter2<String, String> submitRequirementsMismatchingWithLegacy;
 
     @Inject
     Metrics(MetricMaker metricMaker) {
diff --git a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index 28c4da3..e5f6d32 100644
--- a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -935,37 +935,37 @@
   }
 
   @Operator
-  public Predicate<ChangeData> ext(String ext) throws QueryParseException {
+  public Predicate<ChangeData> ext(String ext) {
     return extension(ext);
   }
 
   @Operator
-  public Predicate<ChangeData> extension(String ext) throws QueryParseException {
+  public Predicate<ChangeData> extension(String ext) {
     return new FileExtensionPredicate(ext);
   }
 
   @Operator
-  public Predicate<ChangeData> onlyexts(String extList) throws QueryParseException {
+  public Predicate<ChangeData> onlyexts(String extList) {
     return onlyextensions(extList);
   }
 
   @Operator
-  public Predicate<ChangeData> onlyextensions(String extList) throws QueryParseException {
+  public Predicate<ChangeData> onlyextensions(String extList) {
     return new FileExtensionListPredicate(extList);
   }
 
   @Operator
-  public Predicate<ChangeData> footer(String footer) throws QueryParseException {
+  public Predicate<ChangeData> footer(String footer) {
     return ChangePredicates.footer(footer);
   }
 
   @Operator
-  public Predicate<ChangeData> dir(String directory) throws QueryParseException {
+  public Predicate<ChangeData> dir(String directory) {
     return directory(directory);
   }
 
   @Operator
-  public Predicate<ChangeData> directory(String directory) throws QueryParseException {
+  public Predicate<ChangeData> directory(String directory) {
     if (directory.startsWith("^")) {
       return new RegexDirectoryPredicate(directory);
     }
@@ -1525,7 +1525,7 @@
   }
 
   @Operator
-  public Predicate<ChangeData> submissionId(String value) throws QueryParseException {
+  public Predicate<ChangeData> submissionId(String value) {
     return ChangePredicates.submissionId(value);
   }
 
diff --git a/java/com/google/gerrit/server/query/change/LabelPredicate.java b/java/com/google/gerrit/server/query/change/LabelPredicate.java
index 2e09075..2a5a47d 100644
--- a/java/com/google/gerrit/server/query/change/LabelPredicate.java
+++ b/java/com/google/gerrit/server/query/change/LabelPredicate.java
@@ -214,6 +214,8 @@
       case LESS_EQUAL:
         result.add(count);
         break;
+      case GREATER:
+      case LESS:
       default:
         break;
     }
@@ -226,6 +228,7 @@
       case LESS_EQUAL:
         IntStream.range(0, count).forEach(result::add);
         break;
+      case EQUAL:
       default:
         break;
     }
diff --git a/java/com/google/gerrit/server/query/change/PredicateArgs.java b/java/com/google/gerrit/server/query/change/PredicateArgs.java
index 9f0dffb..ebe4390 100644
--- a/java/com/google/gerrit/server/query/change/PredicateArgs.java
+++ b/java/com/google/gerrit/server/query/change/PredicateArgs.java
@@ -50,7 +50,7 @@
     Operator(String op) {
       this.op = op;
     }
-  };
+  }
 
   @AutoValue
   public abstract static class ValOp {
diff --git a/java/com/google/gerrit/server/restapi/change/PostReview.java b/java/com/google/gerrit/server/restapi/change/PostReview.java
index d4e6205..6a89247 100644
--- a/java/com/google/gerrit/server/restapi/change/PostReview.java
+++ b/java/com/google/gerrit/server/restapi/change/PostReview.java
@@ -1409,7 +1409,7 @@
     }
 
     private boolean updateLabels(ProjectState projectState, ChangeContext ctx)
-        throws ResourceConflictException, IOException {
+        throws ResourceConflictException {
       Map<String, Short> inLabels = firstNonNull(in.labels, Collections.emptyMap());
 
       // If no labels were modified and change is closed, abort early.
@@ -1576,8 +1576,7 @@
     }
 
     private Map<String, PatchSetApproval> scanLabels(
-        ProjectState projectState, ChangeContext ctx, List<PatchSetApproval> del)
-        throws IOException {
+        ProjectState projectState, ChangeContext ctx, List<PatchSetApproval> del) {
       LabelTypes labelTypes = projectState.getLabelTypes(ctx.getNotes());
       Map<String, PatchSetApproval> current = new HashMap<>();
 
diff --git a/java/com/google/gerrit/server/submit/SubmitStrategyOp.java b/java/com/google/gerrit/server/submit/SubmitStrategyOp.java
index de10fe7..d06940c 100644
--- a/java/com/google/gerrit/server/submit/SubmitStrategyOp.java
+++ b/java/com/google/gerrit/server/submit/SubmitStrategyOp.java
@@ -327,7 +327,7 @@
         ctx.getRevWalk(), ctx.getUpdate(psId), psId, alreadyMergedCommit, groups, null, null);
   }
 
-  private void setApproval(ChangeContext ctx, IdentifiedUser user) throws IOException {
+  private void setApproval(ChangeContext ctx, IdentifiedUser user) {
     Change.Id id = ctx.getChange().getId();
     List<SubmitRecord> records = args.commitStatus.getSubmitRecords(id);
     PatchSet.Id oldPsId = toMerge.getPatchsetId();
@@ -518,19 +518,29 @@
     }
   }
 
-  /** See {@link #updateRepo(RepoContext)} */
+  /**
+   * See {@link #updateRepo(RepoContext)}
+   *
+   * @param ctx context for the repository update
+   */
   protected void updateRepoImpl(RepoContext ctx) throws Exception {}
 
   /**
    * Returns a new patch set if one was created by the submit strategy, or null if not
    *
    * <p>See {@link #updateChange(ChangeContext)}
+   *
+   * @param ctx context for the change update
    */
   protected PatchSet updateChangeImpl(ChangeContext ctx) throws Exception {
     return null;
   }
 
-  /** See {@link #postUpdate(PostUpdateContext)} */
+  /**
+   * See {@link #postUpdate(PostUpdateContext)}
+   *
+   * @param ctx context for the post update
+   */
   protected void postUpdateImpl(PostUpdateContext ctx) throws Exception {}
 
   /** Amend the commit with gitlink update */
diff --git a/javatests/com/google/gerrit/acceptance/server/project/SubmitRequirementsEvaluatorIT.java b/javatests/com/google/gerrit/acceptance/server/project/SubmitRequirementsEvaluatorIT.java
index fb51ae0..61e204f 100644
--- a/javatests/com/google/gerrit/acceptance/server/project/SubmitRequirementsEvaluatorIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/project/SubmitRequirementsEvaluatorIT.java
@@ -354,7 +354,7 @@
     ChangeInfo changeInfo = gApi.changes().id(changeId).revert().get();
     String revertId = Integer.toString(changeInfo._number);
     ChangeData revertChangeData =
-        changeQueryProvider.get().byLegacyChangeId(Change.Id.parse(revertId)).get(0);
+        changeQueryProvider.get().byLegacyChangeId(Change.Id.tryParse(revertId).get()).get(0);
     result = evaluator.evaluateRequirement(sr, revertChangeData);
     assertThat(result.status()).isEqualTo(SubmitRequirementResult.Status.SATISFIED);
   }
diff --git a/javatests/com/google/gerrit/acceptance/ssh/AbstractIndexTests.java b/javatests/com/google/gerrit/acceptance/ssh/AbstractIndexTests.java
index 3b38bad..18c4952 100644
--- a/javatests/com/google/gerrit/acceptance/ssh/AbstractIndexTests.java
+++ b/javatests/com/google/gerrit/acceptance/ssh/AbstractIndexTests.java
@@ -29,7 +29,6 @@
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.inject.Inject;
-import com.google.inject.Injector;
 import java.util.List;
 import org.junit.Test;
 
@@ -38,15 +37,12 @@
 public abstract class AbstractIndexTests extends AbstractDaemonTest {
   @Inject private ExtensionRegistry extensionRegistry;
 
-  public void configureIndex(Injector injector) {}
-
   @Test
   @GerritConfig(name = "index.autoReindexIfStale", value = "false")
   public void indexChange() throws Exception {
     ChangeIndexedCounter changeIndexedCounter = new ChangeIndexedCounter();
     try (Registration registration =
         extensionRegistry.newRegistration().add(changeIndexedCounter)) {
-      configureIndex(server.getTestInjector());
 
       PushOneCommit.Result change = createChange("first change", "test1.txt", "test1");
       String changeId = change.getChangeId();
@@ -76,7 +72,6 @@
     ChangeIndexedCounter changeIndexedCounter = new ChangeIndexedCounter();
     try (Registration registration =
         extensionRegistry.newRegistration().add(changeIndexedCounter)) {
-      configureIndex(server.getTestInjector());
 
       PushOneCommit.Result change = createChange("first change", "test1.txt", "test1");
       String changeId = change.getChangeId();
diff --git a/javatests/com/google/gerrit/server/patch/DiffOperationsTest.java b/javatests/com/google/gerrit/server/patch/DiffOperationsTest.java
index 9f0fc29..fa04cf8 100644
--- a/javatests/com/google/gerrit/server/patch/DiffOperationsTest.java
+++ b/javatests/com/google/gerrit/server/patch/DiffOperationsTest.java
@@ -228,7 +228,7 @@
     enum FileType {
       REGULAR,
       SYMLINK
-    };
+    }
 
     FileEntity(String name, String content) {
       this(name, content, FileType.REGULAR);
diff --git a/plugins/replication b/plugins/replication
index e19028c..98926b4 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit e19028c4f7e5d26bbc4be7762aa96434bf1d7781
+Subproject commit 98926b44a199b5a7049232f6c3b3758267368f8f
diff --git a/plugins/webhooks b/plugins/webhooks
index 7830135..d8815bf 160000
--- a/plugins/webhooks
+++ b/plugins/webhooks
@@ -1 +1 @@
-Subproject commit 7830135fd85ff9de10a8ca1f2e6112af59fca15f
+Subproject commit d8815bf9660b6655696db242b8ad2801e866c036
diff --git a/polygerrit-ui/app/BUILD b/polygerrit-ui/app/BUILD
index c721c7d..14f1e95 100644
--- a/polygerrit-ui/app/BUILD
+++ b/polygerrit-ui/app/BUILD
@@ -268,13 +268,17 @@
     ],
 )
 
-nodejs_binary(
+nodejs_test(
     name = "lit_analysis",
     data = [
         ":lit_analysis_src_code",
         "@npm//lit-analyzer",
     ],
     entry_point = "@npm//:node_modules/lit-analyzer/cli.js",
+    tags = [
+        "local",
+        "manual",
+    ],
     templated_args = [
         "**/elements/**/*.ts",
         "--strict",
diff --git a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.ts b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.ts
index dddd6a4..f892410 100644
--- a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.ts
+++ b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.ts
@@ -559,13 +559,19 @@
    */
   _notify(state: SyntaxLayerState) {
     if (state.lineNums.left - state.lastNotify.left) {
-      this._notifyRange(state.lastNotify.left, state.lineNums.left, Side.LEFT);
+      this._notifyRange(
+        state.lastNotify.left,
+        // We have to notify 1-based inclusive, so subtract 1.
+        state.lineNums.left - 1,
+        Side.LEFT
+      );
       state.lastNotify.left = state.lineNums.left;
     }
     if (state.lineNums.right - state.lastNotify.right) {
       this._notifyRange(
         state.lastNotify.right,
-        state.lineNums.right,
+        // We have to notify 1-based inclusive, so subtract 1.
+        state.lineNums.right - 1,
         Side.RIGHT
       );
       state.lastNotify.right = state.lineNums.right;
diff --git a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.js b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.js
index c907a80..2b03bd3 100644
--- a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.js
+++ b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.js
@@ -171,6 +171,7 @@
     window.hljs = mockHLJS;
     const highlightSpy = sinon.spy(mockHLJS, 'highlight');
     const processNextSpy = sinon.spy(element, '_processNextLine');
+    const notifyRangeSpy = sinon.spy(element, '_notifyRange');
     await element.process();
 
     const linesA = diff.meta_a.lines;
@@ -182,6 +183,11 @@
 
     assert.equal(highlightSpy.callCount, linesA + linesB);
 
+    assert.isTrue(notifyRangeSpy.called);
+    assert.equal(notifyRangeSpy.lastCall.args[0], 44);
+    assert.equal(notifyRangeSpy.lastCall.args[1], 48);
+    assert.equal(notifyRangeSpy.lastCall.args[2], 'right');
+
     // The first line of both sides have a range.
     let ranges = [element.baseRanges[0], element.revisionRanges[0]];
     for (const range of ranges) {
diff --git a/polygerrit-ui/app/types/types.ts b/polygerrit-ui/app/types/types.ts
index 42f3d45..c6137eb 100644
--- a/polygerrit-ui/app/types/types.ts
+++ b/polygerrit-ui/app/types/types.ts
@@ -171,7 +171,9 @@
 }
 
 export type DiffLayerListener = (
+  /** 1-based inclusive */
   start: number,
+  /** 1-based inclusive */
   end: number,
   side: Side
 ) => void;
diff --git a/tools/node_tools/launchpad.patch b/tools/node_tools/launchpad.patch
deleted file mode 100644
index 565494b..0000000
--- a/tools/node_tools/launchpad.patch
+++ /dev/null
@@ -1,240 +0,0 @@
-From d430b5d912bebe87529b887f408ee55c82a0e003 Mon Sep 17 00:00:00 2001
-From: Michele Romano <33063403+Mik317@users.noreply.github.com>
-Date: Fri, 26 Jun 2020 20:16:47 +0200
-Subject: [PATCH 1/7] Update version.js
-
----
- lib/local/version.js | 15 ++++++++++++---
- 1 file changed, 12 insertions(+), 3 deletions(-)
-
-diff --git a/tools/node_tools/node_modules/launchpad/lib/local/version.js b/tools/node_tools/node_modules/launchpad/lib/g/local/version.js
-index 0110a74..2c02bef 100644
---- a/tools/node_tools/node_modules/launchpad/lib/local/version.js
-+++ b/tools/node_tools/node_modules/launchpad/lib/local/version.js
-@@ -6,6 +6,15 @@ var plist = require('plist');
- var utils = require('./utils');
- var debug = require('debug')('launchpad:local:version');
- 
-+var validPath = function (filename){
-+  var filter = /[`!@#$%^&*()_+\-=\[\]{};':"\\|,<>\/?~]/;
-+  if (filter.test(filename)){
-+    console.log('\nInvalid characters inside the path to the browser\n');
-+    return
-+  }
-+  return filename;
-+}
-+
- module.exports = function(browser) {
-   if (!browser || !browser.path) {
-     return Q(null);
-@@ -18,7 +27,7 @@ module.exports = function(browser) {
- 
-     debug('Retrieving version for windows executable', command);
-     // Can't use Q.nfcall here unfortunately because of non 0 exit code
--    exec(command, function(error, stdout) {
-+    exec(command.split(' ')[0], command.split(' ').slice(1), function(error, stdout) {
-       var regex = /ProductVersion:\s*(.*)/;
-       // ShowVer.exe returns a non zero status code even if it works
-       if (typeof stdout === 'string' && regex.test(stdout)) {
-@@ -47,8 +56,8 @@ module.exports = function(browser) {
-   }
- 
-   // Try executing <browser> --version (everything else)
--  return Q.nfcall(exec, browser.path + ' --version').then(function(stdout) {
--    debug('Ran ' + browser.path + ' --version', stdout);
-+  return Q.nfcall(exec, validPath(browser.path) + ' --version').then(function(stdout) {
-+    debug('Ran ' + validPath(browser.path) + ' --version', stdout);
-     var version = utils.getStdout(stdout);
-     if (version) {
-       browser.version = version;
-
-From 09ce4fab2fd53cab893ceaa3b4d7f997af9b41d8 Mon Sep 17 00:00:00 2001
-From: Michele Romano <33063403+Mik317@users.noreply.github.com>
-Date: Fri, 26 Jun 2020 20:18:35 +0200
-Subject: [PATCH 2/7] Update instance.js
-
----
- lib/local/instance.js | 11 +++++++++--
- 1 file changed, 9 insertions(+), 2 deletions(-)
-
-diff --git a/tools/node_tools/node_modules/launchpad/lib/local/instance.js b/tools/node_tools/node_modules/launchpad/lib/g/local/instance.js
-index 484a866..b49990f 100644
---- a/tools/node_tools/node_modules/launchpad/lib/local/instance.js
-+++ b/tools/node_tools/node_modules/launchpad/lib/local/instance.js
-@@ -5,8 +5,15 @@ var EventEmitter = require('events').EventEmitter;
- var debug = require('debug')('launchpad:local:instance');
- var rimraf = require('rimraf');
- 
-+var safe = function (str) {
-+   // Avoid quotes makes impossible escape the `multi command` scenario
-+   return str.replace(/['"]+/g, '');
-+}
-+
- var getProcessId = function (name, callback) {
- 
-+  name = safe(name);
-+
-   var commands = {
-     darwin: "ps -clx | grep '" + name + "$' | awk '{print $2}' | head -1",
-     linux: "ps -ax | grep '" + name + "$' | awk '{print $2}' | head -1",
-@@ -90,11 +97,11 @@ Instance.prototype.stop = function (callback) {
-     } catch (error) {}
-   } else {
-     if (this.options.command.indexOf('open') === 0) {
--      command = 'osascript -e \'tell application "' + self.options.process + '" to quit\'';
-+      command = 'osascript -e \'tell application "' + safe(self.options.process) + '" to quit\'';
-       debug('Executing shutdown AppleScript', command);
-       exec(command);
-     } else if (process.platform === 'win32') {
--      command = 'taskkill /IM ' + (this.options.imageName || path.basename(this.cmd));
-+      command = 'taskkill /IM "' + safe(this.options.imageName || path.basename(this.cmd)) + '"';
-       debug('Executing shutdown taskkil', command);
-       exec(command).once('exit', function(data) {
-         self.emit('stop', data);
-
-From d3993fce090ed6ef378c1f0594eff18d125dad1e Mon Sep 17 00:00:00 2001
-From: Michele Romano <33063403+Mik317@users.noreply.github.com>
-Date: Fri, 26 Jun 2020 20:19:17 +0200
-Subject: [PATCH 3/7] Update version.js
-
----
- lib/local/version.js | 1 +
- 1 file changed, 1 insertion(+)
-
-diff --git a/tools/node_tools/node_modules/launchpad/lib/local/version.js b/tools/node_tools/node_modules/launchpad/lib/g/local/version.js
-index 2c02bef..5eac082 100644
---- a/tools/node_tools/node_modules/launchpad/lib/local/version.js
-+++ b/tools/node_tools/node_modules/launchpad/lib/local/version.js
-@@ -6,6 +6,7 @@ var plist = require('plist');
- var utils = require('./utils');
- var debug = require('debug')('launchpad:local:version');
- 
-+// Validate paths supplied by the user in order to avoid "arbitrary command execution"
- var validPath = function (filename){
-   var filter = /[`!@#$%^&*()_+\-=\[\]{};':"\\|,<>\/?~]/;
-   if (filter.test(filename)){
-
-From abf3dbcc79e6b338338594ab2dbef834550e8f65 Mon Sep 17 00:00:00 2001
-From: Michele Romano <33063403+Mik317@users.noreply.github.com>
-Date: Mon, 29 Jun 2020 13:32:50 +0200
-Subject: [PATCH 4/7] Update instance.js
-
----
- lib/local/instance.js | 10 +++++++---
- 1 file changed, 7 insertions(+), 3 deletions(-)
-
-diff --git a/tools/node_tools/node_modules/launchpad/lib/local/instance.js b/tools/node_tools/node_modules/launchpad/lib/g/local/instance.js
-index b49990f..9375d1f 100644
---- a/tools/node_tools/node_modules/launchpad/lib/local/instance.js
-+++ b/tools/node_tools/node_modules/launchpad/lib/local/instance.js
-@@ -1,6 +1,7 @@
- var path = require('path');
- var spawn = require("child_process").spawn;
- var exec = require("child_process").exec;
-+var execFile = require("child_process").execFile;
- var EventEmitter = require('events').EventEmitter;
- var debug = require('debug')('launchpad:local:instance');
- var rimraf = require('rimraf');
-@@ -99,11 +100,14 @@ Instance.prototype.stop = function (callback) {
-     if (this.options.command.indexOf('open') === 0) {
-       command = 'osascript -e \'tell application "' + safe(self.options.process) + '" to quit\'';
-       debug('Executing shutdown AppleScript', command);
--      exec(command);
-+      command = command.split(' ');
-+      execFile(command[0], command.slice(1));
-     } else if (process.platform === 'win32') {
--      command = 'taskkill /IM "' + safe(this.options.imageName || path.basename(this.cmd)) + '"';
-+      //Adding `"` wasn't safe/functional on Win systems
-+      command = 'taskkill /IM ' + (this.options.imageName || path.basename(this.cmd); 
-       debug('Executing shutdown taskkil', command);
--      exec(command).once('exit', function(data) {
-+      command = command.split(' ');
-+      execFile(command[0], command.slice(1)).once('exit', function(data) {
-         self.emit('stop', data);
-       });
-     } else {
-
-From 68518b274c9351f799d41ce85f23499ca4a785e9 Mon Sep 17 00:00:00 2001
-From: Michele Romano <33063403+Mik317@users.noreply.github.com>
-Date: Tue, 30 Jun 2020 00:01:31 +0200
-Subject: [PATCH 5/7] Update instance.js
-
----
- lib/local/instance.js | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/tools/node_tools/node_modules/launchpad/lib/local/instance.js b/tools/node_tools/node_modules/launchpad/lib/g/local/instance.js
-index 9375d1f..f157dd4 100644
---- a/tools/node_tools/node_modules/launchpad/lib/local/instance.js
-+++ b/tools/node_tools/node_modules/launchpad/lib/local/instance.js
-@@ -104,7 +104,7 @@ Instance.prototype.stop = function (callback) {
-       execFile(command[0], command.slice(1));
-     } else if (process.platform === 'win32') {
-       //Adding `"` wasn't safe/functional on Win systems
--      command = 'taskkill /IM ' + (this.options.imageName || path.basename(this.cmd); 
-+      command = 'taskkill /IM ' + (this.options.imageName || path.basename(this.cmd)); 
-       debug('Executing shutdown taskkil', command);
-       command = command.split(' ');
-       execFile(command[0], command.slice(1)).once('exit', function(data) {
-
-From e711d07d40d39162ea4bdb1ed344c58f92bfa10b Mon Sep 17 00:00:00 2001
-From: Michele Romano <33063403+Mik317@users.noreply.github.com>
-Date: Fri, 3 Jul 2020 12:30:31 +0200
-Subject: [PATCH 6/7] Update version.js
-
----
- lib/local/version.js | 5 +++--
- 1 file changed, 3 insertions(+), 2 deletions(-)
-
-diff --git a/tools/node_tools/node_modules/launchpad/lib/local/version.js b/tools/node_tools/node_modules/launchpad/lib/g/local/version.js
-index 5eac082..d1403a0 100644
---- a/tools/node_tools/node_modules/launchpad/lib/local/version.js
-+++ b/tools/node_tools/node_modules/launchpad/lib/local/version.js
-@@ -1,5 +1,6 @@
- var fs = require('fs');
- var exec = require('child_process').exec;
-+var execFile = require('child_process').execFile;
- var Q = require('q');
- var path = require('path');
- var plist = require('plist');
-@@ -8,7 +9,7 @@ var debug = require('debug')('launchpad:local:version');
- 
- // Validate paths supplied by the user in order to avoid "arbitrary command execution"
- var validPath = function (filename){
--  var filter = /[`!@#$%^&*()_+\-=\[\]{};':"\\|,<>\/?~]/;
-+  var filter = /[`!@#$%^&*()_+\-=\[\]{};':"|,<>?~]/;
-   if (filter.test(filename)){
-     console.log('\nInvalid characters inside the path to the browser\n');
-     return
-@@ -28,7 +29,7 @@ module.exports = function(browser) {
- 
-     debug('Retrieving version for windows executable', command);
-     // Can't use Q.nfcall here unfortunately because of non 0 exit code
--    exec(command.split(' ')[0], command.split(' ').slice(1), function(error, stdout) {
-+    execFile(command.split(' ')[0], command.split(' ').slice(1), function(error, stdout) {
-       var regex = /ProductVersion:\s*(.*)/;
-       // ShowVer.exe returns a non zero status code even if it works
-       if (typeof stdout === 'string' && regex.test(stdout)) {
-
-From a3ff1804f0aacfb4fa20dad1312427b81280bb3e Mon Sep 17 00:00:00 2001
-From: Michele Romano <33063403+Mik317@users.noreply.github.com>
-Date: Fri, 3 Jul 2020 12:31:31 +0200
-Subject: [PATCH 7/7] Update version.js
-
----
- lib/local/version.js | 2 +-
- 1 file changed, 1 insertion(+), 1 deletion(-)
-
-diff --git a/tools/node_tools/node_modules/launchpad/lib/local/version.js b/tools/node_tools/node_modules/launchpad/lib/g/local/version.js
-index d1403a0..d937be4 100644
---- a/tools/node_tools/node_modules/launchpad/lib/local/version.js
-+++ b/tools/node_tools/node_modules/launchpad/lib/local/version.js
-@@ -9,7 +9,7 @@ var debug = require('debug')('launchpad:local:version');
- 
- // Validate paths supplied by the user in order to avoid "arbitrary command execution"
- var validPath = function (filename){
--  var filter = /[`!@#$%^&*()_+\-=\[\]{};':"|,<>?~]/;
-+  var filter = /[`!@#$%^&*()_+\-=\[\]{};'"|,<>?~]/;
-   if (filter.test(filename)){
-     console.log('\nInvalid characters inside the path to the browser\n');
-     return
diff --git a/tools/node_tools/package.json b/tools/node_tools/package.json
index 7ee64df..33f9234 100644
--- a/tools/node_tools/package.json
+++ b/tools/node_tools/package.json
@@ -19,12 +19,11 @@
     "typescript": "4.3.2"
   },
   "devDependencies": {},
-  "scripts": {
-    "postinstall": "(git apply --reverse --ignore-whitespace launchpad.patch || true) && git apply --ignore-whitespace launchpad.patch"
-  },
   "license": "Apache-2.0",
   "private": true,
   "resolutions": {
-    "lodash": "4.17.21"
+    "lodash": "4.17.21",
+    "wct-local": "2.1.6",
+    "launchpad": "git+https://github.com/418sec/launchpad.git#de5aca11dc16a8e530195281c77614bdbb08e7be"
   }
-}
+}
\ No newline at end of file
diff --git a/tools/node_tools/yarn.lock b/tools/node_tools/yarn.lock
index 6525c41..6e146ea 100644
--- a/tools/node_tools/yarn.lock
+++ b/tools/node_tools/yarn.lock
@@ -5341,10 +5341,9 @@
   dependencies:
     package-json "^4.0.0"
 
-launchpad@^0.7.0:
+"launchpad@git+https://github.com/418sec/launchpad.git#de5aca11dc16a8e530195281c77614bdbb08e7be", "launchpad@git://github.com/418sec/launchpad.git#de5aca11dc16a8e530195281c77614bdbb08e7be":
   version "0.7.5"
-  resolved "https://registry.yarnpkg.com/launchpad/-/launchpad-0.7.5.tgz#a16950c937572f10ef01c9be945a96f7aef8e427"
-  integrity sha512-gsYFgT8XKL3X2XZHPPPrgwM0JqeQwGpSWnzg7EYadBY3MirbQrTVq6L4fm6l7UE2T+7gnfuhiGkKr/xxuU/fdw==
+  resolved "git+https://github.com/418sec/launchpad.git#de5aca11dc16a8e530195281c77614bdbb08e7be"
   dependencies:
     async "^2.0.1"
     browserstack "^1.2.0"
@@ -8910,10 +8909,10 @@
   dependencies:
     minimalistic-assert "^1.0.0"
 
-wct-local@^2.1.1:
-  version "2.1.5"
-  resolved "https://registry.yarnpkg.com/wct-local/-/wct-local-2.1.5.tgz#f7986753e3ad9a35d39178a9989350523561fff1"
-  integrity sha512-eqoZhjGy4Xq2tY0uB46Grkw/ztq+/rC0ImbYKl62unFHXtOgal+kkvnxR3SLRFNM8ty9+ItgycPeH0IpTqVL+w==
+wct-local@2.1.6, wct-local@^2.1.1:
+  version "2.1.6"
+  resolved "https://registry.yarnpkg.com/wct-local/-/wct-local-2.1.6.tgz#2d099c52996e77265d16e03a5d6d897b77ea9967"
+  integrity sha512-jvTzgOIIfJ43H3DXUfruHPTQ/TJ269SDk4R2CfCpU13EYbwxn3U1B6L5NHYRFu/cgdJmOHraGrn/wREHH6xeXQ==
   dependencies:
     "@types/express" "^4.0.30"
     "@types/freeport" "^1.0.19"
@@ -8922,7 +8921,7 @@
     chalk "^2.3.0"
     cleankill "^2.0.0"
     freeport "^1.0.4"
-    launchpad "^0.7.0"
+    launchpad "git://github.com/418sec/launchpad.git#de5aca11dc16a8e530195281c77614bdbb08e7be"
     selenium-standalone "^6.7.0"
     which "^1.0.8"