Merge branch 'stable-2.15'

* stable-2.15:
  StarredChangesUtil: Handle unexpected format of reviewed/unreviewed labels
  Fix migration of mute stars
  Test that change is reindexed when change is starred/unstarred
  Suggest "is:private" in autocompete
  Trigger GitRefUpdatedEvent when star labels are updated
  Trigger GitReferenceUpdated event when account or change sequence is updated
  Fix bad rounding of relative dates like '1 year, 12 months ago'
  Fix test of RelativeDateFormatter
  Trigger GitReferenceUpdated event when external IDs are updated
  Increase the value of GERRIT_FDS parameter
  Add rel="external" to link to CLA
  Fix invalid operator invalidating web session
  Address test flakiness in gr-project-detail-list
  Update patchset selector styles

Change-Id: I4ec66d2d53c9fbe98878fa02585724db2eda216a
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
index 84f3533..22d27ac 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
@@ -38,6 +38,7 @@
 
 import com.google.common.collect.FluentIterable;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSetMultimap;
 import com.google.common.collect.Iterables;
@@ -67,6 +68,7 @@
 import com.google.gerrit.extensions.common.GpgKeyInfo;
 import com.google.gerrit.extensions.common.SshKeyInfo;
 import com.google.gerrit.extensions.events.AccountIndexedListener;
+import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.extensions.registration.RegistrationHandle;
 import com.google.gerrit.extensions.restapi.AuthException;
@@ -79,6 +81,8 @@
 import com.google.gerrit.gpg.testutil.TestKey;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.Sequences;
 import com.google.gerrit.server.account.AccountConfig;
@@ -106,6 +110,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.EnumSet;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
@@ -155,6 +160,8 @@
 
   @Inject private DynamicSet<AccountIndexedListener> accountIndexedListeners;
 
+  @Inject private DynamicSet<GitReferenceUpdatedListener> refUpdateListeners;
+
   @Inject private Sequences seq;
 
   @Inject private Provider<InternalAccountQuery> accountQueryProvider;
@@ -163,6 +170,8 @@
 
   private AccountIndexedCounter accountIndexedCounter;
   private RegistrationHandle accountIndexEventCounterHandle;
+  private RefUpdateCounter refUpdateCounter;
+  private RegistrationHandle refUpdateCounterHandle;
   private ExternalIdsUpdate externalIdsUpdate;
   private List<ExternalId> savedExternalIds;
 
@@ -180,6 +189,19 @@
   }
 
   @Before
