Merge branch 'stable-3.9'

* stable-3.9:
  GroupCacheImpl: Fix timer to include the correct method
  Simplify running commit-msg tests
  Fix `COMMENT` route for change numbers with unknown repo
  Propagate delete draft ref-updates upon publishing
  Make the indexing operation fail upon StorageException(s)
  ConsistencyCheckerIT: Delete calls to index broken changes that fail silently
  RefAdvertisementIT: Don't call reindex that would fail silently
  Set version to 3.9.2-SNAPSHOT
  Set version to 3.9.1
  Bazel: Remove explicit setting of java.security.manager on JDK 17
  Bazel: Fix dev-bazel docs
  Remove `copy-approvals` command reference from Gerrit commands
  MagicLabelPredicates.ignoresUploaderApprovals: Fix NPE when account is null
  H2CacheImpl: Log when disk cache pruning runs
  MergeUtil: Create an optimized canFastForwardOrMerge

Release-Notes: skip
Forward-Compatible: checked
Change-Id: Iabeca7c3cf8de3a0dce7797948afde046461ee6b
diff --git a/BUILD b/BUILD
index 984fd955..0c10d76 100644
--- a/BUILD
+++ b/BUILD
@@ -3,13 +3,6 @@
 
 package(default_visibility = ["//visibility:public"])
 
