Merge branch 'stable-3.10'

* stable-3.10:
  Match hashtags suggestions with regex as well
  Only show matching hashtags as suggests
  Do not ignore SubmitRecord status from Prolog rules
  Fix name of `Gerrit-Has-Labels` email footer
  gr-revision-parents: Add missing import for gr-commit-info & gr-button
  Set version to 3.10.1-SNAPSHOT
  Set version to 3.10.0
  Set version to 3.9.6-SNAPSHOT
  Set version to 3.9.5
  WorkQueue: Improve task thread names
  Set version to 3.8-SNAPSHOT
  Set version to 3.8.6
  H2Cache: Set a name for disk cache pruning tasks
  Set version to 3.7.10-SNAPSHOT
  Set version to 3.7.9
  Fix issues with account details filling
  Fix issues with account details filling
  Update Getting Started to Gerrit 3.8.5
  Update Getting Started to Gerrit 3.9.4
  Update Getting Started to Gerrit 3.7.8
  Mention who owns the change in comment emails

Release-Notes: skip
Change-Id: I81eaa2f66be0c02876d6a2e028954e08243e2d29
diff --git a/Documentation/linux-quickstart.txt b/Documentation/linux-quickstart.txt
index 13873ed..67f0565 100644
--- a/Documentation/linux-quickstart.txt
+++ b/Documentation/linux-quickstart.txt
@@ -29,10 +29,10 @@
 . Download the desired Gerrit archive.
 
 To view previous archives, see
-link:https://gerrit-releases.storage.googleapis.com/index.html[Gerrit Code Review: Releases,role=external,window=_blank]. The steps below install Gerrit 3.5.1:
+link:https://gerrit-releases.storage.googleapis.com/index.html[Gerrit Code Review: Releases,role=external,window=_blank]. The steps below install Gerrit 3.9.4:
 
 ....
-wget https://gerrit-releases.storage.googleapis.com/gerrit-3.5.1.war
+wget https://gerrit-releases.storage.googleapis.com/gerrit-3.9.4.war
 ....
 
 NOTE: To build and install Gerrit from the source files, see
diff --git a/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java b/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java
index ddfee38..6b19906 100644
--- a/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java
+++ b/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java
@@ -61,6 +61,24 @@
 class H2CacheFactory extends PersistentCacheBaseFactory implements LifecycleListener {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
+  static class PeriodicCachePruner implements Runnable {
+    private final H2CacheImpl<?, ?> cache;
+
+    PeriodicCachePruner(H2CacheImpl<?, ?> cache) {
+      this.cache = cache;
+    }
+
+    @Override
+    public String toString() {
+      return "Disk Cache Pruner (" + cache.getCacheName() + ")";
+    }
+
+    @Override
+    public void run() {
+      cache.prune();
+    }
+  }
+
   private final List<H2CacheImpl<?, ?>> caches;
   private final DynamicMap<Cache<?, ?>> cacheMap;
   private final ExecutorService executor;
@@ -118,13 +136,13 @@
           if (pruneOnStartup) {
             @SuppressWarnings("unused")
             Future<?> possiblyIgnoredError =
-                cleanup.schedule(() -> cache.prune(), 30, TimeUnit.SECONDS);
+                cleanup.schedule(new PeriodicCachePruner(cache), 30, TimeUnit.SECONDS);
           }
 
           @SuppressWarnings("unused")
           Future<?> possiblyIgnoredError =
               cleanup.scheduleAtFixedRate(
-                  () -> cache.prune(),
+                  new PeriodicCachePruner(cache),
                   schedule.initialDelay(),
                   schedule.interval(),
                   TimeUnit.MILLISECONDS);
diff --git a/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java b/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java
index a869946..c31f58a 100644
--- a/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java
+++ b/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java
@@ -234,6 +234,10 @@
     logger.atFine().log("Finished pruning cache %s...", cacheName);
   }
 