+  public void addRefUpdateCounter() {
+    refUpdateCounter = new RefUpdateCounter();
+    refUpdateCounterHandle = refUpdateListeners.add(refUpdateCounter);
+  }
+
+  @After
+  public void removeRefUpdateCounter() {
+    if (refUpdateCounterHandle != null) {
+      refUpdateCounterHandle.remove();
+    }
+  }
+
+  @Before
   public void saveExternalIds() throws Exception {
     externalIdsUpdate = externalIdsUpdateFactory.create();
 
@@ -228,16 +250,29 @@
 
   @Test
   public void create() throws Exception {
-    create(2); // account creation + external ID creation
+    Account.Id accountId = create(2); // account creation + external ID creation
+    refUpdateCounter.assertRefUpdateFor(
+        RefUpdateCounter.projectRef(allUsers, RefNames.refsUsers(accountId)),
+        RefUpdateCounter.projectRef(allUsers, RefNames.REFS_EXTERNAL_IDS),
+        RefUpdateCounter.projectRef(allUsers, RefNames.REFS_SEQUENCES + Sequences.NAME_ACCOUNTS));
   }
 
   @Test
   @UseSsh
   public void createWithSshKeys() throws Exception {
-    create(3); // account creation + external ID creation + adding SSH keys
+    Account.Id accountId = create(3); // account creation + external ID creation + adding SSH keys
+    refUpdateCounter.assertRefUpdateFor(
+        ImmutableMap.of(
+            RefUpdateCounter.projectRef(allUsers, RefNames.refsUsers(accountId)),
+            2,
+            RefUpdateCounter.projectRef(allUsers, RefNames.REFS_EXTERNAL_IDS),
+            1,
+            RefUpdateCounter.projectRef(
+                allUsers, RefNames.REFS_SEQUENCES + Sequences.NAME_ACCOUNTS),
+            1));
   }
 
-  private void create(int expectedAccountReindexCalls) throws Exception {
+  private Account.Id create(int expectedAccountReindexCalls) throws Exception {
     String name = "foo";
     TestAccount foo = accountCreator.create(name);
     AccountInfo info = gApi.accounts().id(foo.id.get()).get();
@@ -245,6 +280,7 @@
     assertThat(info.name).isEqualTo(name);
     accountIndexedCounter.assertReindexOf(foo, expectedAccountReindexCalls);
     assertUserBranch(foo.getId(), name, null);
+    return foo.getId();
   }
 
   @Test
@@ -378,15 +414,24 @@
   public void starUnstarChange() throws Exception {
     PushOneCommit.Result r = createChange();
     String triplet = project.get() + "~master~" + r.getChangeId();
+    refUpdateCounter.clear();
+
     gApi.accounts().self().starChange(triplet);
     ChangeInfo change = info(triplet);
     assertThat(change.starred).isTrue();
     assertThat(change.stars).contains(DEFAULT_LABEL);
+    refUpdateCounter.assertRefUpdateFor(
+        RefUpdateCounter.projectRef(
+            allUsers, RefNames.refsStarredChanges(new Change.Id(change._number), admin.id)));
 
     gApi.accounts().self().unstarChange(triplet);
     change = info(triplet);
     assertThat(change.starred).isNull();
     assertThat(change.stars).isNull();
+    refUpdateCounter.assertRefUpdateFor(
+        RefUpdateCounter.projectRef(
+            allUsers, RefNames.refsStarredChanges(new Change.Id(change._number), admin.id)));
+
     accountIndexedCounter.assertNoReindex();
   }
 
@@ -394,6 +439,8 @@
   public void starUnstarChangeWithLabels() throws Exception {
     PushOneCommit.Result r = createChange();
     String triplet = project.get() + "~master~" + r.getChangeId();
+    refUpdateCounter.clear();
+
     assertThat(gApi.accounts().self().getStars(triplet)).isEmpty();
     assertThat(gApi.accounts().self().getStarredChanges()).isEmpty();
 
@@ -412,6 +459,9 @@
     assertThat(starredChange._number).isEqualTo(r.getChange().getId().get());
     assertThat(starredChange.starred).isTrue();
     assertThat(starredChange.stars).containsExactly("blue", "red", DEFAULT_LABEL).inOrder();
+    refUpdateCounter.assertRefUpdateFor(
+        RefUpdateCounter.projectRef(
+            allUsers, RefNames.refsStarredChanges(new Change.Id(change._number), admin.id)));
 
     gApi.accounts()
         .self()
@@ -428,6 +478,10 @@
     assertThat(starredChange._number).isEqualTo(r.getChange().getId().get());
     assertThat(starredChange.starred).isNull();
     assertThat(starredChange.stars).containsExactly("red", "yellow").inOrder();
+    refUpdateCounter.assertRefUpdateFor(
+        RefUpdateCounter.projectRef(
+            allUsers, RefNames.refsStarredChanges(new Change.Id(change._number), admin.id)));
+
     accountIndexedCounter.assertNoReindex();
 
     setApiUser(user);
@@ -1899,4 +1953,45 @@
       assertThat(countsByAccount).isEmpty();
     }
   }
+
+  private static class RefUpdateCounter implements GitReferenceUpdatedListener {
+    private final AtomicLongMap<String> countsByProjectRefs = AtomicLongMap.create();
+
+    static String projectRef(Project.NameKey project, String ref) {
+      return projectRef(project.get(), ref);
+    }
+
+    static String projectRef(String project, String ref) {
+      return project + ":" + ref;
+    }
+
+    @Override
+    public void onGitReferenceUpdated(Event event) {
+      countsByProjectRefs.incrementAndGet(projectRef(event.getProjectName(), event.getRefName()));
+    }
+
+    void clear() {
+      countsByProjectRefs.clear();
+    }
+
+    long getCount(String projectRef) {
+      return countsByProjectRefs.get(projectRef);
+    }
+
+    void assertRefUpdateFor(String... projectRefs) {
+      Map<String, Integer> expectedRefUpdateCounts = new HashMap<>();
+      for (String projectRef : projectRefs) {
+        expectedRefUpdateCounts.put(projectRef, 1);
+      }
+      assertRefUpdateFor(expectedRefUpdateCounts);
+    }
+
+    void assertRefUpdateFor(Map<String, Integer> expectedProjectRefUpdateCounts) {
+      for (Map.Entry<String, Integer> e : expectedProjectRefUpdateCounts.entrySet()) {
+        assertThat(getCount(e.getKey())).isEqualTo(e.getValue());
+      }
+      assertThat(countsByProjectRefs).hasSize(expectedProjectRefUpdateCounts.size());
+      clear();
+    }
+  }
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
index c2d3184..baa0a68 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -41,6 +41,7 @@
 import static com.google.gerrit.extensions.client.ReviewerState.REVIEWER;
 import static com.google.gerrit.reviewdb.client.RefNames.changeMetaRef;
 import static com.google.gerrit.reviewdb.server.ReviewDbUtil.unwrapDb;
+import static com.google.gerrit.server.StarredChangesUtil.DEFAULT_LABEL;
 import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
 import static com.google.gerrit.server.group.SystemGroupBackend.CHANGE_OWNER;
 import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
@@ -57,6 +58,7 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.AtomicLongMap;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.AcceptanceTestRequestScope;
 import com.google.gerrit.acceptance.GerritConfig;
@@ -105,6 +107,7 @@
 import com.google.gerrit.extensions.common.PureRevertInfo;
 import com.google.gerrit.extensions.common.RevisionInfo;
 import com.google.gerrit.extensions.common.TrackingIdInfo;
+import com.google.gerrit.extensions.events.ChangeIndexedListener;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.extensions.registration.RegistrationHandle;
 import com.google.gerrit.extensions.restapi.AuthException;
@@ -170,6 +173,11 @@
 
   @Inject private DynamicSet<ChangeMessageModifier> changeMessageModifiers;
 
+  @Inject private DynamicSet<ChangeIndexedListener> changeIndexedListeners;
+
+  private ChangeIndexedCounter changeIndexedCounter;
+  private RegistrationHandle changeIndexedCounterHandle;
+
   @Before
   public void setTimeForTesting() {
     systemTimeZone = System.setProperty("user.timezone", "US/Eastern");
@@ -181,6 +189,19 @@
     System.setProperty("user.timezone", systemTimeZone);
   }
 
+  @Before
+  public void addChangeIndexedCounter() {
+    changeIndexedCounter = new ChangeIndexedCounter();
+    changeIndexedCounterHandle = changeIndexedListeners.add(changeIndexedCounter);
+  }
+
+  @After
+  public void removeChangeIndexedCounter() {
+    if (changeIndexedCounterHandle != null) {
+      changeIndexedCounterHandle.remove();
+    }
+  }
+
   @Test
   public void reflog() throws Exception {
     // Tests are using DfsRepository which does not implement getReflogReader,
@@ -3299,6 +3320,25 @@
   }
 
   @Test
+  public void starUnstar() throws Exception {
+    PushOneCommit.Result r = createChange();
+    String triplet = project.get() + "~master~" + r.getChangeId();
+    changeIndexedCounter.clear();
+
+    gApi.accounts().self().starChange(triplet);
+    ChangeInfo change = info(triplet);
+    assertThat(change.starred).isTrue();
+    assertThat(change.stars).contains(DEFAULT_LABEL);
+    changeIndexedCounter.assertReindexOf(change);
+
+    gApi.accounts().self().unstarChange(triplet);
+    change = info(triplet);
+    assertThat(change.starred).isNull();
+    assertThat(change.stars).isNull();
+    changeIndexedCounter.assertReindexOf(change);
+  }
+
+  @Test
   public void ignore() throws Exception {
     TestAccount user2 = accountCreator.user2();
 
@@ -3480,4 +3520,36 @@
     exception.expectMessage("invalid labels: " + invalidLabel);
     gApi.accounts().self().setStars(changeId, new StarsInput(ImmutableSet.of(invalidLabel)));
   }
+
+  private static class ChangeIndexedCounter implements ChangeIndexedListener {
+    private final AtomicLongMap<Integer> countsByChange = AtomicLongMap.create();
+
+    @Override
+    public void onChangeIndexed(int id) {
+      countsByChange.incrementAndGet(id);
+    }
+
+    @Override
+    public void onChangeDeleted(int id) {
+      countsByChange.incrementAndGet(id);
+    }
+
+    void clear() {
+      countsByChange.clear();
+    }
+
+    long getCount(ChangeInfo info) {
+      return countsByChange.get(info._number);
+    }
+
+    void assertReindexOf(ChangeInfo info) {
+      assertReindexOf(info, 1);
+    }
+
+    void assertReindexOf(ChangeInfo info, int expectedCount) {
+      assertThat(getCount(info)).isEqualTo(expectedCount);
+      assertThat(countsByChange).hasSize(1);
+      clear();
+    }
+  }
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java
index 2fe9dcd..4c4bc94 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java
@@ -55,6 +55,7 @@
 import com.google.gerrit.server.account.externalids.ExternalIdsUpdate;
 import com.google.gerrit.server.account.externalids.ExternalIdsUpdate.RefsMetaExternalIdsUpdate;
 import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 import com.google.gerrit.server.git.LockFailureException;
 import com.google.gson.reflect.TypeToken;
 import com.google.gwtorm.server.OrmDuplicateKeyException;
@@ -579,7 +580,17 @@
       noteMap.set(noteId, dataBlob);
 
       ExternalIdsUpdate.commit(
-          repo, rw, ins, rev, noteMap, "Add external ID", admin.getIdent(), admin.getIdent());
+          allUsers,
+          repo,
+          rw,
+          ins,
+          rev,
+          noteMap,
+          "Add external ID",
+          admin.getIdent(),
+          admin.getIdent(),
+          null,
+          GitReferenceUpdated.DISABLED);
       return noteId.getName();
     }
   }
@@ -600,7 +611,17 @@
       noteMap.set(noteId, dataBlob);
 
       ExternalIdsUpdate.commit(
-          repo, rw, ins, rev, noteMap, "Add external ID", admin.getIdent(), admin.getIdent());
+          allUsers,
+          repo,
+          rw,
+          ins,
+          rev,
+          noteMap,
+          "Add external ID",
+          admin.getIdent(),
+          admin.getIdent(),
+          null,
+          GitReferenceUpdated.DISABLED);
       return noteId.getName();
     }
   }
@@ -617,7 +638,17 @@
       noteMap.set(noteId, dataBlob);
 
       ExternalIdsUpdate.commit(
-          repo, rw, ins, rev, noteMap, "Add external ID", admin.getIdent(), admin.getIdent());
+          allUsers,
+          repo,
+          rw,
+          ins,
+          rev,
+          noteMap,
+          "Add external ID",
+          admin.getIdent(),
+          admin.getIdent(),
+          null,
+          GitReferenceUpdated.DISABLED);
       return noteId.getName();
     }
   }
@@ -634,7 +665,17 @@
       noteMap.set(noteId, dataBlob);
 
       ExternalIdsUpdate.commit(
-          repo, rw, ins, rev, noteMap, "Add external ID", admin.getIdent(), admin.getIdent());
+          allUsers,
+          repo,
+          rw,
+          ins,
+          rev,
+          noteMap,
+          "Add external ID",
+          admin.getIdent(),
+          admin.getIdent(),
+          null,
+          GitReferenceUpdated.DISABLED);
       return noteId.getName();
     }
   }