-config_setting(
-    name = "java17",
-    values = {
-        "java_language_version": "17",
-    },
-)
-
 genrule(
     name = "gen_version",
     outs = ["version.txt"],
diff --git a/Documentation/cmd-index.txt b/Documentation/cmd-index.txt
index c959a07..b92a89d 100644
--- a/Documentation/cmd-index.txt
+++ b/Documentation/cmd-index.txt
@@ -58,9 +58,6 @@
 link:cmd-ban-commit.html[gerrit ban-commit]::
 	Bans a commit from a project's repository.
 
-link:cmd-copy-approvals.html[gerrit copy-approvals]::
-	Copy all inferred approvals labels to the latest patch-set.
-
 link:cmd-check-project-access.html[gerrit check-project-access]::
 	Check if user(s) can read non-config refs of a project
 
diff --git a/java/com/google/gerrit/index/Schema.java b/java/com/google/gerrit/index/Schema.java
index 71e1571..ab10d9e 100644
--- a/java/com/google/gerrit/index/Schema.java
+++ b/java/com/google/gerrit/index/Schema.java
@@ -283,22 +283,19 @@
   /**
    * Build all fields in the schema from an input object.
    *
-   * <p>Null values are omitted, as are fields which cause errors, which are logged.
+   * <p>Null values are omitted, as are fields which cause errors, which are logged. If any of the
+   * fields cause a StorageException, the whole operation fails and the exception is propagated to
+   * the caller.
    *
    * @param obj input object.
    * @param skipFields set of field names to skip when indexing the document
    * @return all non-null field values from the object.
    */
   public final ImmutableList<Values<T>> buildFields(T obj, ImmutableSet<String> skipFields) {
-    try {
-      return schemaFields.values().stream()
-          .map(f -> fieldValues(obj, f, skipFields))
-          .filter(Objects::nonNull)
-          .collect(toImmutableList());
-
-    } catch (StorageException e) {
-      return ImmutableList.of();
-    }
+    return schemaFields.values().stream()
+        .map(f -> fieldValues(obj, f, skipFields))
+        .filter(Objects::nonNull)
+        .collect(toImmutableList());
   }
 
   @Override
diff --git a/java/com/google/gerrit/server/ChangeDraftUpdateExecutor.java b/java/com/google/gerrit/server/ChangeDraftUpdateExecutor.java
index 9b21851..441a1a7 100644
--- a/java/com/google/gerrit/server/ChangeDraftUpdateExecutor.java
+++ b/java/com/google/gerrit/server/ChangeDraftUpdateExecutor.java
@@ -51,12 +51,12 @@
     // - A factory creating an interface (rather than a class).
     // To overcome this - we declare the create method in this non-parameterized interface, then
     // extend it with a factory returning an actual class.
-    ChangeDraftUpdateExecutor create();
+    ChangeDraftUpdateExecutor create(CurrentUser currentUser);
   }
 
   interface Factory<T extends ChangeDraftUpdateExecutor> extends AbstractFactory {
     @Override
-    T create();
+    T create(CurrentUser currentUser);
   }
 
   /**
diff --git a/java/com/google/gerrit/server/account/GroupCacheImpl.java b/java/com/google/gerrit/server/account/GroupCacheImpl.java
index 6f4fce9..fac2fd5 100644
--- a/java/com/google/gerrit/server/account/GroupCacheImpl.java
+++ b/java/com/google/gerrit/server/account/GroupCacheImpl.java
@@ -304,7 +304,7 @@
       List<Cache.GroupKeyProto> keyList = new ArrayList<>();
       try (TraceTimer ignored =
               TraceContext.newTimer(
-                  "Loading group from serialized cache",
+                  "Building keys to load group(s) from serialized cache",
                   Metadata.builder().cacheName(BYUUID_NAME_PERSISTED).build());
           Repository allUsers = repoManager.openRepository(allUsersName)) {
         while (uuidIterator.hasNext()) {
@@ -323,8 +323,13 @@
           keyList.add(key);
         }
       }
-      persistedCache.getAll(keyList).entrySet().stream()
-          .forEach(g -> toReturn.put(g.getKey().getUuid(), Optional.of(g.getValue())));
+      try (TraceTimer ignored =
+          TraceContext.newTimer(
+              "Loading group(s) from serialized cache",
+              Metadata.builder().cacheName(BYUUID_NAME_PERSISTED).build())) {
+        persistedCache.getAll(keyList).entrySet().stream()
+            .forEach(g -> toReturn.put(g.getKey().getUuid(), Optional.of(g.getValue())));
+      }
       return toReturn;
     }
   }
diff --git a/java/com/google/gerrit/server/cache/CacheInfo.java b/java/com/google/gerrit/server/cache/CacheInfo.java
index 94a9e05..76756c2 100644
--- a/java/com/google/gerrit/server/cache/CacheInfo.java
+++ b/java/com/google/gerrit/server/cache/CacheInfo.java
@@ -92,7 +92,7 @@
       space = bytes(value);
     }
 
-    private static String bytes(double value) {
+    public static String bytes(double value) {
       value /= 1024;
       String suffix = "k";
 
diff --git a/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java b/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java
index 29bf0e6..f0010e4 100644
--- a/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java
+++ b/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java
@@ -28,6 +28,7 @@
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.gerrit.common.Nullable;
+import com.google.gerrit.server.cache.CacheInfo;
 import com.google.gerrit.server.cache.PersistentCache;
 import com.google.gerrit.server.cache.serialize.CacheSerializer;
 import com.google.gerrit.server.logging.Metadata;
@@ -663,12 +664,19 @@
           try (ResultSet r = s.executeQuery("SELECT SUM(space) FROM data")) {
             used = r.next() ? r.getLong(1) : 0;
           }
+          String formattedMaxSize = CacheInfo.EntriesInfo.bytes(maxSize);
           if (used <= maxSize) {
+            logger.atInfo().log(
+                "Cache %s size (%s) is less than maxSize (%s), not pruning",
+                url, CacheInfo.EntriesInfo.bytes(used), formattedMaxSize);
             return;
           }
 
           try (ResultSet r =
               s.executeQuery("SELECT k, space, created FROM data ORDER BY accessed")) {
+            logger.atInfo().log(
+                "Cache %s size (%s) is greater than maxSize (%s), pruning",
+                url, CacheInfo.EntriesInfo.bytes(used), formattedMaxSize);
             while (maxSize < used && r.next()) {
               K key = keyType.get(r, 1);
               Timestamp created = r.getTimestamp(3);
@@ -679,6 +687,9 @@
                 used -= r.getLong(2);
               }
             }
+            logger.atInfo().log(
+                "Done pruning cache %s, size (%s) is now less than maxSize (%s)",
+                url, CacheInfo.EntriesInfo.bytes(used), formattedMaxSize);
           }
         }
       } catch (IOException | SQLException e) {
diff --git a/java/com/google/gerrit/server/git/MergeUtil.java b/java/com/google/gerrit/server/git/MergeUtil.java
index 73aec64..0d6885e 100644
--- a/java/com/google/gerrit/server/git/MergeUtil.java
+++ b/java/com/google/gerrit/server/git/MergeUtil.java
@@ -696,6 +696,10 @@
       return false;
     }
 
+    return canMerge(mergeTip, repo, toMerge);
+  }
+
+  private boolean canMerge(CodeReviewCommit mergeTip, Repository repo, CodeReviewCommit toMerge) {
     try (ObjectInserter ins = new InMemoryInserter(repo)) {
       return newThreeWayMerger(ins, repo.getConfig()).merge(mergeTip, toMerge);
     } catch (LargeObjectException e) {
@@ -717,6 +721,11 @@
       return false;
     }
 
+    return canFastForward(mergeTip, rw, toMerge);
+  }
+
+  private boolean canFastForward(
+      CodeReviewCommit mergeTip, CodeReviewRevWalk rw, CodeReviewCommit toMerge) {
     try {
       return mergeTip == null
           || rw.isMergedInto(mergeTip, toMerge)
@@ -726,6 +735,19 @@
     }
   }
 
+  public boolean canFastForwardOrMerge(
+      MergeSorter mergeSorter,
+      CodeReviewCommit mergeTip,
+      CodeReviewRevWalk rw,
+      Repository repo,
+      CodeReviewCommit toMerge) {
+    if (hasMissingDependencies(mergeSorter, toMerge)) {
+      return false;
+    }
+
+    return canFastForward(mergeTip, rw, toMerge) || canMerge(mergeTip, repo, toMerge);
+  }
+
   public boolean canCherryPick(
       MergeSorter mergeSorter,
       Repository repo,
@@ -768,8 +790,7 @@
     // by an equivalent merge with a different first parent. So
     // instead behave as though MERGE_IF_NECESSARY was configured.
     //
-    return canFastForward(mergeSorter, mergeTip, rw, toMerge)
-        || canMerge(mergeSorter, repo, mergeTip, toMerge);
+    return canFastForwardOrMerge(mergeSorter, mergeTip, rw, repo, toMerge);
   }
 
   public boolean hasMissingDependencies(MergeSorter mergeSorter, CodeReviewCommit toMerge) {
diff --git a/java/com/google/gerrit/server/notedb/AllUsersAsyncUpdate.java b/java/com/google/gerrit/server/notedb/AllUsersAsyncUpdate.java
index 35611eb..d51b831 100644
--- a/java/com/google/gerrit/server/notedb/AllUsersAsyncUpdate.java
+++ b/java/com/google/gerrit/server/notedb/AllUsersAsyncUpdate.java
@@ -22,9 +22,13 @@
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.MultimapBuilder;
 import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
 import com.google.gerrit.git.RefUpdateUtil;
+import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.FanOutExecutor;
+import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.update.context.RefUpdateContext;
 import com.google.inject.Inject;
@@ -46,6 +50,7 @@
   private final ExecutorService executor;
   private final AllUsersName allUsersName;
   private final GitRepositoryManager repoManager;
+  private final GitReferenceUpdated gitReferenceUpdated;
   private final ListMultimap<String, ChangeDraftNotesUpdate> draftUpdates;
 
   private PersonIdent serverIdent;
@@ -54,10 +59,12 @@
   AllUsersAsyncUpdate(
       @FanOutExecutor ExecutorService executor,
       AllUsersName allUsersName,
-      GitRepositoryManager repoManager) {
+      GitRepositoryManager repoManager,
+      GitReferenceUpdated gitReferenceUpdated) {
     this.executor = executor;
     this.allUsersName = allUsersName;
     this.repoManager = repoManager;
+    this.gitReferenceUpdated = gitReferenceUpdated;
     this.draftUpdates = MultimapBuilder.hashKeys().arrayListValues().build();
   }
 
@@ -83,7 +90,11 @@
   }
 
   /** Executes repository update asynchronously. No-op in case no updates were scheduled. */