+  String getCacheName() {
+    return cacheName;
+  }
+
   static class ValueHolder<V> {
     final V value;
     final Instant created;
diff --git a/java/com/google/gerrit/server/project/SubmitRequirementsAdapter.java b/java/com/google/gerrit/server/project/SubmitRequirementsAdapter.java
index 7f73cd3..ec376e0 100644
--- a/java/com/google/gerrit/server/project/SubmitRequirementsAdapter.java
+++ b/java/com/google/gerrit/server/project/SubmitRequirementsAdapter.java
@@ -186,7 +186,12 @@
     }
     ImmutableList.Builder<SubmitRequirementResult> result = ImmutableList.builder();
     for (Label label : record.labels) {
-      if (skipSubmitRequirementFor(label)) {
+      if (skipSubmitRequirementFor(label)
+          ||
+          // If SubmitRecord is a PASS, then skip all the requirements
+          // that are not a PASS as they would block the overall submit requirement
+          // status from being a PASS
+          (mapStatus(record) == Status.PASS && mapStatus(label) != Status.PASS)) {
         continue;
       }
       String expressionString = String.format("label:%s=%s", label.label, ruleName);
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/AttentionSetIT.java b/javatests/com/google/gerrit/acceptance/rest/change/AttentionSetIT.java
index c7672d1..92da64e 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/AttentionSetIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/AttentionSetIT.java
@@ -3024,8 +3024,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");
@@ -3110,8 +3110,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())
@@ -3198,8 +3198,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 77841b2..d618e2f 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/ChangeReviewersIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/ChangeReviewersIT.java
@@ -450,7 +450,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/server/project/SubmitRequirementsAdapterTest.java b/javatests/com/google/gerrit/server/project/SubmitRequirementsAdapterTest.java
index 0f4bd2e..e651302 100644
--- a/javatests/com/google/gerrit/server/project/SubmitRequirementsAdapterTest.java
+++ b/javatests/com/google/gerrit/server/project/SubmitRequirementsAdapterTest.java
@@ -337,6 +337,29 @@
   }
 
   @Test
+  public void customSubmitRule_withLabels_withStatusOk() {
+    SubmitRecord submitRecord =
+        createSubmitRecord(
+            "gerrit~PrologRule",
+            Status.OK,
+            Arrays.asList(
+                createLabel("custom-need-label-1", Label.Status.NEED),
+                createLabel("custom-pass-label-2", Label.Status.OK),
+                createLabel("custom-may-label-3", Label.Status.MAY)));
+
+    ImmutableList<SubmitRequirementResult> requirements =
+        SubmitRequirementsAdapter.createResult(submitRecord, labelTypes, psCommitId, false);
+
+    assertThat(requirements).hasSize(1);
+    assertResult(
+        requirements.get(0),
+        /* reqName= */ "custom-pass-label-2",
+        /* submitExpression= */ "label:custom-pass-label-2=gerrit~PrologRule",
+        SubmitRequirementResult.Status.SATISFIED,
+        SubmitRequirementExpressionResult.Status.PASS);
+  }
+
+  @Test
   public void customSubmitRule_withMixOfPassingAndFailingLabels() {
     SubmitRecord submitRecord =
         createSubmitRecord(
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts
index f5e403d..650fe41 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.ts
@@ -1265,6 +1265,7 @@
   private getHashtagSuggestions(
     input: string
   ): Promise<AutocompleteSuggestion[]> {
+    const inputReg = input.startsWith('^') ? new RegExp(input) : null;
     return this.restApiService
       .getChangesWithSimilarHashtag(input, throwingErrorCallback)
       .then(response =>
@@ -1272,6 +1273,9 @@
           .flatMap(change => change.hashtags ?? [])
           .filter(isDefined)
           .filter(unique)
+          .filter(hashtag =>
+            inputReg ? inputReg.test(hashtag) : hashtag.includes(input)
+          )
           .map(hashtag => {
             return {name: hashtag, value: hashtag};
           })
diff --git a/resources/com/google/gerrit/server/mail/Comment.soy b/resources/com/google/gerrit/server/mail/Comment.soy
index 4b621b5..8c1840c 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}