@@ -690,6 +731,8 @@
             new DisabledExternalIdCache(),
             serverIdent.get(),
             serverIdent.get(),
+            null,
+            GitReferenceUpdated.DISABLED,
             () -> {
               if (!doneBgUpdate.getAndSet(true)) {
                 try {
@@ -726,6 +769,8 @@
             new DisabledExternalIdCache(),
             serverIdent.get(),
             serverIdent.get(),
+            null,
+            GitReferenceUpdated.DISABLED,
             () -> {
               try {
                 extIdsUpdate
@@ -824,7 +869,17 @@
       NoteMap noteMap = ExternalIdReader.readNoteMap(rw, rev);
       ExternalIdsUpdate.insert(rw, ins, noteMap, extId);
       ExternalIdsUpdate.commit(
-          repo, rw, ins, rev, noteMap, "insert new ID", serverIdent.get(), serverIdent.get());
+          allUsers,
+          repo,
+          rw,
+          ins,
+          rev,
+          noteMap,
+          "insert new ID",
+          serverIdent.get(),
+          serverIdent.get(),
+          null,
+          GitReferenceUpdated.DISABLED);
     }
   }
 
@@ -839,6 +894,7 @@
       }
 
       ExternalIdsUpdate.commit(
+          allUsers,
           testRepo.getRepository(),
           testRepo.getRevWalk(),
           ins,
@@ -846,7 +902,9 @@
           noteMap,
           "Add external ID",
           admin.getIdent(),
-          admin.getIdent());
+          admin.getIdent(),
+          null,
+          GitReferenceUpdated.DISABLED);
     }
   }
 
diff --git a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/api/GpgApiAdapterImpl.java b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/api/GpgApiAdapterImpl.java
index ba79a6f..49c7f67 100644
--- a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/api/GpgApiAdapterImpl.java
+++ b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/api/GpgApiAdapterImpl.java
@@ -29,6 +29,7 @@
 import com.google.gerrit.server.api.accounts.GpgApiAdapter;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 import java.io.IOException;
 import java.util.List;
 import java.util.Map;