-  void execute(PersonIdent refLogIdent, String refLogMessage, PushCertificate pushCert) {
+  void execute(
+      PersonIdent refLogIdent,
+      String refLogMessage,
+      PushCertificate pushCert,
+      CurrentUser currentUser) {
     if (isEmpty()) {
       return;
     }
@@ -110,6 +121,7 @@
                   allUsersRepo.cmds.addTo(bru);
                   bru.setAllowNonFastForwards(true);
                   RefUpdateUtil.executeChecked(bru, allUsersRepo.rw);
+                  gitReferenceUpdated.fire(allUsersName, bru, getAccountState(currentUser));
                 } catch (IOException e) {
                   logger.atSevere().withCause(e).log(
                       "Failed to delete draft comments asynchronously after publishing them");
@@ -117,4 +129,9 @@
               }
             });
   }
+
+  @Nullable
+  private static AccountState getAccountState(CurrentUser user) {
+    return user.isIdentifiedUser() ? user.asIdentifiedUser().state() : null;
+  }
 }
diff --git a/java/com/google/gerrit/server/notedb/ChangeDraftNotesUpdate.java b/java/com/google/gerrit/server/notedb/ChangeDraftNotesUpdate.java
index 972206a..da395aa 100644
--- a/java/com/google/gerrit/server/notedb/ChangeDraftNotesUpdate.java
+++ b/java/com/google/gerrit/server/notedb/ChangeDraftNotesUpdate.java
@@ -32,6 +32,7 @@
 import com.google.gerrit.exceptions.StorageException;
 import com.google.gerrit.server.ChangeDraftUpdate;
 import com.google.gerrit.server.ChangeDraftUpdateExecutor;
