Merge "Revert "Revert "Add project permission for removing votes/labels."""
diff --git a/java/com/google/gerrit/index/project/ProjectSchemaDefinitions.java b/java/com/google/gerrit/index/project/ProjectSchemaDefinitions.java
index ef2c3f5..05c23e1 100644
--- a/java/com/google/gerrit/index/project/ProjectSchemaDefinitions.java
+++ b/java/com/google/gerrit/index/project/ProjectSchemaDefinitions.java
@@ -17,6 +17,7 @@
 import static com.google.gerrit.index.SchemaUtil.schema;
 
 import com.google.common.collect.ImmutableList;
+import com.google.gerrit.index.IndexedField;
 import com.google.gerrit.index.Schema;
 import com.google.gerrit.index.SchemaDefinitions;
 
@@ -38,7 +39,7 @@
               ProjectField.PARENT_NAME_FIELD,
               ProjectField.NAME_PART_FIELD,
               ProjectField.ANCESTOR_NAME_FIELD),
-          ImmutableList.of(
+          ImmutableList.<IndexedField<ProjectData, ?>.SearchSpec>of(
               ProjectField.NAME_SPEC,
               ProjectField.DESCRIPTION_SPEC,
               ProjectField.PARENT_NAME_SPEC,
@@ -50,8 +51,8 @@
       schema(
           V1,
           ImmutableList.of(ProjectField.REF_STATE),
-          ImmutableList.of(ProjectField.STATE_FIELD),
-          ImmutableList.of(ProjectField.STATE_SPEC));
+          ImmutableList.<IndexedField<ProjectData, ?>>of(ProjectField.STATE_FIELD),
+          ImmutableList.<IndexedField<ProjectData, ?>.SearchSpec>of(ProjectField.STATE_SPEC));
 
   // Bump Lucene version requires reindexing
   @Deprecated static final Schema<ProjectData> V3 = schema(V2);
diff --git a/java/com/google/gerrit/index/query/AndPredicate.java b/java/com/google/gerrit/index/query/AndPredicate.java
index 23ae312..fda961d 100644
--- a/java/com/google/gerrit/index/query/AndPredicate.java
+++ b/java/com/google/gerrit/index/query/AndPredicate.java
@@ -134,7 +134,7 @@
       cmp = a.estimateCost() - b.estimateCost();
     }
 
-    if (cmp == 0 && a instanceof DataSource && b instanceof DataSource) {
+    if (cmp == 0 && a instanceof DataSource) {
       DataSource<?> as = (DataSource<?>) a;
       DataSource<?> bs = (DataSource<?>) b;
       cmp = as.getCardinality() - bs.getCardinality();
diff --git a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index da75057..510ca01 100644
--- a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -1141,10 +1141,7 @@
   @Operator
   public Predicate<ChangeData> message(String text) throws QueryParseException {
     if (text.startsWith("^")) {
-      if (!args.index.getSchema().hasField(ChangeField.COMMIT_MESSAGE_EXACT)) {
-        throw new QueryParseException(
-            "'message' operator with regular expression is not supported on this gerrit host");
-      }
+      checkFieldAvailable(ChangeField.COMMIT_MESSAGE_EXACT, "messageexact");
       return new RegexMessagePredicate(text);
     }
     return ChangePredicates.message(text);
diff --git a/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl.ts b/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl.ts
index 746ecf3..0d0c88f 100644
--- a/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl.ts
+++ b/polygerrit-ui/app/services/gr-rest-api/gr-rest-api-impl.ts
@@ -143,7 +143,7 @@
 import {ParsedChangeInfo} from '../../types/types';
 import {ErrorCallback} from '../../api/rest';
 import {addDraftProp, DraftInfo} from '../../utils/comment-util';
-import {BaseScheduler} from '../scheduler/scheduler';
+import {BaseScheduler, Scheduler} from '../scheduler/scheduler';
 import {MaxInFlightScheduler} from '../scheduler/max-in-flight-scheduler';
 import {escapeAndWrapSearchOperatorValue} from '../../utils/string-util';
 
@@ -270,6 +270,11 @@
 function createWriteScheduler() {
   return new MaxInFlightScheduler<Response>(new BaseScheduler<Response>(), 5);
 }
+
+function createSerializingScheduler() {
+  return new MaxInFlightScheduler<Response>(new BaseScheduler<Response>(), 1);
+}
+
 export class GrRestApiServiceImpl implements RestApiService, Finalizable {
   readonly _cache = siteBasedCache; // Shared across instances.
 
@@ -286,6 +291,9 @@
   // Private, but used in tests.
   readonly _restApiHelper: GrRestApiHelper;
 
+  // Used to serialize requests for certain RPCs
+  readonly _serialScheduler: Scheduler<Response>;
+
   constructor(private readonly authService: AuthService) {
     this._restApiHelper = new GrRestApiHelper(
       this._cache,
@@ -294,6 +302,7 @@
       createReadScheduler(),
       createWriteScheduler()
     );
+    this._serialScheduler = createSerializingScheduler();
   }
 
   finalize() {}
@@ -2232,11 +2241,13 @@
     return this.getFromProjectLookup(changeNum).then(project => {
       const encodedRepoName = project ? encodeURIComponent(project) + '~' : '';
       const url = `/accounts/self/starred.changes/${encodedRepoName}${changeNum}`;
-      return this._restApiHelper.send({
-        method: starred ? HttpMethod.PUT : HttpMethod.DELETE,
-        url,
-        anonymizedUrl: '/accounts/self/starred.changes/*',
-      });
+      return this._serialScheduler.schedule(() =>
+        this._restApiHelper.send({
+          method: starred ? HttpMethod.PUT : HttpMethod.DELETE,
+          url,
+          anonymizedUrl: '/accounts/self/starred.changes/*',
+        })
+      );
     });
   }