@@ -38,15 +39,15 @@
 import org.eclipse.jgit.transport.PushCertificateParser;
 
 public class GpgApiAdapterImpl implements GpgApiAdapter {
-  private final PostGpgKeys postGpgKeys;
-  private final GpgKeys gpgKeys;
+  private final Provider<PostGpgKeys> postGpgKeys;
+  private final Provider<GpgKeys> gpgKeys;
   private final GpgKeyApiImpl.Factory gpgKeyApiFactory;
   private final GerritPushCertificateChecker.Factory pushCertCheckerFactory;
 
   @Inject
   GpgApiAdapterImpl(
-      PostGpgKeys postGpgKeys,
-      GpgKeys gpgKeys,
+      Provider<PostGpgKeys> postGpgKeys,
+      Provider<GpgKeys> gpgKeys,
       GpgKeyApiImpl.Factory gpgKeyApiFactory,
       GerritPushCertificateChecker.Factory pushCertCheckerFactory) {
     this.postGpgKeys = postGpgKeys;
@@ -64,7 +65,7 @@
   public Map<String, GpgKeyInfo> listGpgKeys(AccountResource account)
       throws RestApiException, GpgException {
     try {
-      return gpgKeys.list().apply(account);
+      return gpgKeys.get().list().apply(account);
     } catch (OrmException | PGPException | IOException e) {
       throw new GpgException(e);
     }
@@ -78,7 +79,7 @@
     in.add = add;
     in.delete = delete;
     try {
-      return postGpgKeys.apply(account, in);
+      return postGpgKeys.get().apply(account, in);
     } catch (PGPException | OrmException | IOException | ConfigInvalidException e) {
       throw new GpgException(e);
     }
@@ -88,7 +89,7 @@
   public GpgKeyApi gpgKey(AccountResource account, IdString idStr)
       throws RestApiException, GpgException {
     try {
-      return gpgKeyApiFactory.create(gpgKeys.parse(account, idStr));
+      return gpgKeyApiFactory.create(gpgKeys.get().parse(account, idStr));
     } catch (PGPException | OrmException | IOException e) {
       throw new GpgException(e);
     }
diff --git a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/RelativeDateFormatter.java b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/RelativeDateFormatter.java
index 7c62ed7..a0c4aa6 100644
--- a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/RelativeDateFormatter.java
+++ b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/RelativeDateFormatter.java
@@ -14,9 +14,6 @@
 
 package com.google.gerrit.client;
 
-import static com.google.gerrit.client.CommonConstants.C;
-import static com.google.gerrit.client.CommonMessages.M;
-
 import java.util.Date;
 
 /**
@@ -24,6 +21,9 @@
  * defined by {@code git log --relative-date}.
  */
 public class RelativeDateFormatter {
+  private static CommonConstants constants;
+  private static CommonMessages messages;
+
   static final long SECOND_IN_MILLIS = 1000;
   static final long MINUTE_IN_MILLIS = 60 * SECOND_IN_MILLIS;
   static final long HOUR_IN_MILLIS = 60 * MINUTE_IN_MILLIS;
@@ -32,6 +32,19 @@
   static final long MONTH_IN_MILLIS = 30 * DAY_IN_MILLIS;
   static final long YEAR_IN_MILLIS = 365 * DAY_IN_MILLIS;
 
+  static void setConstants(CommonConstants c, CommonMessages m) {
+    constants = c;
+    messages = m;
+  }
+
+  private static CommonConstants c() {
+    return constants != null ? constants : CommonConstants.C;
+  }
+
+  private static CommonMessages m() {
+    return messages != null ? messages : CommonMessages.M;
+  }
+
   /**
    * @param when {@link Date} to format
    * @return age of given {@link Date} compared to now formatted in the same relative format as
@@ -42,81 +55,85 @@
 
     // shouldn't happen in a perfect world
     if (ageMillis < 0) {
-      return C.inTheFuture();
+      return c().inTheFuture();
     }
 
     // seconds
     if (ageMillis < upperLimit(MINUTE_IN_MILLIS)) {
       long seconds = round(ageMillis, SECOND_IN_MILLIS);
       if (seconds == 1) {
-        return C.oneSecondAgo();
+        return c().oneSecondAgo();
       }
-      return M.secondsAgo(seconds);
+      return m().secondsAgo(seconds);
     }
 
     // minutes
     if (ageMillis < upperLimit(HOUR_IN_MILLIS)) {
       long minutes = round(ageMillis, MINUTE_IN_MILLIS);
       if (minutes == 1) {
-        return C.oneMinuteAgo();
+        return c().oneMinuteAgo();
       }
-      return M.minutesAgo(minutes);
+      return m().minutesAgo(minutes);
     }
 
     // hours
     if (ageMillis < upperLimit(DAY_IN_MILLIS)) {
       long hours = round(ageMillis, HOUR_IN_MILLIS);
       if (hours == 1) {
-        return C.oneHourAgo();
+        return c().oneHourAgo();
       }
-      return M.hoursAgo(hours);
+      return m().hoursAgo(hours);
     }
 
     // up to 14 days use days
     if (ageMillis < 14 * DAY_IN_MILLIS) {
       long days = round(ageMillis, DAY_IN_MILLIS);
       if (days == 1) {
-        return C.oneDayAgo();
+        return c().oneDayAgo();
       }
-      return M.daysAgo(days);
+      return m().daysAgo(days);
     }
 
     // up to 10 weeks use weeks
     if (ageMillis < 10 * WEEK_IN_MILLIS) {
       long weeks = round(ageMillis, WEEK_IN_MILLIS);
       if (weeks == 1) {
-        return C.oneWeekAgo();
+        return c().oneWeekAgo();
       }
-      return M.weeksAgo(weeks);
+      return m().weeksAgo(weeks);
     }
 
     // months
     if (ageMillis < YEAR_IN_MILLIS) {
       long months = round(ageMillis, MONTH_IN_MILLIS);
       if (months == 1) {
-        return C.oneMonthAgo();
+        return c().oneMonthAgo();
       }
-      return M.monthsAgo(months);
+      return m().monthsAgo(months);
     }
 
     // up to 5 years use "year, months" rounded to months
     if (ageMillis < 5 * YEAR_IN_MILLIS) {
       long years = ageMillis / YEAR_IN_MILLIS;
-      String yearLabel = (years > 1) ? C.years() : C.year();
+      String yearLabel = (years > 1) ? c().years() : c().year();
       long months = round(ageMillis % YEAR_IN_MILLIS, MONTH_IN_MILLIS);
-      String monthLabel = (months > 1) ? C.months() : (months == 1 ? C.month() : "");
+      String monthLabel = (months > 1) ? c().months() : (months == 1 ? c().month() : "");
       if (months == 0) {
-        return M.years0MonthsAgo(years, yearLabel);
+        return m().years0MonthsAgo(years, yearLabel);
       }
-      return M.yearsMonthsAgo(years, yearLabel, months, monthLabel);
+      if (months == 12) {
+        years++;
+        return m().years0MonthsAgo(years, yearLabel);
+      }
+      return m().yearsMonthsAgo(years, yearLabel, months, monthLabel);
     }
 
     // years
     long years = round(ageMillis, YEAR_IN_MILLIS);
     if (years == 1) {
-      return C.oneYearAgo();
+      return c().oneYearAgo();
     }
-    return M.yearsAgo(years);
+    return m().yearsAgo(years);
   }
 
   private static long upperLimit(long unit) {
diff --git a/gerrit-gwtui-common/src/test/java/com/google/gerrit/client/RelativeDateFormatterTest.java b/gerrit-gwtui-common/src/test/java/com/google/gerrit/client/RelativeDateFormatterTest.java
index 937fc96..5180410 100644
--- a/gerrit-gwtui-common/src/test/java/com/google/gerrit/client/RelativeDateFormatterTest.java
+++ b/gerrit-gwtui-common/src/test/java/com/google/gerrit/client/RelativeDateFormatterTest.java
@@ -22,11 +22,23 @@
 import static org.junit.Assert.assertEquals;
 
 import java.util.Date;
-import org.eclipse.jgit.util.RelativeDateFormatter;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
 import org.junit.Test;
 
 public class RelativeDateFormatterTest {
 
+  @BeforeClass
+  public static void setConstants() {
+    Constants c = new Constants();
+    RelativeDateFormatter.setConstants(c, c);
+  }
+
+  @AfterClass
+  public static void unsetConstants() {
+    RelativeDateFormatter.setConstants(null, null);
+  }
+
   private static void assertFormat(long ageFromNow, long timeUnit, String expectedFormat) {
     Date d = new Date(System.currentTimeMillis() - ageFromNow * timeUnit);
     String s = RelativeDateFormatter.format(d);
@@ -41,7 +53,7 @@
 
   @Test
   public void formatSeconds() {
-    assertFormat(1, SECOND_IN_MILLIS, "1 seconds ago");
+    assertFormat(1, SECOND_IN_MILLIS, "1 second ago");
     assertFormat(89, SECOND_IN_MILLIS, "89 seconds ago");
   }
 
@@ -85,7 +97,7 @@
     assertFormat(380, DAY_IN_MILLIS, "1 year, 1 month ago");
     assertFormat(410, DAY_IN_MILLIS, "1 year, 2 months ago");
     assertFormat(2, YEAR_IN_MILLIS, "2 years ago");
-    assertFormat(1824, DAY_IN_MILLIS, "4 years, 12 months ago");
+    assertFormat(1824, DAY_IN_MILLIS, "5 years ago");
   }
 
   @Test
@@ -93,4 +105,111 @@
     assertFormat(5, YEAR_IN_MILLIS, "5 years ago");
     assertFormat(60, YEAR_IN_MILLIS, "60 years ago");
   }
+
+  private static class Constants implements CommonConstants, CommonMessages {
+    @Override
+    public String inTheFuture() {
+      return "in the future";
+    }
+
+    @Override
+    public String month() {
+      return "month";
+    }
+
+    @Override
+    public String months() {
+      return "months";
+    }
+
+    @Override
+    public String year() {
+      return "year";
+    }
+
+    @Override
+    public String years() {
+      return "years";
+    }
+
+    @Override
+    public String oneSecondAgo() {
+      return "1 second ago";
+    }
+
+    @Override
+    public String oneMinuteAgo() {
+      return "1 minute ago";
+    }
+
+    @Override
+    public String oneHourAgo() {
+      return "1 hour ago";
+    }
+
+    @Override
+    public String oneDayAgo() {
+      return "1 day ago";
+    }
+
+    @Override
+    public String oneWeekAgo() {
+      return "1 week ago";
+    }
+
+    @Override
+    public String oneMonthAgo() {
+      return "1 month ago";
+    }
+
+    @Override
+    public String oneYearAgo() {
+      return "1 year ago";
+    }
+
+    @Override
+    public String secondsAgo(long seconds) {
+      return seconds + " seconds ago";
+    }
+
+    @Override
+    public String minutesAgo(long minutes) {
+      return minutes + " minutes ago";
+    }
+
+    @Override
+    public String hoursAgo(long hours) {
+      return hours + " hours ago";
+    }
+
+    @Override
+    public String daysAgo(long days) {
+      return days + " days ago";
+    }
+
+    @Override
+    public String weeksAgo(long weeks) {
+      return weeks + " weeks ago";
+    }
+
+    @Override
+    public String monthsAgo(long months) {
+      return months + " months ago";
+    }
+
+    @Override
+    public String yearsAgo(long years) {
+      return years + " years ago";
+    }
+
+    @Override
+    public String years0MonthsAgo(long years, String yearLabel) {
+      return years + " " + yearLabel + " ago";
+    }
+
+    @Override
+    public String yearsMonthsAgo(long years, String yearLabel, long months, String monthLabel) {
+      return years + " " + yearLabel + ", " + months + " " + monthLabel + " ago";
+    }
+  }
 }
diff --git a/gerrit-index/BUILD b/gerrit-index/BUILD
index 41eed60b..11c4f08 100644
--- a/gerrit-index/BUILD
+++ b/gerrit-index/BUILD
@@ -1,7 +1,10 @@
 load("//tools/bzl:genrule2.bzl", "genrule2")
 load("//tools/bzl:junit.bzl", "junit_tests")
 
-QUERY_PARSE_EXCEPTION_SRCS = ["src/main/java/com/google/gerrit/index/query/QueryParseException.java"]
+QUERY_PARSE_EXCEPTION_SRCS = [
+    "src/main/java/com/google/gerrit/index/query/QueryParseException.java",
+    "src/main/java/com/google/gerrit/index/query/QueryRequiresAuthException.java",
+]
 
 java_library(
     name = "query_exception",
diff --git a/gerrit-index/src/main/java/com/google/gerrit/index/query/QueryRequiresAuthException.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/QueryRequiresAuthException.java
new file mode 100644
index 0000000..67c159e
--- /dev/null
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/QueryRequiresAuthException.java
@@ -0,0 +1,32 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.index.query;
+
+/**
+ * Exception thrown when a search query is invalid.
+ *
+ * <p><b>NOTE:</b> the message is visible to end users.
+ */
+public class QueryRequiresAuthException extends QueryParseException {
+  private static final long serialVersionUID = 1L;
+
+  public QueryRequiresAuthException(String message) {
+    super(message);
+  }
+
+  public QueryRequiresAuthException(String msg, Throwable why) {
+    super(msg, why);
+  }
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java
index bee9928..d8451d5 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java
@@ -32,6 +32,7 @@
 import com.google.gerrit.pgm.util.ThreadLimiter;
 import com.google.gerrit.server.change.ChangeResource;
 import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 import com.google.gerrit.server.index.IndexModule;
 import com.google.gerrit.server.index.IndexModule.IndexType;
 import com.google.gerrit.server.index.change.ChangeSchemaDefinitions;
@@ -170,6 +171,7 @@
           @Override
           protected void configure() {
             factory(ChangeResource.Factory.class);
+            bind(GitReferenceUpdated.class).toInstance(GitReferenceUpdated.DISABLED);
           }
         });
 
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/ExternalIdsOnInit.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/ExternalIdsOnInit.java
index feb91e7..ab491f7c 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/ExternalIdsOnInit.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/ExternalIdsOnInit.java
@@ -16,11 +16,13 @@
 
 import com.google.gerrit.pgm.init.api.AllUsersNameOnInitProvider;
 import com.google.gerrit.pgm.init.api.InitFlags;
+import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.GerritPersonIdentProvider;
 import com.google.gerrit.server.account.externalids.ExternalId;
 import com.google.gerrit.server.account.externalids.ExternalIdReader;
 import com.google.gerrit.server.account.externalids.ExternalIdsUpdate;
 import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import java.io.File;
@@ -67,7 +69,17 @@
 
         PersonIdent serverIdent = new GerritPersonIdentProvider(flags.cfg).get();
         ExternalIdsUpdate.commit(
-            repo, rw, ins, rev, noteMap, commitMessage, serverIdent, serverIdent);
+            new Project.NameKey(allUsers),
+            repo,
+            rw,
+            ins,
+            rev,
+            noteMap,
+            commitMessage,
+            serverIdent,
+            serverIdent,
+            null,
+            GitReferenceUpdated.DISABLED);
       }
     }
   }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/SequencesOnInit.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/SequencesOnInit.java