+import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.GerritPersonIdent;
 import com.google.gerrit.server.config.AllUsersName;
 import com.google.gerrit.server.git.GitRepositoryManager;
@@ -106,11 +107,15 @@
   }
 
   static class Executor implements ChangeDraftUpdateExecutor, AutoCloseable {
-    interface Factory extends ChangeDraftUpdateExecutor.Factory<Executor> {}
+    interface Factory extends ChangeDraftUpdateExecutor.Factory<Executor> {
+      @Override
+      Executor create(CurrentUser currentUser);
+    }
 
     private final GitRepositoryManager repoManager;
     private final AllUsersName allUsersName;
     private final NoteDbUpdateExecutor noteDbUpdateExecutor;
+    private final CurrentUser currentUser;
     private final AllUsersAsyncUpdate updateAllUsersAsync;
     private OpenRepo allUsersRepo;
     private boolean shouldAllowFastForward = false;
@@ -120,11 +125,13 @@
         GitRepositoryManager repoManager,
         AllUsersName allUsersName,
         NoteDbUpdateExecutor noteDbUpdateExecutor,
-        AllUsersAsyncUpdate updateAllUsersAsync) {
+        AllUsersAsyncUpdate updateAllUsersAsync,
+        @Assisted CurrentUser currentUser) {
       this.updateAllUsersAsync = updateAllUsersAsync;
       this.repoManager = repoManager;
       this.allUsersName = allUsersName;
       this.noteDbUpdateExecutor = noteDbUpdateExecutor;
+      this.currentUser = currentUser;
     }
 
     @Override
@@ -189,7 +196,7 @@
         @Nullable PersonIdent refLogIdent,
         @Nullable String refLogMessage,
         @Nullable PushCertificate pushCert) {
-      updateAllUsersAsync.execute(refLogIdent, refLogMessage, pushCert);
+      updateAllUsersAsync.execute(refLogIdent, refLogMessage, pushCert, currentUser);
     }
 
     @Override
diff --git a/java/com/google/gerrit/server/notedb/ChangeUpdate.java b/java/com/google/gerrit/server/notedb/ChangeUpdate.java
index 8cd32c5..1cf1fa9 100644
--- a/java/com/google/gerrit/server/notedb/ChangeUpdate.java
+++ b/java/com/google/gerrit/server/notedb/ChangeUpdate.java
@@ -199,6 +199,8 @@
   private ImmutableList.Builder<AttentionSetUpdate> attentionSetUpdatesBuilder =
       ImmutableList.builder();
 