index bfad889..e1cef62 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/SequencesOnInit.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/SequencesOnInit.java
@@ -17,6 +17,7 @@
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.Sequences;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.notedb.RepoSequence;
 import com.google.gwtorm.server.OrmException;
@@ -40,6 +41,7 @@
     RepoSequence accountSeq =
         new RepoSequence(
             repoManager,
+            GitReferenceUpdated.DISABLED,
             new Project.NameKey(allUsersName.get()),
             Sequences.NAME_ACCOUNTS,
             accountSeed,
diff --git a/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/init/gerrit.sh b/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/init/gerrit.sh
index d331347..20edbd3 100755
--- a/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/init/gerrit.sh
+++ b/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/init/gerrit.sh
@@ -289,7 +289,11 @@
 
 GERRIT_FDS=`get_config --int core.packedGitOpenFiles`
 test -z "$GERRIT_FDS" && GERRIT_FDS=128
-GERRIT_FDS=`expr $GERRIT_FDS + $GERRIT_FDS`
+FDS_MULTIPLIER=2
+USE_LFS=`get_config --get lfs.plugin`
+test -n "$USE_LFS" && FDS_MULTIPLIER=3
+
+GERRIT_FDS=`expr $FDS_MULTIPLIER \* $GERRIT_FDS`
 test $GERRIT_FDS -lt 1024 && GERRIT_FDS=1024
 
 GERRIT_STARTUP_TIMEOUT=`get_config --get container.startupTimeout`
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/Sequences.java b/gerrit-server/src/main/java/com/google/gerrit/server/Sequences.java
index 28fde9e..930f3f3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/Sequences.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/Sequences.java
@@ -27,6 +27,7 @@
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.config.AllUsersName;
 import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.notedb.NotesMigration;
 import com.google.gerrit.server.notedb.RepoSequence;
@@ -64,6 +65,7 @@
       Provider<ReviewDb> db,
       NotesMigration migration,
       GitRepositoryManager repoManager,
+      GitReferenceUpdated gitRefUpdated,
       AllProjectsName allProjects,
       AllUsersName allUsers,
       MetricMaker metrics) {
@@ -74,6 +76,7 @@
     accountSeq =
         new RepoSequence(
             repoManager,
+            gitRefUpdated,
             allUsers,
             NAME_ACCOUNTS,
             () -> ReviewDb.FIRST_ACCOUNT_ID,
@@ -84,7 +87,8 @@
     RepoSequence.Seed changeSeed = () -> db.get().nextChangeId() + gap;
     int changeBatchSize = cfg.getInt("noteDb", "changes", "sequenceBatchSize", 20);
     changeSeq =
-        new RepoSequence(repoManager, allProjects, NAME_CHANGES, changeSeed, changeBatchSize);
+        new RepoSequence(
+            repoManager, gitRefUpdated, allProjects, NAME_CHANGES, changeSeed, changeBatchSize);
 
     nextIdLatency =
         metrics.newTimer(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/StarredChangesUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/StarredChangesUtil.java
index a8cc0f4..12bd8ff 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/StarredChangesUtil.java
@@ -37,6 +37,7 @@
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.change.ChangeResource;
 import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.index.change.ChangeField;
 import com.google.gerrit.server.index.change.ChangeIndexer;
@@ -163,6 +164,7 @@
       ImmutableSortedSet.of(DEFAULT_LABEL);
 
   private final GitRepositoryManager repoManager;
+  private final GitReferenceUpdated gitRefUpdated;
   private final AllUsersName allUsers;
   private final Provider<ReviewDb> dbProvider;
   private final PersonIdent serverIdent;
@@ -172,12 +174,14 @@
   @Inject
   StarredChangesUtil(
       GitRepositoryManager repoManager,
+      GitReferenceUpdated gitRefUpdated,
       AllUsersName allUsers,
       Provider<ReviewDb> dbProvider,
       @GerritPersonIdent PersonIdent serverIdent,
       ChangeIndexer indexer,
       Provider<InternalChangeQuery> queryProvider) {
     this.repoManager = repoManager;
+    this.gitRefUpdated = gitRefUpdated;
     this.allUsers = allUsers;
     this.dbProvider = dbProvider;
     this.serverIdent = serverIdent;
@@ -403,18 +407,8 @@
       throw new MutuallyExclusiveLabelsException(DEFAULT_LABEL, IGNORE_LABEL);
     }
 
-    Set<Integer> reviewedPatchSets =
-        labels
-            .stream()
-            .filter(l -> l.startsWith(REVIEWED_LABEL))
-            .map(l -> Integer.valueOf(l.substring(REVIEWED_LABEL.length() + 1)))
-            .collect(toSet());
-    Set<Integer> unreviewedPatchSets =
-        labels
-            .stream()
-            .filter(l -> l.startsWith(UNREVIEWED_LABEL))
-            .map(l -> Integer.valueOf(l.substring(UNREVIEWED_LABEL.length() + 1)))
-            .collect(toSet());
+    Set<Integer> reviewedPatchSets = getStarredPatchSets(labels, REVIEWED_LABEL);
+    Set<Integer> unreviewedPatchSets = getStarredPatchSets(labels, UNREVIEWED_LABEL);
     Optional<Integer> ps =
         Sets.intersection(reviewedPatchSets, unreviewedPatchSets).stream().findFirst();
     if (ps.isPresent()) {
@@ -423,6 +417,15 @@
     }
   }
 
+  public static Set<Integer> getStarredPatchSets(Set<String> labels, String label) {
+    return labels
+        .stream()
+        .filter(l -> l.startsWith(label))
+        .filter(l -> Ints.tryParse(l.substring(label.length() + 1)) != null)
+        .map(l -> Integer.valueOf(l.substring(label.length() + 1)))
+        .collect(toSet());
+  }
+
   private static void validateLabels(Collection<String> labels) throws InvalidLabelsException {
     if (labels == null) {
       return;
@@ -455,6 +458,7 @@
         case FORCED:
         case NO_CHANGE:
         case FAST_FORWARD:
+          gitRefUpdated.fire(allUsers, u, null);
           return;
         case IO_FAILURE:
         case LOCK_FAILURE:
@@ -481,6 +485,7 @@
     RefUpdate.Result result = u.delete();
     switch (result) {
       case FORCED:
+        gitRefUpdated.fire(allUsers, u, null);
         return;
       case NEW:
       case NO_CHANGE:
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/externalids/ExternalIdsBatchUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/externalids/ExternalIdsBatchUpdate.java
index e35b0c3..8e5582c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/externalids/ExternalIdsBatchUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/externalids/ExternalIdsBatchUpdate.java
@@ -17,6 +17,7 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.gerrit.server.GerritPersonIdent;
 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.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -41,6 +42,7 @@
  */
 public class ExternalIdsBatchUpdate {
   private final GitRepositoryManager repoManager;
+  private final GitReferenceUpdated gitRefUpdated;
   private final AllUsersName allUsersName;
   private final PersonIdent serverIdent;
   private final ExternalIdCache externalIdCache;
@@ -50,10 +52,12 @@
   @Inject
   public ExternalIdsBatchUpdate(
       GitRepositoryManager repoManager,
+      GitReferenceUpdated gitRefUpdated,
       AllUsersName allUsersName,
       @GerritPersonIdent PersonIdent serverIdent,
       ExternalIdCache externalIdCache) {
     this.repoManager = repoManager;
+    this.gitRefUpdated = gitRefUpdated;
     this.allUsersName = allUsersName;
     this.serverIdent = serverIdent;
     this.externalIdCache = externalIdCache;
@@ -105,7 +109,17 @@
 
       ObjectId newRev =
           ExternalIdsUpdate.commit(
-              repo, rw, ins, rev, noteMap, commitMessage, serverIdent, serverIdent);
+              allUsersName,
+              repo,
+              rw,
+              ins,
+              rev,
+              noteMap,
+              commitMessage,
+              serverIdent,
+              serverIdent,
+              null,
+              gitRefUpdated);
       externalIdCache.onReplace(rev, newRev, toDelete, toAdd);
     }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/externalids/ExternalIdsUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/externalids/ExternalIdsUpdate.java
index e4434fb..00dc05a5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/externalids/ExternalIdsUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/externalids/ExternalIdsUpdate.java
@@ -41,11 +41,13 @@
 import com.google.gerrit.metrics.Description;
 import com.google.gerrit.metrics.MetricMaker;
 import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.GerritPersonIdent;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.AccountCache;
 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.git.LockFailureException;
 import com.google.gwtorm.server.OrmDuplicateKeyException;
@@ -111,6 +113,7 @@
     private final ExternalIds externalIds;
     private final ExternalIdCache externalIdCache;
     private final Provider<PersonIdent> serverIdent;
+    private final GitReferenceUpdated gitRefUpdated;
 
     @Inject
     public Server(
@@ -120,7 +123,8 @@
         MetricMaker metricMaker,
         ExternalIds externalIds,
         ExternalIdCache externalIdCache,
-        @GerritPersonIdent Provider<PersonIdent> serverIdent) {
+        @GerritPersonIdent Provider<PersonIdent> serverIdent,
+        GitReferenceUpdated gitRefUpdated) {
       this.repoManager = repoManager;
       this.accountCache = accountCache;
       this.allUsersName = allUsersName;
@@ -128,12 +132,22 @@
       this.externalIds = externalIds;
       this.externalIdCache = externalIdCache;
       this.serverIdent = serverIdent;
+      this.gitRefUpdated = gitRefUpdated;
     }
 
     public ExternalIdsUpdate create() {
       PersonIdent i = serverIdent.get();
       return new ExternalIdsUpdate(
-          repoManager, accountCache, allUsersName, metricMaker, externalIds, externalIdCache, i, i);
+          repoManager,
+          accountCache,
+          allUsersName,
+          metricMaker,
+          externalIds,
+          externalIdCache,
+          i,
+          i,
+          null,
+          gitRefUpdated);
     }
   }
 
@@ -154,6 +168,7 @@
     private final ExternalIds externalIds;
     private final ExternalIdCache externalIdCache;
     private final Provider<PersonIdent> serverIdent;
+    private final GitReferenceUpdated gitRefUpdated;
 
     @Inject
     public ServerNoReindex(
@@ -162,19 +177,30 @@
         MetricMaker metricMaker,
         ExternalIds externalIds,
         ExternalIdCache externalIdCache,
-        @GerritPersonIdent Provider<PersonIdent> serverIdent) {
+        @GerritPersonIdent Provider<PersonIdent> serverIdent,
+        GitReferenceUpdated gitRefUpdated) {
       this.repoManager = repoManager;
       this.allUsersName = allUsersName;
       this.metricMaker = metricMaker;
       this.externalIds = externalIds;
       this.externalIdCache = externalIdCache;
       this.serverIdent = serverIdent;
+      this.gitRefUpdated = gitRefUpdated;
     }
 
     public ExternalIdsUpdate create() {
       PersonIdent i = serverIdent.get();
       return new ExternalIdsUpdate(
-          repoManager, null, allUsersName, metricMaker, externalIds, externalIdCache, i, i);
+          repoManager,
+          null,
+          allUsersName,
+          metricMaker,
+          externalIds,
+          externalIdCache,
+          i,
+          i,
+          null,
+          gitRefUpdated);
     }
   }
 
@@ -194,6 +220,7 @@
     private final ExternalIdCache externalIdCache;
     private final Provider<PersonIdent> serverIdent;
     private final Provider<IdentifiedUser> identifiedUser;
+    private final GitReferenceUpdated gitRefUpdated;
 
     @Inject
     public User(
@@ -204,7 +231,8 @@
         ExternalIds externalIds,
         ExternalIdCache externalIdCache,
         @GerritPersonIdent Provider<PersonIdent> serverIdent,
-        Provider<IdentifiedUser> identifiedUser) {
+        Provider<IdentifiedUser> identifiedUser,
+        GitReferenceUpdated gitRefUpdated) {
       this.repoManager = repoManager;
       this.accountCache = accountCache;
       this.allUsersName = allUsersName;
@@ -213,9 +241,11 @@
       this.externalIdCache = externalIdCache;
       this.serverIdent = serverIdent;
       this.identifiedUser = identifiedUser;
+      this.gitRefUpdated = gitRefUpdated;
     }
 
     public ExternalIdsUpdate create() {
+      IdentifiedUser user = identifiedUser.get();
       PersonIdent i = serverIdent.get();
       return new ExternalIdsUpdate(
           repoManager,
@@ -224,8 +254,10 @@
           metricMaker,
           externalIds,
           externalIdCache,
-          createPersonIdent(i, identifiedUser.get()),
-          i);
+          createPersonIdent(i, user),
+          i,
+          user,
+          gitRefUpdated);
     }
 
     private PersonIdent createPersonIdent(PersonIdent ident, IdentifiedUser user) {
@@ -253,6 +285,8 @@
   private final ExternalIdCache externalIdCache;
   private final PersonIdent committerIdent;
   private final PersonIdent authorIdent;
+  @Nullable private final IdentifiedUser currentUser;
+  private final GitReferenceUpdated gitRefUpdated;
   private final Runnable afterReadRevision;
   private final Retryer<RefsMetaExternalIdsUpdate> retryer;
   private final Counter0 updateCount;
@@ -265,7 +299,9 @@
       ExternalIds externalIds,
       ExternalIdCache externalIdCache,
       PersonIdent committerIdent,
-      PersonIdent authorIdent) {
+      PersonIdent authorIdent,
+      @Nullable IdentifiedUser currentUser,
+      GitReferenceUpdated gitRefUpdated) {
     this(
         repoManager,
         accountCache,
@@ -275,6 +311,8 @@
         externalIdCache,
         committerIdent,
         authorIdent,
+        currentUser,
+        gitRefUpdated,
         Runnables.doNothing(),
         RETRYER);
   }