+  private final CurrentUser user;
+
   @SuppressWarnings("UnusedMethod")
   @AssistedInject
   private ChangeUpdate(
@@ -260,11 +262,13 @@
     this.serviceUserClassifier = serviceUserClassifier;
     this.patchSetApprovalUuidGenerator = patchSetApprovalUuidGenerator;
     this.approvals = approvals(labelNameComparator);
+    this.user = user;
   }
 
   public ObjectId commit() throws IOException {
     try (RefUpdateContext ctx = RefUpdateContext.open(CHANGE_MODIFICATION)) {
-      try (NoteDbUpdateManager updateManager = updateManagerFactory.create(getProjectName())) {
+      try (NoteDbUpdateManager updateManager =
+          updateManagerFactory.create(getProjectName(), user)) {
         updateManager.add(this);
         updateManager.execute();
       }
diff --git a/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java b/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java
index ae18bb7..07b8d66 100644
--- a/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java
+++ b/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java
@@ -35,6 +35,7 @@
 import com.google.gerrit.metrics.Timer0;
 import com.google.gerrit.server.ChangeDraftUpdate;
 import com.google.gerrit.server.ChangeDraftUpdateExecutor;
+import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.cancellation.RequestStateContext;
 import com.google.gerrit.server.cancellation.RequestStateContext.NonCancellableOperationContext;
 import com.google.gerrit.server.config.AllUsersName;
@@ -78,7 +79,7 @@
   private static final int MAX_PATCH_SETS_DEFAULT = 1000;
 
   public interface Factory {
-    NoteDbUpdateManager create(Project.NameKey projectName);
+    NoteDbUpdateManager create(Project.NameKey projectName, CurrentUser currentUser);
   }
 
   private final GitRepositoryManager repoManager;
@@ -87,6 +88,7 @@
   private final Project.NameKey projectName;
   private final int maxUpdates;
   private final int maxPatchSets;
+  private final CurrentUser currentUser;
   private final ListMultimap<String, ChangeUpdate> changeUpdates;
   private final ListMultimap<String, ChangeDraftUpdate> draftUpdates;
   private final NoteDbUpdateExecutor noteDbUpdateExecutor;
@@ -111,7 +113,8 @@
       NoteDbMetrics metrics,
       @Assisted Project.NameKey projectName,
       NoteDbUpdateExecutor noteDbUpdateExecutor,
-      ChangeDraftUpdateExecutor.AbstractFactory draftUpdatesExecutorFactory) {
+      ChangeDraftUpdateExecutor.AbstractFactory draftUpdatesExecutorFactory,
+      @Assisted CurrentUser currentUser) {
     this.repoManager = repoManager;
     this.allUsersName = allUsersName;
     this.metrics = metrics;
@@ -120,6 +123,7 @@
     this.draftUpdatesExecutorFactory = draftUpdatesExecutorFactory;
     maxUpdates = cfg.getInt("change", null, "maxUpdates", MAX_UPDATES_DEFAULT);
     maxPatchSets = cfg.getInt("change", null, "maxPatchSets", MAX_PATCH_SETS_DEFAULT);
+    this.currentUser = currentUser;
     changeUpdates = MultimapBuilder.hashKeys().arrayListValues().build();
     draftUpdates = MultimapBuilder.hashKeys().arrayListValues().build();
     robotCommentUpdates = MultimapBuilder.hashKeys().arrayListValues().build();
@@ -287,7 +291,7 @@
 
       initChangeRepo();
       if (!draftUpdates.isEmpty() || !changesToDelete.isEmpty()) {
-        draftUpdatesExecutor = draftUpdatesExecutorFactory.create();
+        draftUpdatesExecutor = draftUpdatesExecutorFactory.create(currentUser);
       }
       addCommands();
     }
diff --git a/java/com/google/gerrit/server/submit/MergeIfNecessary.java b/java/com/google/gerrit/server/submit/MergeIfNecessary.java
index 75136f5..b8417b8 100644
--- a/java/com/google/gerrit/server/submit/MergeIfNecessary.java
+++ b/java/com/google/gerrit/server/submit/MergeIfNecessary.java
@@ -49,7 +49,7 @@
 
   static boolean dryRun(
       SubmitDryRun.Arguments args, CodeReviewCommit mergeTip, CodeReviewCommit toMerge) {
-    return args.mergeUtil.canFastForward(args.mergeSorter, mergeTip, args.rw, toMerge)
-        || args.mergeUtil.canMerge(args.mergeSorter, args.repo, mergeTip, toMerge);
+    return args.mergeUtil.canFastForwardOrMerge(
+        args.mergeSorter, mergeTip, args.rw, args.repo, toMerge);
   }
 }
diff --git a/java/com/google/gerrit/server/update/BatchUpdate.java b/java/com/google/gerrit/server/update/BatchUpdate.java
index cf9b01b..532b345 100644
--- a/java/com/google/gerrit/server/update/BatchUpdate.java
+++ b/java/com/google/gerrit/server/update/BatchUpdate.java
@@ -793,7 +793,7 @@
     ChangesHandle handle =
         new ChangesHandle(
             updateManagerFactory
-                .create(project)
+                .create(project, user)
                 .setBatchUpdateListeners(batchUpdateListeners)
                 .setChangeRepo(
                     repo, repoView.getRevWalk(), repoView.getInserter(), repoView.getCommands()),
diff --git a/javatests/com/google/gerrit/acceptance/ssh/StreamEventsIT.java b/javatests/com/google/gerrit/acceptance/ssh/StreamEventsIT.java
index 1a7ed01..2bfc072 100644
--- a/javatests/com/google/gerrit/acceptance/ssh/StreamEventsIT.java
+++ b/javatests/com/google/gerrit/acceptance/ssh/StreamEventsIT.java
@@ -39,6 +39,7 @@
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import java.util.stream.StreamSupport;
+import org.eclipse.jgit.lib.ObjectId;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -159,6 +160,26 @@
         () -> pollEventsContaining("batch-ref-updated", "refs/draft-comments/").size() == 1);
   }
 
+  @Test
+  @GerritConfig(name = "event.stream-events.enableRefUpdatedEvents", value = "true")
+  @GerritConfig(name = "event.stream-events.enableBatchRefUpdatedEvents", value = "false")
+  @GerritConfig(name = "event.stream-events.enableDraftCommentEvents", value = "true")
+  public void draftCommentRefsDeletionShowInStreamEventsUponPublishing() throws Exception {
+    change = createChange().getChange();
+
+    draftReviewChange(PATCHSET_LEVEL, String.format("%s 1", TEST_REVIEW_DRAFT_COMMENT));
+    publishDraftReviews();
+
+    waitForEvent(
+        () ->
+            pollEventsContaining(
+                        "ref-updated",
+                        "refs/draft-comments/",
+                        "\"newRev\":\"" + ObjectId.zeroId().name() + "\"")
+                    .size()
+                == 1);
+  }
+
   private void waitForEvent(Supplier<Boolean> waitCondition) throws InterruptedException {
     waitUntil(() -> waitCondition.get(), MAX_DURATION_FOR_RECEIVING_EVENTS);
   }
diff --git a/javatests/com/google/gerrit/server/notedb/AbstractChangeNotesTest.java b/javatests/com/google/gerrit/server/notedb/AbstractChangeNotesTest.java
index 1df4d45..96a485a 100644
--- a/javatests/com/google/gerrit/server/notedb/AbstractChangeNotesTest.java
+++ b/javatests/com/google/gerrit/server/notedb/AbstractChangeNotesTest.java
@@ -17,6 +17,7 @@
 import static com.google.gerrit.testing.TestActionRefUpdateContext.testRefAction;
 import static com.google.inject.Scopes.SINGLETON;
 import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.mockito.Mockito.mock;
 
 import com.google.common.collect.ImmutableList;
 import com.google.gerrit.entities.Account;
@@ -116,6 +117,7 @@
   protected RevWalk rw;
   protected TestRepository<InMemoryRepository> tr;
   protected AssertableExecutorService assertableFanOutExecutor;
+  protected GitReferenceUpdated gitReferenceUpdated;
 
   @Inject protected IdentifiedUser.GenericFactory userFactory;
 
@@ -162,6 +164,7 @@
     ou.setPreferredEmail("other@account.com");
     accountCache.put(ou.build());
     assertableFanOutExecutor = new AssertableExecutorService();
+    gitReferenceUpdated = mock(GitReferenceUpdated.class);
     changeOwnerId = co.id();
     otherUserId = ou.id();
     internalUser = new InternalUser();
@@ -206,7 +209,7 @@
             bind(GroupBackend.class).to(SystemGroupBackend.class).in(SINGLETON);
             bind(AccountCache.class).toInstance(accountCache);
             bind(PersonIdent.class).annotatedWith(GerritPersonIdent.class).toInstance(serverIdent);
-            bind(GitReferenceUpdated.class).toInstance(GitReferenceUpdated.DISABLED);
+            bind(GitReferenceUpdated.class).toInstance(gitReferenceUpdated);
             bind(MetricMaker.class).to(DisabledMetricMaker.class);
             bind(ExecutorService.class)
                 .annotatedWith(FanOutExecutor.class)
diff --git a/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java b/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java
index c456353..a45ee4d 100644
--- a/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java
+++ b/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java
@@ -2062,7 +2062,7 @@
     ChangeUpdate update2 = newUpdate(c, otherUser);
     update2.putApproval(LabelId.CODE_REVIEW, (short) 2);
 