@@ -289,6 +327,8 @@
       ExternalIdCache externalIdCache,
       PersonIdent committerIdent,
       PersonIdent authorIdent,
+      @Nullable IdentifiedUser currentUser,
+      GitReferenceUpdated gitRefUpdated,
       Runnable afterReadRevision,
       Retryer<RefsMetaExternalIdsUpdate> retryer) {
     this.repoManager = checkNotNull(repoManager, "repoManager");
@@ -298,6 +338,8 @@
     this.externalIds = checkNotNull(externalIds, "externalIds");
     this.externalIdCache = checkNotNull(externalIdCache, "externalIdCache");
     this.authorIdent = checkNotNull(authorIdent, "authorIdent");
+    this.currentUser = currentUser;
+    this.gitRefUpdated = checkNotNull(gitRefUpdated, "gitRefUpdated");
     this.afterReadRevision = checkNotNull(afterReadRevision, "afterReadRevision");
     this.retryer = checkNotNull(retryer, "retryer");
     this.updateCount =
@@ -732,13 +774,26 @@
       NoteMap noteMap,
       UpdatedExternalIds updatedExtIds)
       throws IOException {
-    ObjectId newRev = commit(repo, rw, ins, rev, noteMap, COMMIT_MSG, committerIdent, authorIdent);
+    ObjectId newRev =
+        commit(
+            allUsersName,
+            repo,
+            rw,
+            ins,
+            rev,
+            noteMap,
+            COMMIT_MSG,
+            committerIdent,
+            authorIdent,
+            currentUser,
+            gitRefUpdated);
     updateCount.increment();
     return RefsMetaExternalIdsUpdate.create(rev, newRev, updatedExtIds);
   }
 
   /** Commits updates to the external IDs. */
   public static ObjectId commit(
+      Project.NameKey project,
       Repository repo,
       RevWalk rw,
       ObjectInserter ins,
@@ -746,7 +801,9 @@
       NoteMap noteMap,
       String commitMessage,
       PersonIdent committerIdent,
-      PersonIdent authorIdent)
+      PersonIdent authorIdent,
+      @Nullable IdentifiedUser user,
+      GitReferenceUpdated gitRefUpdated)
       throws IOException {
     CommitBuilder cb = new CommitBuilder();
     cb.setMessage(commitMessage);
@@ -793,6 +850,7 @@
       default:
         throw new IOException("Updating external IDs failed with " + res);
     }