-    try (NoteDbUpdateManager updateManager = updateManagerFactory.create(project)) {
+    try (NoteDbUpdateManager updateManager = updateManagerFactory.create(project, otherUser)) {
       updateManager.add(update1);
       updateManager.add(update2);
       testRefAction(() -> updateManager.execute());
@@ -2091,7 +2091,7 @@
     Instant time1 = TimeUtil.now();
     PatchSet.Id psId = c.currentPatchSetId();
     RevCommit tipCommit;
-    try (NoteDbUpdateManager updateManager = updateManagerFactory.create(project)) {
+    try (NoteDbUpdateManager updateManager = updateManagerFactory.create(project, otherUser)) {
       HumanComment comment1 =
           newComment(
               psId,
@@ -2171,7 +2171,7 @@
     Ref initial2 = repo.exactRef(update2.getRefName());
     assertThat(initial2).isNotNull();
 
-    try (NoteDbUpdateManager updateManager = updateManagerFactory.create(project)) {
+    try (NoteDbUpdateManager updateManager = updateManagerFactory.create(project, otherUser)) {
       updateManager.add(update1);
       updateManager.add(update2);
       testRefAction(() -> updateManager.execute());
@@ -3476,7 +3476,7 @@
     ChangeDraftUpdate draftUpdate = newUpdate(c, otherUser).createDraftUpdateIfNull();
     if (draftUpdate != null) {
       draftUpdate.putDraftComment(comment2);
-      try (NoteDbUpdateManager manager = updateManagerFactory.create(c.getProject())) {
+      try (NoteDbUpdateManager manager = updateManagerFactory.create(c.getProject(), otherUser)) {
         manager.add(draftUpdate);
         testRefAction(() -> manager.execute());
       }
@@ -3540,7 +3540,7 @@
             false);
     update2.putComment(HumanComment.Status.PUBLISHED, comment2);
 
-    try (NoteDbUpdateManager manager = updateManagerFactory.create(project)) {
+    try (NoteDbUpdateManager manager = updateManagerFactory.create(project, otherUser)) {
       manager.add(update1);
       manager.add(update2);
       testRefAction(() -> manager.execute());
diff --git a/javatests/com/google/gerrit/server/notedb/DraftCommentNotesTest.java b/javatests/com/google/gerrit/server/notedb/DraftCommentNotesTest.java
index 31b1db0..e224bdb 100644
--- a/javatests/com/google/gerrit/server/notedb/DraftCommentNotesTest.java
+++ b/javatests/com/google/gerrit/server/notedb/DraftCommentNotesTest.java
@@ -15,13 +15,19 @@
 package com.google.gerrit.server.notedb;
 
 import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.verify;
 
 import com.google.gerrit.entities.Change;
 import com.google.gerrit.entities.HumanComment;
 import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.server.account.AccountState;
+import com.google.gerrit.server.config.AllUsersName;
 import com.google.gerrit.server.util.time.TimeUtil;
+import org.eclipse.jgit.lib.BatchRefUpdate;
 import org.eclipse.jgit.lib.ObjectId;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
 
 public class DraftCommentNotesTest extends AbstractChangeNotesTest {
 
@@ -80,6 +86,23 @@
     assertableFanOutExecutor.assertInteractions(0);
   }
 
+  @Test
+  public void createAndPublishCommentInOneAction_firesRefUpdatedDeletion() throws Exception {
+    Change c = newChange();
+    ChangeUpdate update = newUpdate(c, otherUser);
+    update.setPatchSetId(c.currentPatchSetId());
+    update.putComment(HumanComment.Status.PUBLISHED, comment(c.currentPatchSetId()));
+    update.commit();
+
+    assertThat(newNotes(c).getDraftComments(otherUserId)).isEmpty();
+
+    ArgumentCaptor<AccountState> accountStateCaptor = ArgumentCaptor.forClass(AccountState.class);
+    verify(gitReferenceUpdated)
+        .fire(any(AllUsersName.class), any(BatchRefUpdate.class), accountStateCaptor.capture());
+
+    assertThat(accountStateCaptor.getValue()).isEqualTo(otherUser.state());
+  }
+
   private HumanComment comment(PatchSet.Id psId) {
     return newComment(
         psId,
diff --git a/resources/com/google/gerrit/server/commit-msg_test.sh b/resources/com/google/gerrit/server/commit-msg_test.sh
index 80e40bd..8434a8e 100755
--- a/resources/com/google/gerrit/server/commit-msg_test.sh
+++ b/resources/com/google/gerrit/server/commit-msg_test.sh
@@ -2,7 +2,18 @@
 
 set -eu
 
-hook=$(pwd)/resources/com/google/gerrit/server/tools/root/hooks/commit-msg
+readlink -f / &> /dev/null || readlink() { greadlink "$@" ; } # for MacOS
+test_dir=$(dirname -- "$(readlink -f -- "$0")")
+hook=$test_dir/tools/root/hooks/commit-msg
+
+if [ -z "${TEST_TMPDIR-}" ] ; then
+  TEST_TMPDIR=$(mktemp -d)
+  trap cleanup EXIT
+fi
+
+function cleanup {
+  rm -rf "$TEST_TMPDIR"
+}
 
 cd $TEST_TMPDIR
 
diff --git a/tools/bzl/junit.bzl b/tools/bzl/junit.bzl
index c5951e1..d10e113 100644
--- a/tools/bzl/junit.bzl
+++ b/tools/bzl/junit.bzl
@@ -73,11 +73,6 @@
     "-Djava.locale.providers=COMPAT",
 ]
 
-POST_JDK17_OPTS = [
-    # https://github.com/bazelbuild/bazel/issues/14502
-    "-Djava.security.manager=allow",
-]
-
 def junit_tests(name, srcs, **kwargs):
     s_name = name.replace("-", "_") + "TestSuite"
     _gen_suite(
@@ -86,10 +81,7 @@
         outname = s_name,
     )
     jvm_flags = kwargs.get("jvm_flags", []) + POST_JDK8_OPTS
-    jvm_flags = jvm_flags + select({
-        "//:java17": POST_JDK8_OPTS + POST_JDK17_OPTS,
-        "//conditions:default": POST_JDK8_OPTS,
-    })
+    jvm_flags = jvm_flags + POST_JDK8_OPTS
     java_test(
         name = name,
         test_class = s_name,
diff --git a/tools/maven/gerrit-acceptance-framework_pom.xml b/tools/maven/gerrit-acceptance-framework_pom.xml
index 57e42b4..f5fe24c 100644
--- a/tools/maven/gerrit-acceptance-framework_pom.xml
+++ b/tools/maven/gerrit-acceptance-framework_pom.xml
@@ -2,7 +2,7 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.google.gerrit</groupId>
   <artifactId>gerrit-acceptance-framework</artifactId>
-  <version>3.9.1-SNAPSHOT</version>
+  <version>3.9.2-SNAPSHOT</version>
   <packaging>jar</packaging>
   <name>Gerrit Code Review - Acceptance Test Framework</name>
   <description>Framework for Gerrit's acceptance tests</description>
diff --git a/tools/maven/gerrit-extension-api_pom.xml b/tools/maven/gerrit-extension-api_pom.xml
index d99ccae4..abe1793 100644
--- a/tools/maven/gerrit-extension-api_pom.xml
+++ b/tools/maven/gerrit-extension-api_pom.xml
@@ -2,7 +2,7 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.google.gerrit</groupId>
   <artifactId>gerrit-extension-api</artifactId>
-  <version>3.9.1-SNAPSHOT</version>
+  <version>3.9.2-SNAPSHOT</version>
   <packaging>jar</packaging>
   <name>Gerrit Code Review - Extension API</name>
   <description>API for Gerrit Extensions</description>
diff --git a/tools/maven/gerrit-plugin-api_pom.xml b/tools/maven/gerrit-plugin-api_pom.xml
index a049e47..3240b14 100644
--- a/tools/maven/gerrit-plugin-api_pom.xml
+++ b/tools/maven/gerrit-plugin-api_pom.xml
@@ -2,7 +2,7 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.google.gerrit</groupId>
   <artifactId>gerrit-plugin-api</artifactId>
-  <version>3.9.1-SNAPSHOT</version>
+  <version>3.9.2-SNAPSHOT</version>
   <packaging>jar</packaging>
   <name>Gerrit Code Review - Plugin API</name>
   <description>API for Gerrit Plugins</description>
diff --git a/tools/maven/gerrit-war_pom.xml b/tools/maven/gerrit-war_pom.xml
index 16dc699..52e98a04 100644
--- a/tools/maven/gerrit-war_pom.xml
+++ b/tools/maven/gerrit-war_pom.xml
@@ -2,7 +2,7 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.google.gerrit</groupId>
   <artifactId>gerrit-war</artifactId>
-  <version>3.9.1-SNAPSHOT</version>
+  <version>3.9.2-SNAPSHOT</version>
   <packaging>war</packaging>
   <name>Gerrit Code Review - WAR</name>
   <description>Gerrit WAR</description>
diff --git a/version.bzl b/version.bzl
index 6b03685..d55ddba 100644
--- a/version.bzl
+++ b/version.bzl
@@ -2,4 +2,4 @@
 # Used by :api_install and :api_deploy targets
 # when talking to the destination repository.
 #
-GERRIT_VERSION = "3.9.1-SNAPSHOT"
+GERRIT_VERSION = "3.9.2-SNAPSHOT"