+    gitRefUpdated.fire(project, u, user != null ? user.getAccount() : null);
     return rw.parseCommit(commitId);
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ReindexAfterRefUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ReindexAfterRefUpdate.java
index 2ab5c55..2e1da65 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ReindexAfterRefUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ReindexAfterRefUpdate.java
@@ -88,7 +88,7 @@
   public void onGitReferenceUpdated(Event event) {
     if (allUsersName.get().equals(event.getProjectName())) {
       Account.Id accountId = Account.Id.fromRef(event.getRefName());
-      if (accountId != null) {
+      if (accountId != null && !event.getRefName().startsWith(RefNames.REFS_STARRED_CHANGES)) {
         try {
           accountCache.evict(accountId);
         } catch (IOException e) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RepoSequence.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RepoSequence.java
index 43813f8..777624a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RepoSequence.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RepoSequence.java
@@ -36,6 +36,7 @@
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gwtorm.server.OrmException;
 import java.io.IOException;
@@ -86,6 +87,7 @@
   private static final Retryer<RefUpdate.Result> RETRYER = retryerBuilder().build();
 
   private final GitRepositoryManager repoManager;
+  private final GitReferenceUpdated gitRefUpdated;
   private final Project.NameKey projectName;
   private final String refName;
   private final Seed seed;
@@ -103,16 +105,26 @@
 
   public RepoSequence(
       GitRepositoryManager repoManager,
+      GitReferenceUpdated gitRefUpdated,
       Project.NameKey projectName,
       String name,
       Seed seed,
       int batchSize) {
-    this(repoManager, projectName, name, seed, batchSize, Runnables.doNothing(), RETRYER);
+    this(
+        repoManager,
+        gitRefUpdated,
+        projectName,
+        name,
+        seed,
+        batchSize,
+        Runnables.doNothing(),
+        RETRYER);
   }
 
   @VisibleForTesting
   RepoSequence(
       GitRepositoryManager repoManager,
+      GitReferenceUpdated gitRefUpdated,
       Project.NameKey projectName,
       String name,
       Seed seed,
@@ -120,6 +132,7 @@
       Runnable afterReadRef,
       Retryer<RefUpdate.Result> retryer) {
     this.repoManager = checkNotNull(repoManager, "repoManager");
+    this.gitRefUpdated = checkNotNull(gitRefUpdated, "gitRefUpdated");
     this.projectName = checkNotNull(projectName, "projectName");
 
     checkArgument(
@@ -213,11 +226,15 @@
   }
 
   private void checkResult(RefUpdate.Result result) throws OrmException {
-    if (result != RefUpdate.Result.NEW && result != RefUpdate.Result.FORCED) {
+    if (!refUpdated(result)) {
       throw new OrmException("failed to update " + refName + ": " + result);
     }
   }
 
+  private boolean refUpdated(RefUpdate.Result result) {
+    return result == RefUpdate.Result.NEW || result == RefUpdate.Result.FORCED;
+  }
+
   private class TryAcquire implements Callable<RefUpdate.Result> {
     private final Repository repo;
     private final RevWalk rw;
@@ -275,7 +292,11 @@
     }
     ru.setNewObjectId(newId);
     ru.setForceUpdate(true); // Required for non-commitish updates.
-    return ru.update(rw);
+    RefUpdate.Result result = ru.update(rw);
+    if (refUpdated(result)) {
+      gitRefUpdated.fire(projectName, ru, null);
+    }
+    return result;
   }
 
   public static ReceiveCommand storeNew(ObjectInserter ins, String name, int val)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/NoteDbMigrator.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/NoteDbMigrator.java
index 7913e82..f302177 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/NoteDbMigrator.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/NoteDbMigrator.java
@@ -51,6 +51,7 @@
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.config.GerritServerConfigProvider;
 import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.LockFailureException;
 import com.google.gerrit.server.git.WorkQueue;
@@ -505,6 +506,7 @@
       RepoSequence seq =
           new RepoSequence(
               repoManager,
+              GitReferenceUpdated.DISABLED,
               allProjects,
               Sequences.NAME_CHANGES,
               // If sequenceGap is 0, this writes into the sequence ref the same ID that is returned
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index 1ae579e..1f28dbd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -36,6 +36,7 @@
 import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.index.query.QueryBuilder;
 import com.google.gerrit.index.query.QueryParseException;
+import com.google.gerrit.index.query.QueryRequiresAuthException;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Branch;
@@ -391,23 +392,23 @@
       return asUser(userFactory.create(otherId));
     }
 
-    IdentifiedUser getIdentifiedUser() throws QueryParseException {
+    IdentifiedUser getIdentifiedUser() throws QueryRequiresAuthException {
       try {
         CurrentUser u = getUser();
         if (u.isIdentifiedUser()) {
           return u.asIdentifiedUser();
         }
-        throw new QueryParseException(NotSignedInException.MESSAGE);
+        throw new QueryRequiresAuthException(NotSignedInException.MESSAGE);
       } catch (ProvisionException e) {
-        throw new QueryParseException(NotSignedInException.MESSAGE, e);
+        throw new QueryRequiresAuthException(NotSignedInException.MESSAGE, e);
       }
     }
 
-    CurrentUser getUser() throws QueryParseException {
+    CurrentUser getUser() throws QueryRequiresAuthException {
       try {
         return self.get();
       } catch (ProvisionException e) {
-        throw new QueryParseException(NotSignedInException.MESSAGE, e);
+        throw new QueryRequiresAuthException(NotSignedInException.MESSAGE, e);
       }
     }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
index c7a525a..fadc853 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
@@ -24,6 +24,7 @@
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.extensions.restapi.TopLevelResource;
 import com.google.gerrit.index.query.QueryParseException;
+import com.google.gerrit.index.query.QueryRequiresAuthException;
 import com.google.gerrit.index.query.QueryResult;
 import com.google.gerrit.server.change.ChangeJson;
 import com.google.gwtorm.server.OrmException;
@@ -32,8 +33,6 @@
 import java.util.Collections;
 import java.util.EnumSet;
 import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 import org.kohsuke.args4j.Option;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -110,15 +109,9 @@
     List<List<ChangeInfo>> out;
     try {
       out = query();
+    } catch (QueryRequiresAuthException e) {
+      throw new AuthException("Must be signed-in to use this operator");
     } catch (QueryParseException e) {
-      // This is a hack to detect an operator that requires authentication.
-      Pattern p =
-          Pattern.compile("^Error in operator (.*:self|is:watched|is:owner|is:reviewer|has:.*)$");
-      Matcher m = p.matcher(e.getMessage());
-      if (m.matches()) {
-        String op = m.group(1);
-        throw new AuthException("Must be signed-in to use " + op);
-      }
       log.debug("Reject change query with 400 Bad Request: " + queries, e);
       throw new BadRequestException(e.getMessage(), e);
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_144.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_144.java
index eaa97e4d5..d43b887 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_144.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_144.java
@@ -21,6 +21,7 @@
 import com.google.gerrit.server.account.externalids.ExternalIdReader;
 import com.google.gerrit.server.account.externalids.ExternalIdsUpdate;
 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.gwtorm.jdbc.JdbcSchema;
 import com.google.gwtorm.server.OrmException;
@@ -93,7 +94,18 @@
           ExternalIdsUpdate.upsert(rw, ins, noteMap, extId);
         }
 
-        ExternalIdsUpdate.commit(repo, rw, ins, rev, noteMap, COMMIT_MSG, serverIdent, serverIdent);
+        ExternalIdsUpdate.commit(
+            allUsersName,
+            repo,
+            rw,
+            ins,
+            rev,
+            noteMap,
+            COMMIT_MSG,
+            serverIdent,
+            serverIdent,
+            null,
+            GitReferenceUpdated.DISABLED);
       }
     } catch (IOException | ConfigInvalidException e) {
       throw new OrmException("Failed to migrate external IDs to NoteDb", e);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_148.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_148.java
index 421e28d..47751cd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_148.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_148.java
@@ -23,6 +23,7 @@
 import com.google.gerrit.server.account.externalids.ExternalIdReader;
 import com.google.gerrit.server.account.externalids.ExternalIdsUpdate;
 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.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -84,7 +85,18 @@
         }
       }
       if (dirty) {
-        ExternalIdsUpdate.commit(repo, rw, ins, rev, noteMap, COMMIT_MSG, serverUser, serverUser);
+        ExternalIdsUpdate.commit(
+            allUsersName,
+            repo,
+            rw,
+            ins,
+            rev,
+            noteMap,
+            COMMIT_MSG,
+            serverUser,
+            serverUser,
+            null,
+            GitReferenceUpdated.DISABLED);
       }
     } catch (IOException e) {
       throw new OrmException("Failed to update external IDs", e);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_155.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_155.java
index 64f60e3..2bb2a33 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_155.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_155.java
@@ -17,6 +17,7 @@
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.Sequences;
 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.notedb.RepoSequence;
 import com.google.gwtorm.server.OrmException;
@@ -42,7 +43,13 @@
     @SuppressWarnings("deprecation")
     RepoSequence.Seed accountSeed = () -> db.nextAccountId();
     RepoSequence accountSeq =
-        new RepoSequence(repoManager, allUsersName, Sequences.NAME_ACCOUNTS, accountSeed, 1);
+        new RepoSequence(
+            repoManager,
+            GitReferenceUpdated.DISABLED,
+            allUsersName,
+            Sequences.NAME_ACCOUNTS,
+            accountSeed,
+            1);
 
     // consume one account ID to ensure that the account sequence is initialized in NoteDb
     accountSeq.next();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_161.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_161.java
index 407492d..febe80e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_161.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_161.java
@@ -16,6 +16,7 @@
 
 import static java.util.stream.Collectors.toList;
 
+import com.google.common.primitives.Ints;
 import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.StarredChangesUtil;
@@ -27,6 +28,9 @@
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import java.io.IOException;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
 import org.eclipse.jgit.lib.BatchRefUpdate;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Ref;
@@ -54,19 +58,53 @@
     try (Repository git = repoManager.openRepository(allUsersName);
         RevWalk rw = new RevWalk(git)) {
       BatchRefUpdate bru = git.getRefDatabase().newBatchUpdate();
+      bru.setAllowNonFastForwards(true);
+
       for (Ref ref : git.getRefDatabase().getRefs(RefNames.REFS_STARRED_CHANGES).values()) {
         StarRef starRef = StarredChangesUtil.readLabels(git, ref.getName());
-        if (starRef.labels().contains(MUTE_LABEL)) {
-          ObjectId id =
-              StarredChangesUtil.writeLabels(
-                  git,
-                  starRef
-                      .labels()
-                      .stream()
-                      .map(l -> l.equals(MUTE_LABEL) ? StarredChangesUtil.REVIEWED_LABEL : l)
-                      .collect(toList()));
-          bru.addCommand(new ReceiveCommand(ObjectId.zeroId(), id, ref.getName()));
+
+        Set<Integer> mutedPatchSets =
+            StarredChangesUtil.getStarredPatchSets(starRef.labels(), MUTE_LABEL);
+        if (mutedPatchSets.isEmpty()) {
+          continue;
         }
+
+        Set<Integer> reviewedPatchSets =
+            StarredChangesUtil.getStarredPatchSets(
+                starRef.labels(), StarredChangesUtil.REVIEWED_LABEL);
+        Set<Integer> unreviewedPatchSets =
+            StarredChangesUtil.getStarredPatchSets(
+                starRef.labels(), StarredChangesUtil.UNREVIEWED_LABEL);
+
+        List<String> newLabels =
+            starRef
+                .labels()
+                .stream()
+                .map(
+                    l -> {
+                      if (l.startsWith(MUTE_LABEL)) {
+                        Integer mutedPatchSet = Ints.tryParse(l.substring(MUTE_LABEL.length() + 1));
+                        if (mutedPatchSet == null) {
+                          // unexpected format of mute label, must be a label that was manually
+                          // set, just leave it alone
+                          return l;
+                        }
+                        if (!reviewedPatchSets.contains(mutedPatchSet)
+                            && !unreviewedPatchSets.contains(mutedPatchSet)) {
+                          // convert mute label to reviewed label
+                          return StarredChangesUtil.REVIEWED_LABEL + "/" + mutedPatchSet;
+                        }
+                        // else patch set is muted but has either reviewed or unreviewed label
+                        // -> just drop the mute label
+                        return null;
+                      }
+                      return l;
+                    })
+                .filter(Objects::nonNull)
+                .collect(toList());
+
+        ObjectId id = StarredChangesUtil.writeLabels(git, newLabels);
+        bru.addCommand(new ReceiveCommand(ref.getTarget().getObjectId(), id, ref.getName()));
       }
       bru.execute(rw, new TextProgressMonitor());
     } catch (IOException | IllegalLabelException ex) {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/RepoSequenceTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/RepoSequenceTest.java
index 66ccdad..76be4569 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/RepoSequenceTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/RepoSequenceTest.java
@@ -25,6 +25,7 @@
 import com.google.common.util.concurrent.Runnables;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 import com.google.gerrit.testutil.InMemoryRepositoryManager;
 import com.google.gwtorm.server.OrmException;
 import java.io.IOException;
@@ -264,7 +265,14 @@
       Runnable afterReadRef,
       Retryer<RefUpdate.Result> retryer) {
     return new RepoSequence(
-        repoManager, project, name, () -> start, batchSize, afterReadRef, retryer);
+        repoManager,
+        GitReferenceUpdated.DISABLED,
+        project,
+        name,
+        () -> start,
+        batchSize,
+        afterReadRef,
+        retryer);
   }
 
   private ObjectId writeBlob(String sequenceName, String value) {
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js
index 3605e1d..f811e9e 100644
--- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js
+++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js
@@ -51,6 +51,7 @@
     'is:open',
     'is:owner',
     'is:pending',
+    'is:private',
     'is:reviewed',
     'is:reviewer',
     'is:starred',