Merge "Move SubmoduleSectionParser into util/git/ , a separate java_library."
diff --git a/Documentation/config-labels.txt b/Documentation/config-labels.txt
index cf78c6d..3db621d 100644
--- a/Documentation/config-labels.txt
+++ b/Documentation/config-labels.txt
@@ -368,6 +368,9 @@
 ignored if the label doesn't apply for that branch.
 Additionally, the `branch` modifier has no effect when the submit rule
 is customized in the rules.pl of the project or inherited from parent projects.
+Branch can be a ref pattern similar to what is documented
+link:access-control.html#reference[here], but must not contain `${username}` or
+`${shardeduserid}`.
 
 [[label_example]]
 === Example
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index 35ff40b..5c008c7 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -2734,7 +2734,7 @@
 [source, java]
 ----
 import com.google.gerrit.extensions.annotations.ExtensionPoint;
-import com.google.gerrit.server.mail.receive.MailMessage;
+import com.google.gerrit.mail.MailMessage;
 
 public class MyPlugin implements MailFilter {
   public boolean shouldProcessMessage(MailMessage message) {
@@ -2871,6 +2871,17 @@
 Plugin authors should also consider binding their SubmitRule using a `Gerrit-BatchModule`.
 See link:dev-plugins.html[Batch runtime] for more informations.
 
+
+The SubmitRule extension point allows you to write complex rules, but writing
+small self-contained rules should be preferred: doing so allows end users to
+compose several rules to form more complex submit checks.
+
+The `SubmitRequirement` class allows rules to communicate what the user needs
+to change in order to be compliant. These requirements should be kept once they
+are met, but marked as `OK`. If the requirements were not displayed, reviewers
+would need to use their precious time to manually check that they were met.
+
+
 == SEE ALSO
 
 * link:js-api.html[JavaScript API]
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index 54f8022..792cca8 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -1357,8 +1357,8 @@
 === Index project
 
 Adds or updates the current project (and children, if specified) in the secondary index.
-The indexing task is executed asynchronously in background, so this command
-returns immediately.
+The indexing task is executed asynchronously in background and this command returns
+immediately if `async` is specified in the input.
 
 As an input, a link:#index-project-input[IndexProjectInput] entity can be provided.
 
@@ -1369,6 +1369,7 @@
 
   {
     "index_children": "true"
+    "async": "true"
   }
 ----
 
@@ -3162,6 +3163,8 @@
 |Field Name         ||Description
 |`index_children`   ||
 If children should be indexed recursively.
+|`async`            ||
+If projects should be indexed asynchronously.
 |================================
 
 [[inherited-boolean-info]]
diff --git a/Documentation/user-search-projects.txt b/Documentation/user-search-projects.txt
index ba20adb..11c1326 100644
--- a/Documentation/user-search-projects.txt
+++ b/Documentation/user-search-projects.txt
@@ -24,6 +24,11 @@
 Matches projects whose description contains 'DESCRIPTION', using a
 full-text search.
 
+[[state]]
+state:'STATE'::
++
+Matches project's state. Can be either 'active' or 'read-only'.
+
 == Magical Operators
 
 [[is-visible]]
diff --git a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index abb28ce..53686a7 100644
--- a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -68,6 +68,10 @@
 import com.google.gerrit.extensions.restapi.BinaryResult;
 import com.google.gerrit.extensions.restapi.IdString;
 import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.index.project.ProjectIndex;
+import com.google.gerrit.index.project.ProjectIndexCollection;
+import com.google.gerrit.mail.Address;
+import com.google.gerrit.mail.EmailHeader;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.BooleanProjectConfig;
@@ -108,8 +112,6 @@
 import com.google.gerrit.server.index.change.ChangeIndexCollection;
 import com.google.gerrit.server.index.change.ChangeIndexer;
 import com.google.gerrit.server.index.group.GroupIndexer;
-import com.google.gerrit.server.mail.Address;
-import com.google.gerrit.server.mail.send.EmailHeader;
 import com.google.gerrit.server.notedb.ChangeNoteUtil;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.notedb.MutableNotesMigration;
@@ -274,6 +276,7 @@
 
   @Inject private ChangeIndexCollection changeIndexes;
   @Inject private AccountIndexCollection accountIndexes;
+  @Inject private ProjectIndexCollection projectIndexes;
   @Inject private EventRecorder.Factory eventRecorderFactory;
   @Inject private InProcessProtocol inProcessProtocol;
   @Inject private Provider<AnonymousUser> anonymousUser;
@@ -900,6 +903,41 @@
     };
   }
 
+  protected AutoCloseable disableProjectIndex() {
+    disableProjectIndexWrites();
+    ProjectIndex searchIndex = projectIndexes.getSearchIndex();
+    if (!(searchIndex instanceof DisabledProjectIndex)) {
+      projectIndexes.setSearchIndex(new DisabledProjectIndex(searchIndex), false);
+    }
+
+    return new AutoCloseable() {
+      @Override
+      public void close() {
+        enableProjectIndexWrites();
+        ProjectIndex searchIndex = projectIndexes.getSearchIndex();
+        if (searchIndex instanceof DisabledProjectIndex) {
+          projectIndexes.setSearchIndex(((DisabledProjectIndex) searchIndex).unwrap(), false);
+        }
+      }
+    };
+  }
+
+  protected void disableProjectIndexWrites() {
+    for (ProjectIndex i : projectIndexes.getWriteIndexes()) {
+      if (!(i instanceof DisabledProjectIndex)) {
+        projectIndexes.addWriteIndex(new DisabledProjectIndex(i));
+      }
+    }
+  }
+
+  protected void enableProjectIndexWrites() {
+    for (ProjectIndex i : projectIndexes.getWriteIndexes()) {
+      if (i instanceof DisabledProjectIndex) {
+        projectIndexes.addWriteIndex(((DisabledProjectIndex) i).unwrap());
+      }
+    }
+  }
+
   protected static Gson newGson() {
     return OutputFormat.JSON_COMPACT.newGson();
   }
diff --git a/java/com/google/gerrit/acceptance/AbstractNotificationTest.java b/java/com/google/gerrit/acceptance/AbstractNotificationTest.java
index 2336f2f..f2d0f70 100644
--- a/java/com/google/gerrit/acceptance/AbstractNotificationTest.java
+++ b/java/com/google/gerrit/acceptance/AbstractNotificationTest.java
@@ -34,10 +34,10 @@
 import com.google.gerrit.extensions.client.GeneralPreferencesInfo.EmailStrategy;
 import com.google.gerrit.extensions.client.InheritableBoolean;
 import com.google.gerrit.extensions.client.ReviewerState;
+import com.google.gerrit.mail.Address;
+import com.google.gerrit.mail.EmailHeader;
+import com.google.gerrit.mail.EmailHeader.AddressList;
 import com.google.gerrit.server.account.ProjectWatches.NotifyType;
-import com.google.gerrit.server.mail.Address;
-import com.google.gerrit.server.mail.send.EmailHeader;
-import com.google.gerrit.server.mail.send.EmailHeader.AddressList;
 import com.google.gerrit.testing.FakeEmailSender;
 import com.google.gerrit.testing.FakeEmailSender.Message;
 import java.io.IOException;
diff --git a/java/com/google/gerrit/acceptance/BUILD b/java/com/google/gerrit/acceptance/BUILD
index 25e1d7c..0214cea 100644
--- a/java/com/google/gerrit/acceptance/BUILD
+++ b/java/com/google/gerrit/acceptance/BUILD
@@ -19,6 +19,7 @@
         "//java/com/google/gerrit/index",
         "//java/com/google/gerrit/launcher",
         "//java/com/google/gerrit/lucene",
+        "//java/com/google/gerrit/mail",
         "//java/com/google/gerrit/metrics",
         "//java/com/google/gerrit/pgm",
         "//java/com/google/gerrit/pgm/init",
@@ -98,7 +99,9 @@
         "//java/com/google/gerrit/extensions:api",
         "//java/com/google/gerrit/httpd",
         "//java/com/google/gerrit/index",
+        "//java/com/google/gerrit/index/project",
         "//java/com/google/gerrit/lucene",
+        "//java/com/google/gerrit/mail",
         "//java/com/google/gerrit/metrics",
         "//java/com/google/gerrit/pgm/init",
         "//java/com/google/gerrit/reviewdb:server",
diff --git a/java/com/google/gerrit/acceptance/DisabledProjectIndex.java b/java/com/google/gerrit/acceptance/DisabledProjectIndex.java
new file mode 100644
index 0000000..2524a76
--- /dev/null
+++ b/java/com/google/gerrit/acceptance/DisabledProjectIndex.java
@@ -0,0 +1,76 @@
+// Copyright (C) 2018 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.acceptance;
+
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.project.ProjectData;
+import com.google.gerrit.index.project.ProjectIndex;
+import com.google.gerrit.index.query.DataSource;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.reviewdb.client.Project;
+
+/**
+ * This class wraps an index and assumes the search index can't handle any queries. However, it does
+ * return the current schema as the assumption is that we need a search index for starting Gerrit in
+ * the first place and only later lose the index connection (making it so that we can't send
+ * requests there anymore).
+ */
+public class DisabledProjectIndex implements ProjectIndex {
+  private final ProjectIndex index;
+
+  public DisabledProjectIndex(ProjectIndex index) {
+    this.index = index;
+  }
+
+  public ProjectIndex unwrap() {
+    return index;
+  }
+
+  @Override
+  public Schema<ProjectData> getSchema() {
+    return index.getSchema();
+  }
+
+  @Override
+  public void close() {
+    index.close();
+  }
+
+  @Override
+  public void replace(ProjectData obj) {
+    throw new UnsupportedOperationException("ProjectIndex is disabled");
+  }
+
+  @Override
+  public void delete(Project.NameKey key) {
+    throw new UnsupportedOperationException("ProjectIndex is disabled");
+  }
+
+  @Override
+  public void deleteAll() {
+    throw new UnsupportedOperationException("ProjectIndex is disabled");
+  }
+
+  @Override
+  public DataSource<ProjectData> getSource(Predicate<ProjectData> p, QueryOptions opts) {
+    throw new UnsupportedOperationException("ProjectIndex is disabled");
+  }
+
+  @Override
+  public void markReady(boolean ready) {
+    throw new UnsupportedOperationException("ProjectIndex is disabled");
+  }
+}
diff --git a/java/com/google/gerrit/acceptance/ProjectResetter.java b/java/com/google/gerrit/acceptance/ProjectResetter.java
index f4a8da3..1fed8f8 100644
--- a/java/com/google/gerrit/acceptance/ProjectResetter.java
+++ b/java/com/google/gerrit/acceptance/ProjectResetter.java
@@ -23,6 +23,7 @@
 import com.google.common.collect.MultimapBuilder;
 import com.google.common.collect.Sets;
 import com.google.gerrit.common.Nullable;
+import com.google.gerrit.index.RefState;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
@@ -32,7 +33,6 @@
 import com.google.gerrit.server.account.GroupIncludeCache;
 import com.google.gerrit.server.config.AllUsersName;
 import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.index.RefState;
 import com.google.gerrit.server.index.account.AccountIndexer;
 import com.google.gerrit.server.index.group.GroupIndexer;
 import com.google.gerrit.server.project.ProjectCache;
diff --git a/java/com/google/gerrit/acceptance/TestAccount.java b/java/com/google/gerrit/acceptance/TestAccount.java
index 094e8b0..5ce44ff 100644
--- a/java/com/google/gerrit/acceptance/TestAccount.java
+++ b/java/com/google/gerrit/acceptance/TestAccount.java
@@ -17,8 +17,8 @@
 import static java.util.stream.Collectors.toList;
 
 import com.google.common.net.InetAddresses;
+import com.google.gerrit.mail.Address;
 import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.server.mail.Address;
 import java.net.InetSocketAddress;
 import java.util.Arrays;
 import java.util.List;
diff --git a/java/com/google/gerrit/common/data/GroupReference.java b/java/com/google/gerrit/common/data/GroupReference.java
index dc22d62..cfaad17 100644
--- a/java/com/google/gerrit/common/data/GroupReference.java
+++ b/java/com/google/gerrit/common/data/GroupReference.java
@@ -22,11 +22,6 @@
 
   private static final String PREFIX = "group ";
 
-  /** @return a new reference to the given group description. */
-  public static GroupReference forGroup(AccountGroup group) {
-    return new GroupReference(group.getGroupUUID(), group.getName());
-  }
-
   public static GroupReference forGroup(GroupDescription.Basic group) {
     return new GroupReference(group.getGroupUUID(), group.getName());
   }
@@ -66,6 +61,9 @@
   }
 
   public void setName(String newName) {
+    if (newName == null) {
+      throw new NullPointerException();
+    }
     this.name = newName;
   }
 
diff --git a/java/com/google/gerrit/common/data/PermissionRule.java b/java/com/google/gerrit/common/data/PermissionRule.java
index c50af5c..8ab0a55 100644
--- a/java/com/google/gerrit/common/data/PermissionRule.java
+++ b/java/com/google/gerrit/common/data/PermissionRule.java
@@ -66,27 +66,27 @@
     action = Action.BLOCK;
   }
 
-  public Boolean getForce() {
+  public boolean getForce() {
     return force;
   }
 
-  public void setForce(Boolean newForce) {
+  public void setForce(boolean newForce) {
     force = newForce;
   }
 
-  public Integer getMin() {
+  public int getMin() {
     return min;
   }
 
-  public void setMin(Integer min) {
+  public void setMin(int min) {
     this.min = min;
   }
 
-  public void setMax(Integer max) {
+  public void setMax(int max) {
     this.max = max;
   }
 
-  public Integer getMax() {
+  public int getMax() {
     return max;
   }
 
@@ -266,7 +266,7 @@
   }
 
   public boolean hasRange() {
-    return (!(getMin() == null || getMin() == 0)) || (!(getMax() == null || getMax() == 0));
+    return getMin() != 0 || getMax() != 0;
   }
 
   public static int parseInt(String value) {
diff --git a/java/com/google/gerrit/extensions/api/projects/IndexProjectInput.java b/java/com/google/gerrit/extensions/api/projects/IndexProjectInput.java
index a41f227..1b962c1 100644
--- a/java/com/google/gerrit/extensions/api/projects/IndexProjectInput.java
+++ b/java/com/google/gerrit/extensions/api/projects/IndexProjectInput.java
@@ -16,4 +16,5 @@
 
 public class IndexProjectInput {
   public Boolean indexChildren;
+  public Boolean async;
 }
diff --git a/java/com/google/gerrit/extensions/restapi/AcceptsPost.java b/java/com/google/gerrit/extensions/restapi/AcceptsPost.java
deleted file mode 100644
index da87d32..0000000
--- a/java/com/google/gerrit/extensions/restapi/AcceptsPost.java
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (C) 2012 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.extensions.restapi;
-
-/**
- * Optional interface for {@link RestCollection}.
- *
- * <p>Collections that implement this interface can accept a {@code POST} directly on the collection
- * itself when no id was given in the path. This interface is intended to be used with
- * TopLevelResource collections. Nested collections often bind POST on the parent collection to the
- * view implementation handling the insertion of a new member.
- */
-public interface AcceptsPost<P extends RestResource> {
-  /**
-   * Handle creation of a child resource by POST on the collection.
-   *
-   * @param parent parent collection handle.
-   * @return a view to perform the creation. The id of the newly created resource should be
-   *     determined from the input body.
-   * @throws RestApiException the view cannot be constructed.
-   */
-  RestModifyView<P, ?> post(P parent) throws RestApiException;
-}
diff --git a/java/com/google/gerrit/extensions/restapi/RestApiModule.java b/java/com/google/gerrit/extensions/restapi/RestApiModule.java
index ee31113..d47b094 100644
--- a/java/com/google/gerrit/extensions/restapi/RestApiModule.java
+++ b/java/com/google/gerrit/extensions/restapi/RestApiModule.java
@@ -29,6 +29,7 @@
   protected static final String DELETE = "DELETE";
   protected static final String POST = "POST";
   protected static final String CREATE = "CREATE";
+  protected static final String POST_ON_COLLECTION = "POST_ON_COLLECTION";
 
   protected <R extends RestResource> ReadViewBinder<R> get(TypeLiteral<RestView<R>> viewType) {
     return get(viewType, "/");
@@ -46,6 +47,12 @@
     return delete(viewType, "/");
   }
 
+  protected <R extends RestResource> RestCollectionViewBinder<R> postOnCollection(
+      TypeLiteral<RestView<R>> viewType) {
+    return new RestCollectionViewBinder<>(
+        bind(viewType).annotatedWith(export(POST_ON_COLLECTION, "/")));
+  }
+
   protected <R extends RestResource> CreateViewBinder<R> create(TypeLiteral<RestView<R>> viewType) {
     return new CreateViewBinder<>(bind(viewType).annotatedWith(export(CREATE, "/")));
   }
@@ -142,6 +149,33 @@
     }
   }
 
+  public static class RestCollectionViewBinder<C extends RestResource> {
+    private final LinkedBindingBuilder<RestView<C>> binder;
+
+    private RestCollectionViewBinder(LinkedBindingBuilder<RestView<C>> binder) {
+      this.binder = binder;
+    }
+
+    public <P extends RestResource, T extends RestCollectionView<P, C, ?>> ScopedBindingBuilder to(
+        Class<T> impl) {
+      return binder.to(impl);
+    }
+
+    public <P extends RestResource, T extends RestCollectionView<P, C, ?>> void toInstance(T impl) {
+      binder.toInstance(impl);
+    }
+
+    public <P extends RestResource, T extends RestCollectionView<P, C, ?>>
+        ScopedBindingBuilder toProvider(Class<? extends Provider<? extends T>> providerType) {
+      return binder.toProvider(providerType);
+    }
+
+    public <P extends RestResource, T extends RestCollectionView<P, C, ?>>
+        ScopedBindingBuilder toProvider(Provider<? extends T> provider) {
+      return binder.toProvider(provider);
+    }
+  }
+
   public static class CreateViewBinder<C extends RestResource> {
     private final LinkedBindingBuilder<RestView<C>> binder;
 
diff --git a/java/com/google/gerrit/extensions/restapi/RestCollectionView.java b/java/com/google/gerrit/extensions/restapi/RestCollectionView.java
new file mode 100644
index 0000000..7e8eef0
--- /dev/null
+++ b/java/com/google/gerrit/extensions/restapi/RestCollectionView.java
@@ -0,0 +1,31 @@
+// Copyright (C) 2018 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.extensions.restapi;
+
+/**
+ * RestView on a RestCollection that supports accepting input.
+ *
+ * <p>The input must be supplied as JSON as the body of the HTTP request. RestCollectionViews can
+ * only be invoked by the HTTP method {@code POST}.
+ *
+ * @param <P> type of the parent resource
+ * @param <C> type of the child resource
+ * @param <I> type of input the JSON parser will parse the input into.
+ */
+public interface RestCollectionView<P extends RestResource, C extends RestResource, I>
+    extends RestView<C> {
+
+  Object apply(P parentResource, I input) throws Exception;
+}
diff --git a/java/com/google/gerrit/httpd/BUILD b/java/com/google/gerrit/httpd/BUILD
index bbb5b66..3e71098 100644
--- a/java/com/google/gerrit/httpd/BUILD
+++ b/java/com/google/gerrit/httpd/BUILD
@@ -13,6 +13,7 @@
         "//java/com/google/gerrit/metrics",
         "//java/com/google/gerrit/reviewdb:server",
         "//java/com/google/gerrit/server",
+        "//java/com/google/gerrit/server/audit",
         "//java/com/google/gerrit/server/git/receive",
         "//java/com/google/gerrit/server/ioutil",
         "//java/com/google/gerrit/server/restapi",
diff --git a/java/com/google/gerrit/httpd/auth/oauth/BUILD b/java/com/google/gerrit/httpd/auth/oauth/BUILD
index 96726ad..7315ce1 100644
--- a/java/com/google/gerrit/httpd/auth/oauth/BUILD
+++ b/java/com/google/gerrit/httpd/auth/oauth/BUILD
@@ -10,6 +10,7 @@
         "//java/com/google/gerrit/httpd",
         "//java/com/google/gerrit/reviewdb:server",
         "//java/com/google/gerrit/server",
+        "//java/com/google/gerrit/server/audit",
         "//lib:gson",
         "//lib:guava",
         "//lib:gwtorm",
diff --git a/java/com/google/gerrit/httpd/auth/openid/BUILD b/java/com/google/gerrit/httpd/auth/openid/BUILD
index bfb2551..f80e9d5 100644
--- a/java/com/google/gerrit/httpd/auth/openid/BUILD
+++ b/java/com/google/gerrit/httpd/auth/openid/BUILD
@@ -13,6 +13,7 @@
         "//java/com/google/gerrit/reviewdb:server",
         "//java/com/google/gerrit/util/http",
         "//java/com/google/gerrit/server",
+        "//java/com/google/gerrit/server/audit",
         "//lib:guava",
         "//lib:gwtorm",
         "//lib:servlet-api-3_1",
diff --git a/java/com/google/gerrit/httpd/init/BUILD b/java/com/google/gerrit/httpd/init/BUILD
index 292ceff..d557c0e 100644
--- a/java/com/google/gerrit/httpd/init/BUILD
+++ b/java/com/google/gerrit/httpd/init/BUILD
@@ -18,6 +18,7 @@
         "//java/com/google/gerrit/server",
         "//java/com/google/gerrit/server:module",
         "//java/com/google/gerrit/server/api",
+        "//java/com/google/gerrit/server/audit",
         "//java/com/google/gerrit/server/cache/h2",
         "//java/com/google/gerrit/server/cache/mem",
         "//java/com/google/gerrit/server/git/receive",
diff --git a/java/com/google/gerrit/httpd/init/WebAppInitializer.java b/java/com/google/gerrit/httpd/init/WebAppInitializer.java
index 9ce7690..8e380f5 100644
--- a/java/com/google/gerrit/httpd/init/WebAppInitializer.java
+++ b/java/com/google/gerrit/httpd/init/WebAppInitializer.java
@@ -47,6 +47,7 @@
 import com.google.gerrit.server.account.AccountDeactivator;
 import com.google.gerrit.server.account.InternalAccountDirectory;
 import com.google.gerrit.server.api.GerritApiModule;
+import com.google.gerrit.server.audit.AuditModule;
 import com.google.gerrit.server.cache.h2.H2CacheModule;
 import com.google.gerrit.server.cache.mem.DefaultMemoryCacheModule;
 import com.google.gerrit.server.change.ChangeCleanupRunner;
@@ -342,6 +343,7 @@
     modules.add(new SmtpEmailSender.Module());
     modules.add(new SignedTokenEmailTokenVerifier.Module());
     modules.add(new LocalMergeSuperSetComputation.Module());
+    modules.add(new AuditModule());
 
     // Plugin module needs to be inserted *before* the index module.
     // There is the concept of LifecycleModule, in Gerrit's own extension
diff --git a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
index a454c00..599e98f 100644
--- a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
+++ b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
@@ -69,7 +69,6 @@
 import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.restapi.AcceptsDelete;
-import com.google.gerrit.extensions.restapi.AcceptsPost;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.BinaryResult;
@@ -87,6 +86,7 @@
 import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.extensions.restapi.RestCollection;
+import com.google.gerrit.extensions.restapi.RestCollectionView;
 import com.google.gerrit.extensions.restapi.RestCreateView;
 import com.google.gerrit.extensions.restapi.RestModifyView;
 import com.google.gerrit.extensions.restapi.RestReadView;
@@ -308,12 +308,14 @@
 
         if (isRead(req)) {
           viewData = new ViewData(null, rc.list());
-        } else if (rc instanceof AcceptsPost && isPost(req)) {
-          @SuppressWarnings("unchecked")
-          AcceptsPost<RestResource> ac = (AcceptsPost<RestResource>) rc;
-          viewData = new ViewData(null, ac.post(rsrc));
-        } else {
-          throw new MethodNotAllowedException();
+        } else if (isPost(req)) {
+          RestView<RestResource> restCollectionView =
+              rc.views().get("gerrit", "POST_ON_COLLECTION./");
+          if (restCollectionView != null) {
+            viewData = new ViewData(null, restCollectionView);
+          } else {
+            throw new MethodNotAllowedException();
+          }
         }
       } else {
         IdString id = path.remove(0);
@@ -350,10 +352,14 @@
         if (path.isEmpty()) {
           if (isRead(req)) {
             viewData = new ViewData(null, c.list());
-          } else if (c instanceof AcceptsPost && isPost(req)) {
-            @SuppressWarnings("unchecked")
-            AcceptsPost<RestResource> ac = (AcceptsPost<RestResource>) c;
-            viewData = new ViewData(null, ac.post(rsrc));
+          } else if (isPost(req)) {
+            RestView<RestResource> restCollectionView =
+                c.views().get(viewData.pluginName, "POST_ON_COLLECTION./");
+            if (restCollectionView != null) {
+              viewData = new ViewData(null, restCollectionView);
+            } else {
+              throw new MethodNotAllowedException();
+            }
           } else if (c instanceof AcceptsDelete && isDelete(req)) {
             @SuppressWarnings("unchecked")
             AcceptsDelete<RestResource> ac = (AcceptsDelete<RestResource>) c;
@@ -434,6 +440,19 @@
             ServletUtils.consumeRequestBody(is);
           }
         }
+      } else if (viewData.view instanceof RestCollectionView<?, ?, ?>) {
+        @SuppressWarnings("unchecked")
+        RestCollectionView<RestResource, RestResource, Object> m =
+            (RestCollectionView<RestResource, RestResource, Object>) viewData.view;
+
+        Type type = inputType(m);
+        inputRequestBody = parseRequest(req, type);
+        result = m.apply(rsrc, inputRequestBody);
+        if (inputRequestBody instanceof RawInput) {
+          try (InputStream is = req.getInputStream()) {
+            ServletUtils.consumeRequestBody(is);
+          }
+        }
       } else {
         throw new ResourceNotFoundException();
       }
@@ -778,6 +797,24 @@
     return ((ParameterizedType) supertype).getActualTypeArguments()[2];
   }
 
+  private static Type inputType(RestCollectionView<RestResource, RestResource, Object> m) {
+    // MyCollectionView implements RestCollectionView<SomeResource, SomeResource, MyInput>
+    TypeLiteral<?> typeLiteral = TypeLiteral.get(m.getClass());
+
+    // RestCollectionView<SomeResource, SomeResource, MyInput>
+    // This is smart enough to resolve even when there are intervening subclasses, even if they have
+    // reordered type arguments.
+    TypeLiteral<?> supertypeLiteral = typeLiteral.getSupertype(RestCollectionView.class);
+
+    Type supertype = supertypeLiteral.getType();
+    checkState(
+        supertype instanceof ParameterizedType,
+        "supertype of %s is not parameterized: %s",
+        typeLiteral,
+        supertypeLiteral);
+    return ((ParameterizedType) supertype).getActualTypeArguments()[2];
+  }
+
   private Object parseRequest(HttpServletRequest req, Type type)
       throws IOException, BadRequestException, SecurityException, IllegalArgumentException,
           NoSuchMethodException, IllegalAccessException, InstantiationException,
@@ -1142,11 +1179,12 @@
     String name = method + "." + p.get(0);
     RestView<RestResource> core = views.get("gerrit", name);
     if (core != null) {
-      return new ViewData(null, core);
+      return new ViewData("gerrit", core);
     }
+
     core = views.get("gerrit", "GET." + p.get(0));
     if (core != null) {
-      return new ViewData(null, core);
+      return new ViewData("gerrit", core);
     }
 
     Map<String, RestView<RestResource>> r = new TreeMap<>();
diff --git a/java/com/google/gerrit/httpd/rpc/project/ProjectAccessHandler.java b/java/com/google/gerrit/httpd/rpc/project/ProjectAccessHandler.java
index 0ae9c4c..0bcd8f8 100644
--- a/java/com/google/gerrit/httpd/rpc/project/ProjectAccessHandler.java
+++ b/java/com/google/gerrit/httpd/rpc/project/ProjectAccessHandler.java
@@ -127,8 +127,12 @@
           replace(config, toDelete, section);
 
         } else if (AccessSection.isValid(name)) {
-          if (checkIfOwner && !forProject.ref(name).test(RefPermission.WRITE_CONFIG)) {
-            continue;
+          if (checkIfOwner) {
+            try {
+              forProject.ref(name).check(RefPermission.WRITE_CONFIG);
+            } catch (AuthException e) {
+              continue;
+            }
           }
 
           RefPattern.validate(name);
@@ -143,8 +147,15 @@
             config.remove(config.getAccessSection(name));
           }
 
-        } else if (!checkIfOwner || forProject.ref(name).test(RefPermission.WRITE_CONFIG)) {
+        } else if (!checkIfOwner) {
           config.remove(config.getAccessSection(name));
+        } else {
+          try {
+            forProject.ref(name).check(RefPermission.WRITE_CONFIG);
+            config.remove(config.getAccessSection(name));
+          } catch (AuthException e) {
+            // Do nothing.
+          }
         }
       }
 
diff --git a/java/com/google/gerrit/index/BUILD b/java/com/google/gerrit/index/BUILD
index 2442b593..6604ca1 100644
--- a/java/com/google/gerrit/index/BUILD
+++ b/java/com/google/gerrit/index/BUILD
@@ -37,6 +37,7 @@
         "//java/com/google/gerrit/common:annotations",
         "//java/com/google/gerrit/extensions:api",
         "//java/com/google/gerrit/metrics",
+        "//java/com/google/gerrit/reviewdb:server",
         "//lib:guava",
         "//lib:gwtjsonrpc",
         "//lib:gwtorm",
diff --git a/java/com/google/gerrit/server/index/RefState.java b/java/com/google/gerrit/index/RefState.java
similarity index 98%
rename from java/com/google/gerrit/server/index/RefState.java
rename to java/com/google/gerrit/index/RefState.java
index 6b893f0..f0e465d 100644
--- a/java/com/google/gerrit/server/index/RefState.java
+++ b/java/com/google/gerrit/index/RefState.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.index;
+package com.google.gerrit.index;
 
 import static com.google.common.base.MoreObjects.firstNonNull;
 import static com.google.common.base.Preconditions.checkArgument;
diff --git a/java/com/google/gerrit/index/project/ProjectData.java b/java/com/google/gerrit/index/project/ProjectData.java
index 9d6b571..7365660 100644
--- a/java/com/google/gerrit/index/project/ProjectData.java
+++ b/java/com/google/gerrit/index/project/ProjectData.java
@@ -14,23 +14,43 @@
 
 package com.google.gerrit.index.project;
 
+import static com.google.common.collect.ImmutableList.toImmutableList;
+
 import com.google.common.collect.ImmutableList;
 import com.google.gerrit.reviewdb.client.Project;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
 
 public class ProjectData {
   private final Project project;
-  private final ImmutableList<Project.NameKey> ancestors;
+  private final Optional<ProjectData> parent;
 
-  public ProjectData(Project project, Iterable<Project.NameKey> ancestors) {
+  public ProjectData(Project project, Optional<ProjectData> parent) {
     this.project = project;
-    this.ancestors = ImmutableList.copyOf(ancestors);
+    this.parent = parent;
   }
 
   public Project getProject() {
     return project;
   }
 
-  public ImmutableList<Project.NameKey> getAncestors() {
-    return ancestors;
+  public Optional<ProjectData> getParent() {
+    return parent;
+  }
+
+  /** Returns all {@link ProjectData} in the hierarchy starting with the current one. */
+  public ImmutableList<ProjectData> tree() {
+    List<ProjectData> parents = new ArrayList<>();
+    Optional<ProjectData> curr = Optional.of(this);
+    while (curr.isPresent()) {
+      parents.add(curr.get());
+      curr = curr.get().parent;
+    }
+    return ImmutableList.copyOf(parents);
+  }
+
+  public ImmutableList<String> getParentNames() {
+    return tree().stream().skip(1).map(p -> p.getProject().getName()).collect(toImmutableList());
   }
 }
diff --git a/java/com/google/gerrit/index/project/ProjectField.java b/java/com/google/gerrit/index/project/ProjectField.java
index 1c2f629b..5e484b2 100644
--- a/java/com/google/gerrit/index/project/ProjectField.java
+++ b/java/com/google/gerrit/index/project/ProjectField.java
@@ -14,23 +14,30 @@
 
 package com.google.gerrit.index.project;
 
+import static com.google.common.collect.ImmutableList.toImmutableList;
 import static com.google.gerrit.index.FieldDef.exact;
 import static com.google.gerrit.index.FieldDef.fullText;
 import static com.google.gerrit.index.FieldDef.prefix;
+import static com.google.gerrit.index.FieldDef.storedOnly;
 
-import com.google.common.collect.Iterables;
 import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.RefState;
 import com.google.gerrit.index.SchemaUtil;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
 
 /** Index schema for projects. */
 public class ProjectField {
+  private static byte[] toRefState(Project project) {
+    return RefState.create(RefNames.REFS_CONFIG, project.getConfigRefState())
+        .toByteArray(project.getNameKey());
+  }
 
   public static final FieldDef<ProjectData, String> NAME =
       exact("name").stored().build(p -> p.getProject().getName());
 
   public static final FieldDef<ProjectData, String> DESCRIPTION =
-      fullText("description").build(p -> p.getProject().getDescription());
+      fullText("description").stored().build(p -> p.getProject().getDescription());
 
   public static final FieldDef<ProjectData, String> PARENT_NAME =
       exact("parent_name").build(p -> p.getProject().getParentName());
@@ -38,7 +45,26 @@
   public static final FieldDef<ProjectData, Iterable<String>> NAME_PART =
       prefix("name_part").buildRepeatable(p -> SchemaUtil.getNameParts(p.getProject().getName()));
 
+  public static final FieldDef<ProjectData, String> STATE =
+      exact("state").stored().build(p -> p.getProject().getState().name());
+
   public static final FieldDef<ProjectData, Iterable<String>> ANCESTOR_NAME =
-      exact("ancestor_name")
-          .buildRepeatable(p -> Iterables.transform(p.getAncestors(), Project.NameKey::get));
+      exact("ancestor_name").buildRepeatable(p -> p.getParentNames());
+
+  /**
+   * All values of all refs that were used in the course of indexing this document. This covers
+   * {@code refs/meta/config} of the current project and all of its parents.
+   *
+   * <p>Emitted as UTF-8 encoded strings of the form {@code project:ref/name:[hex sha]}.
+   */
+  public static final FieldDef<ProjectData, Iterable<byte[]>> REF_STATE =
+      storedOnly("ref_state")
+          .buildRepeatable(
+              projectData ->
+                  projectData
+                      .tree()
+                      .stream()
+                      .filter(p -> p.getProject().getConfigRefState() != null)
+                      .map(p -> toRefState(p.getProject()))
+                      .collect(toImmutableList()));
 }
diff --git a/java/com/google/gerrit/index/project/ProjectSchemaDefinitions.java b/java/com/google/gerrit/index/project/ProjectSchemaDefinitions.java
index cbea4fe..07b5adb 100644
--- a/java/com/google/gerrit/index/project/ProjectSchemaDefinitions.java
+++ b/java/com/google/gerrit/index/project/ProjectSchemaDefinitions.java
@@ -21,6 +21,7 @@
 
 public class ProjectSchemaDefinitions extends SchemaDefinitions<ProjectData> {
 
+  @Deprecated
   static final Schema<ProjectData> V1 =
       schema(
           ProjectField.NAME,
@@ -29,6 +30,8 @@
           ProjectField.NAME_PART,
           ProjectField.ANCESTOR_NAME);
 
+  static final Schema<ProjectData> V2 = schema(V1, ProjectField.STATE, ProjectField.REF_STATE);
+
   public static final ProjectSchemaDefinitions INSTANCE = new ProjectSchemaDefinitions();
 
   private ProjectSchemaDefinitions() {
diff --git a/java/com/google/gerrit/server/mail/Address.java b/java/com/google/gerrit/mail/Address.java
similarity index 95%
rename from java/com/google/gerrit/server/mail/Address.java
rename to java/com/google/gerrit/mail/Address.java
index e91f3f3..24ab353 100644
--- a/java/com/google/gerrit/server/mail/Address.java
+++ b/java/com/google/gerrit/mail/Address.java
@@ -12,9 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail;
+package com.google.gerrit.mail;
 
-import com.google.gerrit.server.mail.send.EmailHeader;
+import com.google.gerrit.common.Nullable;
 
 public class Address {
   public static Address parse(String in) {
@@ -50,8 +50,8 @@
     }
   }
 
-  final String name;
-  final String email;
+  @Nullable private final String name;
+  private final String email;
 
   public Address(String email) {
     this(null, email);
@@ -62,6 +62,7 @@
     this.email = email;
   }
 
+  @Nullable
   public String getName() {
     return name;
   }
diff --git a/java/com/google/gerrit/mail/BUILD b/java/com/google/gerrit/mail/BUILD
new file mode 100644
index 0000000..90bb82c
--- /dev/null
+++ b/java/com/google/gerrit/mail/BUILD
@@ -0,0 +1,18 @@
+java_library(
+    name = "mail",
+    srcs = glob(["*.java"]),
+    visibility = ["//visibility:public"],
+    deps = [
+        "//java/com/google/gerrit/common:annotations",
+        "//java/com/google/gerrit/reviewdb:server",
+        "//lib:guava",
+        "//lib/auto:auto-value",
+        "//lib/auto:auto-value-annotations",
+        "//lib/flogger:api",
+        "//lib/jsoup",
+        "//lib/log:jsonevent-layout",
+        "//lib/log:log4j",
+        "//lib/mime4j:core",
+        "//lib/mime4j:dom",
+    ],
+)
diff --git a/java/com/google/gerrit/server/mail/send/EmailHeader.java b/java/com/google/gerrit/mail/EmailHeader.java
similarity index 97%
rename from java/com/google/gerrit/server/mail/send/EmailHeader.java
rename to java/com/google/gerrit/mail/EmailHeader.java
index 29354f2..69d5fcd 100644
--- a/java/com/google/gerrit/server/mail/send/EmailHeader.java
+++ b/java/com/google/gerrit/mail/EmailHeader.java
@@ -12,12 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail.send;
+package com.google.gerrit.mail;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 import com.google.common.base.MoreObjects;
-import com.google.gerrit.server.mail.Address;
 import java.io.IOException;
 import java.io.Writer;
 import java.text.SimpleDateFormat;
@@ -183,7 +182,7 @@
       list.add(addr);
     }
 
-    void remove(java.lang.String email) {
+    public void remove(java.lang.String email) {
       list.removeIf(address -> address.getEmail().equals(email));
     }
 
diff --git a/java/com/google/gerrit/server/mail/receive/HtmlParser.java b/java/com/google/gerrit/mail/HtmlParser.java
similarity index 98%
rename from java/com/google/gerrit/server/mail/receive/HtmlParser.java
rename to java/com/google/gerrit/mail/HtmlParser.java
index d68f076..9821599 100644
--- a/java/com/google/gerrit/server/mail/receive/HtmlParser.java
+++ b/java/com/google/gerrit/mail/HtmlParser.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail.receive;
+package com.google.gerrit.mail;
 
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableSet;
diff --git a/java/com/google/gerrit/server/mail/receive/MailComment.java b/java/com/google/gerrit/mail/MailComment.java
similarity index 84%
rename from java/com/google/gerrit/server/mail/receive/MailComment.java
rename to java/com/google/gerrit/mail/MailComment.java
index 8571e12..fd8198c 100644
--- a/java/com/google/gerrit/server/mail/receive/MailComment.java
+++ b/java/com/google/gerrit/mail/MailComment.java
@@ -12,14 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail.receive;
+package com.google.gerrit.mail;
 
 import com.google.gerrit.reviewdb.client.Comment;
 import java.util.Objects;
 
 /** A comment parsed from inbound email */
 public class MailComment {
-  enum CommentType {
+  public enum CommentType {
     CHANGE_MESSAGE,
     FILE_COMMENT,
     INLINE_COMMENT
@@ -42,6 +42,22 @@
     this.isLink = isLink;
   }
 
+  public CommentType getType() {
+    return type;
+  }
+
+  public Comment getInReplyTo() {
+    return inReplyTo;
+  }
+
+  public String getFileName() {
+    return fileName;
+  }
+
+  public String getMessage() {
+    return message;
+  }
+
   /**
    * Checks if the provided comment concerns the same exact spot in the change. This is basically an
    * equals method except that the message is not checked.
diff --git a/java/com/google/gerrit/server/mail/MailHeader.java b/java/com/google/gerrit/mail/MailHeader.java
similarity index 97%
rename from java/com/google/gerrit/server/mail/MailHeader.java
rename to java/com/google/gerrit/mail/MailHeader.java
index cf145e5..2f31a9c 100644
--- a/java/com/google/gerrit/server/mail/MailHeader.java
+++ b/java/com/google/gerrit/mail/MailHeader.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail;
+package com.google.gerrit.mail;
 
 /** Variables used by emails to hold data */
 public enum MailHeader {
diff --git a/java/com/google/gerrit/server/mail/receive/MailHeaderParser.java b/java/com/google/gerrit/mail/MailHeaderParser.java
similarity index 92%
rename from java/com/google/gerrit/server/mail/receive/MailHeaderParser.java
rename to java/com/google/gerrit/mail/MailHeaderParser.java
index d176095..8eb4d97 100644
--- a/java/com/google/gerrit/server/mail/receive/MailHeaderParser.java
+++ b/java/com/google/gerrit/mail/MailHeaderParser.java
@@ -12,14 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail.receive;
+package com.google.gerrit.mail;
 
 import com.google.common.base.Splitter;
 import com.google.common.base.Strings;
 import com.google.common.flogger.FluentLogger;
 import com.google.common.primitives.Ints;
-import com.google.gerrit.server.mail.MailHeader;
-import com.google.gerrit.server.mail.MailUtil;
 import java.sql.Timestamp;
 import java.time.Instant;
 import java.time.format.DateTimeParseException;
@@ -44,7 +42,8 @@
       } else if (header.startsWith(MailHeader.COMMENT_DATE.fieldWithDelimiter())) {
         String ts = header.substring(MailHeader.COMMENT_DATE.fieldWithDelimiter().length()).trim();
         try {
-          metadata.timestamp = Timestamp.from(MailUtil.rfcDateformatter.parse(ts, Instant::from));
+          metadata.timestamp =
+              Timestamp.from(MailProcessingUtil.rfcDateformatter.parse(ts, Instant::from));
         } catch (DateTimeParseException e) {
           logger.atSevere().withCause(e).log(
               "Mail: Error while parsing timestamp from header of message %s", m.id());
@@ -91,7 +90,8 @@
       } else if (metadata.timestamp == null && line.contains(MailHeader.COMMENT_DATE.getName())) {
         String ts = extractFooter(MailHeader.COMMENT_DATE.withDelimiter(), line);
         try {
-          metadata.timestamp = Timestamp.from(MailUtil.rfcDateformatter.parse(ts, Instant::from));
+          metadata.timestamp =
+              Timestamp.from(MailProcessingUtil.rfcDateformatter.parse(ts, Instant::from));
         } catch (DateTimeParseException e) {
           logger.atSevere().withCause(e).log(
               "Mail: Error while parsing timestamp from footer of message %s", m.id());
diff --git a/java/com/google/gerrit/server/mail/receive/MailMessage.java b/java/com/google/gerrit/mail/MailMessage.java
similarity index 96%
rename from java/com/google/gerrit/server/mail/receive/MailMessage.java
rename to java/com/google/gerrit/mail/MailMessage.java
index 0d20464..bb83dfd 100644
--- a/java/com/google/gerrit/server/mail/receive/MailMessage.java
+++ b/java/com/google/gerrit/mail/MailMessage.java
@@ -12,12 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail.receive;
+package com.google.gerrit.mail;
 
 import com.google.auto.value.AutoValue;
 import com.google.common.collect.ImmutableList;
 import com.google.gerrit.common.Nullable;
-import com.google.gerrit.server.mail.Address;
 import java.time.Instant;
 
 /**
diff --git a/java/com/google/gerrit/server/mail/receive/MailMetadata.java b/java/com/google/gerrit/mail/MailMetadata.java
similarity index 96%
rename from java/com/google/gerrit/server/mail/receive/MailMetadata.java
rename to java/com/google/gerrit/mail/MailMetadata.java
index 04c2add..a311461 100644
--- a/java/com/google/gerrit/server/mail/receive/MailMetadata.java
+++ b/java/com/google/gerrit/mail/MailMetadata.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail.receive;
+package com.google.gerrit.mail;
 
 import com.google.common.base.MoreObjects;
 import java.sql.Timestamp;
diff --git a/java/com/google/gerrit/server/mail/receive/MailParsingException.java b/java/com/google/gerrit/mail/MailParsingException.java
similarity index 94%
rename from java/com/google/gerrit/server/mail/receive/MailParsingException.java
rename to java/com/google/gerrit/mail/MailParsingException.java
index b91bb18..7e85a27 100644
--- a/java/com/google/gerrit/server/mail/receive/MailParsingException.java
+++ b/java/com/google/gerrit/mail/MailParsingException.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail.receive;
+package com.google.gerrit.mail;
 
 /** An {@link Exception} indicating that an email could not be parsed. */
 public class MailParsingException extends Exception {
diff --git a/java/com/google/gerrit/mail/MailProcessingUtil.java b/java/com/google/gerrit/mail/MailProcessingUtil.java
new file mode 100644
index 0000000..b63189a
--- /dev/null
+++ b/java/com/google/gerrit/mail/MailProcessingUtil.java
@@ -0,0 +1,23 @@
+// Copyright (C) 2018 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.mail;
+
+import java.time.format.DateTimeFormatter;
+
+public class MailProcessingUtil {
+
+  public static DateTimeFormatter rfcDateformatter =
+      DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss ZZZ");
+}
diff --git a/java/com/google/gerrit/server/mail/receive/ParserUtil.java b/java/com/google/gerrit/mail/ParserUtil.java
similarity index 98%
rename from java/com/google/gerrit/server/mail/receive/ParserUtil.java
rename to java/com/google/gerrit/mail/ParserUtil.java
index e770a3e..6a27ac4 100644
--- a/java/com/google/gerrit/server/mail/receive/ParserUtil.java
+++ b/java/com/google/gerrit/mail/ParserUtil.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail.receive;
+package com.google.gerrit.mail;
 
 import com.google.common.base.Splitter;
 import com.google.common.collect.Iterables;
diff --git a/java/com/google/gerrit/server/mail/receive/RawMailParser.java b/java/com/google/gerrit/mail/RawMailParser.java
similarity index 98%
rename from java/com/google/gerrit/server/mail/receive/RawMailParser.java
rename to java/com/google/gerrit/mail/RawMailParser.java
index 57fe21f..00754d3 100644
--- a/java/com/google/gerrit/server/mail/receive/RawMailParser.java
+++ b/java/com/google/gerrit/mail/RawMailParser.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail.receive;
+package com.google.gerrit.mail;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 
@@ -21,7 +21,6 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.io.CharStreams;
 import com.google.common.primitives.Ints;
-import com.google.gerrit.server.mail.Address;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStreamReader;
diff --git a/java/com/google/gerrit/server/mail/receive/TextParser.java b/java/com/google/gerrit/mail/TextParser.java
similarity index 98%
rename from java/com/google/gerrit/server/mail/receive/TextParser.java
rename to java/com/google/gerrit/mail/TextParser.java
index b99c608..1a63599 100644
--- a/java/com/google/gerrit/server/mail/receive/TextParser.java
+++ b/java/com/google/gerrit/mail/TextParser.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail.receive;
+package com.google.gerrit.mail;
 
 import com.google.common.base.Splitter;
 import com.google.common.base.Strings;
diff --git a/java/com/google/gerrit/pgm/BUILD b/java/com/google/gerrit/pgm/BUILD
index 95570ec..64dd1d8 100644
--- a/java/com/google/gerrit/pgm/BUILD
+++ b/java/com/google/gerrit/pgm/BUILD
@@ -36,6 +36,7 @@
         "//java/com/google/gerrit/server",
         "//java/com/google/gerrit/server:module",
         "//java/com/google/gerrit/server/api",
+        "//java/com/google/gerrit/server/audit",
         "//java/com/google/gerrit/server/cache/h2",
         "//java/com/google/gerrit/server/cache/mem",
         "//java/com/google/gerrit/server/git/receive",
diff --git a/java/com/google/gerrit/pgm/Daemon.java b/java/com/google/gerrit/pgm/Daemon.java
index c38e7f5..517787c 100644
--- a/java/com/google/gerrit/pgm/Daemon.java
+++ b/java/com/google/gerrit/pgm/Daemon.java
@@ -56,6 +56,7 @@
 import com.google.gerrit.server.account.InternalAccountDirectory;
 import com.google.gerrit.server.api.GerritApiModule;
 import com.google.gerrit.server.api.PluginApiModule;
+import com.google.gerrit.server.audit.AuditModule;
 import com.google.gerrit.server.cache.h2.H2CacheModule;
 import com.google.gerrit.server.cache.mem.DefaultMemoryCacheModule;
 import com.google.gerrit.server.change.ChangeCleanupRunner;
@@ -422,6 +423,7 @@
     modules.add(cfgInjector.getInstance(GerritGlobalModule.class));
     modules.add(new GerritApiModule());
     modules.add(new PluginApiModule());
+    modules.add(new AuditModule());
 
     modules.add(new SearchingChangeCacheImpl.Module(slave));
     modules.add(new InternalAccountDirectory.Module());
diff --git a/java/com/google/gerrit/reviewdb/client/Project.java b/java/com/google/gerrit/reviewdb/client/Project.java
index 921667e..996f1ec 100644
--- a/java/com/google/gerrit/reviewdb/client/Project.java
+++ b/java/com/google/gerrit/reviewdb/client/Project.java
@@ -99,6 +99,8 @@
 
   protected String themeName;
 
+  protected String configRefState;
+
   protected Project() {}
 
   public Project(Project.NameKey nameKey) {
@@ -239,4 +241,14 @@
   public void setParentName(NameKey n) {
     parent = n;
   }
+
+  /** Returns the {@code ObjectId} as 40 digit hex of {@code refs/meta/config}'s HEAD. */
+  public String getConfigRefState() {
+    return configRefState;
+  }
+
+  /** Sets the {@code ObjectId} as 40 digit hex of {@code refs/meta/config}'s HEAD. */
+  public void setConfigRefState(String state) {
+    configRefState = state;
+  }
 }
diff --git a/java/com/google/gerrit/server/ApprovalCopier.java b/java/com/google/gerrit/server/ApprovalCopier.java
index 922922c..095263e 100644
--- a/java/com/google/gerrit/server/ApprovalCopier.java
+++ b/java/com/google/gerrit/server/ApprovalCopier.java
@@ -79,7 +79,6 @@
    *
    * @param db review database.
    * @param notes change notes for user uploading PatchSet
-   * @param user user uploading PatchSet
    * @param ps new PatchSet
    * @param rw open walk that can read the patch set commit; null to open the repo on demand.
    * @param repoConfig repo config used for change kind detection; null to read from repo on demand.
@@ -88,12 +87,11 @@
   public void copyInReviewDb(
       ReviewDb db,
       ChangeNotes notes,
-      CurrentUser user,
       PatchSet ps,
       @Nullable RevWalk rw,
       @Nullable Config repoConfig)
       throws OrmException {
-    copyInReviewDb(db, notes, user, ps, rw, repoConfig, Collections.emptyList());
+    copyInReviewDb(db, notes, ps, rw, repoConfig, Collections.emptyList());
   }
 
   /**
@@ -101,7 +99,6 @@
    *
    * @param db review database.
    * @param notes change notes for user uploading PatchSet
-   * @param user user uploading PatchSet
    * @param ps new PatchSet
    * @param rw open walk that can read the patch set commit; null to open the repo on demand.
    * @param repoConfig repo config used for change kind detection; null to read from repo on demand.
@@ -111,33 +108,30 @@
   public void copyInReviewDb(
       ReviewDb db,
       ChangeNotes notes,
-      CurrentUser user,
       PatchSet ps,
       @Nullable RevWalk rw,
       @Nullable Config repoConfig,
       Iterable<PatchSetApproval> dontCopy)
       throws OrmException {
     if (PrimaryStorage.of(notes.getChange()) == PrimaryStorage.REVIEW_DB) {
-      db.patchSetApprovals().insert(getForPatchSet(db, notes, user, ps, rw, repoConfig, dontCopy));
+      db.patchSetApprovals().insert(getForPatchSet(db, notes, ps, rw, repoConfig, dontCopy));
     }
   }
 
   Iterable<PatchSetApproval> getForPatchSet(
       ReviewDb db,
       ChangeNotes notes,
-      CurrentUser user,
       PatchSet.Id psId,
       @Nullable RevWalk rw,
       @Nullable Config repoConfig)
       throws OrmException {
     return getForPatchSet(
-        db, notes, user, psId, rw, repoConfig, Collections.<PatchSetApproval>emptyList());
+        db, notes, psId, rw, repoConfig, Collections.<PatchSetApproval>emptyList());
   }
 
   Iterable<PatchSetApproval> getForPatchSet(
       ReviewDb db,
       ChangeNotes notes,
-      CurrentUser user,
       PatchSet.Id psId,
       @Nullable RevWalk rw,
       @Nullable Config repoConfig,
@@ -147,13 +141,12 @@
     if (ps == null) {
       return Collections.emptyList();
     }
-    return getForPatchSet(db, notes, user, ps, rw, repoConfig, dontCopy);
+    return getForPatchSet(db, notes, ps, rw, repoConfig, dontCopy);
   }
 
   private Iterable<PatchSetApproval> getForPatchSet(
       ReviewDb db,
       ChangeNotes notes,
-      CurrentUser user,
       PatchSet ps,
       @Nullable RevWalk rw,
       @Nullable Config repoConfig,
@@ -211,7 +204,7 @@
           byUser.put(psa.getLabel(), psa.getAccountId(), copy(psa, ps.getId()));
         }
       }
-      return labelNormalizer.normalize(notes, user, byUser.values()).getNormalized();
+      return labelNormalizer.normalize(notes, byUser.values()).getNormalized();
     } catch (IOException e) {
       throw new OrmException(e);
     }
diff --git a/java/com/google/gerrit/server/ApprovalsUtil.java b/java/com/google/gerrit/server/ApprovalsUtil.java
index 8365ddb..3625de6 100644
--- a/java/com/google/gerrit/server/ApprovalsUtil.java
+++ b/java/com/google/gerrit/server/ApprovalsUtil.java
@@ -263,12 +263,17 @@
 
   private boolean canSee(ReviewDb db, ChangeNotes notes, Account.Id accountId) {
     try {
-      return projectCache.checkedGet(notes.getProjectName()).statePermitsRead()
-          && permissionBackend
-              .absentUser(accountId)
-              .change(notes)
-              .database(db)
-              .test(ChangePermission.READ);
+      if (!projectCache.checkedGet(notes.getProjectName()).statePermitsRead()) {
+        return false;
+      }
+      permissionBackend
+          .absentUser(accountId)
+          .change(notes)
+          .database(db)
+          .check(ChangePermission.READ);
+      return true;
+    } catch (AuthException e) {
+      return false;
     } catch (IOException | PermissionBackendException e) {
       logger.atWarning().withCause(e).log(
           "Failed to check if account %d can see change %d",
@@ -388,7 +393,6 @@
   public Iterable<PatchSetApproval> byPatchSet(
       ReviewDb db,
       ChangeNotes notes,
-      CurrentUser user,
       PatchSet.Id psId,
       @Nullable RevWalk rw,
       @Nullable Config repoConfig)
@@ -396,13 +400,12 @@
     if (!migration.readChanges()) {
       return sortApprovals(db.patchSetApprovals().byPatchSet(psId));
     }
-    return copier.getForPatchSet(db, notes, user, psId, rw, repoConfig);
+    return copier.getForPatchSet(db, notes, psId, rw, repoConfig);
   }
 
   public Iterable<PatchSetApproval> byPatchSetUser(
       ReviewDb db,
       ChangeNotes notes,
-      CurrentUser user,
       PatchSet.Id psId,
       Account.Id accountId,
       @Nullable RevWalk rw,
@@ -411,7 +414,7 @@
     if (!migration.readChanges()) {
       return sortApprovals(db.patchSetApprovals().byPatchSetUser(psId, accountId));
     }
-    return filterApprovals(byPatchSet(db, notes, user, psId, rw, repoConfig), accountId);
+    return filterApprovals(byPatchSet(db, notes, psId, rw, repoConfig), accountId);
   }
 
   public PatchSetApproval getSubmitter(ReviewDb db, ChangeNotes notes, PatchSet.Id c) {
diff --git a/java/com/google/gerrit/server/BUILD b/java/com/google/gerrit/server/BUILD
index 55ce464..e99c686 100644
--- a/java/com/google/gerrit/server/BUILD
+++ b/java/com/google/gerrit/server/BUILD
@@ -36,6 +36,7 @@
         "//java/com/google/gerrit/index:query_exception",
         "//java/com/google/gerrit/index/project",
         "//java/com/google/gerrit/lifecycle",
+        "//java/com/google/gerrit/mail",
         "//java/com/google/gerrit/metrics",
         "//java/com/google/gerrit/prettify:server",
         "//java/com/google/gerrit/reviewdb:server",
diff --git a/java/com/google/gerrit/server/PatchSetUtil.java b/java/com/google/gerrit/server/PatchSetUtil.java
index c9ca972..fe42a61 100644
--- a/java/com/google/gerrit/server/PatchSetUtil.java
+++ b/java/com/google/gerrit/server/PatchSetUtil.java
@@ -181,17 +181,16 @@
   }
 
   /** Check if the current patch set of the change is locked. */
-  public void checkPatchSetNotLocked(ChangeNotes notes, CurrentUser user)
+  public void checkPatchSetNotLocked(ChangeNotes notes)
       throws OrmException, IOException, ResourceConflictException {
-    if (isPatchSetLocked(notes, user)) {
+    if (isPatchSetLocked(notes)) {
       throw new ResourceConflictException(
           String.format("The current patch set of change %s is locked", notes.getChangeId()));
     }
   }
 
   /** Is the current patch set locked against state changes? */
-  public boolean isPatchSetLocked(ChangeNotes notes, CurrentUser user)
-      throws OrmException, IOException {
+  public boolean isPatchSetLocked(ChangeNotes notes) throws OrmException, IOException {
     Change change = notes.getChange();
     if (change.getStatus() == Change.Status.MERGED) {
       return false;
@@ -202,9 +201,8 @@
 
     ApprovalsUtil approvalsUtil = approvalsUtilProvider.get();
     for (PatchSetApproval ap :
-        approvalsUtil.byPatchSet(
-            dbProvider.get(), notes, user, change.currentPatchSetId(), null, null)) {
-      LabelType type = projectState.getLabelTypes(notes, user).byLabel(ap.getLabel());
+        approvalsUtil.byPatchSet(dbProvider.get(), notes, change.currentPatchSetId(), null, null)) {
+      LabelType type = projectState.getLabelTypes(notes).byLabel(ap.getLabel());
       if (type != null
           && ap.getValue() == 1
           && type.getFunction() == LabelFunction.PATCH_SET_LOCK) {
diff --git a/java/com/google/gerrit/server/ReviewerByEmailSet.java b/java/com/google/gerrit/server/ReviewerByEmailSet.java
index c16c9c8..caae45e 100644
--- a/java/com/google/gerrit/server/ReviewerByEmailSet.java
+++ b/java/com/google/gerrit/server/ReviewerByEmailSet.java
@@ -17,7 +17,7 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableTable;
 import com.google.common.collect.Table;
-import com.google.gerrit.server.mail.Address;
+import com.google.gerrit.mail.Address;
 import com.google.gerrit.server.notedb.ReviewerStateInternal;
 import java.sql.Timestamp;
 
diff --git a/java/com/google/gerrit/server/account/AccountDirectory.java b/java/com/google/gerrit/server/account/AccountDirectory.java
index eeca29c..ee9265f 100644
--- a/java/com/google/gerrit/server/account/AccountDirectory.java
+++ b/java/com/google/gerrit/server/account/AccountDirectory.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.server.account;
 
 import com.google.gerrit.extensions.common.AccountInfo;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import java.util.Set;
 
 /**
@@ -47,6 +48,6 @@
     STATUS
   }
 
-  public abstract void fillAccountInfo(
-      Iterable<? extends AccountInfo> in, Set<FillOptions> options);
+  public abstract void fillAccountInfo(Iterable<? extends AccountInfo> in, Set<FillOptions> options)
+      throws PermissionBackendException;
 }
diff --git a/java/com/google/gerrit/server/account/AccountLoader.java b/java/com/google/gerrit/server/account/AccountLoader.java
index ddad864..4398d9e 100644
--- a/java/com/google/gerrit/server/account/AccountLoader.java
+++ b/java/com/google/gerrit/server/account/AccountLoader.java
@@ -20,6 +20,7 @@
 import com.google.gerrit.extensions.common.AccountInfo;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.account.AccountDirectory.FillOptions;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.inject.assistedinject.Assisted;
 import com.google.inject.assistedinject.AssistedInject;
 import java.util.ArrayList;
@@ -83,18 +84,18 @@
     provided.add(info);
   }
 
-  public void fill() {
+  public void fill() throws PermissionBackendException {
     directory.fillAccountInfo(Iterables.concat(created.values(), provided), options);
   }
 
-  public void fill(Collection<? extends AccountInfo> infos) {
+  public void fill(Collection<? extends AccountInfo> infos) throws PermissionBackendException {
     for (AccountInfo info : infos) {
       put(info);
     }
     fill();
   }
 
-  public AccountInfo fillOne(Account.Id id) {
+  public AccountInfo fillOne(Account.Id id) throws PermissionBackendException {
     AccountInfo info = get(id);
     fill();
     return info;
diff --git a/java/com/google/gerrit/server/account/InternalAccountDirectory.java b/java/com/google/gerrit/server/account/InternalAccountDirectory.java
index cf21c70..ce97ff9 100644
--- a/java/com/google/gerrit/server/account/InternalAccountDirectory.java
+++ b/java/com/google/gerrit/server/account/InternalAccountDirectory.java
@@ -18,16 +18,23 @@
 import static java.util.stream.Collectors.toSet;
 
 import com.google.common.base.Strings;
+import com.google.common.collect.Sets;
 import com.google.common.collect.Streams;
 import com.google.gerrit.extensions.common.AccountInfo;
 import com.google.gerrit.extensions.common.AvatarInfo;
 import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.externalids.ExternalId;
 import com.google.gerrit.server.avatar.AvatarProvider;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.inject.AbstractModule;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 import com.google.inject.Singleton;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -35,6 +42,7 @@
 import java.util.EnumSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 
 @Singleton
@@ -51,22 +59,45 @@
   private final AccountCache accountCache;
   private final DynamicItem<AvatarProvider> avatar;
   private final IdentifiedUser.GenericFactory userFactory;
+  private final Provider<CurrentUser> self;
+  private final PermissionBackend permissionBackend;
 
   @Inject
   InternalAccountDirectory(
       AccountCache accountCache,
       DynamicItem<AvatarProvider> avatar,
-      IdentifiedUser.GenericFactory userFactory) {
+      IdentifiedUser.GenericFactory userFactory,
+      Provider<CurrentUser> self,
+      PermissionBackend permissionBackend) {
     this.accountCache = accountCache;
     this.avatar = avatar;
     this.userFactory = userFactory;
+    this.self = self;
+    this.permissionBackend = permissionBackend;
   }
 
   @Override
-  public void fillAccountInfo(Iterable<? extends AccountInfo> in, Set<FillOptions> options) {
+  public void fillAccountInfo(Iterable<? extends AccountInfo> in, Set<FillOptions> options)
+      throws PermissionBackendException {
     if (options.equals(ID_ONLY)) {
       return;
     }
+
+    boolean canModifyAccount = false;
+    Account.Id currentUserId = null;
+    if (self.get().isIdentifiedUser()) {
+      currentUserId = self.get().getAccountId();
+
+      try {
+        permissionBackend.currentUser().check(GlobalPermission.MODIFY_ACCOUNT);
+        canModifyAccount = true;
+      } catch (AuthException e) {
+        canModifyAccount = false;
+      }
+    }
+
+    Set<FillOptions> fillOptionsWithoutSecondaryEmails =
+        Sets.difference(options, EnumSet.of(FillOptions.SECONDARY_EMAILS));
     Set<Account.Id> ids =
         Streams.stream(in).map(a -> new Account.Id(a._accountId)).collect(toSet());
     Map<Account.Id, AccountState> accountStates = accountCache.get(ids);
@@ -74,7 +105,15 @@
       Account.Id id = new Account.Id(info._accountId);
       AccountState state = accountStates.get(id);
       if (state != null) {
-        fill(info, accountStates.get(id), options);
+        if (!options.contains(FillOptions.SECONDARY_EMAILS)
+            || Objects.equals(currentUserId, state.getAccount().getId())
+            || canModifyAccount) {
+          fill(info, accountStates.get(id), options);
+        } else {
+          // user is not allowed to see secondary emails
+          fill(info, accountStates.get(id), fillOptionsWithoutSecondaryEmails);
+        }
+
       } else {
         info._accountId = options.contains(FillOptions.ID) ? id.get() : null;
       }
diff --git a/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java b/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java
index a1d0c4f..9ef5753 100644
--- a/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java
+++ b/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java
@@ -79,7 +79,6 @@
 import com.google.gerrit.server.restapi.account.StarredChanges;
 import com.google.gerrit.server.restapi.account.Stars;
 import com.google.gerrit.server.restapi.change.ChangesCollection;
-import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 import java.util.List;
@@ -393,7 +392,7 @@
   public List<GroupInfo> getGroups() throws RestApiException {
     try {
       return getGroups.apply(account);
-    } catch (OrmException e) {
+    } catch (Exception e) {
       throw asRestApiException("Cannot get groups", e);
     }
   }
@@ -518,7 +517,11 @@
 
   @Override
   public List<AgreementInfo> listAgreements() throws RestApiException {
-    return getAgreements.apply(account);
+    try {
+      return getAgreements.apply(account);
+    } catch (Exception e) {
+      throw asRestApiException("Cannot get agreements", e);
+    }
   }
 
   @Override
diff --git a/java/com/google/gerrit/server/audit/AuditModule.java b/java/com/google/gerrit/server/audit/AuditModule.java
index 0052aaa..df037b6 100644
--- a/java/com/google/gerrit/server/audit/AuditModule.java
+++ b/java/com/google/gerrit/server/audit/AuditModule.java
@@ -16,6 +16,7 @@
 
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.server.audit.group.GroupAuditListener;
+import com.google.gerrit.server.group.GroupAuditService;
 import com.google.inject.AbstractModule;
 
 public class AuditModule extends AbstractModule {
@@ -24,6 +25,6 @@
   protected void configure() {
     DynamicSet.setOf(binder(), AuditListener.class);
     DynamicSet.setOf(binder(), GroupAuditListener.class);
-    bind(AuditService.class);
+    bind(GroupAuditService.class).to(AuditService.class);
   }
 }
diff --git a/java/com/google/gerrit/server/audit/AuditService.java b/java/com/google/gerrit/server/audit/AuditService.java
index 9528670..2055e33 100644
--- a/java/com/google/gerrit/server/audit/AuditService.java
+++ b/java/com/google/gerrit/server/audit/AuditService.java
@@ -22,12 +22,13 @@
 import com.google.gerrit.server.audit.group.GroupAuditListener;
 import com.google.gerrit.server.audit.group.GroupMemberAuditEvent;
 import com.google.gerrit.server.audit.group.GroupSubgroupAuditEvent;
+import com.google.gerrit.server.group.GroupAuditService;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import java.sql.Timestamp;
 
 @Singleton
-public class AuditService {
+public class AuditService implements GroupAuditService {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
   private final DynamicSet<AuditListener> auditListeners;
@@ -47,6 +48,7 @@
     }
   }
 
+  @Override
   public void dispatchAddMembers(
       Account.Id actor,
       AccountGroup.UUID updatedGroup,
@@ -63,6 +65,7 @@
     }
   }
 
+  @Override
   public void dispatchDeleteMembers(
       Account.Id actor,
       AccountGroup.UUID updatedGroup,
@@ -79,6 +82,7 @@
     }
   }
 
+  @Override
   public void dispatchAddSubgroups(
       Account.Id actor,
       AccountGroup.UUID updatedGroup,
@@ -95,6 +99,7 @@
     }
   }
 
+  @Override
   public void dispatchDeleteSubgroups(
       Account.Id actor,
       AccountGroup.UUID updatedGroup,
diff --git a/java/com/google/gerrit/server/audit/BUILD b/java/com/google/gerrit/server/audit/BUILD
new file mode 100644
index 0000000..5efdc5a
--- /dev/null
+++ b/java/com/google/gerrit/server/audit/BUILD
@@ -0,0 +1,73 @@
+java_library(
+    name = "audit",
+    srcs = glob(
+        ["**/*.java"],
+    ),
+    resource_strip_prefix = "resources",
+    resources = ["//resources/com/google/gerrit/server"],
+    visibility = ["//visibility:public"],
+    deps = [
+        "//java/com/google/gerrit/common:annotations",
+        "//java/com/google/gerrit/common:server",
+        "//java/com/google/gerrit/extensions:api",
+        "//java/com/google/gerrit/index",
+        "//java/com/google/gerrit/index:query_exception",
+        "//java/com/google/gerrit/index/project",
+        "//java/com/google/gerrit/lifecycle",
+        "//java/com/google/gerrit/metrics",
+        "//java/com/google/gerrit/prettify:server",
+        "//java/com/google/gerrit/reviewdb:server",
+        "//java/com/google/gerrit/server",
+        "//java/com/google/gerrit/server/ioutil",
+        "//java/com/google/gerrit/util/cli",
+        "//java/com/google/gerrit/util/ssl",
+        "//java/org/apache/commons/net",
+        "//java/org/eclipse/jgit:server",
+        "//lib:args4j",
+        "//lib:automaton",
+        "//lib:blame-cache",
+        "//lib:grappa",
+        "//lib:gson",
+        "//lib:guava",
+        "//lib:guava-retrying",
+        "//lib:gwtjsonrpc",
+        "//lib:gwtorm",
+        "//lib:jsch",
+        "//lib:juniversalchardet",
+        "//lib:mime-util",
+        "//lib:pegdown",
+        "//lib:protobuf",
+        "//lib:servlet-api-3_1",
+        "//lib:soy",
+        "//lib:tukaani-xz",
+        "//lib/auto:auto-value",
+        "//lib/auto:auto-value-annotations",
+        "//lib/bouncycastle:bcpkix-neverlink",
+        "//lib/bouncycastle:bcprov-neverlink",
+        "//lib/commons:codec",
+        "//lib/commons:compress",
+        "//lib/commons:dbcp",
+        "//lib/commons:lang",
+        "//lib/commons:net",
+        "//lib/commons:validator",
+        "//lib/flogger:api",
+        "//lib/guice",
+        "//lib/guice:guice-assistedinject",
+        "//lib/guice:guice-servlet",
+        "//lib/jgit/org.eclipse.jgit.archive:jgit-archive",
+        "//lib/jgit/org.eclipse.jgit:jgit",
+        "//lib/jsoup",
+        "//lib/log:jsonevent-layout",
+        "//lib/log:log4j",
+        "//lib/lucene:lucene-analyzers-common",
+        "//lib/lucene:lucene-core-and-backward-codecs",
+        "//lib/lucene:lucene-queryparser",
+        "//lib/mime4j:core",
+        "//lib/mime4j:dom",
+        "//lib/ow2:ow2-asm",
+        "//lib/ow2:ow2-asm-tree",
+        "//lib/ow2:ow2-asm-util",
+        "//lib/prolog:runtime",
+        "//proto:cache_java_proto",
+    ],
+)
diff --git a/java/com/google/gerrit/server/change/ChangeInserter.java b/java/com/google/gerrit/server/change/ChangeInserter.java
index c6fe93b..e8c55e8 100644
--- a/java/com/google/gerrit/server/change/ChangeInserter.java
+++ b/java/com/google/gerrit/server/change/ChangeInserter.java
@@ -29,6 +29,7 @@
 import com.google.gerrit.common.data.LabelTypes;
 import com.google.gerrit.extensions.api.changes.NotifyHandling;
 import com.google.gerrit.extensions.api.changes.RecipientType;
+import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.reviewdb.client.Account;
@@ -459,18 +460,24 @@
         .stream()
         .filter(
             accountId -> {
+              if (!projectState.statePermitsRead()) {
+                return false;
+              }
+
               try {
-                return permissionBackend
-                        .absentUser(accountId)
-                        .change(notes)
-                        .database(db)
-                        .test(ChangePermission.READ)
-                    && projectState.statePermitsRead();
+                permissionBackend
+                    .absentUser(accountId)
+                    .change(notes)
+                    .database(db)
+                    .check(ChangePermission.READ);
+                return true;
               } catch (PermissionBackendException e) {
                 logger.atWarning().withCause(e).log(
                     "Failed to check if account %d can see change %d",
                     accountId.get(), notes.getChangeId().get());
                 return false;
+              } catch (AuthException e) {
+                return false;
               }
             })
         .collect(toSet());
@@ -521,8 +528,7 @@
     if (fireRevisionCreated) {
       revisionCreated.fire(change, patchSet, ctx.getAccount(), ctx.getWhen(), notify);
       if (approvals != null && !approvals.isEmpty()) {
-        List<LabelType> labels =
-            projectState.getLabelTypes(change.getDest(), ctx.getUser()).getLabelTypes();
+        List<LabelType> labels = projectState.getLabelTypes(change.getDest()).getLabelTypes();
         Map<String, Short> allApprovals = new HashMap<>();
         Map<String, Short> oldApprovals = new HashMap<>();
         for (LabelType lt : labels) {
diff --git a/java/com/google/gerrit/server/change/ChangeJson.java b/java/com/google/gerrit/server/change/ChangeJson.java
index 2feea47..04b649b 100644
--- a/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/java/com/google/gerrit/server/change/ChangeJson.java
@@ -91,6 +91,7 @@
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.Url;
 import com.google.gerrit.index.query.QueryResult;
+import com.google.gerrit.mail.Address;
 import com.google.gerrit.metrics.Description;
 import com.google.gerrit.metrics.Description.Units;
 import com.google.gerrit.metrics.MetricMaker;
@@ -123,7 +124,6 @@
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.MergeUtil;
 import com.google.gerrit.server.index.change.ChangeField;
-import com.google.gerrit.server.mail.Address;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.notedb.ReviewerStateInternal;
 import com.google.gerrit.server.patch.PatchListNotAvailableException;
@@ -415,7 +415,8 @@
     return format(cd, Optional.of(rsrc.getPatchSet().getId()), true);
   }
 
-  public List<List<ChangeInfo>> formatQueryResults(List<QueryResult<ChangeData>> in) {
+  public List<List<ChangeInfo>> formatQueryResults(List<QueryResult<ChangeData>> in)
+      throws PermissionBackendException {
     try (Timer0.Context ignored = metrics.formatQueryResultsLatency.start()) {
       accountLoader = accountLoaderFactory.create(has(DETAILED_ACCOUNTS));
       List<List<ChangeInfo>> res = new ArrayList<>(in.size());
@@ -433,7 +434,8 @@
     }
   }
 
-  public List<ChangeInfo> formatChangeDatas(Collection<ChangeData> in) throws OrmException {
+  public List<ChangeInfo> formatChangeDatas(Collection<ChangeData> in)
+      throws OrmException, PermissionBackendException {
     accountLoader = accountLoaderFactory.create(has(DETAILED_ACCOUNTS));
     ensureLoaded(in);
     List<ChangeInfo> out = new ArrayList<>(in.size());
@@ -658,10 +660,9 @@
       // list permitted labels, since users can't vote on those patch sets.
       if (user.isIdentifiedUser()
           && (!limitToPsId.isPresent() || limitToPsId.get().equals(in.currentPatchSetId()))) {
-        PermissionBackend.ForChange perm = permissionBackendForChange(user, cd);
         out.permittedLabels =
             cd.change().getStatus() != Change.Status.ABANDONED
-                ? permittedLabels(perm, cd)
+                ? permittedLabels(user.getAccountId(), cd)
                 : ImmutableMap.of();
       }
 
@@ -889,7 +890,7 @@
     LabelTypes labelTypes = cd.getLabelTypes();
     for (Account.Id accountId : allUsers) {
       PermissionBackend.ForChange perm = permissionBackendForChange(accountId, cd);
-      Map<String, VotingRangeInfo> pvr = getPermittedVotingRanges(permittedLabels(perm, cd));
+      Map<String, VotingRangeInfo> pvr = getPermittedVotingRanges(permittedLabels(accountId, cd));
       for (Map.Entry<String, LabelWithStatus> e : labels.entrySet()) {
         LabelType lt = labelTypes.byLabel(e.getKey());
         if (lt == null) {
@@ -1030,8 +1031,7 @@
       Map<String, ApprovalInfo> byLabel = Maps.newHashMapWithExpectedSize(labels.size());
       Map<String, VotingRangeInfo> pvr = Collections.emptyMap();
       if (detailed) {
-        PermissionBackend.ForChange perm = permissionBackendForChange(accountId, cd);
-        pvr = getPermittedVotingRanges(permittedLabels(perm, cd));
+        pvr = getPermittedVotingRanges(permittedLabels(accountId, cd));
         for (Map.Entry<String, LabelWithStatus> entry : labels.entrySet()) {
           ApprovalInfo ai = approvalInfo(accountId, 0, null, null, null);
           byLabel.put(entry.getKey(), ai);
@@ -1106,8 +1106,7 @@
   }
 
   private Map<String, Collection<String>> permittedLabels(
-      PermissionBackend.ForChange perm, ChangeData cd)
-      throws OrmException, PermissionBackendException {
+      Account.Id filterApprovalsBy, ChangeData cd) throws OrmException, PermissionBackendException {
     boolean isMerged = cd.change().getStatus() == Change.Status.MERGED;
     LabelTypes labelTypes = cd.getLabelTypes();
     Map<String, LabelType> toCheck = new HashMap<>();
@@ -1123,7 +1122,8 @@
     }
 
     Map<String, Short> labels = null;
-    Set<LabelPermission.WithValue> can = perm.testLabels(toCheck.values());
+    Set<LabelPermission.WithValue> can =
+        permissionBackendForChange(filterApprovalsBy, cd).testLabels(toCheck.values());
     SetMultimap<String, String> permitted = LinkedHashMultimap.create();
     for (SubmitRecord rec : submitRecords(cd)) {
       if (rec.labels == null) {
@@ -1139,7 +1139,7 @@
           boolean ok = can.contains(new LabelPermission.WithValue(type, v));
           if (isMerged) {
             if (labels == null) {
-              labels = currentLabels(perm, cd);
+              labels = currentLabels(filterApprovalsBy, cd);
             }
             short prev = labels.getOrDefault(type.getName(), (short) 0);
             ok &= v.getValue() >= prev;
@@ -1163,17 +1163,15 @@
     return permitted.asMap();
   }
 
-  private Map<String, Short> currentLabels(PermissionBackend.ForChange perm, ChangeData cd)
+  private Map<String, Short> currentLabels(Account.Id accountId, ChangeData cd)
       throws OrmException {
-    IdentifiedUser user = perm.user().asIdentifiedUser();
     Map<String, Short> result = new HashMap<>();
     for (PatchSetApproval psa :
         approvalsUtil.byPatchSetUser(
             db.get(),
             lazyLoad ? cd.notes() : notesFactory.createFromIndexedChange(cd.change()),
-            user,
             cd.change().currentPatchSetId(),
-            user.getAccountId(),
+            accountId,
             null,
             null)) {
       result.put(psa.getLabel(), psa.getValue());
diff --git a/java/com/google/gerrit/server/change/LabelNormalizer.java b/java/com/google/gerrit/server/change/LabelNormalizer.java
index f4dbf21..1ec1717 100644
--- a/java/com/google/gerrit/server/change/LabelNormalizer.java
+++ b/java/com/google/gerrit/server/change/LabelNormalizer.java
@@ -26,11 +26,8 @@
 import com.google.gerrit.common.data.LabelValue;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.project.ProjectCache;
-import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import java.io.IOException;
@@ -71,43 +68,25 @@
     }
   }
 
-  private final IdentifiedUser.GenericFactory userFactory;
   private final ProjectCache projectCache;
 
   @Inject
-  LabelNormalizer(IdentifiedUser.GenericFactory userFactory, ProjectCache projectCache) {
-    this.userFactory = userFactory;
+  LabelNormalizer(ProjectCache projectCache) {
     this.projectCache = projectCache;
   }
 
   /**
-   * @param notes change containing the given approvals.
+   * @param notes change notes containing the given approvals.
    * @param approvals list of approvals.
    * @return copies of approvals normalized to the defined ranges for the label type. Approvals for
    *     unknown labels are not included in the output.
-   * @throws OrmException
    */
   public Result normalize(ChangeNotes notes, Collection<PatchSetApproval> approvals)
-      throws OrmException, IOException {
-    IdentifiedUser user = userFactory.create(notes.getChange().getOwner());
-    return normalize(notes, user, approvals);
-  }
-
-  /**
-   * @param notes change notes containing the given approvals.
-   * @param user current user.
-   * @param approvals list of approvals.
-   * @return copies of approvals normalized to the defined ranges for the label type. Approvals for
-   *     unknown labels are not included in the output.
-   */
-  public Result normalize(
-      ChangeNotes notes, CurrentUser user, Collection<PatchSetApproval> approvals)
       throws IOException {
     List<PatchSetApproval> unchanged = Lists.newArrayListWithCapacity(approvals.size());
     List<PatchSetApproval> updated = Lists.newArrayListWithCapacity(approvals.size());
     List<PatchSetApproval> deleted = Lists.newArrayListWithCapacity(approvals.size());
-    LabelTypes labelTypes =
-        projectCache.checkedGet(notes.getProjectName()).getLabelTypes(notes, user);
+    LabelTypes labelTypes = projectCache.checkedGet(notes.getProjectName()).getLabelTypes(notes);
     for (PatchSetApproval psa : approvals) {
       Change.Id changeId = psa.getKey().getParentKey().getParentKey();
       checkArgument(
diff --git a/java/com/google/gerrit/server/change/PatchSetInserter.java b/java/com/google/gerrit/server/change/PatchSetInserter.java
index b979240..d71a93d 100644
--- a/java/com/google/gerrit/server/change/PatchSetInserter.java
+++ b/java/com/google/gerrit/server/change/PatchSetInserter.java
@@ -273,12 +273,7 @@
     change.setCurrentPatchSet(patchSetInfo);
     if (copyApprovals) {
       approvalCopier.copyInReviewDb(
-          db,
-          ctx.getNotes(),
-          ctx.getUser(),
-          patchSet,
-          ctx.getRevWalk(),
-          ctx.getRepoView().getConfig());
+          db, ctx.getNotes(), patchSet, ctx.getRevWalk(), ctx.getRepoView().getConfig());
     }
     if (changeMessage != null) {
       cmUtil.addChangeMessage(db, update, changeMessage);
@@ -314,7 +309,7 @@
       throws AuthException, ResourceConflictException, IOException, PermissionBackendException,
           OrmException {
     // Not allowed to create a new patch set if the current patch set is locked.
-    psUtil.checkPatchSetNotLocked(origNotes, ctx.getUser());
+    psUtil.checkPatchSetNotLocked(origNotes);
 
     if (checkAddPatchSetPermission) {
       permissionBackend
diff --git a/java/com/google/gerrit/server/change/ReviewerResource.java b/java/com/google/gerrit/server/change/ReviewerResource.java
index 778897e..52f3585 100644
--- a/java/com/google/gerrit/server/change/ReviewerResource.java
+++ b/java/com/google/gerrit/server/change/ReviewerResource.java
@@ -19,10 +19,10 @@
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.extensions.restapi.RestResource;
 import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.mail.Address;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.mail.Address;
 import com.google.inject.TypeLiteral;
 import com.google.inject.assistedinject.Assisted;
 import com.google.inject.assistedinject.AssistedInject;
diff --git a/java/com/google/gerrit/server/config/GerritGlobalModule.java b/java/com/google/gerrit/server/config/GerritGlobalModule.java
index 57255a3..b6a257b 100644
--- a/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -91,7 +91,6 @@
 import com.google.gerrit.server.account.GroupIncludeCacheImpl;
 import com.google.gerrit.server.account.VersionedAuthorizedKeys;
 import com.google.gerrit.server.account.externalids.ExternalIdModule;
-import com.google.gerrit.server.audit.AuditModule;
 import com.google.gerrit.server.auth.AuthBackend;
 import com.google.gerrit.server.auth.UniversalAuthBackend;
 import com.google.gerrit.server.auth.oauth.OAuthTokenCache;
@@ -301,7 +300,6 @@
     bind(IdentifiedUser.GenericFactory.class).in(SINGLETON);
     bind(AccountControl.Factory.class);
 
-    install(new AuditModule());
     bind(UiActions.class);
 
     bind(GitReferenceUpdated.class);
diff --git a/java/com/google/gerrit/server/edit/ChangeEditModifier.java b/java/com/google/gerrit/server/edit/ChangeEditModifier.java
index 80d1cd1..a8c463d 100644
--- a/java/com/google/gerrit/server/edit/ChangeEditModifier.java
+++ b/java/com/google/gerrit/server/edit/ChangeEditModifier.java
@@ -408,7 +408,7 @@
     }
 
     // Not allowed to edit if the current patch set is locked.
-    patchSetUtil.checkPatchSetNotLocked(notes, currentUser.get());
+    patchSetUtil.checkPatchSetNotLocked(notes);
     try {
       permissionBackend
           .currentUser()
diff --git a/java/com/google/gerrit/server/events/EventBroker.java b/java/com/google/gerrit/server/events/EventBroker.java
index 7d35070..1aff0fa 100644
--- a/java/com/google/gerrit/server/events/EventBroker.java
+++ b/java/com/google/gerrit/server/events/EventBroker.java
@@ -171,21 +171,31 @@
       return false;
     }
     ReviewDb db = dbProvider.get();
-    return permissionBackend
-        .user(user)
-        .change(notesFactory.createChecked(db, change))
-        .database(db)
-        .test(ChangePermission.READ);
+    try {
+      permissionBackend
+          .user(user)
+          .change(notesFactory.createChecked(db, change))
+          .database(db)
+          .check(ChangePermission.READ);
+      return true;
+    } catch (AuthException e) {
+      return false;
+    }
   }
 
   protected boolean isVisibleTo(Branch.NameKey branchName, CurrentUser user)
       throws PermissionBackendException {
     ProjectState pe = projectCache.get(branchName.getParentKey());
-    if (pe == null) {
+    if (pe == null || !pe.statePermitsRead()) {
       return false;
     }
-    return pe.statePermitsRead()
-        && permissionBackend.user(user).ref(branchName).test(RefPermission.READ);
+
+    try {
+      permissionBackend.user(user).ref(branchName).check(RefPermission.READ);
+      return true;
+    } catch (AuthException e) {
+      return false;
+    }
   }
 
   protected boolean isVisibleTo(Event event, CurrentUser user)
diff --git a/java/com/google/gerrit/server/git/MergeUtil.java b/java/com/google/gerrit/server/git/MergeUtil.java
index 637be24..0231378 100644
--- a/java/com/google/gerrit/server/git/MergeUtil.java
+++ b/java/com/google/gerrit/server/git/MergeUtil.java
@@ -361,7 +361,7 @@
 
     PatchSetApproval submitAudit = null;
 
-    for (PatchSetApproval a : safeGetApprovals(notes, user, psId)) {
+    for (PatchSetApproval a : safeGetApprovals(notes, psId)) {
       if (a.getValue() <= 0) {
         // Negative votes aren't counted.
         continue;
@@ -460,10 +460,9 @@
     return "Verified".equalsIgnoreCase(id.get());
   }
 
-  private Iterable<PatchSetApproval> safeGetApprovals(
-      ChangeNotes notes, CurrentUser user, PatchSet.Id psId) {
+  private Iterable<PatchSetApproval> safeGetApprovals(ChangeNotes notes, PatchSet.Id psId) {
     try {
-      return approvalsUtil.byPatchSet(db.get(), notes, user, psId, null, null);
+      return approvalsUtil.byPatchSet(db.get(), notes, psId, null, null);
     } catch (OrmException e) {
       logger.atSevere().withCause(e).log("Can't read approval records for %s", psId);
       return Collections.emptyList();
diff --git a/java/com/google/gerrit/server/git/NotifyConfig.java b/java/com/google/gerrit/server/git/NotifyConfig.java
index d2d3a39..d39cf12 100644
--- a/java/com/google/gerrit/server/git/NotifyConfig.java
+++ b/java/com/google/gerrit/server/git/NotifyConfig.java
@@ -16,8 +16,8 @@
 
 import com.google.common.base.Strings;
 import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.mail.Address;
 import com.google.gerrit.server.account.ProjectWatches.NotifyType;
-import com.google.gerrit.server.mail.Address;
 import java.util.EnumSet;
 import java.util.HashSet;
 import java.util.Set;
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
index dd79f3f..e93b2c57 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
@@ -2444,7 +2444,7 @@
       RevCommit priorCommit = revisions.inverse().get(priorPatchSet);
 
       // Not allowed to create a new patch set if the current patch set is locked.
-      if (psUtil.isPatchSetLocked(notes, user)) {
+      if (psUtil.isPatchSetLocked(notes)) {
         reject(inputCommand, "cannot add patch set to " + ontoChange + ".");
         return false;
       }
@@ -2528,11 +2528,23 @@
       if (magicBranch != null
           && (magicBranch.workInProgress || magicBranch.ready)
           && magicBranch.workInProgress != change.isWorkInProgress()
-          && (!user.getAccountId().equals(change.getOwner())
-              && !permissions.test(ProjectPermission.WRITE_CONFIG)
-              && !permissionBackend.user(user).test(GlobalPermission.ADMINISTRATE_SERVER))) {
-        reject(inputCommand, ONLY_CHANGE_OWNER_OR_PROJECT_OWNER_CAN_MODIFY_WIP);
-        return false;
+          && !user.getAccountId().equals(change.getOwner())) {
+        boolean hasWriteConfigPermission = false;
+        try {
+          permissions.check(ProjectPermission.WRITE_CONFIG);
+          hasWriteConfigPermission = true;
+        } catch (AuthException e) {
+          // Do nothing.
+        }
+
+        if (!hasWriteConfigPermission) {
+          try {
+            permissionBackend.user(user).check(GlobalPermission.ADMINISTRATE_SERVER);
+          } catch (AuthException e1) {
+            reject(inputCommand, ONLY_CHANGE_OWNER_OR_PROJECT_OWNER_CAN_MODIFY_WIP);
+            return false;
+          }
+        }
       }
 
       if (magicBranch != null && (magicBranch.edit || magicBranch.draft)) {
diff --git a/java/com/google/gerrit/server/git/receive/ReplaceOp.java b/java/com/google/gerrit/server/git/receive/ReplaceOp.java
index 36c5005..3e7942f 100644
--- a/java/com/google/gerrit/server/git/receive/ReplaceOp.java
+++ b/java/com/google/gerrit/server/git/receive/ReplaceOp.java
@@ -309,7 +309,6 @@
     approvalCopier.copyInReviewDb(
         ctx.getDb(),
         ctx.getNotes(),
-        ctx.getUser(),
         newPatchSet,
         ctx.getRevWalk(),
         ctx.getRepoView().getConfig(),
@@ -408,7 +407,6 @@
           approvalsUtil.byPatchSetUser(
               ctx.getDb(),
               ctx.getNotes(),
-              ctx.getUser(),
               priorPatchSetId,
               ctx.getAccountId(),
               ctx.getRevWalk(),
@@ -541,10 +539,7 @@
      * show a transition from an oldValue of 0 to the new value.
      */
     List<LabelType> labels =
-        projectCache
-            .checkedGet(ctx.getProject())
-            .getLabelTypes(notes, ctx.getUser())
-            .getLabelTypes();
+        projectCache.checkedGet(ctx.getProject()).getLabelTypes(notes).getLabelTypes();
     Map<String, Short> allApprovals = new HashMap<>();
     Map<String, Short> oldApprovals = new HashMap<>();
     for (LabelType lt : labels) {
diff --git a/java/com/google/gerrit/server/git/validators/CommitValidators.java b/java/com/google/gerrit/server/git/validators/CommitValidators.java
index 57786a6..e4c7b35 100644
--- a/java/com/google/gerrit/server/git/validators/CommitValidators.java
+++ b/java/com/google/gerrit/server/git/validators/CommitValidators.java
@@ -335,27 +335,26 @@
       StringBuilder sb = new StringBuilder();
       sb.append("ERROR: ").append(errMsg);
 
+      boolean hinted = false;
       if (c.getFullMessage().contains(CHANGE_ID_PREFIX)) {
         String lastLine = Iterables.getLast(Splitter.on('\n').split(c.getFullMessage()), "");
         if (!lastLine.contains(CHANGE_ID_PREFIX)) {
-          sb.append('\n');
-          sb.append('\n');
-          sb.append("Hint: A potential ");
-          sb.append(FooterConstants.CHANGE_ID.getName());
-          sb.append(" was found, but it was not in the ");
-          sb.append("footer (last paragraph) of the commit message.");
+          hinted = true;
+          sb.append("\n\n")
+              .append("Hint: run\n")
+              .append("  git commit --amend\n")
+              .append("and move 'Change-Id: Ixxx..' to the bottom on a separate line\n");
         }
       }
-      sb.append('\n');
-      sb.append('\n');
-      sb.append("Hint: To automatically insert ");
-      sb.append(FooterConstants.CHANGE_ID.getName());
-      sb.append(", install the hook:\n");
-      sb.append(getCommitMessageHookInstallationHint());
-      sb.append('\n');
-      sb.append("And then amend the commit:\n");
-      sb.append("  git commit --amend\n");
 
+      // Print only one hint to avoid overwhelming the user.
+      if (!hinted) {
+        sb.append("Hint: to automatically insert a Change-Id, install the hook:\n")
+            .append(getCommitMessageHookInstallationHint())
+            .append("\n")
+            .append("and then amend the commit:\n")
+            .append("  git commit --amend\n");
+      }
       return new CommitValidationMessage(sb.toString(), false);
     }
 
diff --git a/java/com/google/gerrit/server/group/GroupAuditService.java b/java/com/google/gerrit/server/group/GroupAuditService.java
new file mode 100644
index 0000000..c543a6e
--- /dev/null
+++ b/java/com/google/gerrit/server/group/GroupAuditService.java
@@ -0,0 +1,48 @@
+// Copyright (C) 2018 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.server.group;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Account.Id;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import java.sql.Timestamp;
+
+public interface GroupAuditService {
+
+  void dispatchAddMembers(
+      Account.Id actor,
+      AccountGroup.UUID updatedGroup,
+      ImmutableSet<Id> addedMembers,
+      Timestamp addedOn);
+
+  void dispatchDeleteMembers(
+      Account.Id actor,
+      AccountGroup.UUID updatedGroup,
+      ImmutableSet<Account.Id> deletedMembers,
+      Timestamp deletedOn);
+
+  void dispatchAddSubgroups(
+      Account.Id actor,
+      AccountGroup.UUID updatedGroup,
+      ImmutableSet<AccountGroup.UUID> addedSubgroups,
+      Timestamp addedOn);
+
+  void dispatchDeleteSubgroups(
+      Account.Id actor,
+      AccountGroup.UUID updatedGroup,
+      ImmutableSet<AccountGroup.UUID> deletedSubgroups,
+      Timestamp deletedOn);
+}
diff --git a/java/com/google/gerrit/server/group/db/GroupsUpdate.java b/java/com/google/gerrit/server/group/db/GroupsUpdate.java
index 38db2e6..cd38c6a 100644
--- a/java/com/google/gerrit/server/group/db/GroupsUpdate.java
+++ b/java/com/google/gerrit/server/group/db/GroupsUpdate.java
@@ -31,13 +31,13 @@
 import com.google.gerrit.server.account.GroupBackend;
 import com.google.gerrit.server.account.GroupCache;
 import com.google.gerrit.server.account.GroupIncludeCache;
-import com.google.gerrit.server.audit.AuditService;
 import com.google.gerrit.server.config.AllUsersName;
 import com.google.gerrit.server.config.GerritServerId;
 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.meta.MetaDataUpdate;
+import com.google.gerrit.server.group.GroupAuditService;
 import com.google.gerrit.server.group.InternalGroup;
 import com.google.gerrit.server.index.group.GroupIndexer;
 import com.google.gerrit.server.update.RefUpdateUtil;
@@ -89,7 +89,7 @@
   private final GroupCache groupCache;
   private final GroupIncludeCache groupIncludeCache;
   private final Provider<GroupIndexer> indexer;
-  private final AuditService auditService;
+  private final GroupAuditService groupAuditService;
   private final RenameGroupOp.Factory renameGroupOpFactory;
   @Nullable private final IdentifiedUser currentUser;
   private final AuditLogFormatter auditLogFormatter;
@@ -106,7 +106,7 @@
       GroupCache groupCache,
       GroupIncludeCache groupIncludeCache,
       Provider<GroupIndexer> indexer,
-      AuditService auditService,
+      GroupAuditService auditService,
       AccountCache accountCache,
       RenameGroupOp.Factory renameGroupOpFactory,
       @GerritServerId String serverId,
@@ -120,7 +120,7 @@
     this.groupCache = groupCache;
     this.groupIncludeCache = groupIncludeCache;
     this.indexer = indexer;
-    this.auditService = auditService;
+    this.groupAuditService = auditService;
     this.renameGroupOpFactory = renameGroupOpFactory;
     this.gitRefUpdated = gitRefUpdated;
     this.retryHelper = retryHelper;
@@ -384,14 +384,14 @@
     }
 
     if (!createdGroup.getMembers().isEmpty()) {
-      auditService.dispatchAddMembers(
+      groupAuditService.dispatchAddMembers(
           currentUser.getAccountId(),
           createdGroup.getGroupUUID(),
           createdGroup.getMembers(),
           createdGroup.getCreatedOn());
     }
     if (!createdGroup.getSubgroups().isEmpty()) {
-      auditService.dispatchAddSubgroups(
+      groupAuditService.dispatchAddSubgroups(
           currentUser.getAccountId(),
           createdGroup.getGroupUUID(),
           createdGroup.getSubgroups(),
@@ -405,19 +405,19 @@
     }
 
     if (!result.getAddedMembers().isEmpty()) {
-      auditService.dispatchAddMembers(
+      groupAuditService.dispatchAddMembers(
           currentUser.getAccountId(), result.getGroupUuid(), result.getAddedMembers(), updatedOn);
     }
     if (!result.getDeletedMembers().isEmpty()) {
-      auditService.dispatchDeleteMembers(
+      groupAuditService.dispatchDeleteMembers(
           currentUser.getAccountId(), result.getGroupUuid(), result.getDeletedMembers(), updatedOn);
     }
     if (!result.getAddedSubgroups().isEmpty()) {
-      auditService.dispatchAddSubgroups(
+      groupAuditService.dispatchAddSubgroups(
           currentUser.getAccountId(), result.getGroupUuid(), result.getAddedSubgroups(), updatedOn);
     }
     if (!result.getDeletedSubgroups().isEmpty()) {
-      auditService.dispatchDeleteSubgroups(
+      groupAuditService.dispatchDeleteSubgroups(
           currentUser.getAccountId(),
           result.getGroupUuid(),
           result.getDeletedSubgroups(),
diff --git a/java/com/google/gerrit/server/index/account/AccountField.java b/java/com/google/gerrit/server/index/account/AccountField.java
index 31e2ada..111991c 100644
--- a/java/com/google/gerrit/server/index/account/AccountField.java
+++ b/java/com/google/gerrit/server/index/account/AccountField.java
@@ -27,11 +27,11 @@
 import com.google.common.collect.Iterables;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.RefState;
 import com.google.gerrit.index.SchemaUtil;
 import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.account.externalids.ExternalId;
-import com.google.gerrit.server.index.RefState;
 import java.sql.Timestamp;
 import java.util.Arrays;
 import java.util.Collections;
diff --git a/java/com/google/gerrit/server/index/account/StalenessChecker.java b/java/com/google/gerrit/server/index/account/StalenessChecker.java
index 6403d3d..0c3e329 100644
--- a/java/com/google/gerrit/server/index/account/StalenessChecker.java
+++ b/java/com/google/gerrit/server/index/account/StalenessChecker.java
@@ -24,6 +24,7 @@
 import com.google.common.collect.MultimapBuilder;
 import com.google.gerrit.index.IndexConfig;
 import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.RefState;
 import com.google.gerrit.index.query.FieldBundle;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Project;
@@ -33,7 +34,6 @@
 import com.google.gerrit.server.config.AllUsersName;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.index.IndexUtils;
-import com.google.gerrit.server.index.RefState;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import java.io.IOException;
diff --git a/java/com/google/gerrit/server/index/change/ChangeField.java b/java/com/google/gerrit/server/index/change/ChangeField.java
index 405e6fc..5d12e79 100644
--- a/java/com/google/gerrit/server/index/change/ChangeField.java
+++ b/java/com/google/gerrit/server/index/change/ChangeField.java
@@ -43,7 +43,9 @@
 import com.google.gerrit.common.data.SubmitRecord;
 import com.google.gerrit.common.data.SubmitRequirement;
 import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.RefState;
 import com.google.gerrit.index.SchemaUtil;
+import com.google.gerrit.mail.Address;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.ChangeMessage;
@@ -56,9 +58,7 @@
 import com.google.gerrit.server.ReviewerSet;
 import com.google.gerrit.server.StarredChangesUtil;
 import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.index.RefState;
 import com.google.gerrit.server.index.change.StalenessChecker.RefStatePattern;
-import com.google.gerrit.server.mail.Address;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
 import com.google.gerrit.server.notedb.ReviewerStateInternal;
diff --git a/java/com/google/gerrit/server/index/change/StalenessChecker.java b/java/com/google/gerrit/server/index/change/StalenessChecker.java
index 642385c..0508b60 100644
--- a/java/com/google/gerrit/server/index/change/StalenessChecker.java
+++ b/java/com/google/gerrit/server/index/change/StalenessChecker.java
@@ -31,12 +31,12 @@
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.index.IndexConfig;
+import com.google.gerrit.index.RefState;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.UsedAt;
 import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.index.RefState;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
 import com.google.gerrit.server.query.change.ChangeData;
diff --git a/java/com/google/gerrit/server/index/project/StalenessChecker.java b/java/com/google/gerrit/server/index/project/StalenessChecker.java
new file mode 100644
index 0000000..5603f08
--- /dev/null
+++ b/java/com/google/gerrit/server/index/project/StalenessChecker.java
@@ -0,0 +1,81 @@
+// Copyright (C) 2018 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.server.index.project;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.MultimapBuilder;
+import com.google.common.collect.SetMultimap;
+import com.google.gerrit.index.IndexConfig;
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.RefState;
+import com.google.gerrit.index.project.ProjectData;
+import com.google.gerrit.index.project.ProjectField;
+import com.google.gerrit.index.project.ProjectIndex;
+import com.google.gerrit.index.project.ProjectIndexCollection;
+import com.google.gerrit.index.query.FieldBundle;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.project.ProjectCache;
+import java.io.IOException;
+import java.util.Optional;
+import javax.inject.Inject;
+
+public class StalenessChecker {
+  private static final ImmutableSet<String> FIELDS =
+      ImmutableSet.of(ProjectField.NAME.getName(), ProjectField.REF_STATE.getName());
+
+  private final ProjectCache projectCache;
+  private final ProjectIndexCollection indexes;
+  private final IndexConfig indexConfig;
+
+  @Inject
+  StalenessChecker(
+      ProjectCache projectCache, ProjectIndexCollection indexes, IndexConfig indexConfig) {
+    this.projectCache = projectCache;
+    this.indexes = indexes;
+    this.indexConfig = indexConfig;
+  }
+
+  public boolean isStale(Project.NameKey project) throws IOException {
+    ProjectData projectData = projectCache.get(project).toProjectData();
+    ProjectIndex i = indexes.getSearchIndex();
+    if (i == null) {
+      return false; // No index; caller couldn't do anything if it is stale.
+    }
+
+    Optional<FieldBundle> result =
+        i.getRaw(project, QueryOptions.create(indexConfig, 0, 1, FIELDS));
+    if (!result.isPresent()) {
+      return true;
+    }
+
+    SetMultimap<Project.NameKey, RefState> indexedRefStates =
+        RefState.parseStates(result.get().getValue(ProjectField.REF_STATE));
+
+    SetMultimap<Project.NameKey, RefState> currentRefStates =
+        MultimapBuilder.hashKeys().hashSetValues().build();
+    projectData
+        .tree()
+        .stream()
+        .filter(p -> p.getProject().getConfigRefState() != null)
+        .forEach(
+            p ->
+                currentRefStates.put(
+                    p.getProject().getNameKey(),
+                    RefState.create(RefNames.REFS_CONFIG, p.getProject().getConfigRefState())));
+
+    return !currentRefStates.equals(indexedRefStates);
+  }
+}
diff --git a/java/com/google/gerrit/server/ioutil/BUILD b/java/com/google/gerrit/server/ioutil/BUILD
index 06843c5..15e67af 100644
--- a/java/com/google/gerrit/server/ioutil/BUILD
+++ b/java/com/google/gerrit/server/ioutil/BUILD
@@ -4,6 +4,7 @@
     visibility = ["//visibility:public"],
     deps = [
         "//java/com/google/gerrit/reviewdb:client",
+        "//lib:automaton",
         "//lib:guava",
         "//lib/jgit/org.eclipse.jgit.archive:jgit-archive",
         "//lib/jgit/org.eclipse.jgit:jgit",
diff --git a/java/com/google/gerrit/server/util/RegexListSearcher.java b/java/com/google/gerrit/server/ioutil/RegexListSearcher.java
similarity index 96%
rename from java/com/google/gerrit/server/util/RegexListSearcher.java
rename to java/com/google/gerrit/server/ioutil/RegexListSearcher.java
index aea1d5c..937a195 100644
--- a/java/com/google/gerrit/server/util/RegexListSearcher.java
+++ b/java/com/google/gerrit/server/ioutil/RegexListSearcher.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2014 The Android Open Source Project
+// Copyright (C) 2018 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.
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.util;
+package com.google.gerrit.server.ioutil;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
diff --git a/java/com/google/gerrit/server/mail/AutoReplyMailFilter.java b/java/com/google/gerrit/server/mail/AutoReplyMailFilter.java
index 9032932..e18fd42 100644
--- a/java/com/google/gerrit/server/mail/AutoReplyMailFilter.java
+++ b/java/com/google/gerrit/server/mail/AutoReplyMailFilter.java
@@ -15,7 +15,8 @@
 package com.google.gerrit.server.mail;
 
 import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.server.mail.receive.MailMessage;
+import com.google.gerrit.mail.MailHeader;
+import com.google.gerrit.mail.MailMessage;
 import com.google.inject.Singleton;
 
 /** Filters out auto-reply messages according to RFC 3834. */
diff --git a/java/com/google/gerrit/server/mail/ListMailFilter.java b/java/com/google/gerrit/server/mail/ListMailFilter.java
index 5a41c77..eee8c60 100644
--- a/java/com/google/gerrit/server/mail/ListMailFilter.java
+++ b/java/com/google/gerrit/server/mail/ListMailFilter.java
@@ -17,8 +17,8 @@
 import static java.util.stream.Collectors.joining;
 
 import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.mail.MailMessage;
 import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.mail.receive.MailMessage;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import java.util.Arrays;
@@ -52,7 +52,7 @@
       return true;
     }
 
-    boolean match = mailPattern.matcher(message.from().email).find();
+    boolean match = mailPattern.matcher(message.from().getEmail()).find();
     if (mode == ListFilterMode.WHITELIST && !match || mode == ListFilterMode.BLACKLIST && match) {
       logger.atInfo().log("Mail message from %s rejected by list filter", message.from());
       return false;
diff --git a/java/com/google/gerrit/server/mail/MailFilter.java b/java/com/google/gerrit/server/mail/MailFilter.java
index d50064d..5fff8a3 100644
--- a/java/com/google/gerrit/server/mail/MailFilter.java
+++ b/java/com/google/gerrit/server/mail/MailFilter.java
@@ -15,7 +15,7 @@
 package com.google.gerrit.server.mail;
 
 import com.google.gerrit.extensions.annotations.ExtensionPoint;
-import com.google.gerrit.server.mail.receive.MailMessage;
+import com.google.gerrit.mail.MailMessage;
 
 /**
  * Listener to filter incoming email.
diff --git a/java/com/google/gerrit/server/mail/MailUtil.java b/java/com/google/gerrit/server/mail/MailUtil.java
index 0487cc0..507b53f 100644
--- a/java/com/google/gerrit/server/mail/MailUtil.java
+++ b/java/com/google/gerrit/server/mail/MailUtil.java
@@ -24,7 +24,6 @@
 import com.google.gerrit.server.account.AccountResolver;
 import com.google.gwtorm.server.OrmException;
 import java.io.IOException;
-import java.time.format.DateTimeFormatter;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
@@ -34,8 +33,6 @@
 import org.eclipse.jgit.revwalk.FooterLine;
 
 public class MailUtil {
-  public static DateTimeFormatter rfcDateformatter =
-      DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss ZZZ");
 
   public static MailRecipients getRecipientsFromFooters(
       AccountResolver accountResolver, List<FooterLine> footerLines)
diff --git a/java/com/google/gerrit/server/mail/receive/ImapMailReceiver.java b/java/com/google/gerrit/server/mail/receive/ImapMailReceiver.java
index 169b41e..648006d 100644
--- a/java/com/google/gerrit/server/mail/receive/ImapMailReceiver.java
+++ b/java/com/google/gerrit/server/mail/receive/ImapMailReceiver.java
@@ -15,6 +15,9 @@
 package com.google.gerrit.server.mail.receive;
 
 import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.mail.MailMessage;
+import com.google.gerrit.mail.MailParsingException;
+import com.google.gerrit.mail.RawMailParser;
 import com.google.gerrit.server.git.WorkQueue;
 import com.google.gerrit.server.mail.EmailSettings;
 import com.google.gerrit.server.mail.Encryption;
diff --git a/java/com/google/gerrit/server/mail/receive/MailProcessor.java b/java/com/google/gerrit/server/mail/receive/MailProcessor.java
index 0e0bca6..75053c8 100644
--- a/java/com/google/gerrit/server/mail/receive/MailProcessor.java
+++ b/java/com/google/gerrit/server/mail/receive/MailProcessor.java
@@ -26,6 +26,12 @@
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
+import com.google.gerrit.mail.HtmlParser;
+import com.google.gerrit.mail.MailComment;
+import com.google.gerrit.mail.MailHeaderParser;
+import com.google.gerrit.mail.MailMessage;
+import com.google.gerrit.mail.MailMetadata;
+import com.google.gerrit.mail.TextParser;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.ChangeMessage;
@@ -283,7 +289,7 @@
 
       comments = new ArrayList<>();
       for (MailComment c : parsedComments) {
-        if (c.type == MailComment.CommentType.CHANGE_MESSAGE) {
+        if (c.getType() == MailComment.CommentType.CHANGE_MESSAGE) {
           continue;
         }
         comments.add(
@@ -301,8 +307,8 @@
     @Override
     public void postUpdate(Context ctx) throws Exception {
       String patchSetComment = null;
-      if (parsedComments.get(0).type == MailComment.CommentType.CHANGE_MESSAGE) {
-        patchSetComment = parsedComments.get(0).message;
+      if (parsedComments.get(0).getType() == MailComment.CommentType.CHANGE_MESSAGE) {
+        patchSetComment = parsedComments.get(0).getMessage();
       }
       // Send email notifications
       outgoingMailFactory
@@ -323,7 +329,6 @@
           .byPatchSetUser(
               ctx.getDb(),
               notes,
-              ctx.getUser(),
               psId,
               ctx.getAccountId(),
               ctx.getRevWalk(),
@@ -343,12 +348,12 @@
 
     private ChangeMessage generateChangeMessage(ChangeContext ctx) {
       String changeMsg = "Patch Set " + psId.get() + ":";
-      if (parsedComments.get(0).type == MailComment.CommentType.CHANGE_MESSAGE) {
+      if (parsedComments.get(0).getType() == MailComment.CommentType.CHANGE_MESSAGE) {
         // Add a blank line after Patch Set to follow the default format
         if (parsedComments.size() > 1) {
           changeMsg += "\n\n" + numComments(parsedComments.size() - 1);
         }
-        changeMsg += "\n\n" + parsedComments.get(0).message;
+        changeMsg += "\n\n" + parsedComments.get(0).getMessage();
       } else {
         changeMsg += "\n\n" + numComments(parsedComments.size());
       }
@@ -357,11 +362,11 @@
 
     private PatchSet targetPatchSetForComment(
         ChangeContext ctx, MailComment mailComment, PatchSet current) throws OrmException {
-      if (mailComment.inReplyTo != null) {
+      if (mailComment.getInReplyTo() != null) {
         return psUtil.get(
             ctx.getDb(),
             ctx.getNotes(),
-            new PatchSet.Id(ctx.getChange().getId(), mailComment.inReplyTo.key.patchSetId));
+            new PatchSet.Id(ctx.getChange().getId(), mailComment.getInReplyTo().key.patchSetId));
       }
       return current;
     }
@@ -373,11 +378,11 @@
       // The patch set that this comment is based on is different if this
       // comment was sent in reply to a comment on a previous patch set.
       Side side;
-      if (mailComment.inReplyTo != null) {
-        fileName = mailComment.inReplyTo.key.filename;
-        side = Side.fromShort(mailComment.inReplyTo.side);
+      if (mailComment.getInReplyTo() != null) {
+        fileName = mailComment.getInReplyTo().key.filename;
+        side = Side.fromShort(mailComment.getInReplyTo().side);
       } else {
-        fileName = mailComment.fileName;
+        fileName = mailComment.getFileName();
         side = Side.REVISION;
       }
 
@@ -387,16 +392,16 @@
               fileName,
               patchSetForComment.getId(),
               (short) side.ordinal(),
-              mailComment.message,
+              mailComment.getMessage(),
               false,
               null);
 
       comment.tag = tag;
-      if (mailComment.inReplyTo != null) {
-        comment.parentUuid = mailComment.inReplyTo.key.uuid;
-        comment.lineNbr = mailComment.inReplyTo.lineNbr;
-        comment.range = mailComment.inReplyTo.range;
-        comment.unresolved = mailComment.inReplyTo.unresolved;
+      if (mailComment.getInReplyTo() != null) {
+        comment.parentUuid = mailComment.getInReplyTo().key.uuid;
+        comment.lineNbr = mailComment.getInReplyTo().lineNbr;
+        comment.range = mailComment.getInReplyTo().range;
+        comment.unresolved = mailComment.getInReplyTo().unresolved;
       }
       CommentsUtil.setCommentRevId(comment, patchListCache, ctx.getChange(), patchSetForComment);
       return comment;
diff --git a/java/com/google/gerrit/server/mail/receive/MailReceiver.java b/java/com/google/gerrit/server/mail/receive/MailReceiver.java
index e4ad969..dc99b46 100644
--- a/java/com/google/gerrit/server/mail/receive/MailReceiver.java
+++ b/java/com/google/gerrit/server/mail/receive/MailReceiver.java
@@ -19,6 +19,7 @@
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.gerrit.mail.MailMessage;
 import com.google.gerrit.server.git.WorkQueue;
 import com.google.gerrit.server.mail.EmailSettings;
 import com.google.gerrit.server.update.UpdateException;
diff --git a/java/com/google/gerrit/server/mail/receive/Pop3MailReceiver.java b/java/com/google/gerrit/server/mail/receive/Pop3MailReceiver.java
index a3ea265..54971c4 100644
--- a/java/com/google/gerrit/server/mail/receive/Pop3MailReceiver.java
+++ b/java/com/google/gerrit/server/mail/receive/Pop3MailReceiver.java
@@ -16,6 +16,9 @@
 
 import com.google.common.flogger.FluentLogger;
 import com.google.common.primitives.Ints;
+import com.google.gerrit.mail.MailMessage;
+import com.google.gerrit.mail.MailParsingException;
+import com.google.gerrit.mail.RawMailParser;
 import com.google.gerrit.server.git.WorkQueue;
 import com.google.gerrit.server.mail.EmailSettings;
 import com.google.gerrit.server.mail.Encryption;
diff --git a/java/com/google/gerrit/server/mail/send/AddKeySender.java b/java/com/google/gerrit/server/mail/send/AddKeySender.java
index ae8ac31..433bd9b 100644
--- a/java/com/google/gerrit/server/mail/send/AddKeySender.java
+++ b/java/com/google/gerrit/server/mail/send/AddKeySender.java
@@ -18,9 +18,9 @@
 import com.google.gerrit.common.errors.EmailException;
 import com.google.gerrit.extensions.api.changes.RecipientType;
 import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.mail.Address;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.AccountSshKey;
-import com.google.gerrit.server.mail.Address;
 import com.google.gerrit.server.permissions.GlobalPermission;
 import com.google.gerrit.server.permissions.PermissionBackend;
 import com.google.gerrit.server.permissions.PermissionBackendException;
diff --git a/java/com/google/gerrit/server/mail/send/ChangeEmail.java b/java/com/google/gerrit/server/mail/send/ChangeEmail.java
index 503fbd0..00a496a2 100644
--- a/java/com/google/gerrit/server/mail/send/ChangeEmail.java
+++ b/java/com/google/gerrit/server/mail/send/ChangeEmail.java
@@ -21,6 +21,7 @@
 import com.google.gerrit.extensions.api.changes.NotifyHandling;
 import com.google.gerrit.extensions.api.changes.RecipientType;
 import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.mail.MailHeader;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Change;
@@ -30,7 +31,6 @@
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.StarredChangesUtil;
 import com.google.gerrit.server.account.ProjectWatches.NotifyType;
-import com.google.gerrit.server.mail.MailHeader;
 import com.google.gerrit.server.mail.send.ProjectWatch.Watchers;
 import com.google.gerrit.server.notedb.ReviewerStateInternal;
 import com.google.gerrit.server.patch.PatchList;
@@ -403,12 +403,19 @@
 
   @Override
   protected boolean isVisibleTo(Account.Id to) throws PermissionBackendException {
-    return projectState.statePermitsRead()
-        && args.permissionBackend
-            .absentUser(to)
-            .change(changeData)
-            .database(args.db)
-            .test(ChangePermission.READ);
+    if (!projectState.statePermitsRead()) {
+      return false;
+    }
+    try {
+      args.permissionBackend
+          .absentUser(to)
+          .change(changeData)
+          .database(args.db)
+          .check(ChangePermission.READ);
+      return true;
+    } catch (AuthException e) {
+      return false;
+    }
   }
 
   /** Find all users who are authors of any part of this change. */
diff --git a/java/com/google/gerrit/server/mail/send/CommentSender.java b/java/com/google/gerrit/server/mail/send/CommentSender.java
index 0095fc1..54176e2 100644
--- a/java/com/google/gerrit/server/mail/send/CommentSender.java
+++ b/java/com/google/gerrit/server/mail/send/CommentSender.java
@@ -23,6 +23,8 @@
 import com.google.gerrit.common.errors.EmailException;
 import com.google.gerrit.common.errors.NoSuchEntityException;
 import com.google.gerrit.extensions.api.changes.NotifyHandling;
+import com.google.gerrit.mail.MailHeader;
+import com.google.gerrit.mail.MailProcessingUtil;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Comment;
@@ -32,8 +34,6 @@
 import com.google.gerrit.server.CommentsUtil;
 import com.google.gerrit.server.account.ProjectWatches.NotifyType;
 import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.mail.MailHeader;
-import com.google.gerrit.server.mail.MailUtil;
 import com.google.gerrit.server.mail.receive.Protocol;
 import com.google.gerrit.server.patch.PatchFile;
 import com.google.gerrit.server.patch.PatchList;
@@ -566,7 +566,7 @@
 
   private String getCommentTimestamp() {
     // Grouping is currently done by timestamp.
-    return MailUtil.rfcDateformatter.format(
+    return MailProcessingUtil.rfcDateformatter.format(
         ZonedDateTime.ofInstant(timestamp.toInstant(), ZoneId.of("UTC")));
   }
 
diff --git a/java/com/google/gerrit/server/mail/send/DeleteReviewerSender.java b/java/com/google/gerrit/server/mail/send/DeleteReviewerSender.java
index c434d06..576d506 100644
--- a/java/com/google/gerrit/server/mail/send/DeleteReviewerSender.java
+++ b/java/com/google/gerrit/server/mail/send/DeleteReviewerSender.java
@@ -16,11 +16,11 @@
 
 import com.google.gerrit.common.errors.EmailException;
 import com.google.gerrit.extensions.api.changes.RecipientType;
+import com.google.gerrit.mail.Address;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.account.ProjectWatches.NotifyType;
-import com.google.gerrit.server.mail.Address;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
diff --git a/java/com/google/gerrit/server/mail/send/EmailSender.java b/java/com/google/gerrit/server/mail/send/EmailSender.java
index 23fa1fe..ce4964d 100644
--- a/java/com/google/gerrit/server/mail/send/EmailSender.java
+++ b/java/com/google/gerrit/server/mail/send/EmailSender.java
@@ -16,7 +16,8 @@
 
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.errors.EmailException;
-import com.google.gerrit.server.mail.Address;
+import com.google.gerrit.mail.Address;
+import com.google.gerrit.mail.EmailHeader;
 import java.util.Collection;
 import java.util.Map;
 
diff --git a/java/com/google/gerrit/server/mail/send/FromAddressGenerator.java b/java/com/google/gerrit/server/mail/send/FromAddressGenerator.java
index 2489063..5baabe9 100644
--- a/java/com/google/gerrit/server/mail/send/FromAddressGenerator.java
+++ b/java/com/google/gerrit/server/mail/send/FromAddressGenerator.java
@@ -14,8 +14,8 @@
 
 package com.google.gerrit.server.mail.send;
 
+import com.google.gerrit.mail.Address;
 import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.server.mail.Address;
 
 /** Constructs an address to send email from. */
 public interface FromAddressGenerator {
diff --git a/java/com/google/gerrit/server/mail/send/FromAddressGeneratorProvider.java b/java/com/google/gerrit/server/mail/send/FromAddressGeneratorProvider.java
index 500eef3..b77909e 100644
--- a/java/com/google/gerrit/server/mail/send/FromAddressGeneratorProvider.java
+++ b/java/com/google/gerrit/server/mail/send/FromAddressGeneratorProvider.java
@@ -17,13 +17,13 @@
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 import com.google.gerrit.common.data.ParameterizedString;
+import com.google.gerrit.mail.Address;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.GerritPersonIdent;
 import com.google.gerrit.server.account.AccountCache;
 import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.config.AnonymousCowardName;
 import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.mail.Address;
 import com.google.gerrit.server.mail.MailUtil;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
diff --git a/java/com/google/gerrit/server/mail/send/InboundEmailRejectionSender.java b/java/com/google/gerrit/server/mail/send/InboundEmailRejectionSender.java
index 5143dc7..65ce128 100644
--- a/java/com/google/gerrit/server/mail/send/InboundEmailRejectionSender.java
+++ b/java/com/google/gerrit/server/mail/send/InboundEmailRejectionSender.java
@@ -18,8 +18,8 @@
 
 import com.google.gerrit.common.errors.EmailException;
 import com.google.gerrit.extensions.api.changes.RecipientType;
-import com.google.gerrit.server.mail.Address;
-import com.google.gerrit.server.mail.MailHeader;
+import com.google.gerrit.mail.Address;
+import com.google.gerrit.mail.MailHeader;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 import org.apache.james.mime4j.dom.field.FieldName;
diff --git a/java/com/google/gerrit/server/mail/send/MergedSender.java b/java/com/google/gerrit/server/mail/send/MergedSender.java
index cf9257c..34f959c 100644
--- a/java/com/google/gerrit/server/mail/send/MergedSender.java
+++ b/java/com/google/gerrit/server/mail/send/MergedSender.java
@@ -70,12 +70,7 @@
       Table<Account.Id, String, PatchSetApproval> neg = HashBasedTable.create();
       for (PatchSetApproval ca :
           args.approvalsUtil.byPatchSet(
-              args.db.get(),
-              changeData.notes(),
-              args.identifiedUserFactory.create(changeData.change().getOwner()),
-              patchSet.getId(),
-              null,
-              null)) {
+              args.db.get(), changeData.notes(), patchSet.getId(), null, null)) {
         LabelType lt = labelTypes.byLabel(ca.getLabelId());
         if (lt == null) {
           continue;
diff --git a/java/com/google/gerrit/server/mail/send/NewChangeSender.java b/java/com/google/gerrit/server/mail/send/NewChangeSender.java
index 9f94fa3..f94f1ca 100644
--- a/java/com/google/gerrit/server/mail/send/NewChangeSender.java
+++ b/java/com/google/gerrit/server/mail/send/NewChangeSender.java
@@ -16,8 +16,8 @@
 
 import com.google.gerrit.common.errors.EmailException;
 import com.google.gerrit.extensions.api.changes.RecipientType;
+import com.google.gerrit.mail.Address;
 import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.server.mail.Address;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gwtorm.server.OrmException;
 import java.util.ArrayList;
diff --git a/java/com/google/gerrit/server/mail/send/NotificationEmail.java b/java/com/google/gerrit/server/mail/send/NotificationEmail.java
index 0cc7a1d..032bcbf 100644
--- a/java/com/google/gerrit/server/mail/send/NotificationEmail.java
+++ b/java/com/google/gerrit/server/mail/send/NotificationEmail.java
@@ -19,11 +19,11 @@
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.common.errors.EmailException;
 import com.google.gerrit.extensions.api.changes.RecipientType;
+import com.google.gerrit.mail.Address;
+import com.google.gerrit.mail.MailHeader;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.server.account.ProjectWatches.NotifyType;
-import com.google.gerrit.server.mail.Address;
-import com.google.gerrit.server.mail.MailHeader;
 import com.google.gerrit.server.mail.send.ProjectWatch.Watchers;
 import com.google.gwtorm.server.OrmException;
 import java.util.HashMap;
diff --git a/java/com/google/gerrit/server/mail/send/OutgoingEmail.java b/java/com/google/gerrit/server/mail/send/OutgoingEmail.java
index 0612237..fe9b446 100644
--- a/java/com/google/gerrit/server/mail/send/OutgoingEmail.java
+++ b/java/com/google/gerrit/server/mail/send/OutgoingEmail.java
@@ -26,12 +26,13 @@
 import com.google.gerrit.extensions.api.changes.RecipientType;
 import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
 import com.google.gerrit.extensions.client.GeneralPreferencesInfo.EmailFormat;
+import com.google.gerrit.mail.Address;
+import com.google.gerrit.mail.EmailHeader;
+import com.google.gerrit.mail.EmailHeader.AddressList;
+import com.google.gerrit.mail.MailHeader;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.UserIdentity;
 import com.google.gerrit.server.account.AccountState;
-import com.google.gerrit.server.mail.Address;
-import com.google.gerrit.server.mail.MailHeader;
-import com.google.gerrit.server.mail.send.EmailHeader.AddressList;
 import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.validators.OutgoingEmailValidationListener;
 import com.google.gerrit.server.validators.ValidationException;
diff --git a/java/com/google/gerrit/server/mail/send/ProjectWatch.java b/java/com/google/gerrit/server/mail/send/ProjectWatch.java
index 15197ef..9a86fdb 100644
--- a/java/com/google/gerrit/server/mail/send/ProjectWatch.java
+++ b/java/com/google/gerrit/server/mail/send/ProjectWatch.java
@@ -21,6 +21,7 @@
 import com.google.gerrit.common.data.GroupReference;
 import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.index.query.QueryParseException;
+import com.google.gerrit.mail.Address;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
@@ -30,7 +31,6 @@
 import com.google.gerrit.server.account.ProjectWatches.NotifyType;
 import com.google.gerrit.server.account.ProjectWatches.ProjectWatchKey;
 import com.google.gerrit.server.git.NotifyConfig;
-import com.google.gerrit.server.mail.Address;
 import com.google.gerrit.server.project.ProjectState;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.ChangeQueryBuilder;
diff --git a/java/com/google/gerrit/server/mail/send/RegisterNewEmailSender.java b/java/com/google/gerrit/server/mail/send/RegisterNewEmailSender.java
index c667026..0dbfb60 100644
--- a/java/com/google/gerrit/server/mail/send/RegisterNewEmailSender.java
+++ b/java/com/google/gerrit/server/mail/send/RegisterNewEmailSender.java
@@ -18,8 +18,8 @@
 
 import com.google.gerrit.common.errors.EmailException;
 import com.google.gerrit.extensions.api.changes.RecipientType;
+import com.google.gerrit.mail.Address;
 import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.mail.Address;
 import com.google.gerrit.server.mail.EmailTokenVerifier;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
diff --git a/java/com/google/gerrit/server/mail/send/SmtpEmailSender.java b/java/com/google/gerrit/server/mail/send/SmtpEmailSender.java
index 10e8b0b..2262b5c 100644
--- a/java/com/google/gerrit/server/mail/send/SmtpEmailSender.java
+++ b/java/com/google/gerrit/server/mail/send/SmtpEmailSender.java
@@ -22,9 +22,10 @@
 import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.Version;
 import com.google.gerrit.common.errors.EmailException;
+import com.google.gerrit.mail.Address;
+import com.google.gerrit.mail.EmailHeader;
 import com.google.gerrit.server.config.ConfigUtil;
 import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.mail.Address;
 import com.google.gerrit.server.mail.Encryption;
 import com.google.inject.AbstractModule;
 import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java b/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java
index cf86c99..5a4d1f5 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java
@@ -16,12 +16,9 @@
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.server.account.AccountCache;
-import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.config.GerritServerId;
 import com.google.inject.Inject;
 import java.util.Date;
-import java.util.Optional;
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.revwalk.FooterKey;
 
@@ -65,17 +62,13 @@
 
   private final LegacyChangeNoteRead legacyChangeNoteRead;
   private final ChangeNoteJson changeNoteJson;
-
-  private final AccountCache accountCache;
   private final String serverId;
 
   @Inject
   public ChangeNoteUtil(
       ChangeNoteJson changeNoteJson,
       LegacyChangeNoteRead legacyChangeNoteRead,
-      AccountCache accountCache,
       @GerritServerId String serverId) {
-    this.accountCache = accountCache;
     this.serverId = serverId;
     this.changeNoteJson = changeNoteJson;
     this.legacyChangeNoteRead = legacyChangeNoteRead;
@@ -90,9 +83,8 @@
   }
 
   public PersonIdent newIdent(Account.Id authorId, Date when, PersonIdent serverIdent) {
-    Optional<Account> author = accountCache.get(authorId).map(AccountState::getAccount);
     return new PersonIdent(
-        author.map(Account::getName).orElseGet(() -> Account.getName(authorId)),
+        "Gerrit User " + authorId.toString(),
         authorId.get() + "@" + serverId,
         when,
         serverIdent.getTimeZone());
@@ -101,6 +93,9 @@
   @VisibleForTesting
   public PersonIdent newIdent(Account author, Date when, PersonIdent serverIdent) {
     return new PersonIdent(
-        author.getName(), author.getId().get() + "@" + serverId, when, serverIdent.getTimeZone());
+        "Gerrit User " + author.getId(),
+        author.getId().get() + "@" + serverId,
+        when,
+        serverIdent.getTimeZone());
   }
 }
diff --git a/java/com/google/gerrit/server/notedb/ChangeNotesParser.java b/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
index 5f2593b..5810c64 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
@@ -55,6 +55,7 @@
 import com.google.common.primitives.Ints;
 import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.common.data.SubmitRecord;
+import com.google.gerrit.mail.Address;
 import com.google.gerrit.metrics.Timer1;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
@@ -70,7 +71,6 @@
 import com.google.gerrit.server.ReviewerByEmailSet;
 import com.google.gerrit.server.ReviewerSet;
 import com.google.gerrit.server.ReviewerStatusUpdate;
-import com.google.gerrit.server.mail.Address;
 import com.google.gerrit.server.notedb.ChangeNotesCommit.ChangeNotesRevWalk;
 import com.google.gerrit.server.util.LabelVote;
 import java.io.IOException;
diff --git a/java/com/google/gerrit/server/notedb/ChangeNotesState.java b/java/com/google/gerrit/server/notedb/ChangeNotesState.java
index 3eb06b2..3543202 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNotesState.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNotesState.java
@@ -39,6 +39,7 @@
 import com.google.common.collect.Table;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.data.SubmitRecord;
+import com.google.gerrit.mail.Address;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Change;
@@ -61,7 +62,6 @@
 import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.ReviewerSetEntryProto;
 import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.ReviewerStatusUpdateProto;
 import com.google.gerrit.server.index.change.ChangeField.StoredSubmitRecord;
-import com.google.gerrit.server.mail.Address;
 import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
 import com.google.gson.Gson;
 import java.io.IOException;
diff --git a/java/com/google/gerrit/server/notedb/ChangeUpdate.java b/java/com/google/gerrit/server/notedb/ChangeUpdate.java
index 69fbfe1..903a982 100644
--- a/java/com/google/gerrit/server/notedb/ChangeUpdate.java
+++ b/java/com/google/gerrit/server/notedb/ChangeUpdate.java
@@ -52,6 +52,7 @@
 import com.google.common.collect.TreeBasedTable;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.data.SubmitRecord;
+import com.google.gerrit.mail.Address;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Comment;
@@ -62,7 +63,6 @@
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.GerritPersonIdent;
 import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.mail.Address;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.util.LabelVote;
 import com.google.gerrit.server.util.RequestId;
diff --git a/java/com/google/gerrit/server/notedb/CommentJsonMigrator.java b/java/com/google/gerrit/server/notedb/CommentJsonMigrator.java
index 9a9a211..5007ec9 100644
--- a/java/com/google/gerrit/server/notedb/CommentJsonMigrator.java
+++ b/java/com/google/gerrit/server/notedb/CommentJsonMigrator.java
@@ -23,11 +23,15 @@
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.reviewdb.client.RevId;
+import com.google.gerrit.server.config.AllUsersName;
 import com.google.gerrit.server.config.GerritServerIdProvider;
+import com.google.gerrit.server.update.RefUpdateUtil;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import java.io.IOException;
 import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.internal.storage.file.FileRepository;
+import org.eclipse.jgit.internal.storage.file.PackInserter;
 import org.eclipse.jgit.lib.BatchRefUpdate;
 import org.eclipse.jgit.lib.CommitBuilder;
 import org.eclipse.jgit.lib.ObjectId;
@@ -47,22 +51,59 @@
 public class CommentJsonMigrator {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
+  public static class ProjectMigrationResult {
+    public int skipped;
+    public boolean ok;
+    public int refsUpdated;
+  }
+
   private final LegacyChangeNoteRead legacyChangeNoteRead;
   private final ChangeNoteJson changeNoteJson;
+  private final AllUsersName allUsers;
 
   @Inject
   CommentJsonMigrator(
-      ChangeNoteJson changeNoteJson, GerritServerIdProvider gerritServerIdProvider) {
+      ChangeNoteJson changeNoteJson,
+      GerritServerIdProvider gerritServerIdProvider,
+      AllUsersName allUsers) {
     this.changeNoteJson = changeNoteJson;
+    this.allUsers = allUsers;
     this.legacyChangeNoteRead = new LegacyChangeNoteRead(gerritServerIdProvider.get());
   }
 
-  CommentJsonMigrator(ChangeNoteJson changeNoteJson, String serverId) {
+  CommentJsonMigrator(ChangeNoteJson changeNoteJson, String serverId, AllUsersName allUsers) {
     this.changeNoteJson = changeNoteJson;
     this.legacyChangeNoteRead = new LegacyChangeNoteRead(serverId);
+    this.allUsers = allUsers;
   }
 
-  public boolean migrateChanges(
+  public ProjectMigrationResult migrateProject(Project.NameKey project, Repository repo) {
+    ProjectMigrationResult progress = new ProjectMigrationResult();
+    progress.ok = true;
+    try (RevWalk rw = new RevWalk(repo);
+        ObjectInserter ins = newPackInserter(repo)) {
+      BatchRefUpdate bru = repo.getRefDatabase().newBatchUpdate();
+      bru.setAllowNonFastForwards(true);
+      progress.ok &= migrateChanges(project, repo, rw, ins, bru);
+      if (project.equals(allUsers)) {
+        progress.ok &= migrateDrafts(allUsers, repo, rw, ins, bru);
+      }
+
+      progress.refsUpdated += bru.getCommands().size();
+      if (!bru.getCommands().isEmpty()) {
+        ins.flush();
+        RefUpdateUtil.executeChecked(bru, rw);
+
+      } else {
+        progress.skipped++;
+      }
+    } catch (IOException e) {
+      progress.ok = false;
+    }
+    return progress;
+  }
+
+  private boolean migrateChanges(
       Project.NameKey project, Repository repo, RevWalk rw, ObjectInserter ins, BatchRefUpdate bru)
       throws IOException {
     boolean ok = true;
@@ -76,7 +117,7 @@
     return ok;
   }
 
-  public boolean migrateDrafts(
+  private boolean migrateDrafts(
       Project.NameKey allUsers,
       Repository allUsersRepo,
       RevWalk rw,
@@ -187,4 +228,13 @@
     rw.sort(RevSort.REVERSE);
     rw.markStart(rw.parseCommit(id));
   }
+
+  private static ObjectInserter newPackInserter(Repository repo) {
+    if (!(repo instanceof FileRepository)) {
+      return repo.newObjectInserter();
+    }
+    PackInserter ins = ((FileRepository) repo).getObjectDatabase().newPackInserter();
+    ins.checkExisting(false);
+    return ins;
+  }
 }
diff --git a/java/com/google/gerrit/server/notedb/LegacyChangeNoteWrite.java b/java/com/google/gerrit/server/notedb/LegacyChangeNoteWrite.java
index 1cf0c7c..7c946b8 100644
--- a/java/com/google/gerrit/server/notedb/LegacyChangeNoteWrite.java
+++ b/java/com/google/gerrit/server/notedb/LegacyChangeNoteWrite.java
@@ -23,8 +23,6 @@
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Comment;
 import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.account.AccountCache;
-import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.config.GerritServerId;
 import com.google.inject.Inject;
 import java.io.OutputStream;
@@ -35,39 +33,30 @@
 import java.util.Collections;
 import java.util.Date;
 import java.util.List;
-import java.util.Optional;
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.util.QuotedString;
 
 public class LegacyChangeNoteWrite {
 
-  private final AccountCache accountCache;
   private final PersonIdent serverIdent;
   private final String serverId;
 
   @Inject
   public LegacyChangeNoteWrite(
-      AccountCache accountCache,
-      @GerritPersonIdent PersonIdent serverIdent,
-      @GerritServerId String serverId) {
-    this.accountCache = accountCache;
+      @GerritPersonIdent PersonIdent serverIdent, @GerritServerId String serverId) {
     this.serverIdent = serverIdent;
     this.serverId = serverId;
   }
 
   public PersonIdent newIdent(Account.Id authorId, Date when, PersonIdent serverIdent) {
-    Optional<Account> author = accountCache.get(authorId).map(AccountState::getAccount);
     return new PersonIdent(
-        author.map(Account::getName).orElseGet(() -> Account.getName(authorId)),
-        authorId.get() + "@" + serverId,
-        when,
-        serverIdent.getTimeZone());
+        authorId.toString(), authorId.get() + "@" + serverId, when, serverIdent.getTimeZone());
   }
 
   @VisibleForTesting
   public PersonIdent newIdent(Account author, Date when, PersonIdent serverIdent) {
     return new PersonIdent(
-        author.getName(), author.getId().get() + "@" + serverId, when, serverIdent.getTimeZone());
+        author.toString(), author.getId().get() + "@" + serverId, when, serverIdent.getTimeZone());
   }
 
   public String getServerId() {
diff --git a/java/com/google/gerrit/server/permissions/ChangeControl.java b/java/com/google/gerrit/server/permissions/ChangeControl.java
index bb752a0..4aaf61d 100644
--- a/java/com/google/gerrit/server/permissions/ChangeControl.java
+++ b/java/com/google/gerrit/server/permissions/ChangeControl.java
@@ -23,6 +23,7 @@
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.common.data.PermissionRange;
+import com.google.gerrit.extensions.conditions.BooleanCondition;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
@@ -92,9 +93,6 @@
   }
 
   private ChangeControl forUser(CurrentUser who) {
-    if (getUser().equals(who)) {
-      return this;
-    }
     return new ChangeControl(
         changeDataFactory, identifiedUserFactory, refControl.forUser(who), notes);
   }
@@ -263,13 +261,8 @@
     }
 
     @Override
-    public CurrentUser user() {
-      return getUser();
-    }
-
-    @Override
     public ForChange user(CurrentUser user) {
-      return user().equals(user) ? this : forUser(user).asForChange(cd, db);
+      return forUser(user).asForChange(cd, db);
     }
 
     @Override
@@ -308,6 +301,11 @@
       return ok;
     }
 
+    @Override
+    public BooleanCondition testCond(ChangePermissionOrLabel perm) {
+      return new PermissionBackendCondition.ForChange(this, perm, getUser());
+    }
+
     private boolean can(ChangePermissionOrLabel perm) throws PermissionBackendException {
       if (perm instanceof ChangePermission) {
         return can((ChangePermission) perm);
diff --git a/java/com/google/gerrit/server/permissions/DefaultPermissionBackend.java b/java/com/google/gerrit/server/permissions/DefaultPermissionBackend.java
index 490b45e..bf68026 100644
--- a/java/com/google/gerrit/server/permissions/DefaultPermissionBackend.java
+++ b/java/com/google/gerrit/server/permissions/DefaultPermissionBackend.java
@@ -23,6 +23,7 @@
 import com.google.gerrit.common.data.PermissionRule.Action;
 import com.google.gerrit.extensions.api.access.GlobalOrPluginPermission;
 import com.google.gerrit.extensions.api.access.PluginPermission;
+import com.google.gerrit.extensions.conditions.BooleanCondition;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
@@ -32,6 +33,7 @@
 import com.google.gerrit.server.PeerDaemonUser;
 import com.google.gerrit.server.account.CapabilityCollection;
 import com.google.gerrit.server.cache.PerThreadCache;
+import com.google.gerrit.server.permissions.PermissionBackendCondition.WithUser;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectState;
 import com.google.inject.Inject;
@@ -98,11 +100,6 @@
     }
 
     @Override
-    public CurrentUser user() {
-      return user;
-    }
-
-    @Override
     public ForProject project(Project.NameKey project) {
       try {
         ProjectState state = projectCache.checkedGet(project);
@@ -138,6 +135,11 @@
       return ok;
     }
 
+    @Override
+    public BooleanCondition testCond(GlobalOrPluginPermission perm) {
+      return new PermissionBackendCondition.WithUser(this, perm, user);
+    }
+
     private boolean can(GlobalOrPluginPermission perm) throws PermissionBackendException {
       if (perm instanceof GlobalPermission) {
         return can((GlobalPermission) perm);
diff --git a/java/com/google/gerrit/server/permissions/DefaultRefFilter.java b/java/com/google/gerrit/server/permissions/DefaultRefFilter.java
index 3b88080..1e5b3c8 100644
--- a/java/com/google/gerrit/server/permissions/DefaultRefFilter.java
+++ b/java/com/google/gerrit/server/permissions/DefaultRefFilter.java
@@ -299,9 +299,14 @@
       Map<Change.Id, Branch.NameKey> visibleChanges = new HashMap<>();
       for (ChangeData cd : changeCache.getChangeData(db.get(), project)) {
         ChangeNotes notes = changeNotesFactory.createFromIndexedChange(cd.change());
-        if (projectState.statePermitsRead()
-            && permissionBackendForProject.indexedChange(cd, notes).test(ChangePermission.READ)) {
+        if (!projectState.statePermitsRead()) {
+          continue;
+        }
+        try {
+          permissionBackendForProject.indexedChange(cd, notes).check(ChangePermission.READ);
           visibleChanges.put(cd.getId(), cd.change().getDest());
+        } catch (AuthException e) {
+          // Do nothing.
         }
       }
       return visibleChanges;
@@ -334,11 +339,16 @@
           "Failed to load change %s in %s", r.id(), projectState.getName());
       return null;
     }
+
+    if (!projectState.statePermitsRead()) {
+      return null;
+    }
+
     try {
-      if (projectState.statePermitsRead()
-          && permissionBackendForProject.change(r.notes()).test(ChangePermission.READ)) {
-        return r.notes();
-      }
+      permissionBackendForProject.change(r.notes()).check(ChangePermission.READ);
+      return r.notes();
+    } catch (AuthException e) {
+      // Skip.
     } catch (PermissionBackendException e) {
       logger.atSevere().withCause(e).log(
           "Failed to check permission for %s in %s", r.id(), projectState.getName());
diff --git a/java/com/google/gerrit/server/permissions/FailedPermissionBackend.java b/java/com/google/gerrit/server/permissions/FailedPermissionBackend.java
index 431bfd9..6c6f136 100644
--- a/java/com/google/gerrit/server/permissions/FailedPermissionBackend.java
+++ b/java/com/google/gerrit/server/permissions/FailedPermissionBackend.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.server.permissions;
 
 import com.google.gerrit.extensions.api.access.GlobalOrPluginPermission;
+import com.google.gerrit.extensions.conditions.BooleanCondition;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -84,11 +85,6 @@
     }
 
     @Override
-    public CurrentUser user() {
-      throw new UnsupportedOperationException("FailedPermissionBackend is not scoped to user");
-    }
-
-    @Override
     public ForProject project(Project.NameKey project) {
       return new FailedProject(message, cause);
     }
@@ -103,6 +99,12 @@
         throws PermissionBackendException {
       throw new PermissionBackendException(message, cause);
     }
+
+    @Override
+    public BooleanCondition testCond(GlobalOrPluginPermission perm) {
+      throw new UnsupportedOperationException(
+          "FailedPermissionBackend does not support conditions");
+    }
   }
 
   private static class FailedProject extends ForProject {
@@ -120,11 +122,6 @@
     }
 
     @Override
-    public CurrentUser user() {
-      throw new UnsupportedOperationException("FailedPermissionBackend is not scoped to user");
-    }
-
-    @Override
     public ForProject user(CurrentUser user) {
       return this;
     }
@@ -157,6 +154,12 @@
     }
 
     @Override
+    public BooleanCondition testCond(ProjectPermission perm) {
+      throw new UnsupportedOperationException(
+          "FailedPermissionBackend does not support conditions");
+    }
+
+    @Override
     public Map<String, Ref> filter(Map<String, Ref> refs, Repository repo, RefFilterOptions opts)
         throws PermissionBackendException {
       throw new PermissionBackendException(message, cause);
@@ -178,11 +181,6 @@
     }
 
     @Override
-    public CurrentUser user() {
-      throw new UnsupportedOperationException("FailedPermissionBackend is not scoped to user");
-    }
-
-    @Override
     public ForRef user(CurrentUser user) {
       return this;
     }
@@ -223,6 +221,12 @@
         throws PermissionBackendException {
       throw new PermissionBackendException(message, cause);
     }
+
+    @Override
+    public BooleanCondition testCond(RefPermission perm) {
+      throw new UnsupportedOperationException(
+          "FailedPermissionBackend does not support conditions");
+    }
   }
 
   private static class FailedChange extends ForChange {
@@ -267,8 +271,9 @@
     }
 
     @Override
-    public CurrentUser user() {
-      throw new UnsupportedOperationException("FailedPermissionBackend is not scoped to user");
+    public BooleanCondition testCond(ChangePermissionOrLabel perm) {
+      throw new UnsupportedOperationException(
+          "FailedPermissionBackend does not support conditions");
     }
   }
 }
diff --git a/java/com/google/gerrit/server/permissions/PermissionBackend.java b/java/com/google/gerrit/server/permissions/PermissionBackend.java
index 357770d..0690d6c 100644
--- a/java/com/google/gerrit/server/permissions/PermissionBackend.java
+++ b/java/com/google/gerrit/server/permissions/PermissionBackend.java
@@ -173,9 +173,6 @@
 
   /** PermissionBackend scoped to a specific user. */
   public abstract static class WithUser extends AcceptsReviewDb<WithUser> {
-    /** Returns the user this instance is scoped to. */
-    public abstract CurrentUser user();
-
     /** Returns an instance scoped for the specified project. */
     public abstract ForProject project(Project.NameKey project);
 
@@ -257,9 +254,7 @@
       }
     }
 
-    public BooleanCondition testCond(GlobalOrPluginPermission perm) {
-      return new PermissionBackendCondition.WithUser(this, perm);
-    }
+    public abstract BooleanCondition testCond(GlobalOrPluginPermission perm);
 
     /**
      * Filter a set of projects using {@code check(perm)}.
@@ -296,9 +291,6 @@
 
   /** PermissionBackend scoped to a user and project. */
   public abstract static class ForProject extends AcceptsReviewDb<ForProject> {
-    /** Returns the user this instance is scoped to. */
-    public abstract CurrentUser user();
-
     /** Returns the fully qualified resource path that this instance is scoped to. */
     public abstract String resourcePath();
 
@@ -355,9 +347,7 @@
       }
     }
 
-    public BooleanCondition testCond(ProjectPermission perm) {
-      return new PermissionBackendCondition.ForProject(this, perm);
-    }
+    public abstract BooleanCondition testCond(ProjectPermission perm);
 
     /**
      * Filter a map of references by visibility.
@@ -407,9 +397,6 @@
 
   /** PermissionBackend scoped to a user, project and reference. */
   public abstract static class ForRef extends AcceptsReviewDb<ForRef> {
-    /** Returns the user this instance is scoped to. */
-    public abstract CurrentUser user();
-
     /** Returns a fully qualified resource path that this instance is scoped to. */
     public abstract String resourcePath();
 
@@ -461,16 +448,11 @@
       }
     }
 
-    public BooleanCondition testCond(RefPermission perm) {
-      return new PermissionBackendCondition.ForRef(this, perm);
-    }
+    public abstract BooleanCondition testCond(RefPermission perm);
   }
 
   /** PermissionBackend scoped to a user, project, reference and change. */
   public abstract static class ForChange extends AcceptsReviewDb<ForChange> {
-    /** Returns the user this instance is scoped to. */
-    public abstract CurrentUser user();
-
     /** Returns the fully qualified resource path that this instance is scoped to. */
     public abstract String resourcePath();
 
@@ -511,9 +493,7 @@
       }
     }
 
-    public BooleanCondition testCond(ChangePermissionOrLabel perm) {
-      return new PermissionBackendCondition.ForChange(this, perm);
-    }
+    public abstract BooleanCondition testCond(ChangePermissionOrLabel perm);
 
     /**
      * Test which values of a label the user may be able to set.
diff --git a/java/com/google/gerrit/server/permissions/PermissionBackendCondition.java b/java/com/google/gerrit/server/permissions/PermissionBackendCondition.java
index 3a661cda..1b6b087 100644
--- a/java/com/google/gerrit/server/permissions/PermissionBackendCondition.java
+++ b/java/com/google/gerrit/server/permissions/PermissionBackendCondition.java
@@ -56,10 +56,13 @@
   public static class WithUser extends PermissionBackendCondition {
     private final PermissionBackend.WithUser impl;
     private final GlobalOrPluginPermission perm;
+    private final CurrentUser user;
 
-    WithUser(PermissionBackend.WithUser impl, GlobalOrPluginPermission perm) {
+    public WithUser(
+        PermissionBackend.WithUser impl, GlobalOrPluginPermission perm, CurrentUser user) {
       this.impl = impl;
       this.perm = perm;
+      this.user = user;
     }
 
     public PermissionBackend.WithUser withUser() {
@@ -82,7 +85,7 @@
 
     @Override
     public int hashCode() {
-      return Objects.hash(perm, hashForUser(impl.user()));
+      return Objects.hash(perm, hashForUser(user));
     }
 
     @Override
@@ -91,17 +94,19 @@
         return false;
       }
       WithUser other = (WithUser) obj;
-      return Objects.equals(perm, other.perm) && usersAreEqual(impl.user(), other.impl.user());
+      return Objects.equals(perm, other.perm) && usersAreEqual(user, other.user);
     }
   }
 
   public static class ForProject extends PermissionBackendCondition {
     private final PermissionBackend.ForProject impl;
     private final ProjectPermission perm;
+    private final CurrentUser user;
 
-    ForProject(PermissionBackend.ForProject impl, ProjectPermission perm) {
+    public ForProject(PermissionBackend.ForProject impl, ProjectPermission perm, CurrentUser user) {
       this.impl = impl;
       this.perm = perm;
+      this.user = user;
     }
 
     public PermissionBackend.ForProject project() {
@@ -124,7 +129,7 @@
 
     @Override
     public int hashCode() {
-      return Objects.hash(perm, impl.resourcePath(), hashForUser(impl.user()));
+      return Objects.hash(perm, impl.resourcePath(), hashForUser(user));
     }
 
     @Override
@@ -135,17 +140,19 @@
       ForProject other = (ForProject) obj;
       return Objects.equals(perm, other.perm)
           && Objects.equals(impl.resourcePath(), other.impl.resourcePath())
-          && usersAreEqual(impl.user(), other.impl.user());
+          && usersAreEqual(user, other.user);
     }
   }
 
   public static class ForRef extends PermissionBackendCondition {
     private final PermissionBackend.ForRef impl;
     private final RefPermission perm;
+    private final CurrentUser user;
 
-    ForRef(PermissionBackend.ForRef impl, RefPermission perm) {
+    public ForRef(PermissionBackend.ForRef impl, RefPermission perm, CurrentUser user) {
       this.impl = impl;
       this.perm = perm;
+      this.user = user;
     }
 
     public PermissionBackend.ForRef ref() {
@@ -168,7 +175,7 @@
 
     @Override
     public int hashCode() {
-      return Objects.hash(perm, impl.resourcePath(), hashForUser(impl.user()));
+      return Objects.hash(perm, impl.resourcePath(), hashForUser(user));
     }
 
     @Override
@@ -179,17 +186,20 @@
       ForRef other = (ForRef) obj;
       return Objects.equals(perm, other.perm)
           && Objects.equals(impl.resourcePath(), other.impl.resourcePath())
-          && usersAreEqual(impl.user(), other.impl.user());
+          && usersAreEqual(user, other.user);
     }
   }
 
   public static class ForChange extends PermissionBackendCondition {
     private final PermissionBackend.ForChange impl;
     private final ChangePermissionOrLabel perm;
+    private final CurrentUser user;
 
-    ForChange(PermissionBackend.ForChange impl, ChangePermissionOrLabel perm) {
+    public ForChange(
+        PermissionBackend.ForChange impl, ChangePermissionOrLabel perm, CurrentUser user) {
       this.impl = impl;
       this.perm = perm;
+      this.user = user;
     }
 
     public PermissionBackend.ForChange change() {
@@ -212,7 +222,7 @@
 
     @Override
     public int hashCode() {
-      return Objects.hash(perm, impl.resourcePath(), hashForUser(impl.user()));
+      return Objects.hash(perm, impl.resourcePath(), hashForUser(user));
     }
 
     @Override
@@ -223,7 +233,7 @@
       ForChange other = (ForChange) obj;
       return Objects.equals(perm, other.perm)
           && Objects.equals(impl.resourcePath(), other.impl.resourcePath())
-          && usersAreEqual(impl.user(), other.impl.user());
+          && usersAreEqual(user, other.user);
     }
   }
 
diff --git a/java/com/google/gerrit/server/permissions/ProjectControl.java b/java/com/google/gerrit/server/permissions/ProjectControl.java
index 2d2a64d..ed12a2b 100644
--- a/java/com/google/gerrit/server/permissions/ProjectControl.java
+++ b/java/com/google/gerrit/server/permissions/ProjectControl.java
@@ -19,6 +19,7 @@
 import com.google.gerrit.common.data.AccessSection;
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.extensions.conditions.BooleanCondition;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
@@ -323,11 +324,6 @@
     private String resourcePath;
 
     @Override
-    public CurrentUser user() {
-      return getUser();
-    }
-
-    @Override
     public ForProject user(CurrentUser user) {
       return forUser(user).asForProject().database(db);
     }
@@ -395,6 +391,11 @@
     }
 
     @Override
+    public BooleanCondition testCond(ProjectPermission perm) {
+      return new PermissionBackendCondition.ForProject(this, perm, getUser());
+    }
+
+    @Override
     public Map<String, Ref> filter(Map<String, Ref> refs, Repository repo, RefFilterOptions opts)
         throws PermissionBackendException {
       if (refFilter == null) {
diff --git a/java/com/google/gerrit/server/permissions/RefControl.java b/java/com/google/gerrit/server/permissions/RefControl.java
index f68a7a2..012d68a 100644
--- a/java/com/google/gerrit/server/permissions/RefControl.java
+++ b/java/com/google/gerrit/server/permissions/RefControl.java
@@ -20,6 +20,7 @@
 import com.google.gerrit.common.data.PermissionRange;
 import com.google.gerrit.common.data.PermissionRule;
 import com.google.gerrit.common.data.PermissionRule.Action;
+import com.google.gerrit.extensions.conditions.BooleanCondition;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
@@ -408,11 +409,6 @@
     private String resourcePath;
 
     @Override
-    public CurrentUser user() {
-      return getUser();
-    }
-
-    @Override
     public ForRef user(CurrentUser user) {
       return forUser(user).asForRef().database(db);
     }
@@ -480,6 +476,11 @@
       return ok;
     }
 
+    @Override
+    public BooleanCondition testCond(RefPermission perm) {
+      return new PermissionBackendCondition.ForRef(this, perm, getUser());
+    }
+
     private boolean can(RefPermission perm) throws PermissionBackendException {
       switch (perm) {
         case READ:
diff --git a/java/com/google/gerrit/server/project/ContributorAgreementsChecker.java b/java/com/google/gerrit/server/project/ContributorAgreementsChecker.java
index d840123..c3c76de 100644
--- a/java/com/google/gerrit/server/project/ContributorAgreementsChecker.java
+++ b/java/com/google/gerrit/server/project/ContributorAgreementsChecker.java
@@ -108,7 +108,11 @@
 
     if (!iUser.getEffectiveGroups().containsAnyOf(okGroupIds)) {
       final StringBuilder msg = new StringBuilder();
-      msg.append("A Contributor Agreement must be completed before uploading");
+      msg.append("No Contributor Agreement on file for user ")
+          .append(iUser.getNameEmail())
+          .append(" (id=")
+          .append(iUser.getAccountId())
+          .append(")");
       if (canonicalWebUrl != null) {
         msg.append(":\n\n  ");
         msg.append(canonicalWebUrl);
diff --git a/java/com/google/gerrit/server/project/ProjectConfig.java b/java/com/google/gerrit/server/project/ProjectConfig.java
index d42b652..e3638b6 100644
--- a/java/com/google/gerrit/server/project/ProjectConfig.java
+++ b/java/com/google/gerrit/server/project/ProjectConfig.java
@@ -41,6 +41,7 @@
 import com.google.gerrit.common.errors.InvalidNameException;
 import com.google.gerrit.extensions.client.InheritableBoolean;
 import com.google.gerrit.extensions.client.ProjectState;
+import com.google.gerrit.mail.Address;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.BooleanProjectConfig;
 import com.google.gerrit.reviewdb.client.Branch;
@@ -55,7 +56,6 @@
 import com.google.gerrit.server.git.ValidationError;
 import com.google.gerrit.server.git.meta.MetaDataUpdate;
 import com.google.gerrit.server.git.meta.VersionedMetaData;
-import com.google.gerrit.server.mail.Address;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -402,10 +402,6 @@
     return mimeTypes;
   }
 
-  public GroupReference resolve(AccountGroup group) {
-    return resolve(GroupReference.forGroup(group));
-  }
-
   public GroupReference resolve(GroupReference group) {
     GroupReference groupRef = groupList.resolve(group);
     if (groupRef != null
@@ -503,6 +499,9 @@
     if (p.getDescription() == null) {
       p.setDescription("");
     }
+    if (revision != null) {
+      p.setConfigRefState(revision.toObjectId().name());
+    }
 
     if (rc.getStringList(ACCESS, null, KEY_INHERIT_FROM).length > 1) {
       // The config must not contain more than one parent to inherit from
diff --git a/java/com/google/gerrit/server/project/ProjectState.java b/java/com/google/gerrit/server/project/ProjectState.java
index a490f10..e36ca4f 100644
--- a/java/com/google/gerrit/server/project/ProjectState.java
+++ b/java/com/google/gerrit/server/project/ProjectState.java
@@ -40,7 +40,6 @@
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.account.CapabilityCollection;
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.config.AllUsersName;
@@ -63,6 +62,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.lib.Ref;
@@ -394,13 +394,13 @@
     return labelTypes;
   }
 
-  /** All available label types for this change and user. */
-  public LabelTypes getLabelTypes(ChangeNotes notes, CurrentUser user) {
-    return getLabelTypes(notes.getChange().getDest(), user);
+  /** All available label types for this change. */
+  public LabelTypes getLabelTypes(ChangeNotes notes) {
+    return getLabelTypes(notes.getChange().getDest());
   }
 
-  /** All available label types for this branch and user. */
-  public LabelTypes getLabelTypes(Branch.NameKey destination, CurrentUser user) {
+  /** All available label types for this branch. */
+  public LabelTypes getLabelTypes(Branch.NameKey destination) {
     List<LabelType> all = getLabelTypes().getLabelTypes();
 
     List<LabelType> r = Lists.newArrayListWithCapacity(all.size());
@@ -410,7 +410,15 @@
         r.add(l);
       } else {
         for (String refPattern : refs) {
-          if (RefConfigSection.isValid(refPattern) && match(destination, refPattern, user)) {
+          if (refPattern.contains("${")) {
+            logger.atWarning().log(
+                "Ref pattern for label %s in project %s contains illegal expanded parameters: %s."
+                    + " Ref pattern will be ignored.",
+                l, getName(), refPattern);
+            continue;
+          }
+
+          if (RefConfigSection.isValid(refPattern) && match(destination, refPattern)) {
             r.add(l);
             break;
           }
@@ -531,7 +539,11 @@
   }
 
   public ProjectData toProjectData() {
-    return new ProjectData(getProject(), parents().transform(s -> s.getProject().getNameKey()));
+    ProjectData project = null;
+    for (ProjectState state : treeInOrder()) {
+      project = new ProjectData(state.getProject(), Optional.ofNullable(project));
+    }
+    return project;
   }
 
   private String readFile(Path p) throws IOException {
@@ -558,7 +570,7 @@
     return new LabelTypes(Collections.unmodifiableList(all));
   }
 
-  private boolean match(Branch.NameKey destination, String refPattern, CurrentUser user) {
-    return RefPatternMatcher.getMatcher(refPattern).match(destination.get(), user);
+  private boolean match(Branch.NameKey destination, String refPattern) {
+    return RefPatternMatcher.getMatcher(refPattern).match(destination.get(), null);
   }
 }
diff --git a/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java b/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java
index e7ffd5e..41d53a2 100644
--- a/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java
@@ -19,6 +19,7 @@
 import com.google.common.flogger.FluentLogger;
 import com.google.common.primitives.Ints;
 import com.google.gerrit.common.errors.NotSignedInException;
+import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.index.Index;
 import com.google.gerrit.index.Schema;
 import com.google.gerrit.index.query.LimitPredicate;
@@ -120,12 +121,17 @@
   public Predicate<AccountState> cansee(String change)
       throws QueryParseException, OrmException, PermissionBackendException {
     ChangeNotes changeNotes = args.changeFinder.findOne(change);
-    if (changeNotes == null
-        || !args.permissionBackend
-            .user(args.getUser())
-            .database(args.db)
-            .change(changeNotes)
-            .test(ChangePermission.READ)) {
+    if (changeNotes == null) {
+      throw error(String.format("change %s not found", change));
+    }
+
+    try {
+      args.permissionBackend
+          .user(args.getUser())
+          .database(args.db)
+          .change(changeNotes)
+          .check(ChangePermission.READ);
+    } catch (AuthException e) {
       throw error(String.format("change %s not found", change));
     }
 
@@ -218,7 +224,12 @@
   }
 
   private boolean canSeeSecondaryEmails() throws PermissionBackendException, QueryParseException {
-    return args.permissionBackend.user(args.getUser()).test(GlobalPermission.MODIFY_ACCOUNT);
+    try {
+      args.permissionBackend.user(args.getUser()).check(GlobalPermission.MODIFY_ACCOUNT);
+      return true;
+    } catch (AuthException e) {
+      return false;
+    }
   }
 
   private boolean checkedCanSeeSecondaryEmails() {
diff --git a/java/com/google/gerrit/server/query/account/CanSeeChangePredicate.java b/java/com/google/gerrit/server/query/account/CanSeeChangePredicate.java
index b008092..fb3549c 100644
--- a/java/com/google/gerrit/server/query/account/CanSeeChangePredicate.java
+++ b/java/com/google/gerrit/server/query/account/CanSeeChangePredicate.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.query.account;
 
+import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.index.query.PostFilterPredicate;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.account.AccountState;
@@ -40,13 +41,16 @@
   @Override
   public boolean match(AccountState accountState) throws OrmException {
     try {
-      return permissionBackend
+      permissionBackend
           .absentUser(accountState.getAccount().getId())
           .database(db)
           .change(changeNotes)
-          .test(ChangePermission.READ);
+          .check(ChangePermission.READ);
+      return true;
     } catch (PermissionBackendException e) {
       throw new OrmException("Failed to check if account can see change", e);
+    } catch (AuthException e) {
+      return false;
     }
   }
 
diff --git a/java/com/google/gerrit/server/query/change/ChangeData.java b/java/com/google/gerrit/server/query/change/ChangeData.java
index 1a336fc..1813211 100644
--- a/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -51,7 +51,6 @@
 import com.google.gerrit.server.ChangeMessagesUtil;
 import com.google.gerrit.server.CommentsUtil;
 import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.PatchSetUtil;
 import com.google.gerrit.server.ReviewerByEmailSet;
 import com.google.gerrit.server.ReviewerSet;
@@ -325,7 +324,7 @@
     ChangeData cd =
         new ChangeData(
             null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-            null, null, null, null, project, id, null, null);
+            null, null, null, project, id, null, null);
     cd.currentPatchSet = new PatchSet(new PatchSet.Id(id, currentPatchSetId));
     return cd;
   }
@@ -338,7 +337,6 @@
   private final ChangeNotes.Factory notesFactory;
   private final CommentsUtil commentsUtil;
   private final GitRepositoryManager repoManager;
-  private final IdentifiedUser.GenericFactory userFactory;
   private final MergeUtil.Factory mergeUtilFactory;
   private final MergeabilityCache mergeabilityCache;
   private final NotesMigration notesMigration;
@@ -407,7 +405,6 @@
       ChangeNotes.Factory notesFactory,
       CommentsUtil commentsUtil,
       GitRepositoryManager repoManager,
-      IdentifiedUser.GenericFactory userFactory,
       MergeUtil.Factory mergeUtilFactory,
       MergeabilityCache mergeabilityCache,
       NotesMigration notesMigration,
@@ -428,7 +425,6 @@
     this.notesFactory = notesFactory;
     this.commentsUtil = commentsUtil;
     this.repoManager = repoManager;
-    this.userFactory = userFactory;
     this.mergeUtilFactory = mergeUtilFactory;
     this.mergeabilityCache = mergeabilityCache;
     this.notesMigration = notesMigration;
@@ -590,7 +586,7 @@
       } catch (IOException e) {
         throw new OrmException("project state not available", e);
       }
-      labelTypes = state.getLabelTypes(change().getDest(), userFactory.create(change().getOwner()));
+      labelTypes = state.getLabelTypes(change().getDest());
     }
     return labelTypes;
   }
@@ -633,13 +629,7 @@
         try {
           currentApprovals =
               ImmutableList.copyOf(
-                  approvalsUtil.byPatchSet(
-                      db,
-                      notes(),
-                      userFactory.create(c.getOwner()),
-                      c.currentPatchSetId(),
-                      null,
-                      null));
+                  approvalsUtil.byPatchSet(db, notes(), c.currentPatchSetId(), null, null));
         } catch (OrmException e) {
           if (e.getCause() instanceof NoSuchChangeException) {
             currentApprovals = Collections.emptyList();
diff --git a/java/com/google/gerrit/server/query/change/ChangeIsVisibleToPredicate.java b/java/com/google/gerrit/server/query/change/ChangeIsVisibleToPredicate.java
index 346ac8e..27baef1 100644
--- a/java/com/google/gerrit/server/query/change/ChangeIsVisibleToPredicate.java
+++ b/java/com/google/gerrit/server/query/change/ChangeIsVisibleToPredicate.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.server.query.change;
 
 import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.index.query.IsVisibleToPredicate;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -83,13 +84,12 @@
       throw new OrmException("unable to read project state", e);
     }
 
-    boolean visible;
     PermissionBackend.WithUser withUser =
         user.isIdentifiedUser()
             ? permissionBackend.absentUser(user.getAccountId())
             : permissionBackend.user(anonymousUserProvider.get());
     try {
-      visible = withUser.indexedChange(cd, notes).database(db).test(ChangePermission.READ);
+      withUser.indexedChange(cd, notes).database(db).check(ChangePermission.READ);
     } catch (PermissionBackendException e) {
       Throwable cause = e.getCause();
       if (cause instanceof RepositoryNotFoundException) {
@@ -98,12 +98,12 @@
         return false;
       }
       throw new OrmException("unable to check permissions on change " + cd.getId(), e);
+    } catch (AuthException e) {
+      return false;
     }
-    if (visible) {
-      cd.cacheVisibleTo(user);
-      return true;
-    }
-    return false;
+
+    cd.cacheVisibleTo(user);
+    return true;
   }
 
   @Override
diff --git a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index 3113504..5855404 100644
--- a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -39,6 +39,7 @@
 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.mail.Address;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Branch;
@@ -65,7 +66,6 @@
 import com.google.gerrit.server.index.change.ChangeIndex;
 import com.google.gerrit.server.index.change.ChangeIndexCollection;
 import com.google.gerrit.server.index.change.ChangeIndexRewriter;
-import com.google.gerrit.server.mail.Address;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.notedb.NotesMigration;
 import com.google.gerrit.server.notedb.ReviewerStateInternal;
diff --git a/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java b/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java
index 19fd4a1..54e22f3 100644
--- a/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java
+++ b/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java
@@ -16,6 +16,7 @@
 
 import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.common.data.LabelTypes;
+import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Change;
@@ -125,10 +126,13 @@
       PermissionBackend.ForChange perm =
           permissionBackend.absentUser(approver).database(dbProvider).change(cd);
       ProjectState projectState = projectCache.checkedGet(cd.project());
-      return projectState != null
-          && projectState.statePermitsRead()
-          && perm.test(ChangePermission.READ);
-    } catch (PermissionBackendException | IOException e) {
+      if (projectState == null || !projectState.statePermitsRead()) {
+        return false;
+      }
+
+      perm.check(ChangePermission.READ);
+      return true;
+    } catch (PermissionBackendException | IOException | AuthException e) {
       return false;
     }
   }
diff --git a/java/com/google/gerrit/server/query/change/RegexPathPredicate.java b/java/com/google/gerrit/server/query/change/RegexPathPredicate.java
index 3764a98..f694904 100644
--- a/java/com/google/gerrit/server/query/change/RegexPathPredicate.java
+++ b/java/com/google/gerrit/server/query/change/RegexPathPredicate.java
@@ -15,7 +15,7 @@
 package com.google.gerrit.server.query.change;
 
 import com.google.gerrit.server.index.change.ChangeField;
-import com.google.gerrit.server.util.RegexListSearcher;
+import com.google.gerrit.server.ioutil.RegexListSearcher;
 import com.google.gwtorm.server.OrmException;
 import java.io.IOException;
 import java.util.List;
diff --git a/java/com/google/gerrit/server/query/change/ReviewerByEmailPredicate.java b/java/com/google/gerrit/server/query/change/ReviewerByEmailPredicate.java
index f4e979c..667c630 100644
--- a/java/com/google/gerrit/server/query/change/ReviewerByEmailPredicate.java
+++ b/java/com/google/gerrit/server/query/change/ReviewerByEmailPredicate.java
@@ -17,8 +17,8 @@
 import static com.google.common.base.Preconditions.checkArgument;
 
 import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.mail.Address;
 import com.google.gerrit.server.index.change.ChangeField;
-import com.google.gerrit.server.mail.Address;
 import com.google.gerrit.server.notedb.ReviewerStateInternal;
 import com.google.gwtorm.server.OrmException;
 
diff --git a/java/com/google/gerrit/server/query/project/ProjectPredicates.java b/java/com/google/gerrit/server/query/project/ProjectPredicates.java
index b4f56d4..2e406aa 100644
--- a/java/com/google/gerrit/server/query/project/ProjectPredicates.java
+++ b/java/com/google/gerrit/server/query/project/ProjectPredicates.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.query.project;
 
+import com.google.gerrit.extensions.client.ProjectState;
 import com.google.gerrit.index.project.ProjectData;
 import com.google.gerrit.index.project.ProjectField;
 import com.google.gerrit.index.project.ProjectPredicate;
@@ -34,5 +35,9 @@
     return new ProjectPredicate(ProjectField.DESCRIPTION, description);
   }
 
+  public static Predicate<ProjectData> state(ProjectState state) {
+    return new ProjectPredicate(ProjectField.STATE, state.name());
+  }
+
   private ProjectPredicates() {}
 }
diff --git a/java/com/google/gerrit/server/query/project/ProjectQueryBuilder.java b/java/com/google/gerrit/server/query/project/ProjectQueryBuilder.java
index be7ea22..872d3e0 100644
--- a/java/com/google/gerrit/server/query/project/ProjectQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/project/ProjectQueryBuilder.java
@@ -17,6 +17,7 @@
 import com.google.common.base.Strings;
 import com.google.common.collect.Lists;
 import com.google.common.primitives.Ints;
+import com.google.gerrit.extensions.client.ProjectState;
 import com.google.gerrit.index.project.ProjectData;
 import com.google.gerrit.index.query.LimitPredicate;
 import com.google.gerrit.index.query.Predicate;
@@ -60,6 +61,23 @@
     return ProjectPredicates.description(description);
   }
 
+  @Operator
+  public Predicate<ProjectData> state(String state) throws QueryParseException {
+    if (Strings.isNullOrEmpty(state)) {
+      throw error("state operator requires a value");
+    }
+    ProjectState parsedState;
+    try {
+      parsedState = ProjectState.valueOf(state.replace('-', '_').toUpperCase());
+    } catch (IllegalArgumentException e) {
+      throw error("state operator must be either 'active' or 'read-only'");
+    }
+    if (parsedState == ProjectState.HIDDEN) {
+      throw error("state operator must be either 'active' or 'read-only'");
+    }
+    return ProjectPredicates.state(parsedState);
+  }
+
   @Override
   protected Predicate<ProjectData> defaultField(String query) throws QueryParseException {
     // Adapt the capacity of this list when adding more default predicates.
diff --git a/java/com/google/gerrit/server/restapi/BUILD b/java/com/google/gerrit/server/restapi/BUILD
index ff114fae..1207c71 100644
--- a/java/com/google/gerrit/server/restapi/BUILD
+++ b/java/com/google/gerrit/server/restapi/BUILD
@@ -12,6 +12,7 @@
         "//java/com/google/gerrit/index",
         "//java/com/google/gerrit/index:query_exception",
         "//java/com/google/gerrit/index/project",
+        "//java/com/google/gerrit/mail",
         "//java/com/google/gerrit/metrics",
         "//java/com/google/gerrit/prettify:server",
         "//java/com/google/gerrit/reviewdb:server",
diff --git a/java/com/google/gerrit/server/restapi/account/AddSshKey.java b/java/com/google/gerrit/server/restapi/account/AddSshKey.java
index ab06e25..9d9c1e3 100644
--- a/java/com/google/gerrit/server/restapi/account/AddSshKey.java
+++ b/java/com/google/gerrit/server/restapi/account/AddSshKey.java
@@ -26,7 +26,7 @@
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.RawInput;
 import com.google.gerrit.extensions.restapi.Response;
-import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.restapi.RestCollectionView;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.AccountResource;
@@ -46,7 +46,8 @@
 import org.eclipse.jgit.errors.ConfigInvalidException;
 
 @Singleton
-public class AddSshKey implements RestModifyView<AccountResource, SshKeyInput> {
+public class AddSshKey
+    implements RestCollectionView<AccountResource, AccountResource.SshKey, SshKeyInput> {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
   private final Provider<CurrentUser> self;
diff --git a/java/com/google/gerrit/server/restapi/account/Capabilities.java b/java/com/google/gerrit/server/restapi/account/Capabilities.java
index ec16e2b..85f9da0 100644
--- a/java/com/google/gerrit/server/restapi/account/Capabilities.java
+++ b/java/com/google/gerrit/server/restapi/account/Capabilities.java
@@ -71,10 +71,12 @@
     }
 
     GlobalOrPluginPermission perm = parse(id);
-    if (permissionBackend.user(target).test(perm)) {
+    try {
+      permissionBackend.user(target).check(perm);
       return new AccountResource.Capability(target, globalOrPluginPermissionName(perm));
+    } catch (AuthException e) {
+      throw new ResourceNotFoundException(id);
     }
-    throw new ResourceNotFoundException(id);
   }
 
   private GlobalOrPluginPermission parse(IdString id) throws ResourceNotFoundException {
diff --git a/java/com/google/gerrit/server/restapi/account/CreateAccount.java b/java/com/google/gerrit/server/restapi/account/CreateAccount.java
index b1581abb..3e43082 100644
--- a/java/com/google/gerrit/server/restapi/account/CreateAccount.java
+++ b/java/com/google/gerrit/server/restapi/account/CreateAccount.java
@@ -49,6 +49,7 @@
 import com.google.gerrit.server.group.db.GroupsUpdate;
 import com.google.gerrit.server.group.db.InternalGroupUpdate;
 import com.google.gerrit.server.mail.send.OutgoingEmailValidator;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.restapi.group.GroupsCollection;
 import com.google.gerrit.server.ssh.SshKeyCache;
 import com.google.gwtorm.server.OrmException;
@@ -100,13 +101,13 @@
   public Response<AccountInfo> apply(
       TopLevelResource rsrc, IdString id, @Nullable AccountInput input)
       throws BadRequestException, ResourceConflictException, UnprocessableEntityException,
-          OrmException, IOException, ConfigInvalidException {
+          OrmException, IOException, ConfigInvalidException, PermissionBackendException {
     return apply(id, input != null ? input : new AccountInput());
   }
 
   public Response<AccountInfo> apply(IdString id, AccountInput input)
       throws BadRequestException, ResourceConflictException, UnprocessableEntityException,
-          OrmException, IOException, ConfigInvalidException {
+          OrmException, IOException, ConfigInvalidException, PermissionBackendException {
     String username = id.get();
     if (input.username != null && !username.equals(input.username)) {
       throw new BadRequestException("username must match URL");
diff --git a/java/com/google/gerrit/server/restapi/account/GetAccount.java b/java/com/google/gerrit/server/restapi/account/GetAccount.java
index 0d8e25e..6b73ae3b 100644
--- a/java/com/google/gerrit/server/restapi/account/GetAccount.java
+++ b/java/com/google/gerrit/server/restapi/account/GetAccount.java
@@ -18,6 +18,7 @@
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.server.account.AccountLoader;
 import com.google.gerrit.server.account.AccountResource;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
@@ -32,7 +33,7 @@
   }
 
   @Override
-  public AccountInfo apply(AccountResource rsrc) throws OrmException {
+  public AccountInfo apply(AccountResource rsrc) throws OrmException, PermissionBackendException {
     AccountLoader loader = infoFactory.create(true);
     AccountInfo info = loader.get(rsrc.getUser().getAccountId());
     loader.fill();
diff --git a/java/com/google/gerrit/server/restapi/account/GetAgreements.java b/java/com/google/gerrit/server/restapi/account/GetAgreements.java
index dced4d7..edcbc35 100644
--- a/java/com/google/gerrit/server/restapi/account/GetAgreements.java
+++ b/java/com/google/gerrit/server/restapi/account/GetAgreements.java
@@ -28,6 +28,7 @@
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.AccountResource;
 import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.restapi.config.AgreementJson;
 import com.google.inject.Inject;
@@ -60,7 +61,8 @@
   }
 
   @Override
-  public List<AgreementInfo> apply(AccountResource resource) throws RestApiException {
+  public List<AgreementInfo> apply(AccountResource resource)
+      throws RestApiException, PermissionBackendException {
     if (!agreementsEnabled) {
       throw new MethodNotAllowedException("contributor agreements disabled");
     }
diff --git a/java/com/google/gerrit/server/restapi/account/GetDetail.java b/java/com/google/gerrit/server/restapi/account/GetDetail.java
index c8f99b3..97d0c60 100644
--- a/java/com/google/gerrit/server/restapi/account/GetDetail.java
+++ b/java/com/google/gerrit/server/restapi/account/GetDetail.java
@@ -14,38 +14,25 @@
 
 package com.google.gerrit.server.restapi.account;
 
-import com.google.common.collect.Sets;
 import com.google.gerrit.extensions.common.AccountDetailInfo;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.account.AccountDirectory.FillOptions;
 import com.google.gerrit.server.account.AccountResource;
 import com.google.gerrit.server.account.InternalAccountDirectory;
-import com.google.gerrit.server.permissions.GlobalPermission;
-import com.google.gerrit.server.permissions.PermissionBackend;
 import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
-import com.google.inject.Provider;
 import com.google.inject.Singleton;
 import java.util.Collections;
 import java.util.EnumSet;
-import java.util.Set;
 
 @Singleton
 public class GetDetail implements RestReadView<AccountResource> {
-  private final Provider<CurrentUser> self;
-  private final PermissionBackend permissionBackend;
   private final InternalAccountDirectory directory;
 
   @Inject
-  public GetDetail(
-      Provider<CurrentUser> self,
-      PermissionBackend permissionBackend,
-      InternalAccountDirectory directory) {
-    this.self = self;
-    this.permissionBackend = permissionBackend;
+  public GetDetail(InternalAccountDirectory directory) {
     this.directory = directory;
   }
 
@@ -56,13 +43,7 @@
     AccountDetailInfo info = new AccountDetailInfo(a.getId().get());
     info.registeredOn = a.getRegisteredOn();
     info.inactive = !a.isActive() ? true : null;
-    Set<FillOptions> fillOptions =
-        self.get().hasSameAccountId(rsrc.getUser())
-                || permissionBackend.currentUser().test(GlobalPermission.MODIFY_ACCOUNT)
-            ? EnumSet.allOf(FillOptions.class)
-            : Sets.difference(
-                EnumSet.allOf(FillOptions.class), EnumSet.of(FillOptions.SECONDARY_EMAILS));
-    directory.fillAccountInfo(Collections.singleton(info), fillOptions);
+    directory.fillAccountInfo(Collections.singleton(info), EnumSet.allOf(FillOptions.class));
     return info;
   }
 }
diff --git a/java/com/google/gerrit/server/restapi/account/GetGroups.java b/java/com/google/gerrit/server/restapi/account/GetGroups.java
index 486a151..ad9746e 100644
--- a/java/com/google/gerrit/server/restapi/account/GetGroups.java
+++ b/java/com/google/gerrit/server/restapi/account/GetGroups.java
@@ -22,6 +22,7 @@
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.AccountResource;
 import com.google.gerrit.server.account.GroupControl;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.restapi.group.GroupJson;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -42,7 +43,8 @@
   }
 
   @Override
-  public List<GroupInfo> apply(AccountResource resource) throws OrmException {
+  public List<GroupInfo> apply(AccountResource resource)
+      throws OrmException, PermissionBackendException {
     IdentifiedUser user = resource.getUser();
     Account.Id userId = user.getAccountId();
     Set<AccountGroup.UUID> knownGroups = user.getEffectiveGroups().getKnownGroups();
diff --git a/java/com/google/gerrit/server/restapi/account/Module.java b/java/com/google/gerrit/server/restapi/account/Module.java
index f37687f..be0924a 100644
--- a/java/com/google/gerrit/server/restapi/account/Module.java
+++ b/java/com/google/gerrit/server/restapi/account/Module.java
@@ -66,12 +66,12 @@
     put(EMAIL_KIND, "preferred").to(PutPreferred.class);
     put(ACCOUNT_KIND, "password.http").to(PutHttpPassword.class);
     delete(ACCOUNT_KIND, "password.http").to(PutHttpPassword.class);
-    child(ACCOUNT_KIND, "sshkeys").to(SshKeys.class);
-    post(ACCOUNT_KIND, "sshkeys").to(AddSshKey.class);
     get(ACCOUNT_KIND, "watched.projects").to(GetWatchedProjects.class);
     post(ACCOUNT_KIND, "watched.projects").to(PostWatchedProjects.class);
     post(ACCOUNT_KIND, "watched.projects:delete").to(DeleteWatchedProjects.class);
 
+    child(ACCOUNT_KIND, "sshkeys").to(SshKeys.class);
+    postOnCollection(SSH_KEY_KIND).to(AddSshKey.class);
     get(SSH_KEY_KIND).to(GetSshKey.class);
     delete(SSH_KEY_KIND).to(DeleteSshKey.class);
 
diff --git a/java/com/google/gerrit/server/restapi/account/QueryAccounts.java b/java/com/google/gerrit/server/restapi/account/QueryAccounts.java
index 8784d23..2c0512c 100644
--- a/java/com/google/gerrit/server/restapi/account/QueryAccounts.java
+++ b/java/com/google/gerrit/server/restapi/account/QueryAccounts.java
@@ -19,6 +19,7 @@
 import com.google.gerrit.extensions.client.ListAccountsOption;
 import com.google.gerrit.extensions.common.AccountInfo;
 import com.google.gerrit.extensions.common.AccountVisibility;
+import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
 import com.google.gerrit.extensions.restapi.RestApiException;
@@ -171,9 +172,15 @@
       fillOptions.addAll(AccountLoader.DETAILED_OPTIONS);
       fillOptions.add(FillOptions.EMAIL);
 
-      if (modifyAccountCapabilityChecked
-          || permissionBackend.currentUser().test(GlobalPermission.MODIFY_ACCOUNT)) {
+      if (modifyAccountCapabilityChecked) {
         fillOptions.add(FillOptions.SECONDARY_EMAILS);
+      } else {
+        try {
+          permissionBackend.currentUser().check(GlobalPermission.MODIFY_ACCOUNT);
+          fillOptions.add(FillOptions.SECONDARY_EMAILS);
+        } catch (AuthException e) {
+          // Do nothing.
+        }
       }
     }
     accountLoader = accountLoaderFactory.create(fillOptions);
diff --git a/java/com/google/gerrit/server/restapi/account/StarredChanges.java b/java/com/google/gerrit/server/restapi/account/StarredChanges.java
index e049539..5d4044c5 100644
--- a/java/com/google/gerrit/server/restapi/account/StarredChanges.java
+++ b/java/com/google/gerrit/server/restapi/account/StarredChanges.java
@@ -89,7 +89,7 @@
     return new RestReadView<AccountResource>() {
       @Override
       public Object apply(AccountResource self)
-          throws BadRequestException, AuthException, OrmException {
+          throws BadRequestException, AuthException, OrmException, PermissionBackendException {
         QueryChanges query = changes.list();
         query.addQuery("starredby:" + self.getUser().getAccountId().get());
         return query.apply(TopLevelResource.INSTANCE);
diff --git a/java/com/google/gerrit/server/restapi/account/Stars.java b/java/com/google/gerrit/server/restapi/account/Stars.java
index fb809ee..5c4c4d5 100644
--- a/java/com/google/gerrit/server/restapi/account/Stars.java
+++ b/java/com/google/gerrit/server/restapi/account/Stars.java
@@ -99,7 +99,7 @@
     @Override
     @SuppressWarnings("unchecked")
     public List<ChangeInfo> apply(AccountResource rsrc)
-        throws BadRequestException, AuthException, OrmException {
+        throws BadRequestException, AuthException, OrmException, PermissionBackendException {
       if (!self.get().hasSameAccountId(rsrc.getUser())) {
         throw new AuthException("not allowed to list stars of another account");
       }
diff --git a/java/com/google/gerrit/server/restapi/change/Abandon.java b/java/com/google/gerrit/server/restapi/change/Abandon.java
index 7978990..c739e54 100644
--- a/java/com/google/gerrit/server/restapi/change/Abandon.java
+++ b/java/com/google/gerrit/server/restapi/change/Abandon.java
@@ -81,7 +81,7 @@
       throws RestApiException, UpdateException, OrmException, PermissionBackendException,
           IOException, ConfigInvalidException {
     // Not allowed to abandon if the current patch set is locked.
-    patchSetUtil.checkPatchSetNotLocked(rsrc.getNotes(), rsrc.getUser());
+    patchSetUtil.checkPatchSetNotLocked(rsrc.getNotes());
 
     rsrc.permissions().database(dbProvider).check(ChangePermission.ABANDON);
 
@@ -155,7 +155,7 @@
     }
 
     try {
-      if (patchSetUtil.isPatchSetLocked(rsrc.getNotes(), rsrc.getUser())) {
+      if (patchSetUtil.isPatchSetLocked(rsrc.getNotes())) {
         return description;
       }
     } catch (OrmException | IOException e) {
diff --git a/java/com/google/gerrit/server/restapi/change/ChangeEdits.java b/java/com/google/gerrit/server/restapi/change/ChangeEdits.java
index 826c792..efc2712 100644
--- a/java/com/google/gerrit/server/restapi/change/ChangeEdits.java
+++ b/java/com/google/gerrit/server/restapi/change/ChangeEdits.java
@@ -20,7 +20,6 @@
 import com.google.gerrit.extensions.common.Input;
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.restapi.AcceptsDelete;
-import com.google.gerrit.extensions.restapi.AcceptsPost;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.BinaryResult;
@@ -32,6 +31,7 @@
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.extensions.restapi.RestCollectionView;
 import com.google.gerrit.extensions.restapi.RestCreateView;
 import com.google.gerrit.extensions.restapi.RestModifyView;
 import com.google.gerrit.extensions.restapi.RestReadView;
@@ -71,26 +71,21 @@
 
 @Singleton
 public class ChangeEdits
-    implements ChildCollection<ChangeResource, ChangeEditResource>,
-        AcceptsPost<ChangeResource>,
-        AcceptsDelete<ChangeResource> {
+    implements ChildCollection<ChangeResource, ChangeEditResource>, AcceptsDelete<ChangeResource> {
   private final DynamicMap<RestView<ChangeEditResource>> views;
   private final DeleteFile.Factory deleteFileFactory;
   private final Provider<Detail> detail;
   private final ChangeEditUtil editUtil;
-  private final Post post;
 
   @Inject
   ChangeEdits(
       DynamicMap<RestView<ChangeEditResource>> views,
       Provider<Detail> detail,
       ChangeEditUtil editUtil,
-      Post post,
       DeleteFile.Factory deleteFileFactory) {
     this.views = views;
     this.detail = detail;
     this.editUtil = editUtil;
-    this.post = post;
     this.deleteFileFactory = deleteFileFactory;
   }
 
@@ -114,11 +109,6 @@
     return new ChangeEditResource(rsrc, edit.get(), id.get());
   }
 
-  @Override
-  public Post post(ChangeResource parent) throws RestApiException {
-    return post;
-  }
-
   /**
    * This method is invoked if a DELETE request on a non-existing member is done. For change edits
    * this is the case if a DELETE request for a file in a change edit is done and the change edit
@@ -247,7 +237,8 @@
    * The combination of two operations in one request is supported.
    */
   @Singleton
-  public static class Post implements RestModifyView<ChangeResource, Post.Input> {
+  public static class Post
+      implements RestCollectionView<ChangeResource, ChangeEditResource, Post.Input> {
     public static class Input {
       public String restorePath;
       public String oldPath;
diff --git a/java/com/google/gerrit/server/restapi/change/ChangeMessages.java b/java/com/google/gerrit/server/restapi/change/ChangeMessages.java
index 251c66d..25fc350 100644
--- a/java/com/google/gerrit/server/restapi/change/ChangeMessages.java
+++ b/java/com/google/gerrit/server/restapi/change/ChangeMessages.java
@@ -22,6 +22,7 @@
 import com.google.gerrit.extensions.restapi.RestView;
 import com.google.gerrit.server.change.ChangeMessageResource;
 import com.google.gerrit.server.change.ChangeResource;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
@@ -51,7 +52,7 @@
 
   @Override
   public ChangeMessageResource parse(ChangeResource parent, IdString id)
-      throws OrmException, ResourceNotFoundException {
+      throws OrmException, ResourceNotFoundException, PermissionBackendException {
     String uuid = id.get();
 
     List<ChangeMessageInfo> changeMessages = listChangeMessages.apply(parent);
diff --git a/java/com/google/gerrit/server/restapi/change/ChangesCollection.java b/java/com/google/gerrit/server/restapi/change/ChangesCollection.java
index 58ea185..e9b4d87 100644
--- a/java/com/google/gerrit/server/restapi/change/ChangesCollection.java
+++ b/java/com/google/gerrit/server/restapi/change/ChangesCollection.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.server.restapi.change;
 
 import com.google.gerrit.extensions.registration.DynamicMap;
-import com.google.gerrit.extensions.restapi.AcceptsPost;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.IdString;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
@@ -43,14 +42,12 @@
 import java.util.List;
 
 @Singleton
-public class ChangesCollection
-    implements RestCollection<TopLevelResource, ChangeResource>, AcceptsPost<TopLevelResource> {
+public class ChangesCollection implements RestCollection<TopLevelResource, ChangeResource> {
   private final Provider<ReviewDb> db;
   private final Provider<CurrentUser> user;
   private final Provider<QueryChanges> queryFactory;
   private final DynamicMap<RestView<ChangeResource>> views;
   private final ChangeFinder changeFinder;
-  private final CreateChange createChange;
   private final ChangeResource.Factory changeResourceFactory;
   private final PermissionBackend permissionBackend;
   private final ProjectCache projectCache;
@@ -62,7 +59,6 @@
       Provider<QueryChanges> queryFactory,
       DynamicMap<RestView<ChangeResource>> views,
       ChangeFinder changeFinder,
-      CreateChange createChange,
       ChangeResource.Factory changeResourceFactory,
       PermissionBackend permissionBackend,
       ProjectCache projectCache) {
@@ -71,7 +67,6 @@
     this.queryFactory = queryFactory;
     this.views = views;
     this.changeFinder = changeFinder;
-    this.createChange = createChange;
     this.changeResourceFactory = changeResourceFactory;
     this.permissionBackend = permissionBackend;
     this.projectCache = projectCache;
@@ -130,11 +125,6 @@
     return changeResourceFactory.create(notes, user);
   }
 
-  @Override
-  public CreateChange post(TopLevelResource parent) throws RestApiException {
-    return createChange;
-  }
-
   private boolean canRead(ChangeNotes notes) throws PermissionBackendException, IOException {
     try {
       permissionBackend.currentUser().change(notes).database(db).check(ChangePermission.READ);
diff --git a/java/com/google/gerrit/server/restapi/change/CommentJson.java b/java/com/google/gerrit/server/restapi/change/CommentJson.java
index e90ce18..e92abe1 100644
--- a/java/com/google/gerrit/server/restapi/change/CommentJson.java
+++ b/java/com/google/gerrit/server/restapi/change/CommentJson.java
@@ -32,6 +32,7 @@
 import com.google.gerrit.reviewdb.client.FixSuggestion;
 import com.google.gerrit.reviewdb.client.RobotComment;
 import com.google.gerrit.server.account.AccountLoader;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.inject.Inject;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -70,7 +71,7 @@
   }
 
   private abstract class BaseCommentFormatter<F extends Comment, T extends CommentInfo> {
-    public T format(F comment) {
+    public T format(F comment) throws PermissionBackendException {
       AccountLoader loader = fillAccounts ? accountLoaderFactory.create(true) : null;
       T info = toInfo(comment, loader);
       if (loader != null) {
@@ -79,7 +80,7 @@
       return info;
     }
 
-    public Map<String, List<T>> format(Iterable<F> comments) {
+    public Map<String, List<T>> format(Iterable<F> comments) throws PermissionBackendException {
       AccountLoader loader = fillAccounts ? accountLoaderFactory.create(true) : null;
 
       Map<String, List<T>> out = new TreeMap<>();
@@ -105,7 +106,7 @@
       return out;
     }
 
-    public List<T> formatAsList(Iterable<F> comments) {
+    public List<T> formatAsList(Iterable<F> comments) throws PermissionBackendException {
       AccountLoader loader = fillAccounts ? accountLoaderFactory.create(true) : null;
 
       List<T> out =
diff --git a/java/com/google/gerrit/server/restapi/change/CreateChange.java b/java/com/google/gerrit/server/restapi/change/CreateChange.java
index 73669a1..0dcbeb0 100644
--- a/java/com/google/gerrit/server/restapi/change/CreateChange.java
+++ b/java/com/google/gerrit/server/restapi/change/CreateChange.java
@@ -50,6 +50,7 @@
 import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.change.ChangeInserter;
 import com.google.gerrit.server.change.ChangeJson;
+import com.google.gerrit.server.change.ChangeResource;
 import com.google.gerrit.server.change.NotifyUtil;
 import com.google.gerrit.server.config.AnonymousCowardName;
 import com.google.gerrit.server.config.GerritServerConfig;
@@ -68,7 +69,7 @@
 import com.google.gerrit.server.restapi.project.ProjectsCollection;
 import com.google.gerrit.server.update.BatchUpdate;
 import com.google.gerrit.server.update.RetryHelper;
-import com.google.gerrit.server.update.RetryingRestModifyView;
+import com.google.gerrit.server.update.RetryingRestCollectionView;
 import com.google.gerrit.server.update.UpdateException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -97,7 +98,8 @@
 
 @Singleton
 public class CreateChange
-    extends RetryingRestModifyView<TopLevelResource, ChangeInput, Response<ChangeInfo>> {
+    extends RetryingRestCollectionView<
+        TopLevelResource, ChangeResource, ChangeInput, Response<ChangeInfo>> {
   private final String anonymousCowardName;
   private final Provider<ReviewDb> db;
   private final GitRepositoryManager gitManager;
diff --git a/java/com/google/gerrit/server/restapi/change/CreateDraftComment.java b/java/com/google/gerrit/server/restapi/change/CreateDraftComment.java
index be689ca..c22fb1e 100644
--- a/java/com/google/gerrit/server/restapi/change/CreateDraftComment.java
+++ b/java/com/google/gerrit/server/restapi/change/CreateDraftComment.java
@@ -35,6 +35,7 @@
 import com.google.gerrit.server.change.RevisionResource;
 import com.google.gerrit.server.patch.PatchListCache;
 import com.google.gerrit.server.patch.PatchListNotAvailableException;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.update.BatchUpdate;
 import com.google.gerrit.server.update.BatchUpdateOp;
 import com.google.gerrit.server.update.ChangeContext;
@@ -75,7 +76,7 @@
   @Override
   protected Response<CommentInfo> applyImpl(
       BatchUpdate.Factory updateFactory, RevisionResource rsrc, DraftInput in)
-      throws RestApiException, UpdateException, OrmException {
+      throws RestApiException, UpdateException, OrmException, PermissionBackendException {
     if (Strings.isNullOrEmpty(in.path)) {
       throw new BadRequestException("path must be non-empty");
     } else if (in.message == null || in.message.trim().isEmpty()) {
diff --git a/java/com/google/gerrit/server/restapi/change/CreateMergePatchSet.java b/java/com/google/gerrit/server/restapi/change/CreateMergePatchSet.java
index ff85880..a5698b6 100644
--- a/java/com/google/gerrit/server/restapi/change/CreateMergePatchSet.java
+++ b/java/com/google/gerrit/server/restapi/change/CreateMergePatchSet.java
@@ -128,7 +128,7 @@
       throws OrmException, IOException, RestApiException, UpdateException,
           PermissionBackendException {
     // Not allowed to create a new patch set if the current patch set is locked.
-    psUtil.checkPatchSetNotLocked(rsrc.getNotes(), rsrc.getUser());
+    psUtil.checkPatchSetNotLocked(rsrc.getNotes());
 
     rsrc.permissions().database(db).check(ChangePermission.ADD_PATCH_SET);
 
diff --git a/java/com/google/gerrit/server/restapi/change/DeleteReviewerByEmailOp.java b/java/com/google/gerrit/server/restapi/change/DeleteReviewerByEmailOp.java
index fac1003..3231d16 100644
--- a/java/com/google/gerrit/server/restapi/change/DeleteReviewerByEmailOp.java
+++ b/java/com/google/gerrit/server/restapi/change/DeleteReviewerByEmailOp.java
@@ -17,12 +17,12 @@
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.extensions.api.changes.DeleteReviewerInput;
 import com.google.gerrit.extensions.api.changes.NotifyHandling;
+import com.google.gerrit.mail.Address;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.ChangeMessage;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.change.NotifyUtil;
-import com.google.gerrit.server.mail.Address;
 import com.google.gerrit.server.mail.send.DeleteReviewerSender;
 import com.google.gerrit.server.update.BatchUpdateOp;
 import com.google.gerrit.server.update.ChangeContext;
diff --git a/java/com/google/gerrit/server/restapi/change/DeleteReviewerOp.java b/java/com/google/gerrit/server/restapi/change/DeleteReviewerOp.java
index 91f5d15..2cc4ce4 100644
--- a/java/com/google/gerrit/server/restapi/change/DeleteReviewerOp.java
+++ b/java/com/google/gerrit/server/restapi/change/DeleteReviewerOp.java
@@ -131,8 +131,7 @@
     currChange = ctx.getChange();
     currPs = psUtil.current(ctx.getDb(), ctx.getNotes());
 
-    LabelTypes labelTypes =
-        projectCache.checkedGet(ctx.getProject()).getLabelTypes(ctx.getNotes(), ctx.getUser());
+    LabelTypes labelTypes = projectCache.checkedGet(ctx.getProject()).getLabelTypes(ctx.getNotes());
     // removing a reviewer will remove all her votes
     for (LabelType lt : labelTypes.getLabelTypes()) {
       newApprovals.put(lt.getName(), (short) 0);
diff --git a/java/com/google/gerrit/server/restapi/change/DeleteVote.java b/java/com/google/gerrit/server/restapi/change/DeleteVote.java
index f268a30..dc44e65 100644
--- a/java/com/google/gerrit/server/restapi/change/DeleteVote.java
+++ b/java/com/google/gerrit/server/restapi/change/DeleteVote.java
@@ -172,7 +172,7 @@
       ps = psUtil.current(db.get(), ctx.getNotes());
 
       boolean found = false;
-      LabelTypes labelTypes = projectState.getLabelTypes(ctx.getNotes(), ctx.getUser());
+      LabelTypes labelTypes = projectState.getLabelTypes(ctx.getNotes());
 
       Account.Id accountId = accountState.getAccount().getId();
 
@@ -180,7 +180,6 @@
           approvalsUtil.byPatchSetUser(
               ctx.getDb(),
               ctx.getNotes(),
-              ctx.getUser(),
               psId,
               accountId,
               ctx.getRevWalk(),
diff --git a/java/com/google/gerrit/server/restapi/change/GetAssignee.java b/java/com/google/gerrit/server/restapi/change/GetAssignee.java
index f78fae2..e95f8d8 100644
--- a/java/com/google/gerrit/server/restapi/change/GetAssignee.java
+++ b/java/com/google/gerrit/server/restapi/change/GetAssignee.java
@@ -20,6 +20,7 @@
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.account.AccountLoader;
 import com.google.gerrit.server.change.ChangeResource;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
@@ -35,7 +36,8 @@
   }
 
   @Override
-  public Response<AccountInfo> apply(ChangeResource rsrc) throws OrmException {
+  public Response<AccountInfo> apply(ChangeResource rsrc)
+      throws OrmException, PermissionBackendException {
     Optional<Account.Id> assignee = Optional.ofNullable(rsrc.getChange().getAssignee());
     if (assignee.isPresent()) {
       return Response.ok(accountLoaderFactory.create(true).fillOne(assignee.get()));
diff --git a/java/com/google/gerrit/server/restapi/change/GetComment.java b/java/com/google/gerrit/server/restapi/change/GetComment.java
index b8db6a5..d067dff 100644
--- a/java/com/google/gerrit/server/restapi/change/GetComment.java
+++ b/java/com/google/gerrit/server/restapi/change/GetComment.java
@@ -17,6 +17,7 @@
 import com.google.gerrit.extensions.common.CommentInfo;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.server.change.CommentResource;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -33,7 +34,7 @@
   }
 
   @Override
-  public CommentInfo apply(CommentResource rsrc) throws OrmException {
+  public CommentInfo apply(CommentResource rsrc) throws OrmException, PermissionBackendException {
     return commentJson.get().newCommentFormatter().format(rsrc.getComment());
   }
 }
diff --git a/java/com/google/gerrit/server/restapi/change/GetDraftComment.java b/java/com/google/gerrit/server/restapi/change/GetDraftComment.java
index 787c93e..6049607 100644
--- a/java/com/google/gerrit/server/restapi/change/GetDraftComment.java
+++ b/java/com/google/gerrit/server/restapi/change/GetDraftComment.java
@@ -17,6 +17,7 @@
 import com.google.gerrit.extensions.common.CommentInfo;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.server.change.DraftCommentResource;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -33,7 +34,8 @@
   }
 
   @Override
-  public CommentInfo apply(DraftCommentResource rsrc) throws OrmException {
+  public CommentInfo apply(DraftCommentResource rsrc)
+      throws OrmException, PermissionBackendException {
     return commentJson.get().newCommentFormatter().format(rsrc.getComment());
   }
 }
diff --git a/java/com/google/gerrit/server/restapi/change/GetPastAssignees.java b/java/com/google/gerrit/server/restapi/change/GetPastAssignees.java
index 354558b..279cfe3 100644
--- a/java/com/google/gerrit/server/restapi/change/GetPastAssignees.java
+++ b/java/com/google/gerrit/server/restapi/change/GetPastAssignees.java
@@ -22,6 +22,7 @@
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.account.AccountLoader;
 import com.google.gerrit.server.change.ChangeResource;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
@@ -39,7 +40,8 @@
   }
 
   @Override
-  public Response<List<AccountInfo>> apply(ChangeResource rsrc) throws OrmException {
+  public Response<List<AccountInfo>> apply(ChangeResource rsrc)
+      throws OrmException, PermissionBackendException {
 
     Set<Account.Id> pastAssignees = rsrc.getNotes().load().getPastAssignees();
     if (pastAssignees == null) {
diff --git a/java/com/google/gerrit/server/restapi/change/GetRobotComment.java b/java/com/google/gerrit/server/restapi/change/GetRobotComment.java
index bd1f66a..0197068 100644
--- a/java/com/google/gerrit/server/restapi/change/GetRobotComment.java
+++ b/java/com/google/gerrit/server/restapi/change/GetRobotComment.java
@@ -17,6 +17,7 @@
 import com.google.gerrit.extensions.common.RobotCommentInfo;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.server.change.RobotCommentResource;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -33,7 +34,8 @@
   }
 
   @Override
-  public RobotCommentInfo apply(RobotCommentResource rsrc) throws OrmException {
+  public RobotCommentInfo apply(RobotCommentResource rsrc)
+      throws OrmException, PermissionBackendException {
     return commentJson.get().newRobotCommentFormatter().format(rsrc.getComment());
   }
 }
diff --git a/java/com/google/gerrit/server/restapi/change/ListChangeComments.java b/java/com/google/gerrit/server/restapi/change/ListChangeComments.java
index 37dc207..40f4642 100644
--- a/java/com/google/gerrit/server/restapi/change/ListChangeComments.java
+++ b/java/com/google/gerrit/server/restapi/change/ListChangeComments.java
@@ -20,6 +20,7 @@
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CommentsUtil;
 import com.google.gerrit.server.change.ChangeResource;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -49,7 +50,7 @@
 
   @Override
   public Map<String, List<CommentInfo>> apply(ChangeResource rsrc)
-      throws AuthException, OrmException {
+      throws AuthException, OrmException, PermissionBackendException {
     ChangeData cd = changeDataFactory.create(db.get(), rsrc.getNotes());
     return commentJson
         .get()
diff --git a/java/com/google/gerrit/server/restapi/change/ListChangeDrafts.java b/java/com/google/gerrit/server/restapi/change/ListChangeDrafts.java
index d7a102a..a524f6d 100644
--- a/java/com/google/gerrit/server/restapi/change/ListChangeDrafts.java
+++ b/java/com/google/gerrit/server/restapi/change/ListChangeDrafts.java
@@ -21,6 +21,7 @@
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CommentsUtil;
 import com.google.gerrit.server.change.ChangeResource;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -50,7 +51,7 @@
 
   @Override
   public Map<String, List<CommentInfo>> apply(ChangeResource rsrc)
-      throws AuthException, OrmException {
+      throws AuthException, OrmException, PermissionBackendException {
     if (!rsrc.getUser().isIdentifiedUser()) {
       throw new AuthException("Authentication required");
     }
diff --git a/java/com/google/gerrit/server/restapi/change/ListChangeMessages.java b/java/com/google/gerrit/server/restapi/change/ListChangeMessages.java
index cf76ef1..39c12f7 100644
--- a/java/com/google/gerrit/server/restapi/change/ListChangeMessages.java
+++ b/java/com/google/gerrit/server/restapi/change/ListChangeMessages.java
@@ -23,6 +23,7 @@
 import com.google.gerrit.server.ChangeMessagesUtil;
 import com.google.gerrit.server.account.AccountLoader;
 import com.google.gerrit.server.change.ChangeResource;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -47,7 +48,8 @@
   }
 
   @Override
-  public List<ChangeMessageInfo> apply(ChangeResource resource) throws OrmException {
+  public List<ChangeMessageInfo> apply(ChangeResource resource)
+      throws OrmException, PermissionBackendException {
     List<ChangeMessage> messages =
         changeMessagesUtil.byChange(dbProvider.get(), resource.getNotes());
     List<ChangeMessageInfo> messageInfos =
diff --git a/java/com/google/gerrit/server/restapi/change/ListChangeRobotComments.java b/java/com/google/gerrit/server/restapi/change/ListChangeRobotComments.java
index dd8de6f..dc92ced 100644
--- a/java/com/google/gerrit/server/restapi/change/ListChangeRobotComments.java
+++ b/java/com/google/gerrit/server/restapi/change/ListChangeRobotComments.java
@@ -20,6 +20,7 @@
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CommentsUtil;
 import com.google.gerrit.server.change.ChangeResource;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -47,7 +48,7 @@
 
   @Override
   public Map<String, List<RobotCommentInfo>> apply(ChangeResource rsrc)
-      throws AuthException, OrmException {
+      throws AuthException, OrmException, PermissionBackendException {
     ChangeData cd = changeDataFactory.create(db.get(), rsrc.getNotes());
     return commentJson
         .get()
diff --git a/java/com/google/gerrit/server/restapi/change/ListReviewers.java b/java/com/google/gerrit/server/restapi/change/ListReviewers.java
index 750e74f..46bb33f 100644
--- a/java/com/google/gerrit/server/restapi/change/ListReviewers.java
+++ b/java/com/google/gerrit/server/restapi/change/ListReviewers.java
@@ -16,12 +16,12 @@
 
 import com.google.gerrit.extensions.api.changes.ReviewerInfo;
 import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.mail.Address;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.change.ChangeResource;
 import com.google.gerrit.server.change.ReviewerResource;
-import com.google.gerrit.server.mail.Address;
 import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/server/restapi/change/ListRevisionDrafts.java b/java/com/google/gerrit/server/restapi/change/ListRevisionDrafts.java
index b7dc553..db8ef0c 100644
--- a/java/com/google/gerrit/server/restapi/change/ListRevisionDrafts.java
+++ b/java/com/google/gerrit/server/restapi/change/ListRevisionDrafts.java
@@ -20,6 +20,7 @@
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CommentsUtil;
 import com.google.gerrit.server.change.RevisionResource;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -51,7 +52,8 @@
   }
 
   @Override
-  public Map<String, List<CommentInfo>> apply(RevisionResource rsrc) throws OrmException {
+  public Map<String, List<CommentInfo>> apply(RevisionResource rsrc)
+      throws OrmException, PermissionBackendException {
     return commentJson
         .get()
         .setFillAccounts(includeAuthorInfo())
@@ -59,7 +61,8 @@
         .format(listComments(rsrc));
   }
 
-  public List<CommentInfo> getComments(RevisionResource rsrc) throws OrmException {
+  public List<CommentInfo> getComments(RevisionResource rsrc)
+      throws OrmException, PermissionBackendException {
     return commentJson
         .get()
         .setFillAccounts(includeAuthorInfo())
diff --git a/java/com/google/gerrit/server/restapi/change/ListRevisionReviewers.java b/java/com/google/gerrit/server/restapi/change/ListRevisionReviewers.java
index d0630b7..32c4ea3 100644
--- a/java/com/google/gerrit/server/restapi/change/ListRevisionReviewers.java
+++ b/java/com/google/gerrit/server/restapi/change/ListRevisionReviewers.java
@@ -17,12 +17,12 @@
 import com.google.gerrit.extensions.api.changes.ReviewerInfo;
 import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
 import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.mail.Address;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.change.ReviewerResource;
 import com.google.gerrit.server.change.RevisionResource;
-import com.google.gerrit.server.mail.Address;
 import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/server/restapi/change/ListRobotComments.java b/java/com/google/gerrit/server/restapi/change/ListRobotComments.java
index 61219d3..66138ab 100644
--- a/java/com/google/gerrit/server/restapi/change/ListRobotComments.java
+++ b/java/com/google/gerrit/server/restapi/change/ListRobotComments.java
@@ -20,6 +20,7 @@
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CommentsUtil;
 import com.google.gerrit.server.change.RevisionResource;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -42,7 +43,8 @@
   }
 
   @Override
-  public Map<String, List<RobotCommentInfo>> apply(RevisionResource rsrc) throws OrmException {
+  public Map<String, List<RobotCommentInfo>> apply(RevisionResource rsrc)
+      throws OrmException, PermissionBackendException {
     return commentJson
         .get()
         .setFillAccounts(true)
@@ -50,7 +52,8 @@
         .format(listComments(rsrc));
   }
 
-  public List<RobotCommentInfo> getComments(RevisionResource rsrc) throws OrmException {
+  public List<RobotCommentInfo> getComments(RevisionResource rsrc)
+      throws OrmException, PermissionBackendException {
     return commentJson
         .get()
         .setFillAccounts(true)
diff --git a/java/com/google/gerrit/server/restapi/change/Module.java b/java/com/google/gerrit/server/restapi/change/Module.java
index ef0ba2d..dca64fd 100644
--- a/java/com/google/gerrit/server/restapi/change/Module.java
+++ b/java/com/google/gerrit/server/restapi/change/Module.java
@@ -72,6 +72,7 @@
     DynamicMap.mapOf(binder(), VOTE_KIND);
     DynamicMap.mapOf(binder(), CHANGE_MESSAGE_KIND);
 
+    postOnCollection(CHANGE_KIND).to(CreateChange.class);
     get(CHANGE_KIND).to(GetChange.class);
     post(CHANGE_KIND, "merge").to(CreateMergePatchSet.class);
     get(CHANGE_KIND, "detail").to(GetDetail.class);
@@ -112,9 +113,9 @@
     post(CHANGE_KIND, "ready").to(SetReadyForReview.class);
     put(CHANGE_KIND, "message").to(PutMessage.class);
 
-    post(CHANGE_KIND, "reviewers").to(PostReviewers.class);
     get(CHANGE_KIND, "suggest_reviewers").to(SuggestChangeReviewers.class);
     child(CHANGE_KIND, "reviewers").to(Reviewers.class);
+    postOnCollection(REVIEWER_KIND).to(PostReviewers.class);
     get(REVIEWER_KIND).to(GetReviewer.class);
     delete(REVIEWER_KIND).to(DeleteReviewer.class);
     post(REVIEWER_KIND, "delete").to(DeleteReviewer.class);
@@ -170,9 +171,12 @@
 
     child(CHANGE_KIND, "edit").to(ChangeEdits.class);
     create(CHANGE_EDIT_KIND).to(ChangeEdits.Create.class);
+    postOnCollection(CHANGE_EDIT_KIND).to(ChangeEdits.Post.class);
     delete(CHANGE_KIND, "edit").to(DeleteChangeEdit.class);
     child(CHANGE_KIND, "edit:publish").to(PublishChangeEdit.class);
+    postOnCollection(CHANGE_EDIT_PUBLISH_KIND).to(PublishChangeEdit.Publish.class);
     child(CHANGE_KIND, "edit:rebase").to(RebaseChangeEdit.class);
+    postOnCollection(CHANGE_EDIT_REBASE_KIND).to(RebaseChangeEdit.Rebase.class);
     put(CHANGE_KIND, "edit:message").to(ChangeEdits.EditMessage.class);
     get(CHANGE_KIND, "edit:message").to(ChangeEdits.GetMessage.class);
     put(CHANGE_EDIT_KIND, "/").to(ChangeEdits.Put.class);
diff --git a/java/com/google/gerrit/server/restapi/change/Move.java b/java/com/google/gerrit/server/restapi/change/Move.java
index 42531c5..3833050 100644
--- a/java/com/google/gerrit/server/restapi/change/Move.java
+++ b/java/com/google/gerrit/server/restapi/change/Move.java
@@ -44,7 +44,6 @@
 import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.ChangeMessagesUtil;
 import com.google.gerrit.server.ChangeUtil;
-import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.PatchSetUtil;
 import com.google.gerrit.server.change.ChangeJson;
@@ -88,7 +87,6 @@
   private final PatchSetUtil psUtil;
   private final ApprovalsUtil approvalsUtil;
   private final ProjectCache projectCache;
-  private final Provider<CurrentUser> userProvider;
 
   @Inject
   Move(
@@ -101,8 +99,7 @@
       RetryHelper retryHelper,
       PatchSetUtil psUtil,
       ApprovalsUtil approvalsUtil,
-      ProjectCache projectCache,
-      Provider<CurrentUser> userProvider) {
+      ProjectCache projectCache) {
     super(retryHelper);
     this.permissionBackend = permissionBackend;
     this.dbProvider = dbProvider;
@@ -113,7 +110,6 @@
     this.psUtil = psUtil;
     this.approvalsUtil = approvalsUtil;
     this.projectCache = projectCache;
-    this.userProvider = userProvider;
   }
 
   @Override
@@ -139,7 +135,7 @@
     }
 
     // Not allowed to move if the current patch set is locked.
-    psUtil.checkPatchSetNotLocked(rsrc.getNotes(), rsrc.getUser());
+    psUtil.checkPatchSetNotLocked(rsrc.getNotes());
 
     // Move requires abandoning this change, and creating a new change.
     try {
@@ -261,15 +257,9 @@
       List<PatchSetApproval> approvals = new ArrayList<>();
       for (PatchSetApproval psa :
           approvalsUtil.byPatchSet(
-              ctx.getDb(),
-              ctx.getNotes(),
-              userProvider.get(),
-              psId,
-              ctx.getRevWalk(),
-              ctx.getRepoView().getConfig())) {
+              ctx.getDb(), ctx.getNotes(), psId, ctx.getRevWalk(), ctx.getRepoView().getConfig())) {
         ProjectState projectState = projectCache.checkedGet(project);
-        LabelType type =
-            projectState.getLabelTypes(ctx.getNotes(), ctx.getUser()).byLabel(psa.getLabelId());
+        LabelType type = projectState.getLabelTypes(ctx.getNotes()).byLabel(psa.getLabelId());
         // Only keep veto votes, defined as votes where:
         // 1- the label function allows minimum values to block submission.
         // 2- the vote holds the minimum value.
@@ -314,7 +304,7 @@
     }
 
     try {
-      if (psUtil.isPatchSetLocked(rsrc.getNotes(), rsrc.getUser())) {
+      if (psUtil.isPatchSetLocked(rsrc.getNotes())) {
         return description;
       }
     } catch (OrmException | IOException e) {
diff --git a/java/com/google/gerrit/server/restapi/change/PostReview.java b/java/com/google/gerrit/server/restapi/change/PostReview.java
index e6f4f69..20f3d8a 100644
--- a/java/com/google/gerrit/server/restapi/change/PostReview.java
+++ b/java/com/google/gerrit/server/restapi/change/PostReview.java
@@ -64,6 +64,7 @@
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
 import com.google.gerrit.extensions.restapi.Url;
+import com.google.gerrit.mail.Address;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.ChangeMessage;
@@ -94,7 +95,6 @@
 import com.google.gerrit.server.change.WorkInProgressOp;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.extensions.events.CommentAdded;
-import com.google.gerrit.server.mail.Address;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.notedb.ChangeUpdate;
 import com.google.gerrit.server.notedb.NotesMigration;
@@ -174,6 +174,7 @@
   private final Config gerritConfig;
   private final WorkInProgressOp.Factory workInProgressOpFactory;
   private final ProjectCache projectCache;
+  private final PermissionBackend permissionBackend;
   private final boolean strictLabels;
 
   @Inject
@@ -196,7 +197,8 @@
       NotifyUtil notifyUtil,
       @GerritServerConfig Config gerritConfig,
       WorkInProgressOp.Factory workInProgressOpFactory,
-      ProjectCache projectCache) {
+      ProjectCache projectCache,
+      PermissionBackend permissionBackend) {
     super(retryHelper);
     this.db = db;
     this.changeResourceFactory = changeResourceFactory;
@@ -216,6 +218,7 @@
     this.gerritConfig = gerritConfig;
     this.workInProgressOpFactory = workInProgressOpFactory;
     this.projectCache = projectCache;
+    this.permissionBackend = permissionBackend;
     this.strictLabels = gerritConfig.getBoolean("change", "strictLabels", false);
   }
 
@@ -237,7 +240,7 @@
       throw new ResourceConflictException("cannot post review on edit");
     }
     ProjectState projectState = projectCache.checkedGet(revision.getProject());
-    LabelTypes labelTypes = projectState.getLabelTypes(revision.getNotes(), revision.getUser());
+    LabelTypes labelTypes = projectState.getLabelTypes(revision.getNotes());
     input.drafts = firstNonNull(input.drafts, DraftHandling.KEEP);
     if (input.onBehalfOf != null) {
       revision = onBehalfOf(revision, labelTypes, input);
@@ -482,7 +485,11 @@
 
     IdentifiedUser reviewer = accounts.parseOnBehalfOf(caller, in.onBehalfOf);
     try {
-      perm.user(reviewer).check(ChangePermission.READ);
+      permissionBackend
+          .user(reviewer)
+          .database(db)
+          .change(rev.getNotes())
+          .check(ChangePermission.READ);
     } catch (AuthException e) {
       throw new UnprocessableEntityException(
           String.format("on_behalf_of account %s cannot see change", reviewer.getAccountId()));
@@ -1148,7 +1155,7 @@
       List<PatchSetApproval> del = new ArrayList<>();
       List<PatchSetApproval> ups = new ArrayList<>();
       Map<String, PatchSetApproval> current = scanLabels(projectState, ctx, del);
-      LabelTypes labelTypes = projectState.getLabelTypes(ctx.getNotes(), ctx.getUser());
+      LabelTypes labelTypes = projectState.getLabelTypes(ctx.getNotes());
       Map<String, Short> allApprovals =
           getAllApprovals(labelTypes, approvalsByKey(current.values()), inLabels);
       Map<String, Short> previous =
@@ -1297,11 +1304,7 @@
           // If no existing label is being set to 0, hack in the caller
           // as a reviewer by picking the first server-wide LabelType.
           LabelId labelId =
-              projectState
-                  .getLabelTypes(ctx.getNotes(), ctx.getUser())
-                  .getLabelTypes()
-                  .get(0)
-                  .getLabelId();
+              projectState.getLabelTypes(ctx.getNotes()).getLabelTypes().get(0).getLabelId();
           PatchSetApproval c = ApprovalsUtil.newApproval(psId, user, labelId, 0, ctx.getWhen());
           c.setTag(in.tag);
           c.setGranted(ctx.getWhen());
@@ -1322,14 +1325,13 @@
     private Map<String, PatchSetApproval> scanLabels(
         ProjectState projectState, ChangeContext ctx, List<PatchSetApproval> del)
         throws OrmException, IOException {
-      LabelTypes labelTypes = projectState.getLabelTypes(ctx.getNotes(), ctx.getUser());
+      LabelTypes labelTypes = projectState.getLabelTypes(ctx.getNotes());
       Map<String, PatchSetApproval> current = new HashMap<>();
 
       for (PatchSetApproval a :
           approvalsUtil.byPatchSetUser(
               ctx.getDb(),
               ctx.getNotes(),
-              ctx.getUser(),
               psId,
               user.getAccountId(),
               ctx.getRevWalk(),
diff --git a/java/com/google/gerrit/server/restapi/change/PostReviewers.java b/java/com/google/gerrit/server/restapi/change/PostReviewers.java
index 65c7db7..0df86d1 100644
--- a/java/com/google/gerrit/server/restapi/change/PostReviewers.java
+++ b/java/com/google/gerrit/server/restapi/change/PostReviewers.java
@@ -37,9 +37,11 @@
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
+import com.google.gerrit.mail.Address;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.BooleanProjectConfig;
+import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -51,10 +53,10 @@
 import com.google.gerrit.server.change.ChangeMessages;
 import com.google.gerrit.server.change.ChangeResource;
 import com.google.gerrit.server.change.NotifyUtil;
+import com.google.gerrit.server.change.ReviewerResource;
 import com.google.gerrit.server.change.RevisionResource;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.group.SystemGroupBackend;
-import com.google.gerrit.server.mail.Address;
 import com.google.gerrit.server.mail.send.OutgoingEmailValidator;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.notedb.NotesMigration;
@@ -69,7 +71,7 @@
 import com.google.gerrit.server.restapi.group.GroupsCollection;
 import com.google.gerrit.server.update.BatchUpdate;
 import com.google.gerrit.server.update.RetryHelper;
-import com.google.gerrit.server.update.RetryingRestModifyView;
+import com.google.gerrit.server.update.RetryingRestCollectionView;
 import com.google.gerrit.server.update.UpdateException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -85,7 +87,8 @@
 
 @Singleton
 public class PostReviewers
-    extends RetryingRestModifyView<ChangeResource, AddReviewerInput, AddReviewerResult> {
+    extends RetryingRestCollectionView<
+        ChangeResource, ReviewerResource, AddReviewerInput, AddReviewerResult> {
 
   public static final int DEFAULT_MAX_REVIEWERS_WITHOUT_CHECK = 10;
   public static final int DEFAULT_MAX_REVIEWERS = 20;
@@ -248,9 +251,7 @@
       return null;
     }
 
-    PermissionBackend.ForRef perm =
-        permissionBackend.absentUser(reviewerUser.getAccountId()).ref(rsrc.getChange().getDest());
-    if (isValidReviewer(reviewerUser.getAccount(), perm)) {
+    if (isValidReviewer(rsrc.getChange().getDest(), reviewerUser.getAccount())) {
       return new Addition(
           reviewer,
           rsrc,
@@ -331,10 +332,8 @@
               ChangeMessages.get().groupManyMembersConfirmation, group.getName(), members.size()));
     }
 
-    PermissionBackend.ForRef perm =
-        permissionBackend.user(rsrc.getUser()).ref(rsrc.getChange().getDest());
     for (Account member : members) {
-      if (isValidReviewer(member, perm)) {
+      if (isValidReviewer(rsrc.getChange().getDest(), member)) {
         reviewers.add(member.getId());
       }
     }
@@ -350,14 +349,17 @@
       NotifyHandling notify,
       ListMultimap<RecipientType, Account.Id> accountsToNotify)
       throws PermissionBackendException {
-    if (!permissionBackend
-        .user(anonymousProvider.get())
-        .change(rsrc.getNotes())
-        .database(dbProvider)
-        .test(ChangePermission.READ)) {
+    try {
+      permissionBackend
+          .user(anonymousProvider.get())
+          .change(rsrc.getNotes())
+          .database(dbProvider)
+          .check(ChangePermission.READ);
+    } catch (AuthException e) {
       return fail(
           reviewer, MessageFormat.format(ChangeMessages.get().reviewerCantSeeChange, reviewer));
     }
+
     if (!migration.readChanges()) {
       // addByEmail depends on NoteDb.
       return fail(
@@ -371,7 +373,7 @@
         reviewer, rsrc, null, ImmutableList.of(adr), state, notify, accountsToNotify, true);
   }
 
-  private boolean isValidReviewer(Account member, PermissionBackend.ForRef perm)
+  private boolean isValidReviewer(Branch.NameKey branch, Account member)
       throws PermissionBackendException {
     if (!member.isActive()) {
       return false;
@@ -380,7 +382,11 @@
     // Does not account for draft status as a user might want to let a
     // reviewer see a draft.
     try {
-      perm.absentUser(member.getId()).check(RefPermission.READ);
+      permissionBackend
+          .absentUser(member.getId())
+          .database(dbProvider)
+          .ref(branch)
+          .check(RefPermission.READ);
       return true;
     } catch (AuthException e) {
       return false;
@@ -452,17 +458,13 @@
       }
 
       ChangeData cd = changeDataFactory.create(dbProvider.get(), notes);
-      PermissionBackend.ForChange perm =
-          permissionBackend.user(caller).database(dbProvider).change(cd);
-
       // Generate result details and fill AccountLoader. This occurs outside
       // the Op because the accounts are in a different table.
       PostReviewersOp.Result opResult = op.getResult();
       if (migration.readChanges() && state == CC) {
         result.ccs = Lists.newArrayListWithCapacity(opResult.addedCCs().size());
         for (Account.Id accountId : opResult.addedCCs()) {
-          result.ccs.add(
-              json.format(new ReviewerInfo(accountId.get()), perm.absentUser(accountId), cd));
+          result.ccs.add(json.format(new ReviewerInfo(accountId.get()), accountId, cd));
         }
         accountLoaderFactory.create(true).fill(result.ccs);
         for (Address a : reviewersByEmail) {
@@ -475,7 +477,7 @@
           result.reviewers.add(
               json.format(
                   new ReviewerInfo(psa.getAccountId().get()),
-                  perm.absentUser(psa.getAccountId()),
+                  psa.getAccountId(),
                   cd,
                   ImmutableList.of(psa)));
         }
diff --git a/java/com/google/gerrit/server/restapi/change/PostReviewersOp.java b/java/com/google/gerrit/server/restapi/change/PostReviewersOp.java
index 0502e91..08b66aa 100644
--- a/java/com/google/gerrit/server/restapi/change/PostReviewersOp.java
+++ b/java/com/google/gerrit/server/restapi/change/PostReviewersOp.java
@@ -30,6 +30,7 @@
 import com.google.gerrit.extensions.api.changes.RecipientType;
 import com.google.gerrit.extensions.client.ReviewerState;
 import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.mail.Address;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
@@ -42,7 +43,6 @@
 import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.change.ChangeResource;
 import com.google.gerrit.server.extensions.events.ReviewerAdded;
-import com.google.gerrit.server.mail.Address;
 import com.google.gerrit.server.mail.send.AddReviewerSender;
 import com.google.gerrit.server.notedb.NotesMigration;
 import com.google.gerrit.server.notedb.ReviewerStateInternal;
@@ -169,7 +169,7 @@
                 ctx.getUpdate(ctx.getChange().currentPatchSetId()),
                 projectCache
                     .checkedGet(rsrc.getProject())
-                    .getLabelTypes(rsrc.getChange().getDest(), ctx.getUser()),
+                    .getLabelTypes(rsrc.getChange().getDest()),
                 rsrc.getChange(),
                 reviewers);
         if (addedReviewers.isEmpty()) {
diff --git a/java/com/google/gerrit/server/restapi/change/PublishChangeEdit.java b/java/com/google/gerrit/server/restapi/change/PublishChangeEdit.java
index 36a0561..7f4844e 100644
--- a/java/com/google/gerrit/server/restapi/change/PublishChangeEdit.java
+++ b/java/com/google/gerrit/server/restapi/change/PublishChangeEdit.java
@@ -16,7 +16,6 @@
 
 import com.google.gerrit.extensions.api.changes.PublishChangeEditInput;
 import com.google.gerrit.extensions.registration.DynamicMap;
-import com.google.gerrit.extensions.restapi.AcceptsPost;
 import com.google.gerrit.extensions.restapi.ChildCollection;
 import com.google.gerrit.extensions.restapi.IdString;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -33,7 +32,7 @@
 import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.gerrit.server.update.BatchUpdate;
 import com.google.gerrit.server.update.RetryHelper;
-import com.google.gerrit.server.update.RetryingRestModifyView;
+import com.google.gerrit.server.update.RetryingRestCollectionView;
 import com.google.gerrit.server.update.UpdateException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -44,16 +43,13 @@
 
 @Singleton
 public class PublishChangeEdit
-    implements ChildCollection<ChangeResource, ChangeEditResource.Publish>,
-        AcceptsPost<ChangeResource> {
+    implements ChildCollection<ChangeResource, ChangeEditResource.Publish> {
 
   private final DynamicMap<RestView<ChangeEditResource.Publish>> views;
-  private final Publish publish;
 
   @Inject
-  PublishChangeEdit(DynamicMap<RestView<ChangeEditResource.Publish>> views, Publish publish) {
+  PublishChangeEdit(DynamicMap<RestView<ChangeEditResource.Publish>> views) {
     this.views = views;
-    this.publish = publish;
   }
 
   @Override
@@ -72,14 +68,10 @@
     throw new ResourceNotFoundException();
   }
 
-  @Override
-  public Publish post(ChangeResource parent) throws RestApiException {
-    return publish;
-  }
-
   @Singleton
   public static class Publish
-      extends RetryingRestModifyView<ChangeResource, PublishChangeEditInput, Response<?>> {
+      extends RetryingRestCollectionView<
+          ChangeResource, ChangeEditResource.Publish, PublishChangeEditInput, Response<?>> {
 
     private final ChangeEditUtil editUtil;
     private final NotifyUtil notifyUtil;
diff --git a/java/com/google/gerrit/server/restapi/change/PutAssignee.java b/java/com/google/gerrit/server/restapi/change/PutAssignee.java
index b7be2a8..21bd3ce 100644
--- a/java/com/google/gerrit/server/restapi/change/PutAssignee.java
+++ b/java/com/google/gerrit/server/restapi/change/PutAssignee.java
@@ -32,6 +32,7 @@
 import com.google.gerrit.server.change.ChangeResource;
 import com.google.gerrit.server.change.SetAssigneeOp;
 import com.google.gerrit.server.permissions.ChangePermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
 import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.restapi.account.AccountsCollection;
 import com.google.gerrit.server.restapi.change.PostReviewers.Addition;
@@ -55,6 +56,7 @@
   private final Provider<ReviewDb> db;
   private final PostReviewers postReviewers;
   private final AccountLoader.Factory accountLoaderFactory;
+  private final PermissionBackend permissionBackend;
 
   @Inject
   PutAssignee(
@@ -63,13 +65,15 @@
       RetryHelper retryHelper,
       Provider<ReviewDb> db,
       PostReviewers postReviewers,
-      AccountLoader.Factory accountLoaderFactory) {
+      AccountLoader.Factory accountLoaderFactory,
+      PermissionBackend permissionBackend) {
     super(retryHelper);
     this.accounts = accounts;
     this.assigneeFactory = assigneeFactory;
     this.db = db;
     this.postReviewers = postReviewers;
     this.accountLoaderFactory = accountLoaderFactory;
+    this.permissionBackend = permissionBackend;
   }
 
   @Override
@@ -89,9 +93,10 @@
       throw new UnprocessableEntityException(input.assignee + " is not active");
     }
     try {
-      rsrc.permissions()
-          .database(db)
+      permissionBackend
           .absentUser(assignee.getAccountId())
+          .database(db)
+          .change(rsrc.getNotes())
           .check(ChangePermission.READ);
     } catch (AuthException e) {
       throw new AuthException("read not permitted for " + input.assignee);
diff --git a/java/com/google/gerrit/server/restapi/change/PutDraftComment.java b/java/com/google/gerrit/server/restapi/change/PutDraftComment.java
index e6ede34..76ef106 100644
--- a/java/com/google/gerrit/server/restapi/change/PutDraftComment.java
+++ b/java/com/google/gerrit/server/restapi/change/PutDraftComment.java
@@ -34,6 +34,7 @@
 import com.google.gerrit.server.notedb.ChangeUpdate;
 import com.google.gerrit.server.patch.PatchListCache;
 import com.google.gerrit.server.patch.PatchListNotAvailableException;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.update.BatchUpdate;
 import com.google.gerrit.server.update.BatchUpdateOp;
 import com.google.gerrit.server.update.ChangeContext;
@@ -80,7 +81,7 @@
   @Override
   protected Response<CommentInfo> applyImpl(
       BatchUpdate.Factory updateFactory, DraftCommentResource rsrc, DraftInput in)
-      throws RestApiException, UpdateException, OrmException {
+      throws RestApiException, UpdateException, OrmException, PermissionBackendException {
     if (in == null || in.message == null || in.message.trim().isEmpty()) {
       return delete.applyImpl(updateFactory, rsrc, null);
     } else if (in.id != null && !rsrc.getId().equals(in.id)) {
diff --git a/java/com/google/gerrit/server/restapi/change/PutMessage.java b/java/com/google/gerrit/server/restapi/change/PutMessage.java
index eb46521..bee0ed7 100644
--- a/java/com/google/gerrit/server/restapi/change/PutMessage.java
+++ b/java/com/google/gerrit/server/restapi/change/PutMessage.java
@@ -184,7 +184,7 @@
     }
 
     // Not allowed to put message if the current patch set is locked.
-    psUtil.checkPatchSetNotLocked(changeNotes, userProvider.get());
+    psUtil.checkPatchSetNotLocked(changeNotes);
     try {
       permissionBackend
           .user(userProvider.get())
diff --git a/java/com/google/gerrit/server/restapi/change/QueryChanges.java b/java/com/google/gerrit/server/restapi/change/QueryChanges.java
index 1c9e420..b53c4e6 100644
--- a/java/com/google/gerrit/server/restapi/change/QueryChanges.java
+++ b/java/com/google/gerrit/server/restapi/change/QueryChanges.java
@@ -28,6 +28,7 @@
 import com.google.gerrit.index.query.QueryRequiresAuthException;
 import com.google.gerrit.index.query.QueryResult;
 import com.google.gerrit.server.change.ChangeJson;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.ChangeQueryBuilder;
 import com.google.gerrit.server.query.change.ChangeQueryProcessor;
@@ -104,7 +105,7 @@
 
   @Override
   public List<?> apply(TopLevelResource rsrc)
-      throws BadRequestException, AuthException, OrmException {
+      throws BadRequestException, AuthException, OrmException, PermissionBackendException {
     List<List<ChangeInfo>> out;
     try {
       out = query();
@@ -117,7 +118,8 @@
     return out.size() == 1 ? out.get(0) : out;
   }
 
-  private List<List<ChangeInfo>> query() throws OrmException, QueryParseException {
+  private List<List<ChangeInfo>> query()
+      throws OrmException, QueryParseException, PermissionBackendException {
     if (imp.isDisabled()) {
       throw new QueryParseException("query disabled");
     }
diff --git a/java/com/google/gerrit/server/restapi/change/Rebase.java b/java/com/google/gerrit/server/restapi/change/Rebase.java
index 99a755ae..0fffb58 100644
--- a/java/com/google/gerrit/server/restapi/change/Rebase.java
+++ b/java/com/google/gerrit/server/restapi/change/Rebase.java
@@ -107,7 +107,7 @@
       throws OrmException, UpdateException, RestApiException, IOException,
           PermissionBackendException {
     // Not allowed to rebase if the current patch set is locked.
-    patchSetUtil.checkPatchSetNotLocked(rsrc.getNotes(), rsrc.getUser());
+    patchSetUtil.checkPatchSetNotLocked(rsrc.getNotes());
 
     rsrc.permissions().database(dbProvider).check(ChangePermission.REBASE);
     projectCache.checkedGet(rsrc.getProject()).checkStatePermitsWrite();
@@ -228,7 +228,7 @@
     }
 
     try {
-      if (patchSetUtil.isPatchSetLocked(rsrc.getNotes(), rsrc.getUser())) {
+      if (patchSetUtil.isPatchSetLocked(rsrc.getNotes())) {
         return description;
       }
     } catch (OrmException | IOException e) {
diff --git a/java/com/google/gerrit/server/restapi/change/RebaseChangeEdit.java b/java/com/google/gerrit/server/restapi/change/RebaseChangeEdit.java
index 47af894..8868e0e 100644
--- a/java/com/google/gerrit/server/restapi/change/RebaseChangeEdit.java
+++ b/java/com/google/gerrit/server/restapi/change/RebaseChangeEdit.java
@@ -16,14 +16,12 @@
 
 import com.google.gerrit.extensions.common.Input;
 import com.google.gerrit.extensions.registration.DynamicMap;
-import com.google.gerrit.extensions.restapi.AcceptsPost;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.ChildCollection;
 import com.google.gerrit.extensions.restapi.IdString;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.Response;
-import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.extensions.restapi.RestView;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.change.ChangeEditResource;
@@ -34,7 +32,7 @@
 import com.google.gerrit.server.project.InvalidChangeOperationException;
 import com.google.gerrit.server.update.BatchUpdate;
 import com.google.gerrit.server.update.RetryHelper;
-import com.google.gerrit.server.update.RetryingRestModifyView;
+import com.google.gerrit.server.update.RetryingRestCollectionView;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
@@ -43,16 +41,13 @@
 
 @Singleton
 public class RebaseChangeEdit
-    implements ChildCollection<ChangeResource, ChangeEditResource.Rebase>,
-        AcceptsPost<ChangeResource> {
+    implements ChildCollection<ChangeResource, ChangeEditResource.Rebase> {
 
   private final DynamicMap<RestView<ChangeEditResource.Rebase>> views;
-  private final Rebase rebase;
 
   @Inject
-  RebaseChangeEdit(DynamicMap<RestView<ChangeEditResource.Rebase>> views, Rebase rebase) {
+  RebaseChangeEdit(DynamicMap<RestView<ChangeEditResource.Rebase>> views) {
     this.views = views;
-    this.rebase = rebase;
   }
 
   @Override
@@ -71,13 +66,10 @@
     throw new ResourceNotFoundException();
   }
 
-  @Override
-  public Rebase post(ChangeResource parent) throws RestApiException {
-    return rebase;
-  }
-
   @Singleton
-  public static class Rebase extends RetryingRestModifyView<ChangeResource, Input, Response<?>> {
+  public static class Rebase
+      extends RetryingRestCollectionView<
+          ChangeResource, ChangeEditResource.Rebase, Input, Response<?>> {
     private final GitRepositoryManager repositoryManager;
     private final ChangeEditModifier editModifier;
 
diff --git a/java/com/google/gerrit/server/restapi/change/Restore.java b/java/com/google/gerrit/server/restapi/change/Restore.java
index 5e4ede3..a29347f 100644
--- a/java/com/google/gerrit/server/restapi/change/Restore.java
+++ b/java/com/google/gerrit/server/restapi/change/Restore.java
@@ -91,7 +91,7 @@
       throws RestApiException, UpdateException, OrmException, PermissionBackendException,
           IOException {
     // Not allowed to restore if the current patch set is locked.
-    psUtil.checkPatchSetNotLocked(rsrc.getNotes(), rsrc.getUser());
+    psUtil.checkPatchSetNotLocked(rsrc.getNotes());
 
     rsrc.permissions().database(dbProvider).check(ChangePermission.RESTORE);
     projectCache.checkedGet(rsrc.getProject()).checkStatePermitsWrite();
@@ -183,7 +183,7 @@
     }
 
     try {
-      if (psUtil.isPatchSetLocked(rsrc.getNotes(), rsrc.getUser())) {
+      if (psUtil.isPatchSetLocked(rsrc.getNotes())) {
         return description;
       }
     } catch (OrmException | IOException e) {
diff --git a/java/com/google/gerrit/server/restapi/change/ReviewerJson.java b/java/com/google/gerrit/server/restapi/change/ReviewerJson.java
index cfd20c2..29c5649 100644
--- a/java/com/google/gerrit/server/restapi/change/ReviewerJson.java
+++ b/java/com/google/gerrit/server/restapi/change/ReviewerJson.java
@@ -22,6 +22,7 @@
 import com.google.gerrit.common.data.LabelTypes;
 import com.google.gerrit.common.data.SubmitRecord;
 import com.google.gerrit.extensions.api.changes.ReviewerInfo;
+import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
@@ -80,10 +81,7 @@
       ReviewerInfo info =
           format(
               new ReviewerInfo(rsrc.getReviewerUser().getAccountId().get()),
-              permissionBackend
-                  .absentUser(rsrc.getReviewerUser().getAccountId())
-                  .database(db)
-                  .change(cd),
+              rsrc.getReviewerUser().getAccountId(),
               cd);
       loader.put(info);
       infos.add(info);
@@ -97,22 +95,19 @@
     return format(ImmutableList.<ReviewerResource>of(rsrc));
   }
 
-  public ReviewerInfo format(ReviewerInfo out, PermissionBackend.ForChange perm, ChangeData cd)
+  public ReviewerInfo format(ReviewerInfo out, Account.Id reviewer, ChangeData cd)
       throws OrmException, PermissionBackendException {
     PatchSet.Id psId = cd.change().currentPatchSetId();
     return format(
         out,
-        perm,
+        reviewer,
         cd,
         approvalsUtil.byPatchSetUser(
-            db.get(), cd.notes(), perm.user(), psId, new Account.Id(out._accountId), null, null));
+            db.get(), cd.notes(), psId, new Account.Id(out._accountId), null, null));
   }
 
   public ReviewerInfo format(
-      ReviewerInfo out,
-      PermissionBackend.ForChange perm,
-      ChangeData cd,
-      Iterable<PatchSetApproval> approvals)
+      ReviewerInfo out, Account.Id reviewer, ChangeData cd, Iterable<PatchSetApproval> approvals)
       throws OrmException, PermissionBackendException {
     LabelTypes labelTypes = cd.getLabelTypes();
 
@@ -128,6 +123,9 @@
     // do not exist in the DB.
     PatchSet ps = cd.currentPatchSet();
     if (ps != null) {
+      PermissionBackend.ForChange perm =
+          permissionBackend.absentUser(reviewer).database(db).change(cd);
+
       for (SubmitRecord rec : submitRuleEvaluator.evaluate(cd)) {
         if (rec.labels == null) {
           continue;
@@ -135,10 +133,15 @@
         for (SubmitRecord.Label label : rec.labels) {
           String name = label.label;
           LabelType type = labelTypes.byLabel(name);
-          if (!out.approvals.containsKey(name)
-              && type != null
-              && perm.test(new LabelPermission(type))) {
+          if (out.approvals.containsKey(name) || type == null) {
+            continue;
+          }
+
+          try {
+            perm.check(new LabelPermission(type));
             out.approvals.put(name, formatValue((short) 0));
+          } catch (AuthException e) {
+            // Do nothing.
           }
         }
       }
diff --git a/java/com/google/gerrit/server/restapi/change/Reviewers.java b/java/com/google/gerrit/server/restapi/change/Reviewers.java
index a4cfbd2..f0aef13 100644
--- a/java/com/google/gerrit/server/restapi/change/Reviewers.java
+++ b/java/com/google/gerrit/server/restapi/change/Reviewers.java
@@ -21,12 +21,12 @@
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.RestView;
 import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.gerrit.mail.Address;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.change.ChangeResource;
 import com.google.gerrit.server.change.ReviewerResource;
-import com.google.gerrit.server.mail.Address;
 import com.google.gerrit.server.restapi.account.AccountsCollection;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/server/restapi/change/ReviewersUtil.java b/java/com/google/gerrit/server/restapi/change/ReviewersUtil.java
index 779ee5a..57ff0a3 100644
--- a/java/com/google/gerrit/server/restapi/change/ReviewersUtil.java
+++ b/java/com/google/gerrit/server/restapi/change/ReviewersUtil.java
@@ -22,6 +22,7 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.data.GroupReference;
@@ -48,8 +49,6 @@
 import com.google.gerrit.server.index.account.AccountField;
 import com.google.gerrit.server.index.account.AccountIndexCollection;
 import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gerrit.server.permissions.GlobalPermission;
-import com.google.gerrit.server.permissions.PermissionBackend;
 import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.gerrit.server.project.ProjectState;
@@ -129,7 +128,6 @@
   private final IndexConfig indexConfig;
   private final AccountControl.Factory accountControlFactory;
   private final Provider<CurrentUser> self;
-  private final PermissionBackend permissionBackend;
 
   @Inject
   ReviewersUtil(
@@ -142,8 +140,7 @@
       AccountIndexCollection accountIndexes,
       IndexConfig indexConfig,
       AccountControl.Factory accountControlFactory,
-      Provider<CurrentUser> self,
-      PermissionBackend permissionBackend) {
+      Provider<CurrentUser> self) {
     this.accountLoaderFactory = accountLoaderFactory;
     this.accountQueryBuilder = accountQueryBuilder;
     this.groupBackend = groupBackend;
@@ -154,7 +151,6 @@
     this.indexConfig = indexConfig;
     this.accountControlFactory = accountControlFactory;
     this.self = self;
-    this.permissionBackend = permissionBackend;
   }
 
   public interface VisibilityControl {
@@ -301,10 +297,7 @@
   private List<SuggestedReviewerInfo> loadAccounts(List<Account.Id> accountIds)
       throws PermissionBackendException {
     Set<FillOptions> fillOptions =
-        permissionBackend.currentUser().test(GlobalPermission.MODIFY_ACCOUNT)
-            ? EnumSet.of(FillOptions.SECONDARY_EMAILS)
-            : EnumSet.noneOf(FillOptions.class);
-    fillOptions.addAll(AccountLoader.DETAILED_OPTIONS);
+        Sets.union(AccountLoader.DETAILED_OPTIONS, EnumSet.of(FillOptions.SECONDARY_EMAILS));
     AccountLoader accountLoader = accountLoaderFactory.create(fillOptions);
 
     try (Timer0.Context ctx = metrics.loadAccountsLatency.start()) {
diff --git a/java/com/google/gerrit/server/restapi/change/RevisionReviewers.java b/java/com/google/gerrit/server/restapi/change/RevisionReviewers.java
index 7cf30e2..b9b7a4f 100644
--- a/java/com/google/gerrit/server/restapi/change/RevisionReviewers.java
+++ b/java/com/google/gerrit/server/restapi/change/RevisionReviewers.java
@@ -22,12 +22,12 @@
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.RestView;
 import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.gerrit.mail.Address;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.change.ReviewerResource;
 import com.google.gerrit.server.change.RevisionResource;
-import com.google.gerrit.server.mail.Address;
 import com.google.gerrit.server.restapi.account.AccountsCollection;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/server/restapi/change/SetReadyForReview.java b/java/com/google/gerrit/server/restapi/change/SetReadyForReview.java
index 8fe5612..faf946f 100644
--- a/java/com/google/gerrit/server/restapi/change/SetReadyForReview.java
+++ b/java/com/google/gerrit/server/restapi/change/SetReadyForReview.java
@@ -65,16 +65,28 @@
   protected Response<?> applyImpl(
       BatchUpdate.Factory updateFactory, ChangeResource rsrc, Input input)
       throws RestApiException, UpdateException, PermissionBackendException {
-    Change change = rsrc.getChange();
-    if (!rsrc.isUserOwner()
-        && !permissionBackend.currentUser().test(GlobalPermission.ADMINISTRATE_SERVER)
-        && !permissionBackend
-            .currentUser()
-            .project(rsrc.getProject())
-            .test(ProjectPermission.WRITE_CONFIG)) {
-      throw new AuthException("not allowed to set ready for review");
+    if (!rsrc.isUserOwner()) {
+      boolean hasAdministrateServerPermission = false;
+      try {
+        permissionBackend.currentUser().check(GlobalPermission.ADMINISTRATE_SERVER);
+        hasAdministrateServerPermission = true;
+      } catch (AuthException e) {
+        // Skip.
+      }
+
+      if (!hasAdministrateServerPermission) {
+        try {
+          permissionBackend
+              .currentUser()
+              .project(rsrc.getProject())
+              .check(ProjectPermission.WRITE_CONFIG);
+        } catch (AuthException exp) {
+          throw new AuthException("not allowed to set ready for review");
+        }
+      }
     }
 
+    Change change = rsrc.getChange();
     if (change.getStatus() != Status.NEW) {
       throw new ResourceConflictException("change is " + ChangeUtil.status(change));
     }
diff --git a/java/com/google/gerrit/server/restapi/change/SetWorkInProgress.java b/java/com/google/gerrit/server/restapi/change/SetWorkInProgress.java
index 9524903..1f9d81f 100644
--- a/java/com/google/gerrit/server/restapi/change/SetWorkInProgress.java
+++ b/java/com/google/gerrit/server/restapi/change/SetWorkInProgress.java
@@ -65,17 +65,28 @@
   protected Response<?> applyImpl(
       BatchUpdate.Factory updateFactory, ChangeResource rsrc, Input input)
       throws RestApiException, UpdateException, PermissionBackendException {
-    Change change = rsrc.getChange();
+    if (!rsrc.isUserOwner()) {
+      boolean hasAdministrateServerPermission = false;
+      try {
+        permissionBackend.currentUser().check(GlobalPermission.ADMINISTRATE_SERVER);
+        hasAdministrateServerPermission = true;
+      } catch (AuthException e) {
+        // Skip.
+      }
 
-    if (!rsrc.isUserOwner()
-        && !permissionBackend.currentUser().test(GlobalPermission.ADMINISTRATE_SERVER)
-        && !permissionBackend
-            .currentUser()
-            .project(rsrc.getProject())
-            .test(ProjectPermission.WRITE_CONFIG)) {
-      throw new AuthException("not allowed to set work in progress");
+      if (!hasAdministrateServerPermission) {
+        try {
+          permissionBackend
+              .currentUser()
+              .project(rsrc.getProject())
+              .check(ProjectPermission.WRITE_CONFIG);
+        } catch (AuthException exp) {
+          throw new AuthException("not allowed to set work in progress");
+        }
+      }
     }
 
+    Change change = rsrc.getChange();
     if (change.getStatus() != Status.NEW) {
       throw new ResourceConflictException("change is " + ChangeUtil.status(change));
     }
diff --git a/java/com/google/gerrit/server/restapi/change/Submit.java b/java/com/google/gerrit/server/restapi/change/Submit.java
index a161767..f9a5087 100644
--- a/java/com/google/gerrit/server/restapi/change/Submit.java
+++ b/java/com/google/gerrit/server/restapi/change/Submit.java
@@ -468,7 +468,11 @@
     CurrentUser caller = rsrc.getUser();
     IdentifiedUser submitter = accounts.parseOnBehalfOf(caller, in.onBehalfOf);
     try {
-      perm.user(submitter).check(ChangePermission.READ);
+      permissionBackend
+          .user(submitter)
+          .database(dbProvider)
+          .change(rsrc.getNotes())
+          .check(ChangePermission.READ);
     } catch (AuthException e) {
       throw new UnprocessableEntityException(
           String.format("on_behalf_of account %s cannot see change", submitter.getAccountId()));
diff --git a/java/com/google/gerrit/server/restapi/change/SuggestChangeReviewers.java b/java/com/google/gerrit/server/restapi/change/SuggestChangeReviewers.java
index bc3dfa7..66534b0 100644
--- a/java/com/google/gerrit/server/restapi/change/SuggestChangeReviewers.java
+++ b/java/com/google/gerrit/server/restapi/change/SuggestChangeReviewers.java
@@ -85,14 +85,18 @@
   }
 
   private VisibilityControl getVisibility(ChangeResource rsrc) {
-    // Use the destination reference, not the change, as private changes deny anyone who is not
-    // already a reviewer.
-    PermissionBackend.ForRef perm = permissionBackend.currentUser().ref(rsrc.getChange().getDest());
+
     return new VisibilityControl() {
       @Override
       public boolean isVisibleTo(Account.Id account) throws OrmException {
+        // Use the destination reference, not the change, as private changes deny anyone who is not
+        // already a reviewer.
         IdentifiedUser who = identifiedUserFactory.create(account);
-        return perm.user(who).testOrFalse(RefPermission.READ);
+        return permissionBackend
+            .user(who)
+            .database(dbProvider)
+            .ref(rsrc.getChange().getDest())
+            .testOrFalse(RefPermission.READ);
       }
     };
   }
diff --git a/java/com/google/gerrit/server/restapi/change/TestSubmitRule.java b/java/com/google/gerrit/server/restapi/change/TestSubmitRule.java
index 2a18612..cdd7426 100644
--- a/java/com/google/gerrit/server/restapi/change/TestSubmitRule.java
+++ b/java/com/google/gerrit/server/restapi/change/TestSubmitRule.java
@@ -25,6 +25,7 @@
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.account.AccountLoader;
 import com.google.gerrit.server.change.RevisionResource;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.project.SubmitRuleEvaluator;
 import com.google.gerrit.server.project.SubmitRuleOptions;
 import com.google.gerrit.server.query.change.ChangeData;
@@ -63,7 +64,7 @@
 
   @Override
   public List<Record> apply(RevisionResource rsrc, TestSubmitRuleInput input)
-      throws AuthException, OrmException {
+      throws AuthException, OrmException, PermissionBackendException {
     if (input == null) {
       input = new TestSubmitRuleInput();
     }
diff --git a/java/com/google/gerrit/server/restapi/change/Votes.java b/java/com/google/gerrit/server/restapi/change/Votes.java
index b931c7e..3b2548c 100644
--- a/java/com/google/gerrit/server/restapi/change/Votes.java
+++ b/java/com/google/gerrit/server/restapi/change/Votes.java
@@ -87,7 +87,6 @@
           approvalsUtil.byPatchSetUser(
               db.get(),
               rsrc.getChangeResource().getNotes(),
-              rsrc.getChangeResource().getUser(),
               rsrc.getChange().currentPatchSetId(),
               rsrc.getReviewerUser().getAccountId(),
               null,
diff --git a/java/com/google/gerrit/server/restapi/config/AgreementJson.java b/java/com/google/gerrit/server/restapi/config/AgreementJson.java
index 548bc03..02e5f68 100644
--- a/java/com/google/gerrit/server/restapi/config/AgreementJson.java
+++ b/java/com/google/gerrit/server/restapi/config/AgreementJson.java
@@ -23,6 +23,7 @@
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.GroupControl;
 import com.google.gerrit.server.group.GroupResource;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.restapi.group.GroupJson;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -48,7 +49,7 @@
     this.groupJson = groupJson;
   }
 
-  public AgreementInfo format(ContributorAgreement ca) {
+  public AgreementInfo format(ContributorAgreement ca) throws PermissionBackendException {
     AgreementInfo info = new AgreementInfo();
     info.name = ca.getName();
     info.description = ca.getDescription();
diff --git a/java/com/google/gerrit/server/restapi/config/CachesCollection.java b/java/com/google/gerrit/server/restapi/config/CachesCollection.java
index e3d9e3c..152fef9 100644
--- a/java/com/google/gerrit/server/restapi/config/CachesCollection.java
+++ b/java/com/google/gerrit/server/restapi/config/CachesCollection.java
@@ -20,12 +20,10 @@
 import com.google.common.cache.Cache;
 import com.google.gerrit.extensions.annotations.RequiresAnyCapability;
 import com.google.gerrit.extensions.registration.DynamicMap;
-import com.google.gerrit.extensions.restapi.AcceptsPost;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.ChildCollection;
 import com.google.gerrit.extensions.restapi.IdString;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
-import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.extensions.restapi.RestView;
 import com.google.gerrit.server.config.CacheResource;
 import com.google.gerrit.server.config.ConfigResource;
@@ -38,27 +36,23 @@
 
 @RequiresAnyCapability({VIEW_CACHES, MAINTAIN_SERVER})
 @Singleton
-public class CachesCollection
-    implements ChildCollection<ConfigResource, CacheResource>, AcceptsPost<ConfigResource> {
+public class CachesCollection implements ChildCollection<ConfigResource, CacheResource> {
 
   private final DynamicMap<RestView<CacheResource>> views;
   private final Provider<ListCaches> list;
   private final PermissionBackend permissionBackend;
   private final DynamicMap<Cache<?, ?>> cacheMap;
-  private final PostCaches postCaches;
 
   @Inject
   CachesCollection(
       DynamicMap<RestView<CacheResource>> views,
       Provider<ListCaches> list,
       PermissionBackend permissionBackend,
-      DynamicMap<Cache<?, ?>> cacheMap,
-      PostCaches postCaches) {
+      DynamicMap<Cache<?, ?>> cacheMap) {
     this.views = views;
     this.list = list;
     this.permissionBackend = permissionBackend;
     this.cacheMap = cacheMap;
-    this.postCaches = postCaches;
   }
 
   @Override
@@ -90,9 +84,4 @@
   public DynamicMap<RestView<CacheResource>> views() {
     return views;
   }
-
-  @Override
-  public PostCaches post(ConfigResource parent) throws RestApiException {
-    return postCaches;
-  }
 }
diff --git a/java/com/google/gerrit/server/restapi/config/GetServerInfo.java b/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
index 70db0f9..b7931bd 100644
--- a/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
+++ b/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
@@ -59,6 +59,7 @@
 import com.google.gerrit.server.index.change.ChangeField;
 import com.google.gerrit.server.index.change.ChangeIndexCollection;
 import com.google.gerrit.server.notedb.NotesMigration;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.restapi.change.AllowedFormats;
 import com.google.gerrit.server.submit.MergeSuperSet;
@@ -148,7 +149,8 @@
   }
 
   @Override
-  public ServerInfo apply(ConfigResource rsrc) throws MalformedURLException {
+  public ServerInfo apply(ConfigResource rsrc)
+      throws MalformedURLException, PermissionBackendException {
     ServerInfo info = new ServerInfo();
     info.accounts = getAccountsInfo(accountVisibilityProvider);
     info.auth = getAuthInfo(authConfig, realm);
@@ -178,7 +180,7 @@
     return info;
   }
 
-  private AuthInfo getAuthInfo(AuthConfig cfg, Realm realm) {
+  private AuthInfo getAuthInfo(AuthConfig cfg, Realm realm) throws PermissionBackendException {
     AuthInfo info = new AuthInfo();
     info.authType = cfg.getAuthType();
     info.useContributorAgreements = toBoolean(cfg.isUseContributorAgreements());
diff --git a/java/com/google/gerrit/server/restapi/config/PostCaches.java b/java/com/google/gerrit/server/restapi/config/PostCaches.java
index f21672c..e20e477 100644
--- a/java/com/google/gerrit/server/restapi/config/PostCaches.java
+++ b/java/com/google/gerrit/server/restapi/config/PostCaches.java
@@ -23,7 +23,7 @@
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.Response;
-import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.restapi.RestCollectionView;
 import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
 import com.google.gerrit.server.config.CacheResource;
 import com.google.gerrit.server.config.ConfigResource;
@@ -36,7 +36,7 @@
 
 @RequiresAnyCapability({FLUSH_CACHES, MAINTAIN_SERVER})
 @Singleton
-public class PostCaches implements RestModifyView<ConfigResource, Input> {
+public class PostCaches implements RestCollectionView<ConfigResource, CacheResource, Input> {
   public static class Input {
     public Operation operation;
     public List<String> caches;
diff --git a/java/com/google/gerrit/server/restapi/config/RestCacheAdminModule.java b/java/com/google/gerrit/server/restapi/config/RestCacheAdminModule.java
index 7283033..c929bc6 100644
--- a/java/com/google/gerrit/server/restapi/config/RestCacheAdminModule.java
+++ b/java/com/google/gerrit/server/restapi/config/RestCacheAdminModule.java
@@ -26,6 +26,7 @@
   protected void configure() {
     DynamicMap.mapOf(binder(), CACHE_KIND);
     child(CONFIG_KIND, "caches").to(CachesCollection.class);
+    postOnCollection(CACHE_KIND).to(PostCaches.class);
     get(CACHE_KIND).to(GetCache.class);
     post(CACHE_KIND, "flush").to(FlushCache.class);
     get(CONFIG_KIND, "summary").to(GetSummary.class);
diff --git a/java/com/google/gerrit/server/restapi/group/AddMembers.java b/java/com/google/gerrit/server/restapi/group/AddMembers.java
index 3e2d1e7..733b6bd 100644
--- a/java/com/google/gerrit/server/restapi/group/AddMembers.java
+++ b/java/com/google/gerrit/server/restapi/group/AddMembers.java
@@ -46,6 +46,7 @@
 import com.google.gerrit.server.group.MemberResource;
 import com.google.gerrit.server.group.db.GroupsUpdate;
 import com.google.gerrit.server.group.db.InternalGroupUpdate;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.restapi.account.AccountsCollection;
 import com.google.gerrit.server.restapi.group.AddMembers.Input;
 import com.google.gwtorm.server.OrmException;
@@ -116,7 +117,8 @@
   @Override
   public List<AccountInfo> apply(GroupResource resource, Input input)
       throws AuthException, NotInternalGroupException, UnprocessableEntityException, OrmException,
-          IOException, ConfigInvalidException, ResourceNotFoundException {
+          IOException, ConfigInvalidException, ResourceNotFoundException,
+          PermissionBackendException {
     GroupDescription.Internal internalGroup =
         resource.asInternalGroup().orElseThrow(NotInternalGroupException::new);
     input = Input.init(input);
@@ -203,7 +205,8 @@
     }
   }
 
-  private List<AccountInfo> toAccountInfoList(Set<Account.Id> accountIds) {
+  private List<AccountInfo> toAccountInfoList(Set<Account.Id> accountIds)
+      throws PermissionBackendException {
     List<AccountInfo> result = new ArrayList<>();
     AccountLoader loader = infoFactory.create(true);
     for (Account.Id accId : accountIds) {
@@ -224,7 +227,7 @@
     @Override
     public AccountInfo apply(GroupResource resource, IdString id, Input input)
         throws AuthException, MethodNotAllowedException, ResourceNotFoundException, OrmException,
-            IOException, ConfigInvalidException {
+            IOException, ConfigInvalidException, PermissionBackendException {
       AddMembers.Input in = new AddMembers.Input();
       in._oneMember = id.get();
       try {
@@ -249,7 +252,8 @@
     }
 
     @Override
-    public AccountInfo apply(MemberResource resource, Input input) throws OrmException {
+    public AccountInfo apply(MemberResource resource, Input input)
+        throws OrmException, PermissionBackendException {
       // Do nothing, the user is already a member.
       return get.apply(resource);
     }
diff --git a/java/com/google/gerrit/server/restapi/group/AddSubgroups.java b/java/com/google/gerrit/server/restapi/group/AddSubgroups.java
index 21b6981..1474e19 100644
--- a/java/com/google/gerrit/server/restapi/group/AddSubgroups.java
+++ b/java/com/google/gerrit/server/restapi/group/AddSubgroups.java
@@ -36,6 +36,7 @@
 import com.google.gerrit.server.group.SubgroupResource;
 import com.google.gerrit.server.group.db.GroupsUpdate;
 import com.google.gerrit.server.group.db.InternalGroupUpdate;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.restapi.group.AddSubgroups.Input;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -92,7 +93,8 @@
   @Override
   public List<GroupInfo> apply(GroupResource resource, Input input)
       throws NotInternalGroupException, AuthException, UnprocessableEntityException, OrmException,
-          ResourceNotFoundException, IOException, ConfigInvalidException {
+          ResourceNotFoundException, IOException, ConfigInvalidException,
+          PermissionBackendException {
     GroupDescription.Internal group =
         resource.asInternalGroup().orElseThrow(NotInternalGroupException::new);
     input = Input.init(input);
@@ -141,7 +143,7 @@
     @Override
     public GroupInfo apply(GroupResource resource, IdString id, Input input)
         throws AuthException, MethodNotAllowedException, ResourceNotFoundException, OrmException,
-            IOException, ConfigInvalidException {
+            IOException, ConfigInvalidException, PermissionBackendException {
       AddSubgroups.Input in = new AddSubgroups.Input();
       in.groups = ImmutableList.of(id.get());
       try {
@@ -166,7 +168,8 @@
     }
 
     @Override
-    public GroupInfo apply(SubgroupResource resource, Input input) throws OrmException {
+    public GroupInfo apply(SubgroupResource resource, Input input)
+        throws OrmException, PermissionBackendException {
       // Do nothing, the group is already included.
       return get.get().apply(resource);
     }
diff --git a/java/com/google/gerrit/server/restapi/group/CreateGroup.java b/java/com/google/gerrit/server/restapi/group/CreateGroup.java
index 6ecb5aa..1014a99 100644
--- a/java/com/google/gerrit/server/restapi/group/CreateGroup.java
+++ b/java/com/google/gerrit/server/restapi/group/CreateGroup.java
@@ -50,6 +50,7 @@
 import com.google.gerrit.server.group.db.GroupsUpdate;
 import com.google.gerrit.server.group.db.InternalGroupCreation;
 import com.google.gerrit.server.group.db.InternalGroupUpdate;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.validators.GroupCreationValidationListener;
 import com.google.gerrit.server.validators.ValidationException;
 import com.google.gwtorm.server.OrmDuplicateKeyException;
@@ -121,7 +122,7 @@
   public GroupInfo apply(TopLevelResource resource, IdString id, GroupInput input)
       throws AuthException, BadRequestException, UnprocessableEntityException,
           ResourceConflictException, OrmException, IOException, ConfigInvalidException,
-          ResourceNotFoundException {
+          ResourceNotFoundException, PermissionBackendException {
     String name = id.get();
     if (input == null) {
       input = new GroupInput();
diff --git a/java/com/google/gerrit/server/restapi/group/GetAuditLog.java b/java/com/google/gerrit/server/restapi/group/GetAuditLog.java
index eb66a37..7af4284 100644
--- a/java/com/google/gerrit/server/restapi/group/GetAuditLog.java
+++ b/java/com/google/gerrit/server/restapi/group/GetAuditLog.java
@@ -35,6 +35,7 @@
 import com.google.gerrit.server.group.InternalGroup;
 import com.google.gerrit.server.group.InternalGroupDescription;
 import com.google.gerrit.server.group.db.Groups;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
@@ -77,7 +78,7 @@
   @Override
   public List<? extends GroupAuditEventInfo> apply(GroupResource rsrc)
       throws AuthException, NotInternalGroupException, OrmException, IOException,
-          ConfigInvalidException {
+          ConfigInvalidException, PermissionBackendException {
     GroupDescription.Internal group =
         rsrc.asInternalGroup().orElseThrow(NotInternalGroupException::new);
     if (!rsrc.getControl().isOwner()) {
diff --git a/java/com/google/gerrit/server/restapi/group/GetDetail.java b/java/com/google/gerrit/server/restapi/group/GetDetail.java
index e7b240e..75d1e34 100644
--- a/java/com/google/gerrit/server/restapi/group/GetDetail.java
+++ b/java/com/google/gerrit/server/restapi/group/GetDetail.java
@@ -18,6 +18,7 @@
 import com.google.gerrit.extensions.common.GroupInfo;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.server.group.GroupResource;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
@@ -32,7 +33,7 @@
   }
 
   @Override
-  public GroupInfo apply(GroupResource rsrc) throws OrmException {
+  public GroupInfo apply(GroupResource rsrc) throws OrmException, PermissionBackendException {
     return json.format(rsrc);
   }
 }
diff --git a/java/com/google/gerrit/server/restapi/group/GetGroup.java b/java/com/google/gerrit/server/restapi/group/GetGroup.java
index 81057fd..c6cddb6 100644
--- a/java/com/google/gerrit/server/restapi/group/GetGroup.java
+++ b/java/com/google/gerrit/server/restapi/group/GetGroup.java
@@ -17,6 +17,7 @@
 import com.google.gerrit.extensions.common.GroupInfo;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.server.group.GroupResource;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
@@ -31,7 +32,7 @@
   }
 
   @Override
-  public GroupInfo apply(GroupResource resource) throws OrmException {
+  public GroupInfo apply(GroupResource resource) throws OrmException, PermissionBackendException {
     return json.format(resource.getGroup());
   }
 }
diff --git a/java/com/google/gerrit/server/restapi/group/GetMember.java b/java/com/google/gerrit/server/restapi/group/GetMember.java
index db33785..95063de 100644
--- a/java/com/google/gerrit/server/restapi/group/GetMember.java
+++ b/java/com/google/gerrit/server/restapi/group/GetMember.java
@@ -18,6 +18,7 @@
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.server.account.AccountLoader;
 import com.google.gerrit.server.group.MemberResource;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
@@ -32,7 +33,7 @@
   }
 
   @Override
-  public AccountInfo apply(MemberResource rsrc) throws OrmException {
+  public AccountInfo apply(MemberResource rsrc) throws OrmException, PermissionBackendException {
     AccountLoader loader = infoFactory.create(true);
     AccountInfo info = loader.get(rsrc.getMember().getAccountId());
     loader.fill();
diff --git a/java/com/google/gerrit/server/restapi/group/GetOwner.java b/java/com/google/gerrit/server/restapi/group/GetOwner.java
index be19a24..0906ce6 100644
--- a/java/com/google/gerrit/server/restapi/group/GetOwner.java
+++ b/java/com/google/gerrit/server/restapi/group/GetOwner.java
@@ -21,6 +21,7 @@
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.server.account.GroupControl;
 import com.google.gerrit.server.group.GroupResource;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
@@ -39,7 +40,8 @@
 
   @Override
   public GroupInfo apply(GroupResource resource)
-      throws NotInternalGroupException, ResourceNotFoundException, OrmException {
+      throws NotInternalGroupException, ResourceNotFoundException, OrmException,
+          PermissionBackendException {
     GroupDescription.Internal group =
         resource.asInternalGroup().orElseThrow(NotInternalGroupException::new);
     try {
diff --git a/java/com/google/gerrit/server/restapi/group/GetSubgroup.java b/java/com/google/gerrit/server/restapi/group/GetSubgroup.java
index 98e6ce5..16e2739 100644
--- a/java/com/google/gerrit/server/restapi/group/GetSubgroup.java
+++ b/java/com/google/gerrit/server/restapi/group/GetSubgroup.java
@@ -17,6 +17,7 @@
 import com.google.gerrit.extensions.common.GroupInfo;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.server.group.SubgroupResource;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
@@ -31,7 +32,7 @@
   }
 
   @Override
-  public GroupInfo apply(SubgroupResource rsrc) throws OrmException {
+  public GroupInfo apply(SubgroupResource rsrc) throws OrmException, PermissionBackendException {
     return json.format(rsrc.getMemberDescription());
   }
 }
diff --git a/java/com/google/gerrit/server/restapi/group/GroupJson.java b/java/com/google/gerrit/server/restapi/group/GroupJson.java
index 3c7799b..a51fad2 100644
--- a/java/com/google/gerrit/server/restapi/group/GroupJson.java
+++ b/java/com/google/gerrit/server/restapi/group/GroupJson.java
@@ -28,6 +28,7 @@
 import com.google.gerrit.server.account.GroupBackend;
 import com.google.gerrit.server.account.GroupControl;
 import com.google.gerrit.server.group.GroupResource;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -75,17 +76,18 @@
     return this;
   }
 
-  public GroupInfo format(GroupResource rsrc) throws OrmException {
+  public GroupInfo format(GroupResource rsrc) throws OrmException, PermissionBackendException {
     return createGroupInfo(rsrc.getGroup(), rsrc::getControl);
   }
 
-  public GroupInfo format(GroupDescription.Basic group) throws OrmException {
+  public GroupInfo format(GroupDescription.Basic group)
+      throws OrmException, PermissionBackendException {
     return createGroupInfo(group, Suppliers.memoize(() -> groupControlFactory.controlFor(group)));
   }
 
   private GroupInfo createGroupInfo(
       GroupDescription.Basic group, Supplier<GroupControl> groupControlSupplier)
-      throws OrmException {
+      throws OrmException, PermissionBackendException {
     GroupInfo info = createBasicGroupInfo(group);
 
     if (group instanceof GroupDescription.Internal) {
@@ -108,7 +110,7 @@
       GroupInfo info,
       GroupDescription.Internal internalGroup,
       Supplier<GroupControl> groupControlSupplier)
-      throws OrmException {
+      throws OrmException, PermissionBackendException {
     info.description = Strings.emptyToNull(internalGroup.getDescription());
     info.groupId = internalGroup.getId().get();
 
diff --git a/java/com/google/gerrit/server/restapi/group/ListGroups.java b/java/com/google/gerrit/server/restapi/group/ListGroups.java
index c7f1d5e..bae5eff 100644
--- a/java/com/google/gerrit/server/restapi/group/ListGroups.java
+++ b/java/com/google/gerrit/server/restapi/group/ListGroups.java
@@ -41,6 +41,7 @@
 import com.google.gerrit.server.account.GroupControl;
 import com.google.gerrit.server.group.InternalGroupDescription;
 import com.google.gerrit.server.group.db.Groups;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.project.ProjectState;
 import com.google.gerrit.server.restapi.account.GetGroups;
 import com.google.gwtorm.server.OrmException;
@@ -246,7 +247,8 @@
 
   @Override
   public SortedMap<String, GroupInfo> apply(TopLevelResource resource)
-      throws OrmException, RestApiException, IOException, ConfigInvalidException {
+      throws OrmException, RestApiException, IOException, ConfigInvalidException,
+          PermissionBackendException {
     SortedMap<String, GroupInfo> output = new TreeMap<>();
     for (GroupInfo info : get()) {
       output.put(MoreObjects.firstNonNull(info.name, "Group " + Url.decode(info.id)), info);
@@ -256,7 +258,8 @@
   }
 
   public List<GroupInfo> get()
-      throws OrmException, RestApiException, IOException, ConfigInvalidException {
+      throws OrmException, RestApiException, IOException, ConfigInvalidException,
+          PermissionBackendException {
     if (!Strings.isNullOrEmpty(suggest)) {
       return suggestGroups();
     }
@@ -280,7 +283,8 @@
     return getAllGroups();
   }
 
-  private List<GroupInfo> getAllGroups() throws OrmException, IOException, ConfigInvalidException {
+  private List<GroupInfo> getAllGroups()
+      throws OrmException, IOException, ConfigInvalidException, PermissionBackendException {
     Pattern pattern = getRegexPattern();
     Stream<GroupDescription.Internal> existingGroups =
         getAllExistingGroups()
@@ -312,7 +316,8 @@
     return groups.getAllGroupReferences();
   }
 
-  private List<GroupInfo> suggestGroups() throws OrmException, BadRequestException {
+  private List<GroupInfo> suggestGroups()
+      throws OrmException, BadRequestException, PermissionBackendException {
     if (conflictingSuggestParameters()) {
       throw new BadRequestException(
           "You should only have no more than one --project and -n with --suggest");
@@ -368,7 +373,7 @@
   }
 
   private List<GroupInfo> filterGroupsOwnedBy(Predicate<GroupDescription.Internal> filter)
-      throws OrmException, IOException, ConfigInvalidException {
+      throws OrmException, IOException, ConfigInvalidException, PermissionBackendException {
     Pattern pattern = getRegexPattern();
     Stream<? extends GroupDescription.Internal> foundGroups =
         groups
@@ -396,13 +401,14 @@
   }
 
   private List<GroupInfo> getGroupsOwnedBy(String id)
-      throws OrmException, RestApiException, IOException, ConfigInvalidException {
+      throws OrmException, RestApiException, IOException, ConfigInvalidException,
+          PermissionBackendException {
     String uuid = groupsCollection.parse(id).getGroupUUID().get();
     return filterGroupsOwnedBy(group -> group.getOwnerGroupUUID().get().equals(uuid));
   }
 
   private List<GroupInfo> getGroupsOwnedBy(IdentifiedUser user)
-      throws OrmException, IOException, ConfigInvalidException {
+      throws OrmException, IOException, ConfigInvalidException, PermissionBackendException {
     return filterGroupsOwnedBy(group -> isOwner(user, group));
   }
 
diff --git a/java/com/google/gerrit/server/restapi/group/ListMembers.java b/java/com/google/gerrit/server/restapi/group/ListMembers.java
index 634e029..4742644 100644
--- a/java/com/google/gerrit/server/restapi/group/ListMembers.java
+++ b/java/com/google/gerrit/server/restapi/group/ListMembers.java
@@ -32,6 +32,7 @@
 import com.google.gerrit.server.group.GroupResource;
 import com.google.gerrit.server.group.InternalGroup;
 import com.google.gerrit.server.group.InternalGroupDescription;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import java.util.ArrayList;
@@ -66,7 +67,7 @@
 
   @Override
   public List<AccountInfo> apply(GroupResource resource)
-      throws NotInternalGroupException, OrmException {
+      throws NotInternalGroupException, OrmException, PermissionBackendException {
     GroupDescription.Internal group =
         resource.asInternalGroup().orElseThrow(NotInternalGroupException::new);
     if (recursive) {
@@ -75,7 +76,8 @@
     return getDirectMembers(group, resource.getControl());
   }
 
-  public List<AccountInfo> getTransitiveMembers(AccountGroup.UUID groupUuid) {
+  public List<AccountInfo> getTransitiveMembers(AccountGroup.UUID groupUuid)
+      throws PermissionBackendException {
     Optional<InternalGroup> group = groupCache.get(groupUuid);
     if (group.isPresent()) {
       InternalGroupDescription internalGroup = new InternalGroupDescription(group.get());
@@ -86,7 +88,8 @@
   }
 
   private List<AccountInfo> getTransitiveMembers(
-      GroupDescription.Internal group, GroupControl groupControl) {
+      GroupDescription.Internal group, GroupControl groupControl)
+      throws PermissionBackendException {
     checkSameGroup(group, groupControl);
     Set<Account.Id> members =
         getTransitiveMemberIds(
@@ -94,19 +97,21 @@
     return toAccountInfos(members);
   }
 
-  public List<AccountInfo> getDirectMembers(InternalGroup group) {
+  public List<AccountInfo> getDirectMembers(InternalGroup group) throws PermissionBackendException {
     InternalGroupDescription internalGroup = new InternalGroupDescription(group);
     return getDirectMembers(internalGroup, groupControlFactory.controlFor(internalGroup));
   }
 
   public List<AccountInfo> getDirectMembers(
-      GroupDescription.Internal group, GroupControl groupControl) {
+      GroupDescription.Internal group, GroupControl groupControl)
+      throws PermissionBackendException {
     checkSameGroup(group, groupControl);
     Set<Account.Id> directMembers = getDirectMemberIds(group, groupControl);
     return toAccountInfos(directMembers);
   }
 
-  private List<AccountInfo> toAccountInfos(Set<Account.Id> members) {
+  private List<AccountInfo> toAccountInfos(Set<Account.Id> members)
+      throws PermissionBackendException {
     List<AccountInfo> memberInfos = new ArrayList<>(members.size());
     for (Account.Id member : members) {
       memberInfos.add(accountLoader.get(member));
diff --git a/java/com/google/gerrit/server/restapi/group/ListSubgroups.java b/java/com/google/gerrit/server/restapi/group/ListSubgroups.java
index 835a613..97a260e 100644
--- a/java/com/google/gerrit/server/restapi/group/ListSubgroups.java
+++ b/java/com/google/gerrit/server/restapi/group/ListSubgroups.java
@@ -24,6 +24,7 @@
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.account.GroupControl;
 import com.google.gerrit.server.group.GroupResource;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
@@ -46,7 +47,8 @@
   }
 
   @Override
-  public List<GroupInfo> apply(GroupResource rsrc) throws NotInternalGroupException, OrmException {
+  public List<GroupInfo> apply(GroupResource rsrc)
+      throws NotInternalGroupException, OrmException, PermissionBackendException {
     GroupDescription.Internal group =
         rsrc.asInternalGroup().orElseThrow(NotInternalGroupException::new);
 
@@ -54,7 +56,8 @@
   }
 
   public List<GroupInfo> getDirectSubgroups(
-      GroupDescription.Internal group, GroupControl groupControl) throws OrmException {
+      GroupDescription.Internal group, GroupControl groupControl)
+      throws OrmException, PermissionBackendException {
     boolean ownerOfParent = groupControl.isOwner();
     List<GroupInfo> included = new ArrayList<>();
     for (AccountGroup.UUID subgroupUuid : group.getSubgroups()) {
diff --git a/java/com/google/gerrit/server/restapi/group/PutOwner.java b/java/com/google/gerrit/server/restapi/group/PutOwner.java
index 5e7563e..56f856d 100644
--- a/java/com/google/gerrit/server/restapi/group/PutOwner.java
+++ b/java/com/google/gerrit/server/restapi/group/PutOwner.java
@@ -29,6 +29,7 @@
 import com.google.gerrit.server.group.GroupResource;
 import com.google.gerrit.server.group.db.GroupsUpdate;
 import com.google.gerrit.server.group.db.InternalGroupUpdate;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -56,7 +57,7 @@
   public GroupInfo apply(GroupResource resource, OwnerInput input)
       throws ResourceNotFoundException, NotInternalGroupException, AuthException,
           BadRequestException, UnprocessableEntityException, OrmException, IOException,
-          ConfigInvalidException {
+          ConfigInvalidException, PermissionBackendException {
     GroupDescription.Internal internalGroup =
         resource.asInternalGroup().orElseThrow(NotInternalGroupException::new);
     if (!resource.getControl().isOwner()) {
diff --git a/java/com/google/gerrit/server/restapi/group/QueryGroups.java b/java/com/google/gerrit/server/restapi/group/QueryGroups.java
index c262003..fa9285d 100644
--- a/java/com/google/gerrit/server/restapi/group/QueryGroups.java
+++ b/java/com/google/gerrit/server/restapi/group/QueryGroups.java
@@ -26,6 +26,7 @@
 import com.google.gerrit.index.query.QueryResult;
 import com.google.gerrit.server.group.InternalGroup;
 import com.google.gerrit.server.group.InternalGroupDescription;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.query.group.GroupQueryBuilder;
 import com.google.gerrit.server.query.group.GroupQueryProcessor;
 import com.google.gwtorm.server.OrmException;
@@ -94,7 +95,8 @@
 
   @Override
   public List<GroupInfo> apply(TopLevelResource resource)
-      throws BadRequestException, MethodNotAllowedException, OrmException {
+      throws BadRequestException, MethodNotAllowedException, OrmException,
+          PermissionBackendException {
     if (Strings.isNullOrEmpty(query)) {
       throw new BadRequestException("missing query field");
     }
diff --git a/java/com/google/gerrit/server/restapi/project/GetAccess.java b/java/com/google/gerrit/server/restapi/project/GetAccess.java
index 6a50c2f..d545f92 100644
--- a/java/com/google/gerrit/server/restapi/project/GetAccess.java
+++ b/java/com/google/gerrit/server/restapi/project/GetAccess.java
@@ -237,11 +237,15 @@
       }
     }
 
-    if (info.ownerOf.isEmpty()
-        && permissionBackend.currentUser().test(GlobalPermission.ADMINISTRATE_SERVER)) {
-      // Special case: If the section list is empty, this project has no current
-      // access control information. Fall back to site administrators.
-      info.ownerOf.add(AccessSection.ALL);
+    if (info.ownerOf.isEmpty()) {
+      try {
+        permissionBackend.currentUser().check(GlobalPermission.ADMINISTRATE_SERVER);
+        // Special case: If the section list is empty, this project has no current
+        // access control information. Fall back to site administrators.
+        info.ownerOf.add(AccessSection.ALL);
+      } catch (AuthException e) {
+        // Do nothing.
+      }
     }
 
     if (config.getRevision() != null) {
diff --git a/java/com/google/gerrit/server/restapi/project/Index.java b/java/com/google/gerrit/server/restapi/project/Index.java
index 5547864..1b2a523 100644
--- a/java/com/google/gerrit/server/restapi/project/Index.java
+++ b/java/com/google/gerrit/server/restapi/project/Index.java
@@ -21,6 +21,7 @@
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
 import com.google.gerrit.extensions.api.projects.IndexProjectInput;
+import com.google.gerrit.extensions.common.ProjectInfo;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.Response;
@@ -62,27 +63,33 @@
           ResourceConflictException {
     String response = "Project " + rsrc.getName() + " submitted for reindexing";
 
-    reindexAsync(rsrc.getNameKey());
+    reindex(rsrc.getNameKey(), input.async);
     if (Boolean.TRUE.equals(input.indexChildren)) {
       ListChildProjects listChildProjects = listChildProjectsProvider.get();
       listChildProjects.setRecursive(true);
-      listChildProjects.apply(rsrc).forEach(p -> reindexAsync(new Project.NameKey(p.name)));
+      for (ProjectInfo child : listChildProjects.apply(rsrc)) {
+        reindex(new Project.NameKey(child.name), input.async);
+      }
 
       response += " (indexing children recursively)";
     }
     return Response.accepted(response);
   }
 
-  private void reindexAsync(Project.NameKey project) {
-    @SuppressWarnings("unused")
-    Future<?> possiblyIgnoredError =
-        executor.submit(
-            () -> {
-              try {
-                indexer.index(project);
-              } catch (IOException e) {
-                logger.atWarning().withCause(e).log("reindexing project %s failed", project);
-              }
-            });
+  private void reindex(Project.NameKey project, Boolean async) throws IOException {
+    if (Boolean.TRUE.equals(async)) {
+      @SuppressWarnings("unused")
+      Future<?> possiblyIgnoredError =
+          executor.submit(
+              () -> {
+                try {
+                  indexer.index(project);
+                } catch (IOException e) {
+                  logger.atWarning().withCause(e).log("reindexing project %s failed", project);
+                }
+              });
+    } else {
+      indexer.index(project);
+    }
   }
 }
diff --git a/java/com/google/gerrit/server/restapi/project/ListBranches.java b/java/com/google/gerrit/server/restapi/project/ListBranches.java
index 0bdf979..bf4a547 100644
--- a/java/com/google/gerrit/server/restapi/project/ListBranches.java
+++ b/java/com/google/gerrit/server/restapi/project/ListBranches.java
@@ -24,6 +24,7 @@
 import com.google.gerrit.extensions.common.ActionInfo;
 import com.google.gerrit.extensions.common.WebLinkInfo;
 import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.extensions.restapi.RestReadView;
@@ -185,9 +186,13 @@
         // showing the resolved value, show the name it references.
         //
         String target = ref.getTarget().getName();
-        if (!perm.ref(target).test(RefPermission.READ)) {
+
+        try {
+          perm.ref(target).check(RefPermission.READ);
+        } catch (AuthException e) {
           continue;
         }
+
         if (target.startsWith(Constants.R_HEADS)) {
           target = target.substring(Constants.R_HEADS.length());
         }
@@ -212,10 +217,13 @@
         continue;
       }
 
-      if (perm.ref(ref.getName()).test(RefPermission.READ)) {
+      try {
+        perm.ref(ref.getName()).check(RefPermission.READ);
         branches.add(
             createBranchInfo(
                 perm.ref(ref.getName()), ref, rsrc.getProjectState(), rsrc.getUser(), targets));
+      } catch (AuthException e) {
+        // Do nothing.
       }
     }
     Collections.sort(branches, new BranchComparator());
diff --git a/java/com/google/gerrit/server/restapi/project/ListDashboards.java b/java/com/google/gerrit/server/restapi/project/ListDashboards.java
index 4ff46cf..06dbdb0 100644
--- a/java/com/google/gerrit/server/restapi/project/ListDashboards.java
+++ b/java/com/google/gerrit/server/restapi/project/ListDashboards.java
@@ -18,6 +18,7 @@
 
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.extensions.api.projects.DashboardInfo;
+import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.reviewdb.client.Project;
@@ -104,8 +105,15 @@
         RevWalk rw = new RevWalk(git)) {
       List<DashboardInfo> all = new ArrayList<>();
       for (Ref ref : git.getRefDatabase().getRefsByPrefix(REFS_DASHBOARDS)) {
-        if (perm.ref(ref.getName()).test(RefPermission.READ) && state.statePermitsRead()) {
+        if (!state.statePermitsRead()) {
+          continue;
+        }
+
+        try {
+          perm.ref(ref.getName()).check(RefPermission.READ);
           all.addAll(scanDashboards(state.getProject(), git, rw, ref, project, setDefault));
+        } catch (AuthException e) {
+          // Do nothing.
         }
       }
       return all;
diff --git a/java/com/google/gerrit/server/restapi/project/ListProjects.java b/java/com/google/gerrit/server/restapi/project/ListProjects.java
index 72a0788..8c1b0b3 100644
--- a/java/com/google/gerrit/server/restapi/project/ListProjects.java
+++ b/java/com/google/gerrit/server/restapi/project/ListProjects.java
@@ -42,6 +42,7 @@
 import com.google.gerrit.server.WebLinks;
 import com.google.gerrit.server.account.GroupControl;
 import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.ioutil.RegexListSearcher;
 import com.google.gerrit.server.ioutil.StringUtil;
 import com.google.gerrit.server.permissions.PermissionBackend;
 import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -50,7 +51,6 @@
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectState;
 import com.google.gerrit.server.restapi.group.GroupsCollection;
-import com.google.gerrit.server.util.RegexListSearcher;
 import com.google.gerrit.server.util.TreeFormatter;
 import com.google.gson.reflect.TypeToken;
 import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/server/schema/Schema_169.java b/java/com/google/gerrit/server/schema/Schema_169.java
index 11aebdd..75dd459 100644
--- a/java/com/google/gerrit/server/schema/Schema_169.java
+++ b/java/com/google/gerrit/server/schema/Schema_169.java
@@ -18,32 +18,25 @@
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.config.AllUsersName;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.notedb.CommentJsonMigrator;
+import com.google.gerrit.server.notedb.CommentJsonMigrator.ProjectMigrationResult;
 import com.google.gerrit.server.notedb.MutableNotesMigration;
 import com.google.gerrit.server.notedb.NotesMigration;
-import com.google.gerrit.server.update.RefUpdateUtil;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import java.io.IOException;
 import java.util.SortedSet;
-import org.eclipse.jgit.internal.storage.file.FileRepository;
-import org.eclipse.jgit.internal.storage.file.PackInserter;
-import org.eclipse.jgit.lib.BatchRefUpdate;
 import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.TextProgressMonitor;
-import org.eclipse.jgit.revwalk.RevWalk;
 
 /** Migrate NoteDb inline comments to JSON format. */
 public class Schema_169 extends SchemaVersion {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-  private final AllUsersName allUsers;
   private final CommentJsonMigrator migrator;
   private final GitRepositoryManager repoManager;
   private final NotesMigration notesMigration;
@@ -51,12 +44,10 @@
   @Inject
   Schema_169(
       Provider<Schema_168> prior,
-      AllUsersName allUsers,
       CommentJsonMigrator migrator,
       GitRepositoryManager repoManager,
       @GerritServerConfig Config config) {
     super(prior);
-    this.allUsers = allUsers;
     this.migrator = migrator;
     this.repoManager = repoManager;
     this.notesMigration = MutableNotesMigration.fromConfig(config);
@@ -67,15 +58,6 @@
     migrateData(ui);
   }
 
-  private static ObjectInserter newPackInserter(Repository repo) {
-    if (!(repo instanceof FileRepository)) {
-      return repo.newObjectInserter();
-    }
-    PackInserter ins = ((FileRepository) repo).getObjectDatabase().newPackInserter();
-    ins.checkExisting(false);
-    return ins;
-  }
-
   @VisibleForTesting
   protected void migrateData(UpdateUI ui) throws OrmException {
     //  If the migration hasn't started, no need to look for non-JSON
@@ -89,28 +71,16 @@
     pm.beginTask("Migrating projects", projects.size());
     int skipped = 0;
     for (Project.NameKey project : projects) {
-      try (Repository repo = repoManager.openRepository(project);
-          RevWalk rw = new RevWalk(repo);
-          ObjectInserter ins = newPackInserter(repo)) {
-        BatchRefUpdate bru = repo.getRefDatabase().newBatchUpdate();
-        bru.setAllowNonFastForwards(true);
-        ok &= migrator.migrateChanges(project, repo, rw, ins, bru);
-        if (project.equals(allUsers)) {
-          ok &= migrator.migrateDrafts(allUsers, repo, rw, ins, bru);
-        }
-
-        if (!bru.getCommands().isEmpty()) {
-          ins.flush();
-          RefUpdateUtil.executeChecked(bru, rw);
-        } else {
-          skipped++;
-        }
+      try (Repository repo = repoManager.openRepository(project)) {
+        ProjectMigrationResult progress = migrator.migrateProject(project, repo);
+        skipped += progress.skipped;
       } catch (IOException e) {
-        logger.atWarning().log("Error migrating project " + project, e);
         ok = false;
+        logger.atWarning().log("Error migrating project " + project, e);
       }
       pm.update(1);
     }
+
     pm.endTask();
     ui.message(
         "Skipped " + skipped + " project" + (skipped == 1 ? "" : "s") + " with no legacy comments");
diff --git a/java/com/google/gerrit/server/submit/LocalMergeSuperSetComputation.java b/java/com/google/gerrit/server/submit/LocalMergeSuperSetComputation.java
index 06f57b5..9efb976 100644
--- a/java/com/google/gerrit/server/submit/LocalMergeSuperSetComputation.java
+++ b/java/com/google/gerrit/server/submit/LocalMergeSuperSetComputation.java
@@ -25,6 +25,7 @@
 import com.google.gerrit.common.data.SubmitTypeRecord;
 import com.google.gerrit.extensions.client.SubmitType;
 import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -182,14 +183,19 @@
         changeSet.ids().contains(cd.getId())
             && (projectState != null)
             && projectState.statePermitsRead();
-    if (visible
-        && !permissionBackend.user(user).change(cd).database(db).test(ChangePermission.READ)) {
+    if (!visible) {
+      return false;
+    }
+
+    try {
+      permissionBackend.user(user).change(cd).database(db).check(ChangePermission.READ);
+      return true;
+    } catch (AuthException e) {
       // We thought the change was visible, but it isn't.
       // This can happen if the ACL changes during the
       // completeChangeSet computation, for example.
-      visible = false;
+      return false;
     }
-    return visible;
   }
 
   private SubmitType submitType(ChangeData cd) throws OrmException {
diff --git a/java/com/google/gerrit/server/submit/MergeOp.java b/java/com/google/gerrit/server/submit/MergeOp.java
index a39e4e9..cb2e306 100644
--- a/java/com/google/gerrit/server/submit/MergeOp.java
+++ b/java/com/google/gerrit/server/submit/MergeOp.java
@@ -787,19 +787,33 @@
       }
 
       if (!revisions.containsEntry(id, ps.getId())) {
-        // TODO this is actually an error, the branch is gone but we
-        // want to merge the issue. We can't safely do that if the
-        // tip is not reachable.
-        //
+        if (revisions.containsValue(ps.getId())) {
+          // TODO This is actually an error, the patch set ref exists but points to a revision that
+          // is different from the revision that we have stored for the patch set in the change
+          // meta data.
+          commitStatus.logProblem(
+              changeId,
+              "Revision "
+                  + idstr
+                  + " of patch set "
+                  + ps.getPatchSetId()
+                  + " does not match the revision of the patch set ref "
+                  + ps.getId().toRefName());
+          continue;
+        }
+
+        // The patch set ref is not found but we want to merge the change. We can't safely do that
+        // if the patch set ref is missing. In a multi-master setup this can indicate a replication
+        // lag (e.g. the change meta data was already replicated, but the replication of the patch
+        // set ref is still pending).
         commitStatus.logProblem(
             changeId,
-            "Revision "
-                + idstr
-                + " of patch set "
-                + ps.getPatchSetId()
-                + " does not match "
+            "Patch set ref "
                 + ps.getId().toRefName()
-                + " for change");
+                + " not found. Expected patch set ref of "
+                + ps.getPatchSetId()
+                + " to point to revision "
+                + idstr);
         continue;
       }
 
diff --git a/java/com/google/gerrit/server/submit/MergeSuperSet.java b/java/com/google/gerrit/server/submit/MergeSuperSet.java
index 3e9f068..31bcc2a 100644
--- a/java/com/google/gerrit/server/submit/MergeSuperSet.java
+++ b/java/com/google/gerrit/server/submit/MergeSuperSet.java
@@ -20,6 +20,7 @@
 import com.google.common.base.Strings;
 import com.google.common.collect.Iterables;
 import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
@@ -100,17 +101,22 @@
       List<ChangeData> cds = queryProvider.get().byLegacyChangeId(change.getId());
       checkState(cds.size() == 1, "Expected exactly one ChangeData, got " + cds.size());
       ChangeData cd = Iterables.getFirst(cds, null);
-      ProjectState projectState = projectCache.checkedGet(cd.project());
-      ChangeSet changeSet =
-          new ChangeSet(
-              cd,
-              projectState != null
-                  && projectState.statePermitsRead()
-                  && permissionBackend
-                      .user(user)
-                      .change(cd)
-                      .database(db)
-                      .test(ChangePermission.READ));
+
+      boolean visible = false;
+      if (cd != null) {
+        ProjectState projectState = projectCache.checkedGet(cd.project());
+
+        if (projectState.statePermitsRead()) {
+          try {
+            permissionBackend.user(user).change(cd).database(db).check(ChangePermission.READ);
+            visible = true;
+          } catch (AuthException e) {
+            // Do nothing.
+          }
+        }
+      }
+
+      ChangeSet changeSet = new ChangeSet(cd, visible);
       if (wholeTopicEnabled(cfg)) {
         return completeChangeSetIncludingTopics(db, changeSet, user);
       }
@@ -203,8 +209,15 @@
   private boolean canRead(ReviewDb db, CurrentUser user, ChangeData cd)
       throws PermissionBackendException, IOException {
     ProjectState projectState = projectCache.checkedGet(cd.project());
-    return projectState != null
-        && projectState.statePermitsRead()
-        && permissionBackend.user(user).change(cd).database(db).test(ChangePermission.READ);
+    if (projectState == null || !projectState.statePermitsRead()) {
+      return false;
+    }
+
+    try {
+      permissionBackend.user(user).change(cd).database(db).check(ChangePermission.READ);
+      return true;
+    } catch (AuthException e) {
+      return false;
+    }
   }
 }
diff --git a/java/com/google/gerrit/server/submit/SubmitStrategyOp.java b/java/com/google/gerrit/server/submit/SubmitStrategyOp.java
index 62dabae..abe3632 100644
--- a/java/com/google/gerrit/server/submit/SubmitStrategyOp.java
+++ b/java/com/google/gerrit/server/submit/SubmitStrategyOp.java
@@ -357,12 +357,7 @@
     Map<PatchSetApproval.Key, PatchSetApproval> byKey = new HashMap<>();
     for (PatchSetApproval psa :
         args.approvalsUtil.byPatchSet(
-            ctx.getDb(),
-            ctx.getNotes(),
-            ctx.getUser(),
-            psId,
-            ctx.getRevWalk(),
-            ctx.getRepoView().getConfig())) {
+            ctx.getDb(), ctx.getNotes(), psId, ctx.getRevWalk(), ctx.getRepoView().getConfig())) {
       byKey.put(psa.getKey(), psa);
     }
 
@@ -376,7 +371,7 @@
     // was added. So we need to make sure votes are accurate now. This way if
     // permissions get modified in the future, historical records stay accurate.
     LabelNormalizer.Result normalized =
-        args.labelNormalizer.normalize(ctx.getNotes(), ctx.getUser(), byKey.values());
+        args.labelNormalizer.normalize(ctx.getNotes(), byKey.values());
     update.putApproval(submitter.getLabel(), submitter.getValue());
     saveApprovals(normalized, ctx, update, false);
     return normalized;
diff --git a/java/com/google/gerrit/server/update/RetryingRestCollectionView.java b/java/com/google/gerrit/server/update/RetryingRestCollectionView.java
new file mode 100644
index 0000000..e0e3148
--- /dev/null
+++ b/java/com/google/gerrit/server/update/RetryingRestCollectionView.java
@@ -0,0 +1,40 @@
+// Copyright (C) 2018 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.server.update;
+
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.RestCollectionView;
+import com.google.gerrit.extensions.restapi.RestResource;
+
+public abstract class RetryingRestCollectionView<
+        P extends RestResource, C extends RestResource, I, O>
+    implements RestCollectionView<P, C, I> {
+  private final RetryHelper retryHelper;
+
+  protected RetryingRestCollectionView(RetryHelper retryHelper) {
+    this.retryHelper = retryHelper;
+  }
+
+  @Override
+  public final O apply(P parentResource, I input)
+      throws AuthException, BadRequestException, ResourceConflictException, Exception {
+    return retryHelper.execute((updateFactory) -> applyImpl(updateFactory, parentResource, input));
+  }
+
+  protected abstract O applyImpl(BatchUpdate.Factory updateFactory, P parentResource, I input)
+      throws Exception;
+}
diff --git a/java/com/google/gerrit/server/validators/OutgoingEmailValidationListener.java b/java/com/google/gerrit/server/validators/OutgoingEmailValidationListener.java
index 9f152a5..996ad87 100644
--- a/java/com/google/gerrit/server/validators/OutgoingEmailValidationListener.java
+++ b/java/com/google/gerrit/server/validators/OutgoingEmailValidationListener.java
@@ -16,8 +16,8 @@
 
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.extensions.annotations.ExtensionPoint;
-import com.google.gerrit.server.mail.Address;
-import com.google.gerrit.server.mail.send.EmailHeader;
+import com.google.gerrit.mail.Address;
+import com.google.gerrit.mail.EmailHeader;
 import java.util.Map;
 import java.util.Set;
 
diff --git a/java/com/google/gerrit/sshd/BUILD b/java/com/google/gerrit/sshd/BUILD
index 6c810a3..4743b35 100644
--- a/java/com/google/gerrit/sshd/BUILD
+++ b/java/com/google/gerrit/sshd/BUILD
@@ -11,6 +11,7 @@
         "//java/com/google/gerrit/metrics",
         "//java/com/google/gerrit/reviewdb:server",
         "//java/com/google/gerrit/server",
+        "//java/com/google/gerrit/server/audit",
         "//java/com/google/gerrit/server/cache/h2",
         "//java/com/google/gerrit/server/git/receive",
         "//java/com/google/gerrit/server/ioutil",
diff --git a/java/com/google/gerrit/sshd/ChangeArgumentParser.java b/java/com/google/gerrit/sshd/ChangeArgumentParser.java
index 9fc4cda..2ab13ee 100644
--- a/java/com/google/gerrit/sshd/ChangeArgumentParser.java
+++ b/java/com/google/gerrit/sshd/ChangeArgumentParser.java
@@ -86,15 +86,22 @@
     }
     for (ChangeNotes notes : matched) {
       if (!changes.containsKey(notes.getChangeId())
-          && inProject(projectState, notes.getProjectName())
-          && (canMaintainServer
-              || (permissionBackend
-                      .currentUser()
-                      .change(notes)
-                      .database(db)
-                      .test(ChangePermission.READ)
-                  && projectState.statePermitsRead()))) {
-        toAdd.add(notes);
+          && inProject(projectState, notes.getProjectName())) {
+        if (canMaintainServer) {
+          toAdd.add(notes);
+          continue;
+        }
+
+        if (!projectState.statePermitsRead()) {
+          continue;
+        }
+
+        try {
+          permissionBackend.currentUser().change(notes).database(db).check(ChangePermission.READ);
+          toAdd.add(notes);
+        } catch (AuthException e) {
+          // Do nothing.
+        }
       }
     }
 
diff --git a/java/com/google/gerrit/sshd/commands/CreateAccountCommand.java b/java/com/google/gerrit/sshd/commands/CreateAccountCommand.java
index c83660d..8a37cce 100644
--- a/java/com/google/gerrit/sshd/commands/CreateAccountCommand.java
+++ b/java/com/google/gerrit/sshd/commands/CreateAccountCommand.java
@@ -24,6 +24,7 @@
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.extensions.restapi.TopLevelResource;
 import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.restapi.account.CreateAccount;
 import com.google.gerrit.sshd.CommandMetaData;
 import com.google.gerrit.sshd.SshCommand;
@@ -70,7 +71,9 @@
   @Inject private CreateAccount createAccount;
 
   @Override
-  protected void run() throws OrmException, IOException, ConfigInvalidException, UnloggedFailure {
+  protected void run()
+      throws OrmException, IOException, ConfigInvalidException, UnloggedFailure,
+          PermissionBackendException {
     AccountInput input = new AccountInput();
     input.username = username;
     input.email = email;
diff --git a/java/com/google/gerrit/sshd/commands/CreateGroupCommand.java b/java/com/google/gerrit/sshd/commands/CreateGroupCommand.java
index 03f9616..917c138 100644
--- a/java/com/google/gerrit/sshd/commands/CreateGroupCommand.java
+++ b/java/com/google/gerrit/sshd/commands/CreateGroupCommand.java
@@ -26,6 +26,7 @@
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.group.GroupResource;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.restapi.group.AddMembers;
 import com.google.gerrit.server.restapi.group.AddSubgroups;
 import com.google.gerrit.server.restapi.group.CreateGroup;
@@ -100,7 +101,9 @@
   @Inject private AddSubgroups addSubgroups;
 
   @Override
-  protected void run() throws Failure, OrmException, IOException, ConfigInvalidException {
+  protected void run()
+      throws Failure, OrmException, IOException, ConfigInvalidException,
+          PermissionBackendException {
     try {
       GroupResource rsrc = createGroup();
 
@@ -117,7 +120,8 @@
   }
 
   private GroupResource createGroup()
-      throws RestApiException, OrmException, IOException, ConfigInvalidException {
+      throws RestApiException, OrmException, IOException, ConfigInvalidException,
+          PermissionBackendException {
     GroupInput input = new GroupInput();
     input.description = groupDescription;
     input.visibleToAll = visibleToAll;
@@ -132,7 +136,8 @@
   }
 
   private void addMembers(GroupResource rsrc)
-      throws RestApiException, OrmException, IOException, ConfigInvalidException {
+      throws RestApiException, OrmException, IOException, ConfigInvalidException,
+          PermissionBackendException {
     AddMembers.Input input =
         AddMembers.Input.fromMembers(
             initialMembers.stream().map(Object::toString).collect(toList()));
@@ -140,7 +145,8 @@
   }
 
   private void addSubgroups(GroupResource rsrc)
-      throws RestApiException, OrmException, IOException, ConfigInvalidException {
+      throws RestApiException, OrmException, IOException, ConfigInvalidException,
+          PermissionBackendException {
     AddSubgroups.Input input =
         AddSubgroups.Input.fromGroups(
             initialGroups.stream().map(AccountGroup.UUID::get).collect(toList()));
diff --git a/java/com/google/gerrit/sshd/commands/ListMembersCommand.java b/java/com/google/gerrit/sshd/commands/ListMembersCommand.java
index cfd0a37..1565ecb 100644
--- a/java/com/google/gerrit/sshd/commands/ListMembersCommand.java
+++ b/java/com/google/gerrit/sshd/commands/ListMembersCommand.java
@@ -25,6 +25,7 @@
 import com.google.gerrit.server.account.GroupControl;
 import com.google.gerrit.server.group.InternalGroup;
 import com.google.gerrit.server.ioutil.ColumnFormatter;
+import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.restapi.group.ListMembers;
 import com.google.gerrit.sshd.CommandMetaData;
 import com.google.gerrit.sshd.SshCommand;
@@ -67,7 +68,7 @@
       this.groupCache = groupCache;
     }
 
-    void display(PrintWriter writer) {
+    void display(PrintWriter writer) throws PermissionBackendException {
       Optional<InternalGroup> group = groupCache.get(new AccountGroup.NameKey(name));
       String errorText = "Group not found or not visible\n";
 
diff --git a/java/com/google/gerrit/testing/BUILD b/java/com/google/gerrit/testing/BUILD
index 43aa978..cf65908 100644
--- a/java/com/google/gerrit/testing/BUILD
+++ b/java/com/google/gerrit/testing/BUILD
@@ -19,12 +19,14 @@
         "//java/com/google/gerrit/index",
         "//java/com/google/gerrit/index/project",
         "//java/com/google/gerrit/lifecycle",
+        "//java/com/google/gerrit/mail",
         "//java/com/google/gerrit/metrics",
         "//java/com/google/gerrit/pgm/init",
         "//java/com/google/gerrit/reviewdb:server",
         "//java/com/google/gerrit/server",
         "//java/com/google/gerrit/server:module",
         "//java/com/google/gerrit/server/api",
+        "//java/com/google/gerrit/server/audit",
         "//java/com/google/gerrit/server/cache/h2",
         "//java/com/google/gerrit/server/cache/mem",
         "//java/com/google/gerrit/server/restapi",
diff --git a/java/com/google/gerrit/testing/FakeEmailSender.java b/java/com/google/gerrit/testing/FakeEmailSender.java
index 28946dc..e81d0f4 100644
--- a/java/com/google/gerrit/testing/FakeEmailSender.java
+++ b/java/com/google/gerrit/testing/FakeEmailSender.java
@@ -22,10 +22,10 @@
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.errors.EmailException;
+import com.google.gerrit.mail.Address;
+import com.google.gerrit.mail.EmailHeader;
+import com.google.gerrit.mail.MailHeader;
 import com.google.gerrit.server.git.WorkQueue;
-import com.google.gerrit.server.mail.Address;
-import com.google.gerrit.server.mail.MailHeader;
-import com.google.gerrit.server.mail.send.EmailHeader;
 import com.google.gerrit.server.mail.send.EmailSender;
 import com.google.inject.AbstractModule;
 import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/testing/InMemoryModule.java b/java/com/google/gerrit/testing/InMemoryModule.java
index b3f6222..8f9aa14 100644
--- a/java/com/google/gerrit/testing/InMemoryModule.java
+++ b/java/com/google/gerrit/testing/InMemoryModule.java
@@ -35,6 +35,7 @@
 import com.google.gerrit.server.PluginUser;
 import com.google.gerrit.server.api.GerritApiModule;
 import com.google.gerrit.server.api.PluginApiModule;
+import com.google.gerrit.server.audit.AuditModule;
 import com.google.gerrit.server.cache.h2.H2CacheModule;
 import com.google.gerrit.server.cache.mem.DefaultMemoryCacheModule;
 import com.google.gerrit.server.config.AllProjectsName;
@@ -178,6 +179,7 @@
     install(new DefaultPermissionBackendModule());
     install(new SearchingChangeCacheImpl.Module());
     factory(GarbageCollection.Factory.class);
+    install(new AuditModule());
 
     bindScope(RequestScoped.class, PerThreadRequestScope.REQUEST);
 
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
index 0bcf02b..aa9ef80 100644
--- a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
@@ -90,6 +90,7 @@
 import com.google.gerrit.gpg.Fingerprint;
 import com.google.gerrit.gpg.PublicKeyStore;
 import com.google.gerrit.gpg.testing.TestKey;
+import com.google.gerrit.mail.Address;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
@@ -111,7 +112,6 @@
 import com.google.gerrit.server.git.meta.MetaDataUpdate;
 import com.google.gerrit.server.index.account.AccountIndexer;
 import com.google.gerrit.server.index.account.StalenessChecker;
-import com.google.gerrit.server.mail.Address;
 import com.google.gerrit.server.notedb.rebuild.ChangeRebuilderImpl;
 import com.google.gerrit.server.project.ProjectConfig;
 import com.google.gerrit.server.project.RefPattern;
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/AgreementsIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/AgreementsIT.java
index c3a0dc9..7a4a901 100644
--- a/javatests/com/google/gerrit/acceptance/api/accounts/AgreementsIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/AgreementsIT.java
@@ -177,7 +177,7 @@
     setApiUser(user);
     setUseContributorAgreements(InheritableBoolean.TRUE);
     exception.expect(AuthException.class);
-    exception.expectMessage("A Contributor Agreement must be completed");
+    exception.expectMessage("Contributor Agreement");
     gApi.changes().id(change.changeId).revert();
   }
 
@@ -209,7 +209,7 @@
     in.destination = dest.ref;
     in.message = change.subject;
     exception.expect(AuthException.class);
-    exception.expectMessage("A Contributor Agreement must be completed");
+    exception.expectMessage("Contributor Agreement");
     gApi.changes().id(change.changeId).current().cherryPick(in);
   }
 
@@ -227,7 +227,7 @@
       gApi.changes().create(newChangeInput());
       fail("Expected AuthException");
     } catch (AuthException e) {
-      assertThat(e.getMessage()).contains("A Contributor Agreement must be completed");
+      assertThat(e.getMessage()).contains("Contributor Agreement");
     }
 
     // Sign the agreement
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/BUILD b/javatests/com/google/gerrit/acceptance/api/accounts/BUILD
index b52efe7..9bc30ba 100644
--- a/javatests/com/google/gerrit/acceptance/api/accounts/BUILD
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/BUILD
@@ -8,4 +8,5 @@
         "noci",
         "no_windows",
     ],
+    deps = ["//java/com/google/gerrit/mail"],
 )
diff --git a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
index f9284bd..624e1b0 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -126,6 +126,7 @@
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
+import com.google.gerrit.mail.Address;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Branch;
@@ -140,7 +141,6 @@
 import com.google.gerrit.server.change.ChangeResource;
 import com.google.gerrit.server.git.ChangeMessageModifier;
 import com.google.gerrit.server.group.SystemGroupBackend;
-import com.google.gerrit.server.mail.Address;
 import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
 import com.google.gerrit.server.project.testing.Util;
 import com.google.gerrit.server.restapi.change.PostReview;
diff --git a/javatests/com/google/gerrit/acceptance/api/project/BUILD b/javatests/com/google/gerrit/acceptance/api/project/BUILD
index 768c20b..97c6f33 100644
--- a/javatests/com/google/gerrit/acceptance/api/project/BUILD
+++ b/javatests/com/google/gerrit/acceptance/api/project/BUILD
@@ -4,4 +4,5 @@
     srcs = glob(["*IT.java"]),
     group = "api_project",
     labels = ["api"],
+    deps = ["//java/com/google/gerrit/index/project"],
 )
diff --git a/javatests/com/google/gerrit/acceptance/api/project/ProjectIndexerIT.java b/javatests/com/google/gerrit/acceptance/api/project/ProjectIndexerIT.java
new file mode 100644
index 0000000..6fde012
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/api/project/ProjectIndexerIT.java
@@ -0,0 +1,129 @@
+// Copyright (C) 2018 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.acceptance.api.project;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.GitUtil.fetch;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.index.IndexConfig;
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.RefState;
+import com.google.gerrit.index.project.ProjectField;
+import com.google.gerrit.index.project.ProjectIndex;
+import com.google.gerrit.index.project.ProjectIndexCollection;
+import com.google.gerrit.index.project.ProjectIndexer;
+import com.google.gerrit.index.query.FieldBundle;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.index.project.StalenessChecker;
+import com.google.gerrit.server.project.ProjectConfig;
+import com.google.inject.Inject;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Consumer;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.Ref;
+import org.junit.Test;
+
+public class ProjectIndexerIT extends AbstractDaemonTest {
+  @Inject private ProjectIndexer projectIndexer;
+  @Inject private ProjectIndexCollection indexes;
+  @Inject private IndexConfig indexConfig;
+  @Inject private StalenessChecker stalenessChecker;
+
+  private static final ImmutableSet<String> FIELDS =
+      ImmutableSet.of(ProjectField.NAME.getName(), ProjectField.REF_STATE.getName());
+
+  @Test
+  public void indexProject_indexesRefStateOfProjectAndParents() throws Exception {
+    projectIndexer.index(project);
+    ProjectIndex i = indexes.getSearchIndex();
+    assertThat(i.getSchema().hasField(ProjectField.REF_STATE)).isTrue();
+
+    Optional<FieldBundle> result =
+        i.getRaw(project, QueryOptions.create(indexConfig, 0, 1, FIELDS));
+
+    assertThat(result.isPresent()).isTrue();
+    Iterable<byte[]> refState = result.get().getValue(ProjectField.REF_STATE);
+    assertThat(refState).isNotEmpty();
+
+    Map<Project.NameKey, Collection<RefState>> states = RefState.parseStates(refState).asMap();
+
+    fetch(testRepo, "refs/meta/config:refs/meta/config");
+    Ref projectConfigRef = testRepo.getRepository().exactRef("refs/meta/config");
+    TestRepository<InMemoryRepository> allProjectsRepo = cloneProject(allProjects, admin);
+    fetch(allProjectsRepo, "refs/meta/config:refs/meta/config");
+    Ref allProjectConfigRef = allProjectsRepo.getRepository().exactRef("refs/meta/config");
+    assertThat(states)
+        .containsExactly(
+            project,
+            ImmutableSet.of(RefState.of(projectConfigRef)),
+            allProjects,
+            ImmutableSet.of(RefState.of(allProjectConfigRef)));
+  }
+
+  @Test
+  public void stalenessChecker_currentProject_notStale() throws Exception {
+    assertThat(stalenessChecker.isStale(project)).isFalse();
+  }
+
+  @Test
+  public void stalenessChecker_currentProjectUpdates_isStale() throws Exception {
+    updateProjectConfigWithoutIndexUpdate(project);
+    assertThat(stalenessChecker.isStale(project)).isTrue();
+  }
+
+  @Test
+  public void stalenessChecker_parentProjectUpdates_isStale() throws Exception {
+    updateProjectConfigWithoutIndexUpdate(allProjects);
+    assertThat(stalenessChecker.isStale(project)).isTrue();
+  }
+
+  @Test
+  public void stalenessChecker_hierarchyChange_isStale() throws Exception {
+    Project.NameKey p1 = createProject("p1", allProjects);
+    Project.NameKey p2 = createProject("p2", allProjects);
+    try (ProjectConfigUpdate u = updateProject(project)) {
+      u.getConfig().getProject().setParentName(p1);
+      u.save();
+    }
+    assertThat(stalenessChecker.isStale(project)).isFalse();
+
+    updateProjectConfigWithoutIndexUpdate(p1, c -> c.getProject().setParentName(p2));
+    assertThat(stalenessChecker.isStale(project)).isTrue();
+  }
+
+  private void updateProjectConfigWithoutIndexUpdate(Project.NameKey project) throws Exception {
+    updateProjectConfigWithoutIndexUpdate(
+        project, c -> c.getProject().setDescription("making it stale"));
+  }
+
+  private void updateProjectConfigWithoutIndexUpdate(
+      Project.NameKey project, Consumer<ProjectConfig> update) throws Exception {
+    try (AutoCloseable ignored = disableProjectIndex()) {
+      try (ProjectConfigUpdate u = updateProject(project)) {
+        update.accept(u.getConfig());
+        u.save();
+      }
+    } catch (UnsupportedOperationException e) {
+      // Drop, as we just wanted to drop the index update
+      return;
+    }
+    fail("should have a UnsupportedOperationException");
+  }
+}
diff --git a/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java b/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
index cfa7ec4..b4ae8a2 100644
--- a/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
+++ b/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
@@ -71,6 +71,7 @@
 import com.google.gerrit.extensions.common.LabelInfo;
 import com.google.gerrit.extensions.common.RevisionInfo;
 import com.google.gerrit.extensions.common.testing.EditInfoSubject;
+import com.google.gerrit.mail.Address;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.BooleanProjectConfig;
 import com.google.gerrit.reviewdb.client.Change;
@@ -84,7 +85,6 @@
 import com.google.gerrit.server.git.receive.ReceiveConstants;
 import com.google.gerrit.server.git.validators.CommitValidators.ChangeIdValidator;
 import com.google.gerrit.server.group.SystemGroupBackend;
-import com.google.gerrit.server.mail.Address;
 import com.google.gerrit.server.project.testing.Util;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.testing.FakeEmailSender.Message;
diff --git a/javatests/com/google/gerrit/acceptance/git/BUILD b/javatests/com/google/gerrit/acceptance/git/BUILD
index 7a6a3c4..3541fec 100644
--- a/javatests/com/google/gerrit/acceptance/git/BUILD
+++ b/javatests/com/google/gerrit/acceptance/git/BUILD
@@ -16,6 +16,7 @@
     srcs = ["AbstractPushForReview.java"],
     deps = [
         "//java/com/google/gerrit/acceptance:lib",
+        "//java/com/google/gerrit/mail",
     ],
 )
 
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/BUILD b/javatests/com/google/gerrit/acceptance/rest/change/BUILD
index 6a4b4a7..46eb0ba 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/BUILD
+++ b/javatests/com/google/gerrit/acceptance/rest/change/BUILD
@@ -15,6 +15,7 @@
     labels = ["rest"],
     deps = [
         ":submit_util",
+        "//java/com/google/gerrit/mail",
     ],
 )
 
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/ChangeReviewersByEmailIT.java b/javatests/com/google/gerrit/acceptance/rest/change/ChangeReviewersByEmailIT.java
index 84c9c03..06b67d8 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/ChangeReviewersByEmailIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/ChangeReviewersByEmailIT.java
@@ -32,7 +32,7 @@
 import com.google.gerrit.extensions.client.ReviewerState;
 import com.google.gerrit.extensions.common.AccountInfo;
 import com.google.gerrit.extensions.common.ChangeInfo;
-import com.google.gerrit.server.mail.Address;
+import com.google.gerrit.mail.Address;
 import com.google.gerrit.testing.FakeEmailSender.Message;
 import java.util.List;
 import org.junit.Before;
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/ChangeReviewersIT.java b/javatests/com/google/gerrit/acceptance/rest/change/ChangeReviewersIT.java
index 129d98a..2259999 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/ChangeReviewersIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/ChangeReviewersIT.java
@@ -46,8 +46,8 @@
 import com.google.gerrit.extensions.common.LabelInfo;
 import com.google.gerrit.extensions.common.ReviewerUpdateInfo;
 import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.mail.Address;
 import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.server.mail.Address;
 import com.google.gerrit.server.restapi.change.PostReviewers;
 import com.google.gerrit.testing.FakeEmailSender.Message;
 import com.google.gson.stream.JsonReader;
diff --git a/javatests/com/google/gerrit/acceptance/server/mail/AbstractMailIT.java b/javatests/com/google/gerrit/acceptance/server/mail/AbstractMailIT.java
index 32f1ce5..c1b5cf4 100644
--- a/javatests/com/google/gerrit/acceptance/server/mail/AbstractMailIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/mail/AbstractMailIT.java
@@ -22,7 +22,7 @@
 import com.google.gerrit.extensions.api.changes.ReviewInput.CommentInput;
 import com.google.gerrit.extensions.client.Comment;
 import com.google.gerrit.extensions.client.Side;
-import com.google.gerrit.server.mail.receive.MailMessage;
+import com.google.gerrit.mail.MailMessage;
 import java.time.Instant;
 import java.util.HashMap;
 import org.junit.Ignore;
diff --git a/javatests/com/google/gerrit/acceptance/server/mail/BUILD b/javatests/com/google/gerrit/acceptance/server/mail/BUILD
index 4175272..b5ad425 100644
--- a/javatests/com/google/gerrit/acceptance/server/mail/BUILD
+++ b/javatests/com/google/gerrit/acceptance/server/mail/BUILD
@@ -3,6 +3,7 @@
 DEPS = [
     "//lib/greenmail",
     "//lib/mail",
+    "//java/com/google/gerrit/mail",
 ]
 
 acceptance_tests(
diff --git a/javatests/com/google/gerrit/acceptance/server/mail/ListMailFilterIT.java b/javatests/com/google/gerrit/acceptance/server/mail/ListMailFilterIT.java
index 438954c..13f0416 100644
--- a/javatests/com/google/gerrit/acceptance/server/mail/ListMailFilterIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/mail/ListMailFilterIT.java
@@ -21,8 +21,8 @@
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.common.ChangeMessageInfo;
 import com.google.gerrit.extensions.common.CommentInfo;
-import com.google.gerrit.server.mail.MailUtil;
-import com.google.gerrit.server.mail.receive.MailMessage;
+import com.google.gerrit.mail.MailMessage;
+import com.google.gerrit.mail.MailProcessingUtil;
 import com.google.gerrit.server.mail.receive.MailProcessor;
 import com.google.inject.Inject;
 import java.time.ZoneId;
@@ -100,7 +100,7 @@
     ChangeInfo changeInfo = gApi.changes().id(changeId).get();
     List<CommentInfo> comments = gApi.changes().id(changeId).current().commentsAsList();
     String ts =
-        MailUtil.rfcDateformatter.format(
+        MailProcessingUtil.rfcDateformatter.format(
             ZonedDateTime.ofInstant(comments.get(0).updated.toInstant(), ZoneId.of("UTC")));
 
     // Build Message
diff --git a/javatests/com/google/gerrit/acceptance/server/mail/MailMetadataIT.java b/javatests/com/google/gerrit/acceptance/server/mail/MailMetadataIT.java
index 3315a33..988b051 100644
--- a/javatests/com/google/gerrit/acceptance/server/mail/MailMetadataIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/mail/MailMetadataIT.java
@@ -22,8 +22,8 @@
 import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.extensions.api.changes.ReviewInput;
 import com.google.gerrit.extensions.common.ChangeMessageInfo;
-import com.google.gerrit.server.mail.MailUtil;
-import com.google.gerrit.server.mail.send.EmailHeader;
+import com.google.gerrit.mail.EmailHeader;
+import com.google.gerrit.mail.MailProcessingUtil;
 import com.google.gerrit.testing.FakeEmailSender;
 import com.google.gerrit.testing.TestTimeUtil;
 import java.sql.Timestamp;
@@ -147,7 +147,7 @@
             .contains(
                 entry.getKey()
                     + ": "
-                    + MailUtil.rfcDateformatter.format(
+                    + MailProcessingUtil.rfcDateformatter.format(
                         ZonedDateTime.ofInstant(
                             ((Timestamp) entry.getValue()).toInstant(), ZoneId.of("UTC"))));
       } else {
diff --git a/javatests/com/google/gerrit/acceptance/server/mail/MailProcessorIT.java b/javatests/com/google/gerrit/acceptance/server/mail/MailProcessorIT.java
index f34fe33..b9f30d6 100644
--- a/javatests/com/google/gerrit/acceptance/server/mail/MailProcessorIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/mail/MailProcessorIT.java
@@ -20,8 +20,8 @@
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.common.ChangeMessageInfo;
 import com.google.gerrit.extensions.common.CommentInfo;
-import com.google.gerrit.server.mail.MailUtil;
-import com.google.gerrit.server.mail.receive.MailMessage;
+import com.google.gerrit.mail.MailMessage;
+import com.google.gerrit.mail.MailProcessingUtil;
 import com.google.gerrit.server.mail.receive.MailProcessor;
 import com.google.gerrit.testing.FakeEmailSender.Message;
 import com.google.inject.Inject;
@@ -40,7 +40,7 @@
     ChangeInfo changeInfo = gApi.changes().id(changeId).get();
     List<CommentInfo> comments = gApi.changes().id(changeId).current().commentsAsList();
     String ts =
-        MailUtil.rfcDateformatter.format(
+        MailProcessingUtil.rfcDateformatter.format(
             ZonedDateTime.ofInstant(comments.get(0).updated.toInstant(), ZoneId.of("UTC")));
 
     // Build Message
@@ -68,7 +68,7 @@
     ChangeInfo changeInfo = gApi.changes().id(changeId).get();
     List<CommentInfo> comments = gApi.changes().id(changeId).current().commentsAsList();
     String ts =
-        MailUtil.rfcDateformatter.format(
+        MailProcessingUtil.rfcDateformatter.format(
             ZonedDateTime.ofInstant(comments.get(0).updated.toInstant(), ZoneId.of("UTC")));
 
     // Build Message
@@ -104,7 +104,7 @@
     ChangeInfo changeInfo = gApi.changes().id(changeId).get();
     List<CommentInfo> comments = gApi.changes().id(changeId).current().commentsAsList();
     String ts =
-        MailUtil.rfcDateformatter.format(
+        MailProcessingUtil.rfcDateformatter.format(
             ZonedDateTime.ofInstant(comments.get(0).updated.toInstant(), ZoneId.of("UTC")));
 
     // Build Message
@@ -141,7 +141,7 @@
     ChangeInfo changeInfo = gApi.changes().id(changeId).get();
     List<CommentInfo> comments = gApi.changes().id(changeId).current().commentsAsList();
     String ts =
-        MailUtil.rfcDateformatter.format(
+        MailProcessingUtil.rfcDateformatter.format(
             ZonedDateTime.ofInstant(comments.get(0).updated.toInstant(), ZoneId.of("UTC")));
 
     // Build Message
@@ -171,7 +171,7 @@
     ChangeInfo changeInfo = gApi.changes().id(changeId).get();
     List<CommentInfo> comments = gApi.changes().id(changeId).current().commentsAsList();
     String ts =
-        MailUtil.rfcDateformatter.format(
+        MailProcessingUtil.rfcDateformatter.format(
             ZonedDateTime.ofInstant(comments.get(0).updated.toInstant(), ZoneId.of("UTC")));
     assertThat(comments).hasSize(2);
 
@@ -206,7 +206,7 @@
     List<CommentInfo> comments = gApi.changes().id(changeId).current().commentsAsList();
     assertThat(comments).hasSize(2);
     String ts =
-        MailUtil.rfcDateformatter.format(
+        MailProcessingUtil.rfcDateformatter.format(
             ZonedDateTime.ofInstant(comments.get(0).updated.toInstant(), ZoneId.of("UTC")));
 
     // Build Message
diff --git a/javatests/com/google/gerrit/acceptance/server/mail/MailSenderIT.java b/javatests/com/google/gerrit/acceptance/server/mail/MailSenderIT.java
index 4f51e1f..8d21b5b 100644
--- a/javatests/com/google/gerrit/acceptance/server/mail/MailSenderIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/mail/MailSenderIT.java
@@ -17,7 +17,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import com.google.gerrit.acceptance.GerritConfig;
-import com.google.gerrit.server.mail.send.EmailHeader;
+import com.google.gerrit.mail.EmailHeader;
 import java.net.URI;
 import java.util.Map;
 import org.junit.Test;
diff --git a/javatests/com/google/gerrit/acceptance/server/project/BUILD b/javatests/com/google/gerrit/acceptance/server/project/BUILD
index efa1cdb..42dfbac 100644
--- a/javatests/com/google/gerrit/acceptance/server/project/BUILD
+++ b/javatests/com/google/gerrit/acceptance/server/project/BUILD
@@ -4,4 +4,5 @@
     srcs = glob(["*IT.java"]),
     group = "server_project",
     labels = ["server"],
+    deps = ["//java/com/google/gerrit/mail"],
 )
diff --git a/javatests/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java b/javatests/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java
index ca0cae4..cfdd781 100644
--- a/javatests/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java
@@ -26,11 +26,11 @@
 import com.google.gerrit.extensions.api.changes.ReviewInput;
 import com.google.gerrit.extensions.api.changes.StarsInput;
 import com.google.gerrit.extensions.common.GroupInfo;
+import com.google.gerrit.mail.Address;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.account.ProjectWatches.NotifyType;
 import com.google.gerrit.server.git.NotifyConfig;
-import com.google.gerrit.server.mail.Address;
 import com.google.gerrit.testing.FakeEmailSender.Message;
 import java.util.EnumSet;
 import java.util.List;
diff --git a/javatests/com/google/gerrit/acceptance/tests.bzl b/javatests/com/google/gerrit/acceptance/tests.bzl
index 4b3b802d..08556a0 100644
--- a/javatests/com/google/gerrit/acceptance/tests.bzl
+++ b/javatests/com/google/gerrit/acceptance/tests.bzl
@@ -1,21 +1,21 @@
 load("//tools/bzl:junit.bzl", "junit_tests")
 
 def acceptance_tests(
-    group,
-    deps = [],
-    labels = [],
-    vm_args = ['-Xmx256m'],
-    **kwargs):
-  junit_tests(
-    name = group,
-    deps = deps + [
-      '//java/com/google/gerrit/acceptance:lib',
-    ],
-    tags = labels + [
-      'acceptance',
-      'slow',
-    ],
-    size = "large",
-    jvm_flags = vm_args,
-    **kwargs
-  )
+        group,
+        deps = [],
+        labels = [],
+        vm_args = ["-Xmx256m"],
+        **kwargs):
+    junit_tests(
+        name = group,
+        deps = deps + [
+            "//java/com/google/gerrit/acceptance:lib",
+        ],
+        tags = labels + [
+            "acceptance",
+            "slow",
+        ],
+        size = "large",
+        jvm_flags = vm_args,
+        **kwargs
+    )
diff --git a/javatests/com/google/gerrit/common/BUILD b/javatests/com/google/gerrit/common/BUILD
index ba9a5bc..c4f2c0a 100644
--- a/javatests/com/google/gerrit/common/BUILD
+++ b/javatests/com/google/gerrit/common/BUILD
@@ -1,32 +1,14 @@
 load("//tools/bzl:junit.bzl", "junit_tests")
 
-SERVER_TEST_SRCS = [
-    "AutoValueTest.java",
-    "VersionTest.java",
-]
-
 junit_tests(
-    name = "client_tests",
-    srcs = glob(
-        ["**/*.java"],
-        exclude = SERVER_TEST_SRCS,
-    ),
-    deps = [
-        "//java/com/google/gerrit/common:client",
-        "//lib:guava",
-        "//lib:junit",
-        "//lib/truth",
-    ],
-)
-
-junit_tests(
-    name = "server_tests",
-    srcs = SERVER_TEST_SRCS,
+    name = "common_tests",
+    srcs = glob(["**/*.java"]),
     tags = ["no_windows"],
     deps = [
         "//java/com/google/gerrit/common:server",
         "//java/com/google/gerrit/common:version",
         "//java/com/google/gerrit/launcher",
+        "//java/com/google/gerrit/reviewdb:server",
         "//lib:guava",
         "//lib/auto:auto-value",
         "//lib/auto:auto-value-annotations",
diff --git a/javatests/com/google/gerrit/common/data/GroupReferenceTest.java b/javatests/com/google/gerrit/common/data/GroupReferenceTest.java
new file mode 100644
index 0000000..717e122
--- /dev/null
+++ b/javatests/com/google/gerrit/common/data/GroupReferenceTest.java
@@ -0,0 +1,157 @@
+// Copyright (C) 2018 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.common.data;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.client.AccountGroup.UUID;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+public class GroupReferenceTest {
+  @Rule public ExpectedException exception = ExpectedException.none();
+
+  @Test
+  public void forGroupDescription() {
+    String name = "foo";
+    AccountGroup.UUID uuid = new AccountGroup.UUID("uuid-foo");
+    GroupReference groupReference =
+        GroupReference.forGroup(
+            new GroupDescription.Basic() {
+
+              @Override
+              public String getUrl() {
+                return null;
+              }
+
+              @Override
+              public String getName() {
+                return name;
+              }
+
+              @Override
+              public UUID getGroupUUID() {
+                return uuid;
+              }
+
+              @Override
+              public String getEmailAddress() {
+                return null;
+              }
+            });
+    assertThat(groupReference.getName()).isEqualTo(name);
+    assertThat(groupReference.getUUID()).isEqualTo(uuid);
+  }
+
+  @Test
+  public void create() {
+    AccountGroup.UUID uuid = new AccountGroup.UUID("uuid");
+    String name = "foo";
+    GroupReference groupReference = new GroupReference(uuid, name);
+    assertThat(groupReference.getUUID()).isEqualTo(uuid);
+    assertThat(groupReference.getName()).isEqualTo(name);
+  }
+
+  @Test
+  public void createWithoutUuid() {
+    // GroupReferences where the UUID is null are used to represent groups from project.config that
+    // cannot be resolved.
+    String name = "foo";
+    GroupReference groupReference = new GroupReference(null, name);
+    assertThat(groupReference.getUUID()).isNull();
+    assertThat(groupReference.getName()).isEqualTo(name);
+  }
+
+  @Test
+  public void cannotCreateWithoutName() {
+    exception.expect(NullPointerException.class);
+    new GroupReference(new AccountGroup.UUID("uuid"), null);
+  }
+
+  @Test
+  public void isGroupReference() {
+    assertThat(GroupReference.isGroupReference("foo")).isFalse();
+    assertThat(GroupReference.isGroupReference("groupfoo")).isFalse();
+    assertThat(GroupReference.isGroupReference("group foo")).isTrue();
+    assertThat(GroupReference.isGroupReference("group foo-bar")).isTrue();
+    assertThat(GroupReference.isGroupReference("group foo bar")).isTrue();
+  }
+
+  @Test
+  public void extractGroupName() {
+    assertThat(GroupReference.extractGroupName("foo")).isNull();
+    assertThat(GroupReference.extractGroupName("groupfoo")).isNull();
+    assertThat(GroupReference.extractGroupName("group foo")).isEqualTo("foo");
+    assertThat(GroupReference.extractGroupName("group foo-bar")).isEqualTo("foo-bar");
+    assertThat(GroupReference.extractGroupName("group foo bar")).isEqualTo("foo bar");
+  }
+
+  @Test
+  public void getAndSetUuid() {
+    AccountGroup.UUID uuid = new AccountGroup.UUID("uuid-foo");
+    String name = "foo";
+    GroupReference groupReference = new GroupReference(uuid, name);
+    assertThat(groupReference.getUUID()).isEqualTo(uuid);
+
+    AccountGroup.UUID uuid2 = new AccountGroup.UUID("uuid-bar");
+    groupReference.setUUID(uuid2);
+    assertThat(groupReference.getUUID()).isEqualTo(uuid2);
+
+    // GroupReferences where the UUID is null are used to represent groups from project.config that
+    // cannot be resolved.
+    groupReference.setUUID(null);
+    assertThat(groupReference.getUUID()).isNull();
+  }
+
+  @Test
+  public void getAndSetName() {
+    AccountGroup.UUID uuid = new AccountGroup.UUID("uuid-foo");
+    String name = "foo";
+    GroupReference groupReference = new GroupReference(uuid, name);
+    assertThat(groupReference.getName()).isEqualTo(name);
+
+    String name2 = "bar";
+    groupReference.setName(name2);
+    assertThat(groupReference.getName()).isEqualTo(name2);
+
+    exception.expect(NullPointerException.class);
+    groupReference.setName(null);
+  }
+
+  @Test
+  public void toConfigValue() {
+    String name = "foo";
+    GroupReference groupReference = new GroupReference(new AccountGroup.UUID("uuid-foo"), name);
+    assertThat(groupReference.toConfigValue()).isEqualTo("group " + name);
+  }
+
+  @Test
+  public void testEquals() {
+    AccountGroup.UUID uuid1 = new AccountGroup.UUID("uuid-foo");
+    AccountGroup.UUID uuid2 = new AccountGroup.UUID("uuid-bar");
+    String name1 = "foo";
+    String name2 = "bar";
+
+    GroupReference groupReference1 = new GroupReference(uuid1, name1);
+    GroupReference groupReference2 = new GroupReference(uuid1, name2);
+    GroupReference groupReference3 = new GroupReference(uuid2, name1);
+
+    assertThat(groupReference1.equals(groupReference2)).isTrue();
+    assertThat(groupReference1.equals(groupReference3)).isFalse();
+    assertThat(groupReference2.equals(groupReference3)).isFalse();
+  }
+}
diff --git a/javatests/com/google/gerrit/common/data/PermissionRuleTest.java b/javatests/com/google/gerrit/common/data/PermissionRuleTest.java
new file mode 100644
index 0000000..14c47b4
--- /dev/null
+++ b/javatests/com/google/gerrit/common/data/PermissionRuleTest.java
@@ -0,0 +1,399 @@
+// Copyright (C) 2018 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.common.data;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.common.data.PermissionRule.Action;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+public class PermissionRuleTest {
+  @Rule public ExpectedException exception = ExpectedException.none();
+
+  private GroupReference groupReference;
+  private PermissionRule permissionRule;
+
+  @Before
+  public void setup() {
+    this.groupReference = new GroupReference(new AccountGroup.UUID("uuid"), "group");
+    this.permissionRule = new PermissionRule(groupReference);
+  }
+
+  @Test
+  public void getAndSetAction() {
+    assertThat(permissionRule.getAction()).isEqualTo(Action.ALLOW);
+
+    permissionRule.setAction(Action.DENY);
+    assertThat(permissionRule.getAction()).isEqualTo(Action.DENY);
+  }
+
+  @Test
+  public void cannotSetActionToNull() {
+    exception.expect(NullPointerException.class);
+    permissionRule.setAction(null);
+  }
+
+  @Test
+  public void setDeny() {
+    assertThat(permissionRule.isDeny()).isFalse();
+
+    permissionRule.setDeny();
+    assertThat(permissionRule.isDeny()).isTrue();
+  }
+
+  @Test
+  public void setBlock() {
+    assertThat(permissionRule.isBlock()).isFalse();
+
+    permissionRule.setBlock();
+    assertThat(permissionRule.isBlock()).isTrue();
+  }
+
+  @Test
+  public void setForce() {
+    assertThat(permissionRule.getForce()).isFalse();
+
+    permissionRule.setForce(true);
+    assertThat(permissionRule.getForce()).isTrue();
+
+    permissionRule.setForce(false);
+    assertThat(permissionRule.getForce()).isFalse();
+  }
+
+  @Test
+  public void setMin() {
+    assertThat(permissionRule.getMin()).isEqualTo(0);
+
+    permissionRule.setMin(-2);
+    assertThat(permissionRule.getMin()).isEqualTo(-2);
+
+    permissionRule.setMin(2);
+    assertThat(permissionRule.getMin()).isEqualTo(2);
+  }
+
+  @Test
+  public void setMax() {
+    assertThat(permissionRule.getMax()).isEqualTo(0);
+
+    permissionRule.setMax(2);
+    assertThat(permissionRule.getMax()).isEqualTo(2);
+
+    permissionRule.setMax(-2);
+    assertThat(permissionRule.getMax()).isEqualTo(-2);
+  }
+
+  @Test
+  public void setRange() {
+    assertThat(permissionRule.getMin()).isEqualTo(0);
+    assertThat(permissionRule.getMax()).isEqualTo(0);
+
+    permissionRule.setRange(-2, 2);
+    assertThat(permissionRule.getMin()).isEqualTo(-2);
+    assertThat(permissionRule.getMax()).isEqualTo(2);
+
+    permissionRule.setRange(2, -2);
+    assertThat(permissionRule.getMin()).isEqualTo(-2);
+    assertThat(permissionRule.getMax()).isEqualTo(2);
+
+    permissionRule.setRange(1, 1);
+    assertThat(permissionRule.getMin()).isEqualTo(1);
+    assertThat(permissionRule.getMax()).isEqualTo(1);
+  }
+
+  @Test
+  public void hasRange() {
+    assertThat(permissionRule.hasRange()).isFalse();
+
+    permissionRule.setMin(-1);
+    assertThat(permissionRule.hasRange()).isTrue();
+
+    permissionRule.setMax(1);
+    assertThat(permissionRule.hasRange()).isTrue();
+  }
+
+  @Test
+  public void getGroup() {
+    assertThat(permissionRule.getGroup()).isEqualTo(groupReference);
+  }
+
+  @Test
+  public void setGroup() {
+    GroupReference groupReference2 = new GroupReference(new AccountGroup.UUID("uuid2"), "group2");
+    assertThat(groupReference2).isNotEqualTo(groupReference);
+
+    assertThat(permissionRule.getGroup()).isEqualTo(groupReference);
+
+    permissionRule.setGroup(groupReference2);
+    assertThat(permissionRule.getGroup()).isEqualTo(groupReference2);
+  }
+
+  @Test
+  public void mergeFromAnyBlock() {
+    GroupReference groupReference1 = new GroupReference(new AccountGroup.UUID("uuid1"), "group1");
+    PermissionRule permissionRule1 = new PermissionRule(groupReference1);
+
+    GroupReference groupReference2 = new GroupReference(new AccountGroup.UUID("uuid2"), "group2");
+    PermissionRule permissionRule2 = new PermissionRule(groupReference2);
+
+    permissionRule1.mergeFrom(permissionRule2);
+    assertThat(permissionRule1.isBlock()).isFalse();
+    assertThat(permissionRule2.isBlock()).isFalse();
+
+    permissionRule2.setBlock();
+    permissionRule1.mergeFrom(permissionRule2);
+    assertThat(permissionRule1.isBlock()).isTrue();
+    assertThat(permissionRule2.isBlock()).isTrue();
+
+    permissionRule2.setDeny();
+    permissionRule1.mergeFrom(permissionRule2);
+    assertThat(permissionRule1.isBlock()).isTrue();
+    assertThat(permissionRule2.isBlock()).isFalse();
+
+    permissionRule2.setAction(Action.BATCH);
+    permissionRule1.mergeFrom(permissionRule2);
+    assertThat(permissionRule1.isBlock()).isTrue();
+    assertThat(permissionRule2.isBlock()).isFalse();
+  }
+
+  @Test
+  public void mergeFromAnyDeny() {
+    GroupReference groupReference1 = new GroupReference(new AccountGroup.UUID("uuid1"), "group1");
+    PermissionRule permissionRule1 = new PermissionRule(groupReference1);
+
+    GroupReference groupReference2 = new GroupReference(new AccountGroup.UUID("uuid2"), "group2");
+    PermissionRule permissionRule2 = new PermissionRule(groupReference2);
+
+    permissionRule1.mergeFrom(permissionRule2);
+    assertThat(permissionRule1.isDeny()).isFalse();
+    assertThat(permissionRule2.isDeny()).isFalse();
+
+    permissionRule2.setDeny();
+    permissionRule1.mergeFrom(permissionRule2);
+    assertThat(permissionRule1.isDeny()).isTrue();
+    assertThat(permissionRule2.isDeny()).isTrue();
+
+    permissionRule2.setAction(Action.BATCH);
+    permissionRule1.mergeFrom(permissionRule2);
+    assertThat(permissionRule1.isDeny()).isTrue();
+    assertThat(permissionRule2.isDeny()).isFalse();
+  }
+
+  @Test
+  public void mergeFromAnyBatch() {
+    GroupReference groupReference1 = new GroupReference(new AccountGroup.UUID("uuid1"), "group1");
+    PermissionRule permissionRule1 = new PermissionRule(groupReference1);
+
+    GroupReference groupReference2 = new GroupReference(new AccountGroup.UUID("uuid2"), "group2");
+    PermissionRule permissionRule2 = new PermissionRule(groupReference2);
+
+    permissionRule1.mergeFrom(permissionRule2);
+    assertThat(permissionRule1.getAction()).isNotEqualTo(Action.BATCH);
+    assertThat(permissionRule2.getAction()).isNotEqualTo(Action.BATCH);
+
+    permissionRule2.setAction(Action.BATCH);
+    permissionRule1.mergeFrom(permissionRule2);
+    assertThat(permissionRule1.getAction()).isEqualTo(Action.BATCH);
+    assertThat(permissionRule2.getAction()).isEqualTo(Action.BATCH);
+
+    permissionRule2.setAction(Action.ALLOW);
+    permissionRule1.mergeFrom(permissionRule2);
+    assertThat(permissionRule1.getAction()).isEqualTo(Action.BATCH);
+    assertThat(permissionRule2.getAction()).isNotEqualTo(Action.BATCH);
+  }
+
+  @Test
+  public void mergeFromAnyForce() {
+    GroupReference groupReference1 = new GroupReference(new AccountGroup.UUID("uuid1"), "group1");
+    PermissionRule permissionRule1 = new PermissionRule(groupReference1);
+
+    GroupReference groupReference2 = new GroupReference(new AccountGroup.UUID("uuid2"), "group2");
+    PermissionRule permissionRule2 = new PermissionRule(groupReference2);
+
+    permissionRule1.mergeFrom(permissionRule2);
+    assertThat(permissionRule1.getForce()).isFalse();
+    assertThat(permissionRule2.getForce()).isFalse();
+
+    permissionRule2.setForce(true);
+    permissionRule1.mergeFrom(permissionRule2);
+    assertThat(permissionRule1.getForce()).isTrue();
+    assertThat(permissionRule2.getForce()).isTrue();
+
+    permissionRule2.setForce(false);
+    permissionRule1.mergeFrom(permissionRule2);
+    assertThat(permissionRule1.getForce()).isTrue();
+    assertThat(permissionRule2.getForce()).isFalse();
+  }
+
+  @Test
+  public void mergeFromMergeRange() {
+    GroupReference groupReference1 = new GroupReference(new AccountGroup.UUID("uuid1"), "group1");
+    PermissionRule permissionRule1 = new PermissionRule(groupReference1);
+    permissionRule1.setRange(-1, 2);
+
+    GroupReference groupReference2 = new GroupReference(new AccountGroup.UUID("uuid2"), "group2");
+    PermissionRule permissionRule2 = new PermissionRule(groupReference2);
+    permissionRule2.setRange(-2, 1);
+
+    permissionRule1.mergeFrom(permissionRule2);
+    assertThat(permissionRule1.getMin()).isEqualTo(-2);
+    assertThat(permissionRule1.getMax()).isEqualTo(2);
+    assertThat(permissionRule2.getMin()).isEqualTo(-2);
+    assertThat(permissionRule2.getMax()).isEqualTo(1);
+  }
+
+  @Test
+  public void mergeFromGroupNotChanged() {
+    GroupReference groupReference1 = new GroupReference(new AccountGroup.UUID("uuid1"), "group1");
+    PermissionRule permissionRule1 = new PermissionRule(groupReference1);
+
+    GroupReference groupReference2 = new GroupReference(new AccountGroup.UUID("uuid2"), "group2");
+    PermissionRule permissionRule2 = new PermissionRule(groupReference2);
+
+    permissionRule1.mergeFrom(permissionRule2);
+    assertThat(permissionRule1.getGroup()).isEqualTo(groupReference1);
+    assertThat(permissionRule2.getGroup()).isEqualTo(groupReference2);
+  }
+
+  @Test
+  public void asString() {
+    assertThat(permissionRule.asString(true)).isEqualTo("group " + groupReference.getName());
+
+    permissionRule.setDeny();
+    assertThat(permissionRule.asString(true)).isEqualTo("deny group " + groupReference.getName());
+
+    permissionRule.setBlock();
+    assertThat(permissionRule.asString(true)).isEqualTo("block group " + groupReference.getName());
+
+    permissionRule.setAction(Action.BATCH);
+    assertThat(permissionRule.asString(true)).isEqualTo("batch group " + groupReference.getName());
+
+    permissionRule.setAction(Action.INTERACTIVE);
+    assertThat(permissionRule.asString(true))
+        .isEqualTo("interactive group " + groupReference.getName());
+
+    permissionRule.setForce(true);
+    assertThat(permissionRule.asString(true))
+        .isEqualTo("interactive +force group " + groupReference.getName());
+
+    permissionRule.setAction(Action.ALLOW);
+    assertThat(permissionRule.asString(true)).isEqualTo("+force group " + groupReference.getName());
+
+    permissionRule.setMax(1);
+    assertThat(permissionRule.asString(true))
+        .isEqualTo("+force +0..+1 group " + groupReference.getName());
+
+    permissionRule.setMin(-1);
+    assertThat(permissionRule.asString(true))
+        .isEqualTo("+force -1..+1 group " + groupReference.getName());
+
+    assertThat(permissionRule.asString(false))
+        .isEqualTo("+force group " + groupReference.getName());
+  }
+
+  @Test
+  public void fromString() {
+    PermissionRule permissionRule = PermissionRule.fromString("group A", true);
+    assertPermissionRule(permissionRule, "A", Action.ALLOW, false, 0, 0);
+
+    permissionRule = PermissionRule.fromString("deny group A", true);
+    assertPermissionRule(permissionRule, "A", Action.DENY, false, 0, 0);
+
+    permissionRule = PermissionRule.fromString("block group A", true);
+    assertPermissionRule(permissionRule, "A", Action.BLOCK, false, 0, 0);
+
+    permissionRule = PermissionRule.fromString("batch group A", true);
+    assertPermissionRule(permissionRule, "A", Action.BATCH, false, 0, 0);
+
+    permissionRule = PermissionRule.fromString("interactive group A", true);
+    assertPermissionRule(permissionRule, "A", Action.INTERACTIVE, false, 0, 0);
+
+    permissionRule = PermissionRule.fromString("interactive +force group A", true);
+    assertPermissionRule(permissionRule, "A", Action.INTERACTIVE, true, 0, 0);
+
+    permissionRule = PermissionRule.fromString("+force group A", true);
+    assertPermissionRule(permissionRule, "A", Action.ALLOW, true, 0, 0);
+
+    permissionRule = PermissionRule.fromString("+force +0..+1 group A", true);
+    assertPermissionRule(permissionRule, "A", Action.ALLOW, true, 0, 1);
+
+    permissionRule = PermissionRule.fromString("+force -1..+1 group A", true);
+    assertPermissionRule(permissionRule, "A", Action.ALLOW, true, -1, 1);
+
+    permissionRule = PermissionRule.fromString("+force group A", false);
+    assertPermissionRule(permissionRule, "A", Action.ALLOW, true, 0, 0);
+  }
+
+  @Test
+  public void parseInt() {
+    assertThat(PermissionRule.parseInt("0")).isEqualTo(0);
+    assertThat(PermissionRule.parseInt("+0")).isEqualTo(0);
+    assertThat(PermissionRule.parseInt("-0")).isEqualTo(0);
+    assertThat(PermissionRule.parseInt("1")).isEqualTo(1);
+    assertThat(PermissionRule.parseInt("+1")).isEqualTo(1);
+    assertThat(PermissionRule.parseInt("-1")).isEqualTo(-1);
+  }
+
+  @Test
+  public void testEquals() {
+    GroupReference groupReference2 = new GroupReference(new AccountGroup.UUID("uuid2"), "group2");
+    PermissionRule permissionRuleOther = new PermissionRule(groupReference2);
+    assertThat(permissionRule.equals(permissionRuleOther)).isFalse();
+
+    permissionRuleOther.setGroup(groupReference);
+    assertThat(permissionRule.equals(permissionRuleOther)).isTrue();
+
+    permissionRule.setDeny();
+    assertThat(permissionRule.equals(permissionRuleOther)).isFalse();
+
+    permissionRuleOther.setDeny();
+    assertThat(permissionRule.equals(permissionRuleOther)).isTrue();
+
+    permissionRule.setForce(true);
+    assertThat(permissionRule.equals(permissionRuleOther)).isFalse();
+
+    permissionRuleOther.setForce(true);
+    assertThat(permissionRule.equals(permissionRuleOther)).isTrue();
+
+    permissionRule.setMin(-1);
+    assertThat(permissionRule.equals(permissionRuleOther)).isFalse();
+
+    permissionRuleOther.setMin(-1);
+    assertThat(permissionRule.equals(permissionRuleOther)).isTrue();
+
+    permissionRule.setMax(1);
+    assertThat(permissionRule.equals(permissionRuleOther)).isFalse();
+
+    permissionRuleOther.setMax(1);
+    assertThat(permissionRule.equals(permissionRuleOther)).isTrue();
+  }
+
+  private void assertPermissionRule(
+      PermissionRule permissionRule,
+      String expectedGroupName,
+      Action expectedAction,
+      boolean expectedForce,
+      int expectedMin,
+      int expectedMax) {
+    assertThat(permissionRule.getGroup().getName()).isEqualTo(expectedGroupName);
+    assertThat(permissionRule.getAction()).isEqualTo(expectedAction);
+    assertThat(permissionRule.getForce()).isEqualTo(expectedForce);
+    assertThat(permissionRule.getMin()).isEqualTo(expectedMin);
+    assertThat(permissionRule.getMax()).isEqualTo(expectedMax);
+  }
+}
diff --git a/javatests/com/google/gerrit/elasticsearch/BUILD b/javatests/com/google/gerrit/elasticsearch/BUILD
index 1249909..fe9d516 100644
--- a/javatests/com/google/gerrit/elasticsearch/BUILD
+++ b/javatests/com/google/gerrit/elasticsearch/BUILD
@@ -10,49 +10,84 @@
     visibility = ["//visibility:public"],
     deps = [
         "//java/com/google/gerrit/elasticsearch",
-        "//java/com/google/gerrit/extensions:api",
         "//java/com/google/gerrit/index",
-        "//java/com/google/gerrit/index/project",
-        "//java/com/google/gerrit/reviewdb:server",
         "//java/com/google/gerrit/server",
-        "//lib:gson",
         "//lib:guava",
         "//lib:junit",
         "//lib/guice",
         "//lib/httpcomponents:httpcore",
         "//lib/jgit/org.eclipse.jgit:jgit",
-        "//lib/jgit/org.eclipse.jgit.junit:junit",
         "//lib/testcontainers",
-        "//lib/truth",
     ],
 )
 
-ELASTICSEARCH_TESTS = {i: "ElasticQuery" + i.capitalize() + "sTest.java" for i in [
+ELASTICSEARCH_DEPS = [
+    ":elasticsearch_test_utils",
+    "//java/com/google/gerrit/elasticsearch",
+    "//java/com/google/gerrit/testing:gerrit-test-util",
+    "//lib/guice",
+    "//lib/jgit/org.eclipse.jgit:jgit",
+]
+
+QUERY_TESTS_DEP = "//javatests/com/google/gerrit/server/query/%s:abstract_query_tests"
+
+TYPES = [
     "account",
     "change",
     "group",
     "project",
-]}
+]
+
+SUFFIX = "sTest.java"
+
+ELASTICSEARCH_TESTS = {i: "ElasticQuery" + i.capitalize() + SUFFIX for i in TYPES}
+
+ELASTICSEARCH_TESTS_V5 = {i: "ElasticV5Query" + i.capitalize() + SUFFIX for i in TYPES}
+
+ELASTICSEARCH_TESTS_V6 = {i: "ElasticV6Query" + i.capitalize() + SUFFIX for i in TYPES}
+
+ELASTICSEARCH_TAGS = [
+    "docker",
+    "elastic",
+]
 
 [junit_tests(
-    name = "elasticsearch_%ss_test" % name,
+    name = "elasticsearch_query_%ss_test" % name,
     size = "large",
     srcs = [src],
-    tags = [
-        "docker",
-        "elastic",
-    ],
-    deps = [
-        ":elasticsearch_test_utils",
-        "//java/com/google/gerrit/elasticsearch",
-        "//java/com/google/gerrit/server",
-        "//java/com/google/gerrit/server/project/testing:project-test-util",
-        "//java/com/google/gerrit/testing:gerrit-test-util",
-        "//javatests/com/google/gerrit/server/query/%s:abstract_query_tests" % name,
-        "//lib/guice",
-        "//lib/httpcomponents:httpcore",
-        "//lib/jgit/org.eclipse.jgit:jgit",
-        "//lib/jgit/org.eclipse.jgit.junit:junit",
-        "//lib/testcontainers",
-    ],
+    tags = ELASTICSEARCH_TAGS,
+    deps = ELASTICSEARCH_DEPS + [QUERY_TESTS_DEP % name],
 ) for name, src in ELASTICSEARCH_TESTS.items()]
+
+[junit_tests(
+    name = "elasticsearch_query_%ss_test_V5" % name,
+    size = "large",
+    srcs = [src],
+    tags = ELASTICSEARCH_TAGS,
+    deps = ELASTICSEARCH_DEPS + [QUERY_TESTS_DEP % name],
+) for name, src in ELASTICSEARCH_TESTS_V5.items()]
+
+[junit_tests(
+    name = "elasticsearch_query_%ss_test_V6" % name,
+    size = "large",
+    srcs = [src],
+    tags = ELASTICSEARCH_TAGS + ["flaky"],
+    deps = ELASTICSEARCH_DEPS + [QUERY_TESTS_DEP % name],
+) for name, src in ELASTICSEARCH_TESTS_V6.items()]
+
+junit_tests(
+    name = "elasticsearch_tests",
+    size = "small",
+    srcs = glob(
+        ["*Test.java"],
+        exclude = ["Elastic*Query*" + SUFFIX],
+    ),
+    tags = ["elastic"],
+    deps = [
+        "//java/com/google/gerrit/elasticsearch",
+        "//lib:guava",
+        "//lib/guice",
+        "//lib/jgit/org.eclipse.jgit:jgit",
+        "//lib/truth",
+    ],
+)
diff --git a/javatests/com/google/gerrit/server/mail/receive/AbstractParserTest.java b/javatests/com/google/gerrit/mail/AbstractParserTest.java
similarity index 97%
rename from javatests/com/google/gerrit/server/mail/receive/AbstractParserTest.java
rename to javatests/com/google/gerrit/mail/AbstractParserTest.java
index 0e894a6..5219ce8 100644
--- a/javatests/com/google/gerrit/server/mail/receive/AbstractParserTest.java
+++ b/javatests/com/google/gerrit/mail/AbstractParserTest.java
@@ -12,13 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail.receive;
+package com.google.gerrit.mail;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Comment;
-import com.google.gerrit.server.mail.Address;
 import java.sql.Timestamp;
 import java.time.Instant;
 import java.util.ArrayList;
diff --git a/javatests/com/google/gerrit/server/mail/AddressTest.java b/javatests/com/google/gerrit/mail/AddressTest.java
similarity index 81%
rename from javatests/com/google/gerrit/server/mail/AddressTest.java
rename to javatests/com/google/gerrit/mail/AddressTest.java
index 7dbd563..53ff1fe 100644
--- a/javatests/com/google/gerrit/server/mail/AddressTest.java
+++ b/javatests/com/google/gerrit/mail/AddressTest.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail;
+package com.google.gerrit.mail;
 
 import static com.google.common.truth.Truth.assertThat;
 import static org.junit.Assert.fail;
@@ -24,57 +24,57 @@
   @Test
   public void parse_NameEmail1() {
     final Address a = Address.parse("A U Thor <author@example.com>");
-    assertThat(a.name).isEqualTo("A U Thor");
-    assertThat(a.email).isEqualTo("author@example.com");
+    assertThat(a.getName()).isEqualTo("A U Thor");
+    assertThat(a.getEmail()).isEqualTo("author@example.com");
   }
 
   @Test
   public void parse_NameEmail2() {
     final Address a = Address.parse("A <a@b>");
-    assertThat(a.name).isEqualTo("A");
-    assertThat(a.email).isEqualTo("a@b");
+    assertThat(a.getName()).isEqualTo("A");
+    assertThat(a.getEmail()).isEqualTo("a@b");
   }
 
   @Test
   public void parse_NameEmail3() {
     final Address a = Address.parse("<a@b>");
-    assertThat(a.name).isNull();
-    assertThat(a.email).isEqualTo("a@b");
+    assertThat(a.getName()).isNull();
+    assertThat(a.getEmail()).isEqualTo("a@b");
   }
 
   @Test
   public void parse_NameEmail4() {
     final Address a = Address.parse("A U Thor<author@example.com>");
-    assertThat(a.name).isEqualTo("A U Thor");
-    assertThat(a.email).isEqualTo("author@example.com");
+    assertThat(a.getName()).isEqualTo("A U Thor");
+    assertThat(a.getEmail()).isEqualTo("author@example.com");
   }
 
   @Test
   public void parse_NameEmail5() {
     final Address a = Address.parse("A U Thor  <author@example.com>");
-    assertThat(a.name).isEqualTo("A U Thor");
-    assertThat(a.email).isEqualTo("author@example.com");
+    assertThat(a.getName()).isEqualTo("A U Thor");
+    assertThat(a.getEmail()).isEqualTo("author@example.com");
   }
 
   @Test
   public void parse_Email1() {
     final Address a = Address.parse("author@example.com");
-    assertThat(a.name).isNull();
-    assertThat(a.email).isEqualTo("author@example.com");
+    assertThat(a.getName()).isNull();
+    assertThat(a.getEmail()).isEqualTo("author@example.com");
   }
 
   @Test
   public void parse_Email2() {
     final Address a = Address.parse("a@b");
-    assertThat(a.name).isNull();
-    assertThat(a.email).isEqualTo("a@b");
+    assertThat(a.getName()).isNull();
+    assertThat(a.getEmail()).isEqualTo("a@b");
   }
 
   @Test
   public void parse_NewTLD() {
     Address a = Address.parse("A U Thor <author@example.systems>");
-    assertThat(a.name).isEqualTo("A U Thor");
-    assertThat(a.email).isEqualTo("author@example.systems");
+    assertThat(a.getName()).isEqualTo("A U Thor");
+    assertThat(a.getEmail()).isEqualTo("author@example.systems");
   }
 
   @Test
diff --git a/javatests/com/google/gerrit/mail/BUILD b/javatests/com/google/gerrit/mail/BUILD
new file mode 100644
index 0000000..488bbcc
--- /dev/null
+++ b/javatests/com/google/gerrit/mail/BUILD
@@ -0,0 +1,36 @@
+load("//tools/bzl:junit.bzl", "junit_tests")
+
+junit_tests(
+    name = "maillib_tests",
+    size = "small",
+    srcs = glob(
+        ["**/*.java"],
+    ),
+    visibility = ["//visibility:public"],
+    deps = [
+        "//java/com/google/gerrit/common:annotations",
+        "//java/com/google/gerrit/common:server",
+        "//java/com/google/gerrit/extensions:api",
+        "//java/com/google/gerrit/extensions/common/testing:common-test-util",
+        "//java/com/google/gerrit/index",
+        "//java/com/google/gerrit/index:query_exception",
+        "//java/com/google/gerrit/lifecycle",
+        "//java/com/google/gerrit/mail",
+        "//java/com/google/gerrit/metrics",
+        "//java/com/google/gerrit/reviewdb:server",
+        "//java/com/google/gerrit/server",
+        "//java/com/google/gerrit/server/project/testing:project-test-util",
+        "//java/com/google/gerrit/testing:gerrit-test-util",
+        "//java/org/eclipse/jgit:server",
+        "//lib:grappa",
+        "//lib:gson",
+        "//lib:guava-retrying",
+        "//lib:gwtorm",
+        "//lib/commons:codec",
+        "//lib/guice",
+        "//lib/jgit/org.eclipse.jgit:jgit",
+        "//lib/jgit/org.eclipse.jgit.junit:junit",
+        "//lib/truth",
+        "//lib/truth:truth-java8-extension",
+    ],
+)
diff --git a/javatests/com/google/gerrit/server/mail/receive/GenericHtmlParserTest.java b/javatests/com/google/gerrit/mail/GenericHtmlParserTest.java
similarity index 98%
rename from javatests/com/google/gerrit/server/mail/receive/GenericHtmlParserTest.java
rename to javatests/com/google/gerrit/mail/GenericHtmlParserTest.java
index f78953d..4718bad 100644
--- a/javatests/com/google/gerrit/server/mail/receive/GenericHtmlParserTest.java
+++ b/javatests/com/google/gerrit/mail/GenericHtmlParserTest.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail.receive;
+package com.google.gerrit.mail;
 
 /** Test parser for a generic Html email client response */
 public class GenericHtmlParserTest extends HtmlParserTest {
diff --git a/javatests/com/google/gerrit/server/mail/receive/GmailHtmlParserTest.java b/javatests/com/google/gerrit/mail/GmailHtmlParserTest.java
similarity index 98%
rename from javatests/com/google/gerrit/server/mail/receive/GmailHtmlParserTest.java
rename to javatests/com/google/gerrit/mail/GmailHtmlParserTest.java
index df71629..f597dee 100644
--- a/javatests/com/google/gerrit/server/mail/receive/GmailHtmlParserTest.java
+++ b/javatests/com/google/gerrit/mail/GmailHtmlParserTest.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail.receive;
+package com.google.gerrit.mail;
 
 public class GmailHtmlParserTest extends HtmlParserTest {
   @Override
diff --git a/javatests/com/google/gerrit/server/mail/receive/HtmlParserTest.java b/javatests/com/google/gerrit/mail/HtmlParserTest.java
similarity index 98%
rename from javatests/com/google/gerrit/server/mail/receive/HtmlParserTest.java
rename to javatests/com/google/gerrit/mail/HtmlParserTest.java
index d88e09f..d630bd6 100644
--- a/javatests/com/google/gerrit/server/mail/receive/HtmlParserTest.java
+++ b/javatests/com/google/gerrit/mail/HtmlParserTest.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail.receive;
+package com.google.gerrit.mail;
 
 import static com.google.common.truth.Truth.assertThat;
 
diff --git a/javatests/com/google/gerrit/server/mail/receive/MailHeaderParserTest.java b/javatests/com/google/gerrit/mail/MailHeaderParserTest.java
similarity index 96%
rename from javatests/com/google/gerrit/server/mail/receive/MailHeaderParserTest.java
rename to javatests/com/google/gerrit/mail/MailHeaderParserTest.java
index 071dc4b..2d2c2ea 100644
--- a/javatests/com/google/gerrit/server/mail/receive/MailHeaderParserTest.java
+++ b/javatests/com/google/gerrit/mail/MailHeaderParserTest.java
@@ -12,12 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail.receive;
+package com.google.gerrit.mail;
 
 import static com.google.common.truth.Truth.assertThat;
 
-import com.google.gerrit.server.mail.Address;
-import com.google.gerrit.server.mail.MailHeader;
 import java.time.Instant;
 import java.time.LocalDateTime;
 import java.time.Month;
diff --git a/javatests/com/google/gerrit/server/mail/receive/ParserUtilTest.java b/javatests/com/google/gerrit/mail/ParserUtilTest.java
similarity index 97%
rename from javatests/com/google/gerrit/server/mail/receive/ParserUtilTest.java
rename to javatests/com/google/gerrit/mail/ParserUtilTest.java
index dfa492c..47a5367 100644
--- a/javatests/com/google/gerrit/server/mail/receive/ParserUtilTest.java
+++ b/javatests/com/google/gerrit/mail/ParserUtilTest.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail.receive;
+package com.google.gerrit.mail;
 
 import static com.google.common.truth.Truth.assertThat;
 
diff --git a/javatests/com/google/gerrit/server/mail/receive/RawMailParserTest.java b/javatests/com/google/gerrit/mail/RawMailParserTest.java
similarity index 83%
rename from javatests/com/google/gerrit/server/mail/receive/RawMailParserTest.java
rename to javatests/com/google/gerrit/mail/RawMailParserTest.java
index fb52947..9049704 100644
--- a/javatests/com/google/gerrit/server/mail/receive/RawMailParserTest.java
+++ b/javatests/com/google/gerrit/mail/RawMailParserTest.java
@@ -12,17 +12,17 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail.receive;
+package com.google.gerrit.mail;
 
 import static com.google.common.truth.Truth.assertThat;
 
-import com.google.gerrit.server.mail.receive.data.AttachmentMessage;
-import com.google.gerrit.server.mail.receive.data.Base64HeaderMessage;
-import com.google.gerrit.server.mail.receive.data.HtmlMimeMessage;
-import com.google.gerrit.server.mail.receive.data.NonUTF8Message;
-import com.google.gerrit.server.mail.receive.data.QuotedPrintableHeaderMessage;
-import com.google.gerrit.server.mail.receive.data.RawMailMessage;
-import com.google.gerrit.server.mail.receive.data.SimpleTextMessage;
+import com.google.gerrit.mail.data.AttachmentMessage;
+import com.google.gerrit.mail.data.Base64HeaderMessage;
+import com.google.gerrit.mail.data.HtmlMimeMessage;
+import com.google.gerrit.mail.data.NonUTF8Message;
+import com.google.gerrit.mail.data.QuotedPrintableHeaderMessage;
+import com.google.gerrit.mail.data.RawMailMessage;
+import com.google.gerrit.mail.data.SimpleTextMessage;
 import com.google.gerrit.testing.GerritBaseTests;
 import org.junit.Test;
 
diff --git a/javatests/com/google/gerrit/server/mail/receive/TextParserTest.java b/javatests/com/google/gerrit/mail/TextParserTest.java
similarity index 99%
rename from javatests/com/google/gerrit/server/mail/receive/TextParserTest.java
rename to javatests/com/google/gerrit/mail/TextParserTest.java
index 89e1f22..e11321a 100644
--- a/javatests/com/google/gerrit/server/mail/receive/TextParserTest.java
+++ b/javatests/com/google/gerrit/mail/TextParserTest.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail.receive;
+package com.google.gerrit.mail;
 
 import static com.google.common.truth.Truth.assertThat;
 
diff --git a/javatests/com/google/gerrit/server/mail/receive/data/AttachmentMessage.java b/javatests/com/google/gerrit/mail/data/AttachmentMessage.java
similarity index 95%
rename from javatests/com/google/gerrit/server/mail/receive/data/AttachmentMessage.java
rename to javatests/com/google/gerrit/mail/data/AttachmentMessage.java
index eb4d180..1d94d68 100644
--- a/javatests/com/google/gerrit/server/mail/receive/data/AttachmentMessage.java
+++ b/javatests/com/google/gerrit/mail/data/AttachmentMessage.java
@@ -12,10 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail.receive.data;
+package com.google.gerrit.mail.data;
 
-import com.google.gerrit.server.mail.Address;
-import com.google.gerrit.server.mail.receive.MailMessage;
+import com.google.gerrit.mail.Address;
+import com.google.gerrit.mail.MailMessage;
 import java.time.LocalDateTime;
 import java.time.Month;
 import java.time.ZoneOffset;
diff --git a/javatests/com/google/gerrit/server/mail/receive/data/Base64HeaderMessage.java b/javatests/com/google/gerrit/mail/data/Base64HeaderMessage.java
similarity index 93%
rename from javatests/com/google/gerrit/server/mail/receive/data/Base64HeaderMessage.java
rename to javatests/com/google/gerrit/mail/data/Base64HeaderMessage.java
index 91dc6f1..aa19537 100644
--- a/javatests/com/google/gerrit/server/mail/receive/data/Base64HeaderMessage.java
+++ b/javatests/com/google/gerrit/mail/data/Base64HeaderMessage.java
@@ -12,10 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail.receive.data;
+package com.google.gerrit.mail.data;
 
-import com.google.gerrit.server.mail.Address;
-import com.google.gerrit.server.mail.receive.MailMessage;
+import com.google.gerrit.mail.Address;
+import com.google.gerrit.mail.MailMessage;
 import java.time.LocalDateTime;
 import java.time.Month;
 import java.time.ZoneOffset;
diff --git a/javatests/com/google/gerrit/server/mail/receive/data/HtmlMimeMessage.java b/javatests/com/google/gerrit/mail/data/HtmlMimeMessage.java
similarity index 96%
rename from javatests/com/google/gerrit/server/mail/receive/data/HtmlMimeMessage.java
rename to javatests/com/google/gerrit/mail/data/HtmlMimeMessage.java
index 756581f..1d68cc8 100644
--- a/javatests/com/google/gerrit/server/mail/receive/data/HtmlMimeMessage.java
+++ b/javatests/com/google/gerrit/mail/data/HtmlMimeMessage.java
@@ -12,10 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail.receive.data;
+package com.google.gerrit.mail.data;
 
-import com.google.gerrit.server.mail.Address;
-import com.google.gerrit.server.mail.receive.MailMessage;
+import com.google.gerrit.mail.Address;
+import com.google.gerrit.mail.MailMessage;
 import java.time.LocalDateTime;
 import java.time.Month;
 import java.time.ZoneOffset;
diff --git a/javatests/com/google/gerrit/server/mail/receive/data/NonUTF8Message.java b/javatests/com/google/gerrit/mail/data/NonUTF8Message.java
similarity index 93%
rename from javatests/com/google/gerrit/server/mail/receive/data/NonUTF8Message.java
rename to javatests/com/google/gerrit/mail/data/NonUTF8Message.java
index 3fafd4b..32915e7 100644
--- a/javatests/com/google/gerrit/server/mail/receive/data/NonUTF8Message.java
+++ b/javatests/com/google/gerrit/mail/data/NonUTF8Message.java
@@ -11,10 +11,10 @@
 // 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.server.mail.receive.data;
+package com.google.gerrit.mail.data;
 
-import com.google.gerrit.server.mail.Address;
-import com.google.gerrit.server.mail.receive.MailMessage;
+import com.google.gerrit.mail.Address;
+import com.google.gerrit.mail.MailMessage;
 import java.time.LocalDateTime;
 import java.time.Month;
 import java.time.ZoneOffset;
diff --git a/javatests/com/google/gerrit/server/mail/receive/data/QuotedPrintableHeaderMessage.java b/javatests/com/google/gerrit/mail/data/QuotedPrintableHeaderMessage.java
similarity index 93%
rename from javatests/com/google/gerrit/server/mail/receive/data/QuotedPrintableHeaderMessage.java
rename to javatests/com/google/gerrit/mail/data/QuotedPrintableHeaderMessage.java
index 2dc48b5..47e813a 100644
--- a/javatests/com/google/gerrit/server/mail/receive/data/QuotedPrintableHeaderMessage.java
+++ b/javatests/com/google/gerrit/mail/data/QuotedPrintableHeaderMessage.java
@@ -12,10 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail.receive.data;
+package com.google.gerrit.mail.data;
 
-import com.google.gerrit.server.mail.Address;
-import com.google.gerrit.server.mail.receive.MailMessage;
+import com.google.gerrit.mail.Address;
+import com.google.gerrit.mail.MailMessage;
 import java.time.LocalDateTime;
 import java.time.Month;
 import java.time.ZoneOffset;
diff --git a/javatests/com/google/gerrit/server/mail/receive/data/RawMailMessage.java b/javatests/com/google/gerrit/mail/data/RawMailMessage.java
similarity index 89%
rename from javatests/com/google/gerrit/server/mail/receive/data/RawMailMessage.java
rename to javatests/com/google/gerrit/mail/data/RawMailMessage.java
index 2af82ad..53c782c 100644
--- a/javatests/com/google/gerrit/server/mail/receive/data/RawMailMessage.java
+++ b/javatests/com/google/gerrit/mail/data/RawMailMessage.java
@@ -12,9 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail.receive.data;
+package com.google.gerrit.mail.data;
 
-import com.google.gerrit.server.mail.receive.MailMessage;
+import com.google.gerrit.mail.MailMessage;
 import org.junit.Ignore;
 
 /** Base class for all email parsing tests. */
diff --git a/javatests/com/google/gerrit/server/mail/receive/data/SimpleTextMessage.java b/javatests/com/google/gerrit/mail/data/SimpleTextMessage.java
similarity index 97%
rename from javatests/com/google/gerrit/server/mail/receive/data/SimpleTextMessage.java
rename to javatests/com/google/gerrit/mail/data/SimpleTextMessage.java
index aa5b78a..a8f5b94 100644
--- a/javatests/com/google/gerrit/server/mail/receive/data/SimpleTextMessage.java
+++ b/javatests/com/google/gerrit/mail/data/SimpleTextMessage.java
@@ -12,10 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail.receive.data;
+package com.google.gerrit.mail.data;
 
-import com.google.gerrit.server.mail.Address;
-import com.google.gerrit.server.mail.receive.MailMessage;
+import com.google.gerrit.mail.Address;
+import com.google.gerrit.mail.MailMessage;
 import java.time.LocalDateTime;
 import java.time.Month;
 import java.time.ZoneOffset;
diff --git a/javatests/com/google/gerrit/server/BUILD b/javatests/com/google/gerrit/server/BUILD
index f405c28..569b0b9 100644
--- a/javatests/com/google/gerrit/server/BUILD
+++ b/javatests/com/google/gerrit/server/BUILD
@@ -41,6 +41,7 @@
         "//java/com/google/gerrit/index",
         "//java/com/google/gerrit/index:query_exception",
         "//java/com/google/gerrit/lifecycle",
+        "//java/com/google/gerrit/mail",
         "//java/com/google/gerrit/metrics",
         "//java/com/google/gerrit/reviewdb:server",
         "//java/com/google/gerrit/server",
diff --git a/javatests/com/google/gerrit/server/extensions/webui/UiActionsTest.java b/javatests/com/google/gerrit/server/extensions/webui/UiActionsTest.java
index 834f658..f63b181 100644
--- a/javatests/com/google/gerrit/server/extensions/webui/UiActionsTest.java
+++ b/javatests/com/google/gerrit/server/extensions/webui/UiActionsTest.java
@@ -18,6 +18,7 @@
 
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.extensions.conditions.BooleanCondition;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.CurrentUser;
@@ -43,31 +44,6 @@
     private boolean allowValueQueries = true;
 
     @Override
-    public CurrentUser user() {
-      return new CurrentUser() {
-        @Override
-        public GroupMembership getEffectiveGroups() {
-          throw new UnsupportedOperationException("not implemented");
-        }
-
-        @Override
-        public Object getCacheKey() {
-          return new Object();
-        }
-
-        @Override
-        public boolean isIdentifiedUser() {
-          return true;
-        }
-
-        @Override
-        public Account.Id getAccountId() {
-          return new Account.Id(1);
-        }
-      };
-    }
-
-    @Override
     public String resourcePath() {
       return "/projects/test-project";
     }
@@ -100,6 +76,11 @@
     }
 
     @Override
+    public BooleanCondition testCond(ProjectPermission perm) {
+      return new PermissionBackendCondition.ForProject(this, perm, fakeUser());
+    }
+
+    @Override
     public Map<String, Ref> filter(Map<String, Ref> refs, Repository repo, RefFilterOptions opts)
         throws PermissionBackendException {
       throw new UnsupportedOperationException("not implemented");
@@ -108,6 +89,30 @@
     private void disallowValueQueries() {
       allowValueQueries = false;
     }
+
+    private static CurrentUser fakeUser() {
+      return new CurrentUser() {
+        @Override
+        public GroupMembership getEffectiveGroups() {
+          throw new UnsupportedOperationException("not implemented");
+        }
+
+        @Override
+        public Object getCacheKey() {
+          return new Object();
+        }
+
+        @Override
+        public boolean isIdentifiedUser() {
+          return true;
+        }
+
+        @Override
+        public Account.Id getAccountId() {
+          return new Account.Id(1);
+        }
+      };
+    }
   }
 
   @Test
diff --git a/javatests/com/google/gerrit/server/index/change/StalenessCheckerTest.java b/javatests/com/google/gerrit/server/index/change/StalenessCheckerTest.java
index acb33e9..51bda66 100644
--- a/javatests/com/google/gerrit/server/index/change/StalenessCheckerTest.java
+++ b/javatests/com/google/gerrit/server/index/change/StalenessCheckerTest.java
@@ -24,11 +24,11 @@
 import com.google.common.collect.ImmutableListMultimap;
 import com.google.common.collect.ImmutableSetMultimap;
 import com.google.common.collect.ListMultimap;
+import com.google.gerrit.index.RefState;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.index.RefState;
 import com.google.gerrit.server.index.change.StalenessChecker.RefStatePattern;
 import com.google.gerrit.server.notedb.NoteDbChangeState;
 import com.google.gerrit.testing.GerritBaseTests;
diff --git a/javatests/com/google/gerrit/server/ioutil/BUILD b/javatests/com/google/gerrit/server/ioutil/BUILD
index 721c6f9..ac9530f 100644
--- a/javatests/com/google/gerrit/server/ioutil/BUILD
+++ b/javatests/com/google/gerrit/server/ioutil/BUILD
@@ -10,5 +10,8 @@
     visibility = ["//visibility:public"],
     deps = [
         "//java/com/google/gerrit/server/ioutil",
+        "//lib:guava",
+        "//lib/truth",
+        "//lib/truth:truth-java8-extension",
     ],
 )
diff --git a/javatests/com/google/gerrit/server/util/RegexListSearcherTest.java b/javatests/com/google/gerrit/server/ioutil/RegexListSearcherTest.java
similarity index 95%
rename from javatests/com/google/gerrit/server/util/RegexListSearcherTest.java
rename to javatests/com/google/gerrit/server/ioutil/RegexListSearcherTest.java
index 01964a8..3043985 100644
--- a/javatests/com/google/gerrit/server/util/RegexListSearcherTest.java
+++ b/javatests/com/google/gerrit/server/ioutil/RegexListSearcherTest.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2014 The Android Open Source Project
+// Copyright (C) 2018 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.
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.util;
+package com.google.gerrit.server.ioutil;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth8.assertThat;
diff --git a/javatests/com/google/gerrit/server/mail/AutoReplyMailFilterTest.java b/javatests/com/google/gerrit/server/mail/AutoReplyMailFilterTest.java
index a7234f4..f8a613a 100644
--- a/javatests/com/google/gerrit/server/mail/AutoReplyMailFilterTest.java
+++ b/javatests/com/google/gerrit/server/mail/AutoReplyMailFilterTest.java
@@ -16,7 +16,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import com.google.gerrit.server.mail.receive.MailMessage;
+import com.google.gerrit.mail.Address;
+import com.google.gerrit.mail.MailMessage;
 import com.google.gerrit.testing.GerritBaseTests;
 import java.time.Instant;
 import org.junit.Test;
diff --git a/javatests/com/google/gerrit/server/mail/send/FromAddressGeneratorProviderTest.java b/javatests/com/google/gerrit/server/mail/send/FromAddressGeneratorProviderTest.java
index 7028bb2..f99c356 100644
--- a/javatests/com/google/gerrit/server/mail/send/FromAddressGeneratorProviderTest.java
+++ b/javatests/com/google/gerrit/server/mail/send/FromAddressGeneratorProviderTest.java
@@ -22,12 +22,12 @@
 import static org.easymock.EasyMock.verify;
 
 import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.mail.Address;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.account.AccountCache;
 import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.config.AllUsersName;
 import com.google.gerrit.server.config.AllUsersNameProvider;
-import com.google.gerrit.server.mail.Address;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Optional;
diff --git a/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java b/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java
index 3d65eae..574f6ac 100644
--- a/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java
+++ b/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java
@@ -30,6 +30,7 @@
 import com.google.common.collect.Iterables;
 import com.google.gerrit.common.data.SubmitRecord;
 import com.google.gerrit.common.data.SubmitRequirement;
+import com.google.gerrit.mail.Address;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.ChangeMessage;
@@ -47,7 +48,6 @@
 import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.ReviewerByEmailSetEntryProto;
 import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.ReviewerSetEntryProto;
 import com.google.gerrit.server.cache.proto.Cache.ChangeNotesStateProto.ReviewerStatusUpdateProto;
-import com.google.gerrit.server.mail.Address;
 import com.google.gerrit.server.notedb.ChangeNotesState.ChangeColumns;
 import com.google.gerrit.server.notedb.ChangeNotesState.Serializer;
 import com.google.gwtorm.client.KeyUtil;
diff --git a/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java b/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java
index bea61ed..852c8bc 100644
--- a/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java
+++ b/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java
@@ -35,6 +35,7 @@
 import com.google.common.collect.Lists;
 import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.data.SubmitRecord;
+import com.google.gerrit.mail.Address;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Change;
@@ -50,7 +51,6 @@
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.ReviewerSet;
 import com.google.gerrit.server.config.GerritServerId;
-import com.google.gerrit.server.mail.Address;
 import com.google.gerrit.server.notedb.ChangeNotesCommit.ChangeNotesRevWalk;
 import com.google.gerrit.server.util.RequestId;
 import com.google.gerrit.testing.TestChanges;
@@ -691,7 +691,7 @@
     try (RevWalk rw = new RevWalk(repo)) {
       RevCommit commit = rw.parseCommit(update.getResult());
       rw.parseBody(commit);
-      String strIdent = otherUser.getName() + " <" + otherUserId + "@" + serverId + ">";
+      String strIdent = "Gerrit User " + otherUserId + " <" + otherUserId + "@" + serverId + ">";
       assertThat(commit.getFullMessage()).contains("Assignee: " + strIdent);
     }
   }
diff --git a/javatests/com/google/gerrit/server/notedb/CommentJsonMigratorTest.java b/javatests/com/google/gerrit/server/notedb/CommentJsonMigratorTest.java
index a5c4e49..718e287 100644
--- a/javatests/com/google/gerrit/server/notedb/CommentJsonMigratorTest.java
+++ b/javatests/com/google/gerrit/server/notedb/CommentJsonMigratorTest.java
@@ -33,7 +33,8 @@
 import com.google.gerrit.reviewdb.client.RevId;
 import com.google.gerrit.server.CommentsUtil;
 import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.update.RefUpdateUtil;
+import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.notedb.CommentJsonMigrator.ProjectMigrationResult;
 import com.google.gerrit.testing.TestChanges;
 import com.google.inject.Inject;
 import java.io.ByteArrayOutputStream;
@@ -57,13 +58,14 @@
   @Inject private ChangeNoteUtil noteUtil;
   @Inject private CommentsUtil commentsUtil;
   @Inject private LegacyChangeNoteWrite legacyChangeNoteWrite;
+  @Inject private AllUsersName allUsersName;
 
   private AtomicInteger uuidCounter;
 
   @Before
   public void setUpCounter() {
     uuidCounter = new AtomicInteger();
-    migrator = new CommentJsonMigrator(new ChangeNoteJson(), "gerrit");
+    migrator = new CommentJsonMigrator(new ChangeNoteJson(), "gerrit", allUsersName);
   }
 
   @Test
@@ -90,7 +92,7 @@
             getRevId(notes, 2), ps2Comment.toString());
 
     ChangeNotes oldNotes = notes;
-    migrate(project, migrator::migrateChanges, 0);
+    migrate(project, 0);
     assertNoDifferences(notes, oldNotes);
     assertThat(notes.getMetaId()).isEqualTo(oldNotes.getMetaId());
   }
@@ -163,7 +165,7 @@
         .containsExactly(ps1Comment1.key, true, ps1Comment2.key, true, ps2Comment1.key, true);
 
     ChangeNotes oldNotes = notes;
-    migrate(project, migrator::migrateChanges, 1);
+    migrate(project, 1);
 
     // Comment content is the same.
     notes = newNotes(c);
@@ -265,7 +267,7 @@
         .containsExactly(otherCommentPs1.key, true);
 
     ChangeNotes oldNotes = notes;
-    migrate(allUsers, migrator::migrateDrafts, 2);
+    migrate(allUsers, 2);
     assertNoDifferences(notes, oldNotes);
 
     // Migration doesn't touch change ref.
@@ -366,7 +368,7 @@
         .containsExactly(ps1Comment.key, true, ps2Comment.key, false, ps3Comment.key, true);
 
     ChangeNotes oldNotes = notes;
-    migrate(project, migrator::migrateChanges, 1);
+    migrate(project, 1);
     assertNoDifferences(notes, oldNotes);
 
     // Comment content is the same.
@@ -402,19 +404,12 @@
         throws Exception;
   }
 
-  private void migrate(Project.NameKey project, MigrateFunction func, int expectedCommands)
-      throws Exception {
-    try (Repository repo = repoManager.openRepository(project);
-        RevWalk rw = new RevWalk(repo);
-        ObjectInserter ins = repo.newObjectInserter()) {
-      BatchRefUpdate bru = repo.getRefDatabase().newBatchUpdate();
-      bru.setAllowNonFastForwards(true);
-      assertThat(func.call(project, repo, rw, ins, bru)).isTrue();
-      assertThat(bru.getCommands()).hasSize(expectedCommands);
-      if (!bru.getCommands().isEmpty()) {
-        ins.flush();
-        RefUpdateUtil.executeChecked(bru, rw);
-      }
+  private void migrate(Project.NameKey project, int expectedCommands) throws Exception {
+    try (Repository repo = repoManager.openRepository(project)) {
+      ProjectMigrationResult progress = migrator.migrateProject(project, repo);
+
+      assertThat(progress.ok).isTrue();
+      assertThat(progress.refsUpdated).isEqualTo(expectedCommands);
     }
   }
 
diff --git a/javatests/com/google/gerrit/server/notedb/CommitMessageOutputTest.java b/javatests/com/google/gerrit/server/notedb/CommitMessageOutputTest.java
index f826fec..963546f 100644
--- a/javatests/com/google/gerrit/server/notedb/CommitMessageOutputTest.java
+++ b/javatests/com/google/gerrit/server/notedb/CommitMessageOutputTest.java
@@ -20,10 +20,10 @@
 
 import com.google.common.collect.ImmutableList;
 import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.mail.Address;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.mail.Address;
 import com.google.gerrit.server.util.RequestId;
 import com.google.gerrit.testing.ConfigSuite;
 import com.google.gerrit.testing.TestChanges;
@@ -62,14 +62,14 @@
             + "Commit: "
             + update.getCommit().name()
             + "\n"
-            + "Reviewer: Change Owner <1@gerrit>\n"
-            + "CC: Other Account <2@gerrit>\n"
+            + "Reviewer: Gerrit User 1 <1@gerrit>\n"
+            + "CC: Gerrit User 2 <2@gerrit>\n"
             + "Label: Code-Review=-1\n"
             + "Label: Verified=+1\n",
         commit);
 
     PersonIdent author = commit.getAuthorIdent();
-    assertThat(author.getName()).isEqualTo("Change Owner");
+    assertThat(author.getName()).isEqualTo("Gerrit User 1");
     assertThat(author.getEmailAddress()).isEqualTo("1@gerrit");
     assertThat(author.getWhen()).isEqualTo(new Date(c.getCreatedOn().getTime() + 1000));
     assertThat(author.getTimeZone()).isEqualTo(TimeZone.getTimeZone("GMT-7:00"));
@@ -177,15 +177,15 @@
             + submissionId.toStringForStorage()
             + "\n"
             + "Submitted-with: NOT_READY\n"
-            + "Submitted-with: OK: Verified: Change Owner <1@gerrit>\n"
+            + "Submitted-with: OK: Verified: Gerrit User 1 <1@gerrit>\n"
             + "Submitted-with: NEED: Code-Review\n"
             + "Submitted-with: NOT_READY\n"
-            + "Submitted-with: OK: Verified: Change Owner <1@gerrit>\n"
+            + "Submitted-with: OK: Verified: Gerrit User 1 <1@gerrit>\n"
             + "Submitted-with: NEED: Alternative-Code-Review\n",
         commit);
 
     PersonIdent author = commit.getAuthorIdent();
-    assertThat(author.getName()).isEqualTo("Change Owner");
+    assertThat(author.getName()).isEqualTo("Gerrit User 1");
     assertThat(author.getEmailAddress()).isEqualTo("1@gerrit");
     assertThat(author.getWhen()).isEqualTo(new Date(c.getCreatedOn().getTime() + 2000));
     assertThat(author.getTimeZone()).isEqualTo(TimeZone.getTimeZone("GMT-7:00"));
@@ -210,7 +210,7 @@
     assertBodyEquals("Update patch set 1\n\nComment on the change.\n\nPatch-set: 1\n", commit);
 
     PersonIdent author = commit.getAuthorIdent();
-    assertThat(author.getName()).isEqualTo("GerritAccount #3");
+    assertThat(author.getName()).isEqualTo("Gerrit User 3");
     assertThat(author.getEmailAddress()).isEqualTo("3@gerrit");
   }
 
@@ -245,7 +245,7 @@
     update.commit();
 
     assertBodyEquals(
-        "Update patch set 1\n\nPatch-set: 1\nReviewer: Change Owner <1@gerrit>\n",
+        "Update patch set 1\n\nPatch-set: 1\nReviewer: Gerrit User 1 <1@gerrit>\n",
         update.getResult());
   }
 
@@ -360,7 +360,7 @@
 
     RevCommit commit = parseCommit(update.getResult());
     PersonIdent author = commit.getAuthorIdent();
-    assertThat(author.getName()).isEqualTo("Other Account");
+    assertThat(author.getName()).isEqualTo("Gerrit User 2");
     assertThat(author.getEmailAddress()).isEqualTo("2@gerrit");
 
     assertBodyEquals(
@@ -369,7 +369,7 @@
             + "Message on behalf of other user\n"
             + "\n"
             + "Patch-set: 1\n"
-            + "Real-user: Change Owner <1@gerrit>\n",
+            + "Real-user: Gerrit User 1 <1@gerrit>\n",
         commit);
   }
 
diff --git a/javatests/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java b/javatests/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java
index bc3c9a9..3d9d661 100644
--- a/javatests/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java
+++ b/javatests/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java
@@ -541,17 +541,19 @@
 
   @Test
   public void withSecondaryEmailsWithoutModifyAccountCapability() throws Exception {
-    AccountInfo user = newAccount("myuser", "My User", "abc@example.com", true);
+    AccountInfo user = newAccount("myuser", "My User", "other@example.com", true);
+
+    AccountInfo otherUser = newAccount("otheruser", "Other User", "abc@example.com", true);
     String[] secondaryEmails = new String[] {"dfg@example.com", "hij@example.com"};
-    addEmails(user, secondaryEmails);
+    addEmails(otherUser, secondaryEmails);
 
     requestContext.setContext(newRequestContext(new Account.Id(user._accountId)));
 
-    List<AccountInfo> result = newQuery(user.username).withSuggest(true).get();
+    List<AccountInfo> result = newQuery(otherUser.username).withSuggest(true).get();
     assertThat(result.get(0).secondaryEmails).isNull();
 
     exception.expect(AuthException.class);
-    newQuery(user.username).withOption(ListAccountsOption.ALL_EMAILS).get();
+    newQuery(otherUser.username).withOption(ListAccountsOption.ALL_EMAILS).get();
   }
 
   @Test
diff --git a/javatests/com/google/gerrit/server/query/project/AbstractQueryProjectsTest.java b/javatests/com/google/gerrit/server/query/project/AbstractQueryProjectsTest.java
index e34746c..2eec006 100644
--- a/javatests/com/google/gerrit/server/query/project/AbstractQueryProjectsTest.java
+++ b/javatests/com/google/gerrit/server/query/project/AbstractQueryProjectsTest.java
@@ -15,15 +15,21 @@
 package com.google.gerrit.server.query.project;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.TruthJUnit.assume;
 import static java.util.stream.Collectors.toList;
 
 import com.google.common.base.CharMatcher;
 import com.google.gerrit.extensions.api.GerritApi;
+import com.google.gerrit.extensions.api.projects.ConfigInput;
 import com.google.gerrit.extensions.api.projects.ProjectInput;
 import com.google.gerrit.extensions.api.projects.Projects.QueryRequest;
+import com.google.gerrit.extensions.client.ProjectState;
 import com.google.gerrit.extensions.common.AccountInfo;
 import com.google.gerrit.extensions.common.ProjectInfo;
 import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.project.ProjectData;
+import com.google.gerrit.index.project.ProjectIndexCollection;
 import com.google.gerrit.lifecycle.LifecycleManager;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Project;
@@ -38,7 +44,6 @@
 import com.google.gerrit.server.account.AccountsUpdate;
 import com.google.gerrit.server.account.AuthRequest;
 import com.google.gerrit.server.config.AllProjectsName;
-import com.google.gerrit.server.query.account.InternalAccountQuery;
 import com.google.gerrit.server.schema.SchemaCreator;
 import com.google.gerrit.server.util.ManualRequestContext;
 import com.google.gerrit.server.util.OneOffRequestContext;
@@ -83,7 +88,7 @@
 
   @Inject protected OneOffRequestContext oneOffRequestContext;
 
-  @Inject protected InternalAccountQuery internalAccountQuery;
+  @Inject protected ProjectIndexCollection indexes;
 
   @Inject protected AllProjectsName allProjects;
 
@@ -211,6 +216,30 @@
   }
 
   @Test
+  public void byState() throws Exception {
+    assume().that(getSchemaVersion() >= 2).isTrue();
+
+    ProjectInfo project1 = createProjectWithState(name("project1"), ProjectState.ACTIVE);
+    ProjectInfo project2 = createProjectWithState(name("project2"), ProjectState.READ_ONLY);
+    assertQuery("state:active", project1);
+    assertQuery("state:read-only", project2);
+  }
+
+  @Test
+  public void byState_emptyQuery() throws Exception {
+    exception.expect(BadRequestException.class);
+    exception.expectMessage("state operator requires a value");
+    assertQuery("state:\"\"");
+  }
+
+  @Test
+  public void byState_badQuery() throws Exception {
+    exception.expect(BadRequestException.class);
+    exception.expectMessage("state operator must be either 'active' or 'read-only'");
+    assertQuery("state:bla");
+  }
+
+  @Test
   public void byDefaultField() throws Exception {
     ProjectInfo project1 = createProject(name("foo-project"));
     ProjectInfo project2 = createProject(name("project2"));
@@ -291,6 +320,14 @@
     return gApi.projects().create(in).get();
   }
 
+  protected ProjectInfo createProjectWithState(String name, ProjectState state) throws Exception {
+    ProjectInfo info = createProject(name);
+    ConfigInput config = new ConfigInput();
+    config.state = state;
+    gApi.projects().name(info.name).config(config);
+    return info;
+  }
+
   protected ProjectInfo getProject(Project.NameKey nameKey) throws Exception {
     return gApi.projects().name(nameKey.get()).get();
   }
@@ -354,6 +391,14 @@
     return b.toString();
   }
 
+  protected int getSchemaVersion() {
+    return getSchema().getVersion();
+  }
+
+  protected Schema<ProjectData> getSchema() {
+    return indexes.getSearchIndex().getSchema();
+  }
+
   protected static Iterable<String> names(ProjectInfo... projects) {
     return names(Arrays.asList(projects));
   }
diff --git a/javatests/com/google/gerrit/server/query/project/BUILD b/javatests/com/google/gerrit/server/query/project/BUILD
index eaa3df3..f0c455e 100644
--- a/javatests/com/google/gerrit/server/query/project/BUILD
+++ b/javatests/com/google/gerrit/server/query/project/BUILD
@@ -9,6 +9,8 @@
     visibility = ["//visibility:public"],
     deps = [
         "//java/com/google/gerrit/extensions:api",
+        "//java/com/google/gerrit/index",
+        "//java/com/google/gerrit/index/project",
         "//java/com/google/gerrit/lifecycle",
         "//java/com/google/gerrit/reviewdb:server",
         "//java/com/google/gerrit/server",
diff --git a/javatests/com/google/gerrit/server/schema/GroupRebuilderTest.java b/javatests/com/google/gerrit/server/schema/GroupRebuilderTest.java
index a6178ac..ead824f 100644
--- a/javatests/com/google/gerrit/server/schema/GroupRebuilderTest.java
+++ b/javatests/com/google/gerrit/server/schema/GroupRebuilderTest.java
@@ -542,6 +542,8 @@
   public void combineWithBatchGroupNameNotes() throws Exception {
     AccountGroup g1 = newGroup("a");
     AccountGroup g2 = newGroup("b");
+    GroupReference gr1 = new GroupReference(g1.getGroupUUID(), g1.getName());
+    GroupReference gr2 = new GroupReference(g2.getGroupUUID(), g2.getName());
 
     GroupBundle b1 = builder().group(g1).build();
     GroupBundle b2 = builder().group(g2).build();
@@ -551,8 +553,7 @@
     rebuilder.rebuild(repo, b1, bru);
     rebuilder.rebuild(repo, b2, bru);
     try (ObjectInserter inserter = repo.newObjectInserter()) {
-      ImmutableList<GroupReference> refs =
-          ImmutableList.of(GroupReference.forGroup(g1), GroupReference.forGroup(g2));
+      ImmutableList<GroupReference> refs = ImmutableList.of(gr1, gr2);
       GroupNameNotes.updateAllGroups(repo, inserter, bru, refs, newPersonIdent());
       inserter.flush();
     }
@@ -569,9 +570,7 @@
     assertMigratedCleanly(reload(g1), b1);
     assertMigratedCleanly(reload(g2), b2);
 
-    GroupReference group1 = GroupReference.forGroup(g1);
-    GroupReference group2 = GroupReference.forGroup(g2);
-    assertThat(GroupNameNotes.loadAllGroups(repo)).containsExactly(group1, group2);
+    assertThat(GroupNameNotes.loadAllGroups(repo)).containsExactly(gr1, gr2);
   }
 
   @Test
diff --git a/lib/codemirror/cm.bzl b/lib/codemirror/cm.bzl
index 5088a05..593caa3 100644
--- a/lib/codemirror/cm.bzl
+++ b/lib/codemirror/cm.bzl
@@ -234,126 +234,142 @@
                         DIFF_MATCH_PATCH_VERSION)
 
 def pkg_cm():
-  for archive, suffix, top, license in [
-      ('@codemirror-original-gwt//jar', '', TOP, LICENSE),
-      ('@codemirror-minified-gwt//jar', '_r', TOP_MINIFIED, LICENSE_MINIFIED)
-  ]:
-    # Main JavaScript and addons
-    genrule2(
-      name = 'cm' + suffix,
-      cmd = ' && '.join([
-          "echo '/** @license' >$@",
-          'unzip -p $(location %s) %s/LICENSE >>$@' % (archive, top),
-          "echo '*/' >>$@",
-        ] +
-        ['unzip -p $(location %s) %s/%s >>$@' % (archive, top, n) for n in CM_JS] +
-        ['unzip -p $(location %s) %s/addon/%s >>$@' % (archive, top, n)
-         for n in CM_ADDONS]
-      ),
-      tools = [archive],
-      outs = ['cm%s.js' % suffix],
-    )
+    for archive, suffix, top, license in [
+        ("@codemirror-original-gwt//jar", "", TOP, LICENSE),
+        ("@codemirror-minified-gwt//jar", "_r", TOP_MINIFIED, LICENSE_MINIFIED),
+    ]:
+        # Main JavaScript and addons
+        genrule2(
+            name = "cm" + suffix,
+            cmd = " && ".join(
+                [
+                    "echo '/** @license' >$@",
+                    "unzip -p $(location %s) %s/LICENSE >>$@" % (archive, top),
+                    "echo '*/' >>$@",
+                ] +
+                ["unzip -p $(location %s) %s/%s >>$@" % (archive, top, n) for n in CM_JS] +
+                [
+                    "unzip -p $(location %s) %s/addon/%s >>$@" % (archive, top, n)
+                    for n in CM_ADDONS
+                ],
+            ),
+            tools = [archive],
+            outs = ["cm%s.js" % suffix],
+        )
 
-    # Main CSS
-    genrule2(
-      name = 'css' + suffix,
-      cmd = ' && '.join([
-          "echo '/** @license' >$@",
-          'unzip -p $(location %s) %s/LICENSE >>$@' % (archive, top),
-          "echo '*/' >>$@",
-        ] +
-        ['unzip -p $(location %s) %s/%s >>$@' % (archive, top, n)
-         for n in CM_CSS]
-      ),
-      tools = [archive],
-      outs = ['cm%s.css' % suffix],
-    )
+        # Main CSS
+        genrule2(
+            name = "css" + suffix,
+            cmd = " && ".join(
+                [
+                    "echo '/** @license' >$@",
+                    "unzip -p $(location %s) %s/LICENSE >>$@" % (archive, top),
+                    "echo '*/' >>$@",
+                ] +
+                [
+                    "unzip -p $(location %s) %s/%s >>$@" % (archive, top, n)
+                    for n in CM_CSS
+                ],
+            ),
+            tools = [archive],
+            outs = ["cm%s.css" % suffix],
+        )
 
-    # Modes
-    for n in CM_MODES:
-      genrule2(
-        name = 'mode_%s%s' % (n, suffix),
-        cmd = ' && '.join([
-            "echo '/** @license' >$@",
-            'unzip -p $(location %s) %s/LICENSE >>$@' % (archive, top),
-            "echo '*/' >>$@",
-            'unzip -p $(location %s) %s/mode/%s/%s.js >>$@' % (archive, top, n, n),
-          ]
-        ),
-        tools = [archive],
-        outs = ['mode_%s%s.js' % (n, suffix)],
-      )
+        # Modes
+        for n in CM_MODES:
+            genrule2(
+                name = "mode_%s%s" % (n, suffix),
+                cmd = " && ".join(
+                    [
+                        "echo '/** @license' >$@",
+                        "unzip -p $(location %s) %s/LICENSE >>$@" % (archive, top),
+                        "echo '*/' >>$@",
+                        "unzip -p $(location %s) %s/mode/%s/%s.js >>$@" % (archive, top, n, n),
+                    ],
+                ),
+                tools = [archive],
+                outs = ["mode_%s%s.js" % (n, suffix)],
+            )
 
-    # Themes
-    for n in CM_THEMES:
-      genrule2(
-        name = 'theme_%s%s' % (n, suffix),
-        cmd = ' && '.join([
-            "echo '/** @license' >$@",
-            'unzip -p $(location %s) %s/LICENSE >>$@' % (archive, top),
-            "echo '*/' >>$@",
-            'unzip -p $(location %s) %s/theme/%s.css >>$@' % (archive, top, n)
-          ]
-        ),
-        tools = [archive],
-        outs = ['theme_%s%s.css' % (n, suffix)],
-      )
+        # Themes
+        for n in CM_THEMES:
+            genrule2(
+                name = "theme_%s%s" % (n, suffix),
+                cmd = " && ".join(
+                    [
+                        "echo '/** @license' >$@",
+                        "unzip -p $(location %s) %s/LICENSE >>$@" % (archive, top),
+                        "echo '*/' >>$@",
+                        "unzip -p $(location %s) %s/theme/%s.css >>$@" % (archive, top, n),
+                    ],
+                ),
+                tools = [archive],
+                outs = ["theme_%s%s.css" % (n, suffix)],
+            )
 
-    # Merge Addon bundled with diff-match-patch
-    genrule2(
-      name = 'addon_merge_with_diff_match_patch%s' % suffix,
-      cmd = ' && '.join([
-          "echo '/** @license' >$@",
-          'unzip -p $(location %s) %s/LICENSE >>$@' % (archive, top),
-          "echo '*/\n' >>$@",
-          "echo '// The google-diff-match-patch library is from https://repo1.maven.org/maven2/org/webjars/google-diff-match-patch/%s/google-diff-match-patch-%s.jar\n' >> $@" % (DIFF_MATCH_PATCH_VERSION, DIFF_MATCH_PATCH_VERSION),
-          "echo '/** @license' >>$@",
-          "echo 'LICENSE-Apache2.0' >>$@",
-          "echo '*/' >>$@",
-          'unzip -p $(location @diff-match-patch//jar) %s/diff_match_patch.js >>$@' % DIFF_MATCH_PATCH_TOP,
-          "echo ';' >> $@",
-          'unzip -p $(location %s) %s/addon/merge/merge.js >>$@' % (archive, top)
-        ]
-      ),
-      tools = [
-        '@diff-match-patch//jar',
-        # dependency just for license tracking.
-        ':diff-match-patch',
-        archive,
-        "//lib:LICENSE-Apache2.0",
-      ],
-      outs = ['addon_merge_with_diff_match_patch%s.js' % suffix],
-    )
+        # Merge Addon bundled with diff-match-patch
+        genrule2(
+            name = "addon_merge_with_diff_match_patch%s" % suffix,
+            cmd = " && ".join(
+                [
+                    "echo '/** @license' >$@",
+                    "unzip -p $(location %s) %s/LICENSE >>$@" % (archive, top),
+                    "echo '*/\n' >>$@",
+                    "echo '// The google-diff-match-patch library is from https://repo1.maven.org/maven2/org/webjars/google-diff-match-patch/%s/google-diff-match-patch-%s.jar\n' >> $@" % (DIFF_MATCH_PATCH_VERSION, DIFF_MATCH_PATCH_VERSION),
+                    "echo '/** @license' >>$@",
+                    "echo 'LICENSE-Apache2.0' >>$@",
+                    "echo '*/' >>$@",
+                    "unzip -p $(location @diff-match-patch//jar) %s/diff_match_patch.js >>$@" % DIFF_MATCH_PATCH_TOP,
+                    "echo ';' >> $@",
+                    "unzip -p $(location %s) %s/addon/merge/merge.js >>$@" % (archive, top),
+                ],
+            ),
+            tools = [
+                "@diff-match-patch//jar",
+                # dependency just for license tracking.
+                ":diff-match-patch",
+                archive,
+                "//lib:LICENSE-Apache2.0",
+            ],
+            outs = ["addon_merge_with_diff_match_patch%s.js" % suffix],
+        )
 
-    # Jar packaging
-    genrule2(
-      name = 'jar' + suffix,
-      cmd = ' && '.join([
-        'cd $$TMP',
-        'mkdir -p net/codemirror/{addon,lib,mode,theme}',
-        'cp $$ROOT/$(location :css%s) net/codemirror/lib/cm.css' % suffix,
-        'cp $$ROOT/$(location :cm%s) net/codemirror/lib/cm.js' % suffix]
-        + ['cp $$ROOT/$(location :mode_%s%s) net/codemirror/mode/%s.js' % (n, suffix, n)
-           for n in CM_MODES]
-        + ['cp $$ROOT/$(location :theme_%s%s) net/codemirror/theme/%s.css' % (n, suffix, n)
-           for n in CM_THEMES]
-        + ['cp $$ROOT/$(location :addon_merge_with_diff_match_patch%s) net/codemirror/addon/merge_bundled.js' % suffix]
-        + ['zip -qr $$ROOT/$@ net/codemirror/{addon,lib,mode,theme}']),
-      tools = [
-        ':addon_merge_with_diff_match_patch%s' % suffix,
-        ':cm%s' % suffix,
-        ':css%s' % suffix,
-      ] + [
-        ':mode_%s%s' % (n, suffix) for n in CM_MODES
-      ] + [
-        ':theme_%s%s' % (n, suffix) for n in CM_THEMES
-      ],
-      outs = ['codemirror%s.jar' % suffix],
-    )
+        # Jar packaging
+        genrule2(
+            name = "jar" + suffix,
+            cmd = " && ".join([
+                                  "cd $$TMP",
+                                  "mkdir -p net/codemirror/{addon,lib,mode,theme}",
+                                  "cp $$ROOT/$(location :css%s) net/codemirror/lib/cm.css" % suffix,
+                                  "cp $$ROOT/$(location :cm%s) net/codemirror/lib/cm.js" % suffix,
+                              ] +
+                              [
+                                  "cp $$ROOT/$(location :mode_%s%s) net/codemirror/mode/%s.js" % (n, suffix, n)
+                                  for n in CM_MODES
+                              ] +
+                              [
+                                  "cp $$ROOT/$(location :theme_%s%s) net/codemirror/theme/%s.css" % (n, suffix, n)
+                                  for n in CM_THEMES
+                              ] +
+                              ["cp $$ROOT/$(location :addon_merge_with_diff_match_patch%s) net/codemirror/addon/merge_bundled.js" % suffix] +
+                              ["zip -qr $$ROOT/$@ net/codemirror/{addon,lib,mode,theme}"]),
+            tools = [
+                ":addon_merge_with_diff_match_patch%s" % suffix,
+                ":cm%s" % suffix,
+                ":css%s" % suffix,
+            ] + [
+                ":mode_%s%s" % (n, suffix)
+                for n in CM_MODES
+            ] + [
+                ":theme_%s%s" % (n, suffix)
+                for n in CM_THEMES
+            ],
+            outs = ["codemirror%s.jar" % suffix],
+        )
 
-    native.java_import(
-      name = 'codemirror' + suffix,
-      jars = [':jar%s' % suffix],
-      visibility = ['//visibility:public'],
-      data = [license],
-    )
+        native.java_import(
+            name = "codemirror" + suffix,
+            jars = [":jar%s" % suffix],
+            visibility = ["//visibility:public"],
+            data = [license],
+        )
diff --git a/lib/highlightjs/highlight.min.js b/lib/highlightjs/highlight.min.js
index 9775c0d..069a018 100644
--- a/lib/highlightjs/highlight.min.js
+++ b/lib/highlightjs/highlight.min.js
@@ -1,62 +1,124 @@
 /*
  highlight.js v9.12.0 | BSD3 License | git.io/hljslicense */
-(function(b){var l="object"===typeof window&&window||"object"===typeof self&&self;"undefined"!==typeof exports?b(exports):l&&(l.hljs=b({}),"function"===typeof define&&define.amd&&define([],function(){return l.hljs}))})(function(b){function l(a){return a.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function C(a,d){var e=a&&a.exec(d);return e&&0===e.index}function r(a){var d,e={},b=Array.prototype.slice.call(arguments,1);for(d in a)e[d]=a[d];b.forEach(function(a){for(d in a)e[d]=
-a[d]});return e}function G(a){var d=[];(function h(a,b){for(var k=a.firstChild;k;k=k.nextSibling)3===k.nodeType?b+=k.nodeValue.length:1===k.nodeType&&(d.push({event:"start",offset:b,node:k}),b=h(k,b),k.nodeName.toLowerCase().match(/br|hr|img|input/)||d.push({event:"stop",offset:b,node:k}));return b})(a,0);return d}function L(a,d,e){function b(){return a.length&&d.length?a[0].offset!==d[0].offset?a[0].offset<d[0].offset?a:d:"start"===d[0].event?a:d:a.length?a:d}function c(a){n+="<"+a.nodeName.toLowerCase()+
-H.map.call(a.attributes,function(a){return" "+a.nodeName+'="'+l(a.value).replace('"',"&quot;")+'"'}).join("")+">"}function f(a){n+="</"+a.nodeName.toLowerCase()+">"}function k(a){("start"===a.event?c:f)(a.node)}for(var v=0,n="",p=[];a.length||d.length;){var g=b(),n=n+l(e.substring(v,g[0].offset)),v=g[0].offset;if(g===a){p.reverse().forEach(f);do k(g.splice(0,1)[0]),g=b();while(g===a&&g.length&&g[0].offset===v);p.reverse().forEach(c)}else"start"===g[0].event?p.push(g[0].node):p.pop(),k(g.splice(0,
-1)[0])}return n+l(e.substr(v))}function M(a){a.variants&&!a.cached_variants&&(a.cached_variants=a.variants.map(function(d){return r(a,{variants:null},d)}));return a.cached_variants||a.endsWithParent&&[r(a)]||[a]}function N(a){function d(a){return a&&a.source||a}function e(c,e){return new RegExp(d(c),"m"+(a.case_insensitive?"i":"")+(e?"g":""))}function b(c,f){if(!c.compiled){c.compiled=!0;c.keywords=c.keywords||c.beginKeywords;if(c.keywords){var k={},l=function(d,c){a.case_insensitive&&(c=c.toLowerCase());
-c.split(" ").forEach(function(a){a=a.split("|");k[a[0]]=[d,a[1]?Number(a[1]):1]})};"string"===typeof c.keywords?l("keyword",c.keywords):w(c.keywords).forEach(function(a){l(a,c.keywords[a])});c.keywords=k}c.lexemesRe=e(c.lexemes||/\w+/,!0);f&&(c.beginKeywords&&(c.begin="\\b("+c.beginKeywords.split(" ").join("|")+")\\b"),c.begin||(c.begin=/\B|\b/),c.beginRe=e(c.begin),c.end||c.endsWithParent||(c.end=/\B|\b/),c.end&&(c.endRe=e(c.end)),c.terminator_end=d(c.end)||"",c.endsWithParent&&f.terminator_end&&
-(c.terminator_end+=(c.end?"|":"")+f.terminator_end));c.illegal&&(c.illegalRe=e(c.illegal));null==c.relevance&&(c.relevance=1);c.contains||(c.contains=[]);c.contains=Array.prototype.concat.apply([],c.contains.map(function(a){return M("self"===a?c:a)}));c.contains.forEach(function(a){b(a,c)});c.starts&&b(c.starts,f);var n=c.contains.map(function(a){return a.beginKeywords?"\\.?("+a.begin+")\\.?":a.begin}).concat([c.terminator_end,c.illegal]).map(d).filter(Boolean);c.terminators=n.length?e(n.join("|"),
-!0):{exec:function(){return null}}}}b(a)}function A(a,d,e,b){function c(a,d){if(C(a.endRe,d)){for(;a.endsParent&&a.parent;)a=a.parent;return a}if(a.endsWithParent)return c(a.parent,d)}function f(a,d,b,c){return'<span class="'+(c?"":q.classPrefix)+(a+'">')+d+(b?"":"</span>")}function k(){var a=t,d;if(null!=g.subLanguage)if((d="string"===typeof g.subLanguage)&&!x[g.subLanguage])d=l(m);else{var b=d?A(g.subLanguage,m,!0,u[g.subLanguage]):E(m,g.subLanguage.length?g.subLanguage:void 0);0<g.relevance&&(r+=
-b.relevance);d&&(u[g.subLanguage]=b.top);d=f(b.language,b.value,!1,!0)}else{var c;if(g.keywords){b="";c=0;g.lexemesRe.lastIndex=0;for(d=g.lexemesRe.exec(m);d;){b+=l(m.substring(c,d.index));c=g;var e=d,e=p.case_insensitive?e[0].toLowerCase():e[0];(c=c.keywords.hasOwnProperty(e)&&c.keywords[e])?(r+=c[1],b+=f(c[0],l(d[0]))):b+=l(d[0]);c=g.lexemesRe.lastIndex;d=g.lexemesRe.exec(m)}d=b+l(m.substr(c))}else d=l(m)}t=a+d;m=""}function v(a){t+=a.className?f(a.className,"",!0):"";g=Object.create(a,{parent:{value:g}})}
-function n(a,d){m+=a;if(null==d)return k(),0;var b;a:{b=g;var f,h;f=0;for(h=b.contains.length;f<h;f++)if(C(b.contains[f].beginRe,d)){b=b.contains[f];break a}b=void 0}if(b)return b.skip?m+=d:(b.excludeBegin&&(m+=d),k(),b.returnBegin||b.excludeBegin||(m=d)),v(b,d),b.returnBegin?0:d.length;if(b=c(g,d)){f=g;f.skip?m+=d:(f.returnEnd||f.excludeEnd||(m+=d),k(),f.excludeEnd&&(m=d));do g.className&&(t+="</span>"),g.skip||(r+=g.relevance),g=g.parent;while(g!==b.parent);b.starts&&v(b.starts,"");return f.returnEnd?
-0:d.length}if(!e&&C(g.illegalRe,d))throw Error('Illegal lexeme "'+d+'" for mode "'+(g.className||"<unnamed>")+'"');m+=d;return d.length||1}var p=y(a);if(!p)throw Error('Unknown language: "'+a+'"');N(p);var g=b||p,u={},t="";for(b=g;b!==p;b=b.parent)b.className&&(t=f(b.className,"",!0)+t);var m="",r=0;try{for(var z,w,B=0;;){g.terminators.lastIndex=B;z=g.terminators.exec(d);if(!z)break;w=n(d.substring(B,z.index),z[0]);B=z.index+w}n(d.substr(B));for(b=g;b.parent;b=b.parent)b.className&&(t+="</span>");
-return{relevance:r,value:t,language:a,top:g}}catch(D){if(D.message&&-1!==D.message.indexOf("Illegal"))return{relevance:0,value:l(d)};throw D;}}function E(a,d){d=d||q.languages||w(x);var b={relevance:0,value:l(a)},h=b;d.filter(y).forEach(function(d){var f=A(d,a,!1);f.language=d;f.relevance>h.relevance&&(h=f);f.relevance>b.relevance&&(h=b,b=f)});h.language&&(b.second_best=h);return b}function I(a){return q.tabReplace||q.useBR?a.replace(O,function(a,b){return q.useBR&&"\n"===a?"<br>":q.tabReplace?b.replace(/\t/g,
-q.tabReplace):""}):a}function J(a){var d,b,h,c,f;a:if(b=a.className+" ",b+=a.parentNode?a.parentNode.className:"",f=P.exec(b))f=y(f[1])?f[1]:"no-highlight";else{b=b.split(/\s+/);f=0;for(c=b.length;f<c;f++)if(d=b[f],K.test(d)||y(d)){f=d;break a}f=void 0}K.test(f)||(q.useBR?(d=document.createElementNS("http://www.w3.org/1999/xhtml","div"),d.innerHTML=a.innerHTML.replace(/\n/g,"").replace(/<br[ \/]*>/g,"\n")):d=a,c=d.textContent,b=f?A(f,c,!0):E(c),d=G(d),d.length&&(h=document.createElementNS("http://www.w3.org/1999/xhtml",
-"div"),h.innerHTML=b.value,b.value=L(d,G(h),c)),b.value=I(b.value),a.innerHTML=b.value,c=a.className,f=f?F[f]:b.language,d=[c.trim()],c.match(/\bhljs\b/)||d.push("hljs"),-1===c.indexOf(f)&&d.push(f),f=d.join(" ").trim(),a.className=f,a.result={language:b.language,re:b.relevance},b.second_best&&(a.second_best={language:b.second_best.language,re:b.second_best.relevance}))}function u(){if(!u.called){u.called=!0;var a=document.querySelectorAll("pre code");H.forEach.call(a,J)}}function y(a){a=(a||"").toLowerCase();
-return x[a]||x[F[a]]}var H=[],w=Object.keys,x={},F={},K=/^(no-?highlight|plain|text)$/i,P=/\blang(?:uage)?-([\w-]+)\b/i,O=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,q={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0};b.highlight=A;b.highlightAuto=E;b.fixMarkup=I;b.highlightBlock=J;b.configure=function(a){q=r(q,a)};b.initHighlighting=u;b.initHighlightingOnLoad=function(){addEventListener("DOMContentLoaded",u,!1);addEventListener("load",u,!1)};b.registerLanguage=function(a,d){var e=x[a]=d(b);e.aliases&&
-e.aliases.forEach(function(b){F[b]=a})};b.listLanguages=function(){return w(x)};b.getLanguage=y;b.inherit=r;b.IDENT_RE="[a-zA-Z]\\w*";b.UNDERSCORE_IDENT_RE="[a-zA-Z_]\\w*";b.NUMBER_RE="\\b\\d+(\\.\\d+)?";b.C_NUMBER_RE="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)";b.BINARY_NUMBER_RE="\\b(0b[01]+)";b.RE_STARTERS_RE="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~";b.BACKSLASH_ESCAPE=
-{begin:"\\\\[\\s\\S]",relevance:0};b.APOS_STRING_MODE={className:"string",begin:"'",end:"'",illegal:"\\n",contains:[b.BACKSLASH_ESCAPE]};b.QUOTE_STRING_MODE={className:"string",begin:'"',end:'"',illegal:"\\n",contains:[b.BACKSLASH_ESCAPE]};b.PHRASAL_WORDS_MODE={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/};b.COMMENT=function(a,d,e){a=b.inherit({className:"comment",begin:a,end:d,contains:[]},e||{});
+var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.ASSUME_ES5=!1;$jscomp.ASSUME_NO_NATIVE_MAP=!1;$jscomp.ASSUME_NO_NATIVE_SET=!1;$jscomp.defineProperty=$jscomp.ASSUME_ES5||"function"==typeof Object.defineProperties?Object.defineProperty:function(b,g,l){b!=Array.prototype&&b!=Object.prototype&&(b[g]=l.value)};$jscomp.getGlobal=function(b){return"undefined"!=typeof window&&window===b?b:"undefined"!=typeof global&&null!=global?global:b};$jscomp.global=$jscomp.getGlobal(this);$jscomp.SYMBOL_PREFIX="jscomp_symbol_";
+$jscomp.initSymbol=function(){$jscomp.initSymbol=function(){};$jscomp.global.Symbol||($jscomp.global.Symbol=$jscomp.Symbol)};$jscomp.symbolCounter_=0;$jscomp.Symbol=function(b){return $jscomp.SYMBOL_PREFIX+(b||"")+$jscomp.symbolCounter_++};
+$jscomp.initSymbolIterator=function(){$jscomp.initSymbol();var b=$jscomp.global.Symbol.iterator;b||(b=$jscomp.global.Symbol.iterator=$jscomp.global.Symbol("iterator"));"function"!=typeof Array.prototype[b]&&$jscomp.defineProperty(Array.prototype,b,{configurable:!0,writable:!0,value:function(){return $jscomp.arrayIterator(this)}});$jscomp.initSymbolIterator=function(){}};$jscomp.arrayIterator=function(b){var g=0;return $jscomp.iteratorPrototype(function(){return g<b.length?{done:!1,value:b[g++]}:{done:!0}})};
+$jscomp.iteratorPrototype=function(b){$jscomp.initSymbolIterator();b={next:b};b[$jscomp.global.Symbol.iterator]=function(){return this};return b};$jscomp.iteratorFromArray=function(b,g){$jscomp.initSymbolIterator();b instanceof String&&(b+="");var l=0,k={next:function(){if(l<b.length){var m=l++;return{value:g(m,b[m]),done:!1}}k.next=function(){return{done:!0,value:void 0}};return k.next()}};k[Symbol.iterator]=function(){return k};return k};
+$jscomp.polyfill=function(b,g,l,k){if(g){l=$jscomp.global;b=b.split(".");for(k=0;k<b.length-1;k++){var m=b[k];m in l||(l[m]={});l=l[m]}b=b[b.length-1];k=l[b];g=g(k);g!=k&&null!=g&&$jscomp.defineProperty(l,b,{configurable:!0,writable:!0,value:g})}};$jscomp.polyfill("Array.prototype.keys",function(b){return b?b:function(){return $jscomp.iteratorFromArray(this,function(b){return b})}},"es6","es3");
+(function(b){var g="object"===typeof window&&window||"object"===typeof self&&self;"undefined"!==typeof exports?b(exports):g&&(g.hljs=b({}),"function"===typeof define&&define.amd&&define([],function(){return g.hljs}))})(function(b){function g(a){return a.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function l(a,f){return(a=a&&a.exec(f))&&0===a.index}function k(a){var f,b={},e=Array.prototype.slice.call(arguments,1);for(f in a)b[f]=a[f];e.forEach(function(a){for(f in a)b[f]=a[f]});
+return b}function m(a){var f=[];(function e(a,b){for(a=a.firstChild;a;a=a.nextSibling)3===a.nodeType?b+=a.nodeValue.length:1===a.nodeType&&(f.push({event:"start",offset:b,node:a}),b=e(a,b),a.nodeName.toLowerCase().match(/br|hr|img|input/)||f.push({event:"stop",offset:b,node:a}));return b})(a,0);return f}function L(a,f,b){function d(){return a.length&&f.length?a[0].offset!==f[0].offset?a[0].offset<f[0].offset?a:f:"start"===f[0].event?a:f:a.length?a:f}function c(a){B+="<"+a.nodeName.toLowerCase()+H.map.call(a.attributes,
+function(a){return" "+a.nodeName+'="'+g(a.value).replace('"',"&quot;")+'"'}).join("")+">"}function q(a){B+="</"+a.nodeName.toLowerCase()+">"}function w(a){("start"===a.event?c:q)(a.node)}for(var r=0,B="",n=[];a.length||f.length;){var h=d();B+=g(b.substring(r,h[0].offset));r=h[0].offset;if(h===a){n.reverse().forEach(q);do w(h.splice(0,1)[0]),h=d();while(h===a&&h.length&&h[0].offset===r);n.reverse().forEach(c)}else"start"===h[0].event?n.push(h[0].node):n.pop(),w(h.splice(0,1)[0])}return B+g(b.substr(r))}
+function M(a){a.variants&&!a.cached_variants&&(a.cached_variants=a.variants.map(function(f){return k(a,{variants:null},f)}));return a.cached_variants||a.endsWithParent&&[k(a)]||[a]}function N(a){function f(a){return a&&a.source||a}function b(b,d){return new RegExp(f(b),"m"+(a.case_insensitive?"i":"")+(d?"g":""))}function e(c,d){if(!c.compiled){c.compiled=!0;c.keywords=c.keywords||c.beginKeywords;if(c.keywords){var q={},g=function(f,b){a.case_insensitive&&(b=b.toLowerCase());b.split(" ").forEach(function(a){a=
+a.split("|");q[a[0]]=[f,a[1]?Number(a[1]):1]})};"string"===typeof c.keywords?g("keyword",c.keywords):x(c.keywords).forEach(function(a){g(a,c.keywords[a])});c.keywords=q}c.lexemesRe=b(c.lexemes||/\w+/,!0);d&&(c.beginKeywords&&(c.begin="\\b("+c.beginKeywords.split(" ").join("|")+")\\b"),c.begin||(c.begin=/\B|\b/),c.beginRe=b(c.begin),c.end||c.endsWithParent||(c.end=/\B|\b/),c.end&&(c.endRe=b(c.end)),c.terminator_end=f(c.end)||"",c.endsWithParent&&d.terminator_end&&(c.terminator_end+=(c.end?"|":"")+
+d.terminator_end));c.illegal&&(c.illegalRe=b(c.illegal));null==c.relevance&&(c.relevance=1);c.contains||(c.contains=[]);c.contains=Array.prototype.concat.apply([],c.contains.map(function(a){return M("self"===a?c:a)}));c.contains.forEach(function(a){e(a,c)});c.starts&&e(c.starts,d);d=c.contains.map(function(a){return a.beginKeywords?"\\.?("+a.begin+")\\.?":a.begin}).concat([c.terminator_end,c.illegal]).map(f).filter(Boolean);c.terminators=d.length?b(d.join("|"),!0):{exec:function(){return null}}}}
+e(a)}function C(a,f,b,e){function c(a,b){if(l(a.endRe,b)){for(;a.endsParent&&a.parent;)a=a.parent;return a}if(a.endsWithParent)return c(a.parent,b)}function d(a,b,f,d){return'<span class="'+(d?"":t.classPrefix)+(a+'">')+b+(f?"":"</span>")}function w(){var a=v,b;if(null!=h.subLanguage)if((b="string"===typeof h.subLanguage)&&!y[h.subLanguage])b=g(p);else{var f=b?C(h.subLanguage,p,!0,m[h.subLanguage]):F(p,h.subLanguage.length?h.subLanguage:void 0);0<h.relevance&&(u+=f.relevance);b&&(m[h.subLanguage]=
+f.top);b=d(f.language,f.value,!1,!0)}else if(h.keywords){f="";var c=0;h.lexemesRe.lastIndex=0;for(b=h.lexemesRe.exec(p);b;){f+=g(p.substring(c,b.index));c=h;var e=b;e=n.case_insensitive?e[0].toLowerCase():e[0];(c=c.keywords.hasOwnProperty(e)&&c.keywords[e])?(u+=c[1],f+=d(c[0],g(b[0]))):f+=g(b[0]);c=h.lexemesRe.lastIndex;b=h.lexemesRe.exec(p)}b=f+g(p.substr(c))}else b=g(p);v=a+b;p=""}function r(a){v+=a.className?d(a.className,"",!0):"";h=Object.create(a,{parent:{value:h}})}function k(a,f){p+=a;if(null==
+f)return w(),0;a:{a=h;var d;var e=0;for(d=a.contains.length;e<d;e++)if(l(a.contains[e].beginRe,f)){a=a.contains[e];break a}a=void 0}if(a)return a.skip?p+=f:(a.excludeBegin&&(p+=f),w(),a.returnBegin||a.excludeBegin||(p=f)),r(a,f),a.returnBegin?0:f.length;if(a=c(h,f)){e=h;e.skip?p+=f:(e.returnEnd||e.excludeEnd||(p+=f),w(),e.excludeEnd&&(p=f));do h.className&&(v+="</span>"),h.skip||(u+=h.relevance),h=h.parent;while(h!==a.parent);a.starts&&r(a.starts,"");return e.returnEnd?0:f.length}if(!b&&l(h.illegalRe,
+f))throw Error('Illegal lexeme "'+f+'" for mode "'+(h.className||"<unnamed>")+'"');p+=f;return f.length||1}var n=z(a);if(!n)throw Error('Unknown language: "'+a+'"');N(n);var h=e||n,m={},v="";for(e=h;e!==n;e=e.parent)e.className&&(v=d(e.className,"",!0)+v);var p="",u=0;try{for(var A,x,D=0;;){h.terminators.lastIndex=D;A=h.terminators.exec(f);if(!A)break;x=k(f.substring(D,A.index),A[0]);D=A.index+x}k(f.substr(D));for(e=h;e.parent;e=e.parent)e.className&&(v+="</span>");return{relevance:u,value:v,language:a,
+top:h}}catch(E){if(E.message&&-1!==E.message.indexOf("Illegal"))return{relevance:0,value:g(f)};throw E;}}function F(a,f){f=f||t.languages||x(y);var b={relevance:0,value:g(a)},e=b;f.filter(z).forEach(function(f){var c=C(f,a,!1);c.language=f;c.relevance>e.relevance&&(e=c);c.relevance>b.relevance&&(e=b,b=c)});e.language&&(b.second_best=e);return b}function I(a){return t.tabReplace||t.useBR?a.replace(O,function(a,b){return t.useBR&&"\n"===a?"<br>":t.tabReplace?b.replace(/\t/g,t.tabReplace):""}):a}function J(a){var b,
+d;a:{var e=a.className+" ";e+=a.parentNode?a.parentNode.className:"";if(d=P.exec(e))d=z(d[1])?d[1]:"no-highlight";else{e=e.split(/\s+/);d=0;for(b=e.length;d<b;d++){var c=e[d];if(K.test(c)||z(c)){d=c;break a}}d=void 0}}if(!K.test(d)){t.useBR?(c=document.createElementNS("http://www.w3.org/1999/xhtml","div"),c.innerHTML=a.innerHTML.replace(/\n/g,"").replace(/<br[ \/]*>/g,"\n")):c=a;b=c.textContent;e=d?C(d,b,!0):F(b);c=m(c);if(c.length){var q=document.createElementNS("http://www.w3.org/1999/xhtml","div");
+q.innerHTML=e.value;e.value=L(c,m(q),b)}e.value=I(e.value);a.innerHTML=e.value;b=a.className;d=d?G[d]:e.language;c=[b.trim()];b.match(/\bhljs\b/)||c.push("hljs");-1===b.indexOf(d)&&c.push(d);d=c.join(" ").trim();a.className=d;a.result={language:e.language,re:e.relevance};e.second_best&&(a.second_best={language:e.second_best.language,re:e.second_best.relevance})}}function u(){if(!u.called){u.called=!0;var a=document.querySelectorAll("pre code");H.forEach.call(a,J)}}function z(a){a=(a||"").toLowerCase();
+return y[a]||y[G[a]]}var H=[],x=Object.keys,y={},G={},K=/^(no-?highlight|plain|text)$/i,P=/\blang(?:uage)?-([\w-]+)\b/i,O=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,t={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0};b.highlight=C;b.highlightAuto=F;b.fixMarkup=I;b.highlightBlock=J;b.configure=function(a){t=k(t,a)};b.initHighlighting=u;b.initHighlightingOnLoad=function(){addEventListener("DOMContentLoaded",u,!1);addEventListener("load",u,!1)};b.registerLanguage=function(a,f){f=y[a]=f(b);f.aliases&&
+f.aliases.forEach(function(b){G[b]=a})};b.listLanguages=function(){return x(y)};b.getLanguage=z;b.inherit=k;b.IDENT_RE="[a-zA-Z]\\w*";b.UNDERSCORE_IDENT_RE="[a-zA-Z_]\\w*";b.NUMBER_RE="\\b\\d+(\\.\\d+)?";b.C_NUMBER_RE="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)";b.BINARY_NUMBER_RE="\\b(0b[01]+)";b.RE_STARTERS_RE="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~";b.BACKSLASH_ESCAPE=
+{begin:"\\\\[\\s\\S]",relevance:0};b.APOS_STRING_MODE={className:"string",begin:"'",end:"'",illegal:"\\n",contains:[b.BACKSLASH_ESCAPE]};b.QUOTE_STRING_MODE={className:"string",begin:'"',end:'"',illegal:"\\n",contains:[b.BACKSLASH_ESCAPE]};b.PHRASAL_WORDS_MODE={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/};b.COMMENT=function(a,f,d){a=b.inherit({className:"comment",begin:a,end:f,contains:[]},d||{});
 a.contains.push(b.PHRASAL_WORDS_MODE);a.contains.push({className:"doctag",begin:"(?:TODO|FIXME|NOTE|BUG|XXX):",relevance:0});return a};b.C_LINE_COMMENT_MODE=b.COMMENT("//","$");b.C_BLOCK_COMMENT_MODE=b.COMMENT("/\\*","\\*/");b.HASH_COMMENT_MODE=b.COMMENT("#","$");b.NUMBER_MODE={className:"number",begin:b.NUMBER_RE,relevance:0};b.C_NUMBER_MODE={className:"number",begin:b.C_NUMBER_RE,relevance:0};b.BINARY_NUMBER_MODE={className:"number",begin:b.BINARY_NUMBER_RE,relevance:0};b.CSS_NUMBER_MODE={className:"number",
 begin:b.NUMBER_RE+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0};b.REGEXP_MODE={className:"regexp",begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[b.BACKSLASH_ESCAPE,{begin:/\[/,end:/\]/,relevance:0,contains:[b.BACKSLASH_ESCAPE]}]};b.TITLE_MODE={className:"title",begin:b.IDENT_RE,relevance:0};b.UNDERSCORE_TITLE_MODE={className:"title",begin:b.UNDERSCORE_IDENT_RE,relevance:0};b.METHOD_GUARD={begin:"\\.\\s*"+b.UNDERSCORE_IDENT_RE,relevance:0};
-b.registerLanguage("bash",function(a){var b={className:"variable",variants:[{begin:/\$[\w\d#@][\w\d_]*/},{begin:/\$\{(.*?)}/}]},e={className:"string",begin:/"/,end:/"/,contains:[a.BACKSLASH_ESCAPE,b,{className:"variable",begin:/\$\(/,end:/\)/,contains:[a.BACKSLASH_ESCAPE]}]};return{aliases:["sh","zsh"],lexemes:/\b-?[a-z\._]+\b/,keywords:{keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",
-_:"-ne -eq -lt -gt -f -d -e -s -l -a"},contains:[{className:"meta",begin:/^#![^\n]+sh\s*$/,relevance:10},{className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,contains:[a.inherit(a.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0},a.HASH_COMMENT_MODE,e,{className:"string",begin:/'/,end:/'/},b]}});b.registerLanguage("cpp",function(a){var b={className:"keyword",begin:"\\b[a-z\\d_]*_t\\b"},e={className:"string",variants:[{begin:'(u8?|U)?L?"',end:'"',illegal:"\\n",contains:[a.BACKSLASH_ESCAPE]},
-{begin:'(u8?|U)?R"',end:'"',contains:[a.BACKSLASH_ESCAPE]},{begin:"'\\\\?.",end:"'",illegal:"."}]},h={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},c={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{"meta-keyword":"if else elif endif define undef warning error line pragma ifdef ifndef include"},contains:[{begin:/\\\n/,
-relevance:0},a.inherit(e,{className:"meta-string"}),{className:"meta-string",begin:/<[^\n>]*>/,end:/$/,illegal:"\\n"},a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE]},f=a.IDENT_RE+"\\s*\\(",k={keyword:"int float while private char catch import module export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using asm case typeid short reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignof constexpr decltype noexcept static_assert thread_local restrict _Bool complex _Complex _Imaginary atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong new throw return and or not",
+b.registerLanguage("bash",function(a){var b={className:"variable",variants:[{begin:/\$[\w\d#@][\w\d_]*/},{begin:/\$\{(.*?)}/}]},d={className:"string",begin:/"/,end:/"/,contains:[a.BACKSLASH_ESCAPE,b,{className:"variable",begin:/\$\(/,end:/\)/,contains:[a.BACKSLASH_ESCAPE]}]};return{aliases:["sh","zsh"],lexemes:/\b-?[a-z\._]+\b/,keywords:{keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",
+_:"-ne -eq -lt -gt -f -d -e -s -l -a"},contains:[{className:"meta",begin:/^#![^\n]+sh\s*$/,relevance:10},{className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,contains:[a.inherit(a.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0},a.HASH_COMMENT_MODE,d,{className:"string",begin:/'/,end:/'/},b]}});b.registerLanguage("clojure",function(a){var b={className:"number",begin:"[-+]?\\d+(\\.\\d+)?",relevance:0},d=a.inherit(a.QUOTE_STRING_MODE,{illegal:null}),e=a.COMMENT(";","$",{relevance:0}),
+c={className:"literal",begin:/\b(true|false|nil)\b/},q={begin:"[\\[\\{]",end:"[\\]\\}]"},g={className:"comment",begin:"\\^[a-zA-Z_\\-!.?+*=<>&#'][a-zA-Z_\\-!.?+*=<>&#'0-9/;:]*"},r=a.COMMENT("\\^\\{","\\}"),k={className:"symbol",begin:"[:]{1,2}[a-zA-Z_\\-!.?+*=<>&#'][a-zA-Z_\\-!.?+*=<>&#'0-9/;:]*"},n={begin:"\\(",end:"\\)"},h={endsWithParent:!0,relevance:0},l={keywords:{"builtin-name":"def defonce cond apply if-not if-let if not not= = < > <= >= == + / * - rem quot neg? pos? delay? symbol? keyword? true? false? integer? empty? coll? list? set? ifn? fn? associative? sequential? sorted? counted? reversible? number? decimal? class? distinct? isa? float? rational? reduced? ratio? odd? even? char? seq? vector? string? map? nil? contains? zero? instance? not-every? not-any? libspec? -> ->> .. . inc compare do dotimes mapcat take remove take-while drop letfn drop-last take-last drop-while while intern condp case reduced cycle split-at split-with repeat replicate iterate range merge zipmap declare line-seq sort comparator sort-by dorun doall nthnext nthrest partition eval doseq await await-for let agent atom send send-off release-pending-sends add-watch mapv filterv remove-watch agent-error restart-agent set-error-handler error-handler set-error-mode! error-mode shutdown-agents quote var fn loop recur throw try monitor-enter monitor-exit defmacro defn defn- macroexpand macroexpand-1 for dosync and or when when-not when-let comp juxt partial sequence memoize constantly complement identity assert peek pop doto proxy defstruct first rest cons defprotocol cast coll deftype defrecord last butlast sigs reify second ffirst fnext nfirst nnext defmulti defmethod meta with-meta ns in-ns create-ns import refer keys select-keys vals key val rseq name namespace promise into transient persistent! conj! assoc! dissoc! pop! disj! use class type num float double short byte boolean bigint biginteger bigdec print-method print-dup throw-if printf format load compile get-in update-in pr pr-on newline flush read slurp read-line subvec with-open memfn time re-find re-groups rand-int rand mod locking assert-valid-fdecl alias resolve ref deref refset swap! reset! set-validator! compare-and-set! alter-meta! reset-meta! commute get-validator alter ref-set ref-history-count ref-min-history ref-max-history ensure sync io! new next conj set! to-array future future-call into-array aset gen-class reduce map filter find empty hash-map hash-set sorted-map sorted-map-by sorted-set sorted-set-by vec vector seq flatten reverse assoc dissoc list disj get union difference intersection extend extend-type extend-protocol int nth delay count concat chunk chunk-buffer chunk-append chunk-first chunk-rest max min dec unchecked-inc-int unchecked-inc unchecked-dec-inc unchecked-dec unchecked-negate unchecked-add-int unchecked-add unchecked-subtract-int unchecked-subtract chunk-next chunk-cons chunked-seq? prn vary-meta lazy-seq spread list* str find-keyword keyword symbol gensym force rationalize"},
+lexemes:"[a-zA-Z_\\-!.?+*=<>&#'][a-zA-Z_\\-!.?+*=<>&#'0-9/;:]*",className:"name",begin:"[a-zA-Z_\\-!.?+*=<>&#'][a-zA-Z_\\-!.?+*=<>&#'0-9/;:]*",starts:h},m=[n,d,g,r,e,k,q,b,c,{begin:"[a-zA-Z_\\-!.?+*=<>&#'][a-zA-Z_\\-!.?+*=<>&#'0-9/;:]*",relevance:0}];n.contains=[a.COMMENT("comment",""),l,h];h.contains=m;q.contains=m;r.contains=[q];return{aliases:["clj"],illegal:/\S/,contains:[n,d,g,r,e,k,q,b,c]}});b.registerLanguage("cpp",function(a){var b={className:"keyword",begin:"\\b[a-z\\d_]*_t\\b"},d={className:"string",
+variants:[{begin:'(u8?|U)?L?"',end:'"',illegal:"\\n",contains:[a.BACKSLASH_ESCAPE]},{begin:'(u8?|U)?R"',end:'"',contains:[a.BACKSLASH_ESCAPE]},{begin:"'\\\\?.",end:"'",illegal:"."}]},e={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},c={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{"meta-keyword":"if else elif endif define undef warning error line pragma ifdef ifndef include"},
+contains:[{begin:/\\\n/,relevance:0},a.inherit(d,{className:"meta-string"}),{className:"meta-string",begin:/<[^\n>]*>/,end:/$/,illegal:"\\n"},a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE]},q=a.IDENT_RE+"\\s*\\(",g={keyword:"int float while private char catch import module export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using asm case typeid short reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignof constexpr decltype noexcept static_assert thread_local restrict _Bool complex _Complex _Imaginary atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong new throw return and or not",
 built_in:"std string cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap array shared_ptr abort abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr",
-literal:"true false nullptr NULL"},l=[b,a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,h,e];return{aliases:"c cc h c++ h++ hpp".split(" "),keywords:k,illegal:"</",contains:l.concat([c,{begin:"\\b(deque|list|queue|stack|vector|map|set|bitset|multiset|multimap|unordered_map|unordered_set|unordered_multiset|unordered_multimap|array)\\s*<",end:">",keywords:k,contains:["self",b]},{begin:a.IDENT_RE+"::",keywords:k},{variants:[{begin:/=/,end:/;/},{begin:/\(/,end:/\)/},{beginKeywords:"new throw return else",
-end:/;/}],keywords:k,contains:l.concat([{begin:/\(/,end:/\)/,keywords:k,contains:l.concat(["self"]),relevance:0}]),relevance:0},{className:"function",begin:"("+a.IDENT_RE+"[\\*&\\s]+)+"+f,returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:k,illegal:/[^\w\s\*&]/,contains:[{begin:f,returnBegin:!0,contains:[a.TITLE_MODE],relevance:0},{className:"params",begin:/\(/,end:/\)/,keywords:k,relevance:0,contains:[a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,e,h,b]},a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,
-c]},{className:"class",beginKeywords:"class struct",end:/[{;:]/,contains:[{begin:/</,end:/>/,contains:["self"]},a.TITLE_MODE]}]),exports:{preprocessor:c,strings:e,keywords:k}}});b.registerLanguage("css",function(a){return{case_insensitive:!0,illegal:/[=\/|'\$]/,contains:[a.C_BLOCK_COMMENT_MODE,{className:"selector-id",begin:/#[A-Za-z0-9_-]+/},{className:"selector-class",begin:/\.[A-Za-z0-9_-]+/},{className:"selector-attr",begin:/\[/,end:/\]/,illegal:"$"},{className:"selector-pseudo",begin:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},
-{begin:"@(font-face|page)",lexemes:"[a-z-]+",keywords:"font-face page"},{begin:"@",end:"[{;]",illegal:/:/,contains:[{className:"keyword",begin:/\w+/},{begin:/\s/,endsWithParent:!0,excludeEnd:!0,relevance:0,contains:[a.APOS_STRING_MODE,a.QUOTE_STRING_MODE,a.CSS_NUMBER_MODE]}]},{className:"selector-tag",begin:"[a-zA-Z-][a-zA-Z0-9_-]*",relevance:0},{begin:"{",end:"}",illegal:/\S/,contains:[a.C_BLOCK_COMMENT_MODE,{begin:/[A-Z\_\.\-]+\s*:/,returnBegin:!0,end:";",endsWithParent:!0,contains:[{className:"attribute",
-begin:/\S/,end:":",excludeEnd:!0,starts:{endsWithParent:!0,excludeEnd:!0,contains:[{begin:/[\w-]+\(/,returnBegin:!0,contains:[{className:"built_in",begin:/[\w-]+/},{begin:/\(/,end:/\)/,contains:[a.APOS_STRING_MODE,a.QUOTE_STRING_MODE]}]},a.CSS_NUMBER_MODE,a.QUOTE_STRING_MODE,a.APOS_STRING_MODE,a.C_BLOCK_COMMENT_MODE,{className:"number",begin:"#[0-9A-Fa-f]+"},{className:"meta",begin:"!important"}]}}]}]}]}});b.registerLanguage("go",function(a){var b={keyword:"break default func interface select case map struct chan else goto package switch const fallthrough if range type continue for import return var go defer bool byte complex64 complex128 float32 float64 int8 int16 int32 int64 string uint8 uint16 uint32 uint64 int uint uintptr rune",
+literal:"true false nullptr NULL"},k=[b,a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,e,d];return{aliases:"c cc h c++ h++ hpp".split(" "),keywords:g,illegal:"</",contains:k.concat([c,{begin:"\\b(deque|list|queue|stack|vector|map|set|bitset|multiset|multimap|unordered_map|unordered_set|unordered_multiset|unordered_multimap|array)\\s*<",end:">",keywords:g,contains:["self",b]},{begin:a.IDENT_RE+"::",keywords:g},{variants:[{begin:/=/,end:/;/},{begin:/\(/,end:/\)/},{beginKeywords:"new throw return else",
+end:/;/}],keywords:g,contains:k.concat([{begin:/\(/,end:/\)/,keywords:g,contains:k.concat(["self"]),relevance:0}]),relevance:0},{className:"function",begin:"("+a.IDENT_RE+"[\\*&\\s]+)+"+q,returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:g,illegal:/[^\w\s\*&]/,contains:[{begin:q,returnBegin:!0,contains:[a.TITLE_MODE],relevance:0},{className:"params",begin:/\(/,end:/\)/,keywords:g,relevance:0,contains:[a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,d,e,b]},a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,
+c]},{className:"class",beginKeywords:"class struct",end:/[{;:]/,contains:[{begin:/</,end:/>/,contains:["self"]},a.TITLE_MODE]}]),exports:{preprocessor:c,strings:d,keywords:g}}});b.registerLanguage("cs",function(a){var b={keyword:"abstract as base bool break byte case catch char checked const continue decimal default delegate do double enum event explicit extern finally fixed float for foreach goto if implicit in int interface internal is lock long nameof object operator out override params private protected public readonly ref sbyte sealed short sizeof stackalloc static string struct switch this try typeof uint ulong unchecked unsafe ushort using virtual void volatile while add alias ascending async await by descending dynamic equals from get global group into join let on orderby partial remove select set value var where yield",
+literal:"null false true"},d={className:"string",begin:'@"',end:'"',contains:[{begin:'""'}]},e=a.inherit(d,{illegal:/\n/}),c={className:"subst",begin:"{",end:"}",keywords:b},g=a.inherit(c,{illegal:/\n/}),k={className:"string",begin:/\$"/,end:'"',illegal:/\n/,contains:[{begin:"{{"},{begin:"}}"},a.BACKSLASH_ESCAPE,g]},l={className:"string",begin:/\$@"/,end:'"',contains:[{begin:"{{"},{begin:"}}"},{begin:'""'},c]},m=a.inherit(l,{illegal:/\n/,contains:[{begin:"{{"},{begin:"}}"},{begin:'""'},g]});c.contains=
+[l,k,d,a.APOS_STRING_MODE,a.QUOTE_STRING_MODE,a.C_NUMBER_MODE,a.C_BLOCK_COMMENT_MODE];g.contains=[m,k,e,a.APOS_STRING_MODE,a.QUOTE_STRING_MODE,a.C_NUMBER_MODE,a.inherit(a.C_BLOCK_COMMENT_MODE,{illegal:/\n/})];d={variants:[l,k,d,a.APOS_STRING_MODE,a.QUOTE_STRING_MODE]};e=a.IDENT_RE+"(<"+a.IDENT_RE+"(\\s*,\\s*"+a.IDENT_RE+")*>)?(\\[\\])?";return{aliases:["csharp"],keywords:b,illegal:/::/,contains:[a.COMMENT("///","$",{returnBegin:!0,contains:[{className:"doctag",variants:[{begin:"///",relevance:0},
+{begin:"\x3c!--|--\x3e"},{begin:"</?",end:">"}]}]}),a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,{className:"meta",begin:"#",end:"$",keywords:{"meta-keyword":"if else elif endif define undef warning error line region endregion pragma checksum"}},d,a.C_NUMBER_MODE,{beginKeywords:"class interface",end:/[{;=]/,illegal:/[^\s:]/,contains:[a.TITLE_MODE,a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE]},{beginKeywords:"namespace",end:/[{;=]/,illegal:/[^\s:]/,contains:[a.inherit(a.TITLE_MODE,{begin:"[a-zA-Z](\\.?\\w)*"}),
+a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE]},{className:"meta",begin:"^\\s*\\[",excludeBegin:!0,end:"\\]",excludeEnd:!0,contains:[{className:"meta-string",begin:/"/,end:/"/}]},{beginKeywords:"new return throw await else",relevance:0},{className:"function",begin:"("+e+"\\s+)+"+a.IDENT_RE+"\\s*\\(",returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:b,contains:[{begin:a.IDENT_RE+"\\s*\\(",returnBegin:!0,contains:[a.TITLE_MODE],relevance:0},{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,
+keywords:b,relevance:0,contains:[d,a.C_NUMBER_MODE,a.C_BLOCK_COMMENT_MODE]},a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE]}]}});b.registerLanguage("css",function(a){return{case_insensitive:!0,illegal:/[=\/|'\$]/,contains:[a.C_BLOCK_COMMENT_MODE,{className:"selector-id",begin:/#[A-Za-z0-9_-]+/},{className:"selector-class",begin:/\.[A-Za-z0-9_-]+/},{className:"selector-attr",begin:/\[/,end:/\]/,illegal:"$"},{className:"selector-pseudo",begin:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{begin:"@(font-face|page)",
+lexemes:"[a-z-]+",keywords:"font-face page"},{begin:"@",end:"[{;]",illegal:/:/,contains:[{className:"keyword",begin:/\w+/},{begin:/\s/,endsWithParent:!0,excludeEnd:!0,relevance:0,contains:[a.APOS_STRING_MODE,a.QUOTE_STRING_MODE,a.CSS_NUMBER_MODE]}]},{className:"selector-tag",begin:"[a-zA-Z-][a-zA-Z0-9_-]*",relevance:0},{begin:"{",end:"}",illegal:/\S/,contains:[a.C_BLOCK_COMMENT_MODE,{begin:/[A-Z\_\.\-]+\s*:/,returnBegin:!0,end:";",endsWithParent:!0,contains:[{className:"attribute",begin:/\S/,end:":",
+excludeEnd:!0,starts:{endsWithParent:!0,excludeEnd:!0,contains:[{begin:/[\w-]+\(/,returnBegin:!0,contains:[{className:"built_in",begin:/[\w-]+/},{begin:/\(/,end:/\)/,contains:[a.APOS_STRING_MODE,a.QUOTE_STRING_MODE]}]},a.CSS_NUMBER_MODE,a.QUOTE_STRING_MODE,a.APOS_STRING_MODE,a.C_BLOCK_COMMENT_MODE,{className:"number",begin:"#[0-9A-Fa-f]+"},{className:"meta",begin:"!important"}]}}]}]}]}});b.registerLanguage("d",function(a){var b=a.COMMENT("\\/\\+","\\+\\/",{contains:["self"],relevance:10});return{lexemes:a.UNDERSCORE_IDENT_RE,
+keywords:{keyword:"abstract alias align asm assert auto body break byte case cast catch class const continue debug default delete deprecated do else enum export extern final finally for foreach foreach_reverse|10 goto if immutable import in inout int interface invariant is lazy macro mixin module new nothrow out override package pragma private protected public pure ref return scope shared static struct super switch synchronized template this throw try typedef typeid typeof union unittest version void volatile while with __FILE__ __LINE__ __gshared|10 __thread __traits __DATE__ __EOF__ __TIME__ __TIMESTAMP__ __VENDOR__ __VERSION__",
+built_in:"bool cdouble cent cfloat char creal dchar delegate double dstring float function idouble ifloat ireal long real short string ubyte ucent uint ulong ushort wchar wstring",literal:"false null true"},contains:[a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,b,{className:"string",begin:'x"[\\da-fA-F\\s\\n\\r]*"[cwd]?',relevance:10},{className:"string",begin:'"',contains:[{begin:"\\\\(['\"\\?\\\\abfnrtv]|u[\\dA-Fa-f]{4}|[0-7]{1,3}|x[\\dA-Fa-f]{2}|U[\\dA-Fa-f]{8})|&[a-zA-Z\\d]{2,};",relevance:0}],
+end:'"[cwd]?'},{className:"string",begin:'[rq]"',end:'"[cwd]?',relevance:5},{className:"string",begin:"`",end:"`[cwd]?"},{className:"string",begin:'q"\\{',end:'\\}"'},{className:"number",begin:"\\b(((0[xX](([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*)\\.([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*)|\\.?([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*))[pP][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d))|((0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)(\\.\\d*|([eE][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)))|\\d+\\.(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)|\\.(0|[1-9][\\d_]*)([eE][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d))?))([fF]|L|i|[fF]i|Li)?|((0|[1-9][\\d_]*)|0[bB][01_]+|0[xX]([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*))(i|[fF]i|Li))",
+relevance:0},{className:"number",begin:"\\b((0|[1-9][\\d_]*)|0[bB][01_]+|0[xX]([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*))(L|u|U|Lu|LU|uL|UL)?",relevance:0},{className:"string",begin:"'(\\\\(['\"\\?\\\\abfnrtv]|u[\\dA-Fa-f]{4}|[0-7]{1,3}|x[\\dA-Fa-f]{2}|U[\\dA-Fa-f]{8})|&[a-zA-Z\\d]{2,};|.)",end:"'",illegal:"."},{className:"meta",begin:"^#!",end:"$",relevance:5},{className:"meta",begin:"#(line)",end:"$",relevance:5},{className:"keyword",begin:"@[a-zA-Z_][a-zA-Z_\\d]*"}]}});b.registerLanguage("markdown",
+function(a){return{aliases:["md","mkdown","mkd"],contains:[{className:"section",variants:[{begin:"^#{1,6}",end:"$"},{begin:"^.+?\\n[=-]{2,}$"}]},{begin:"<",end:">",subLanguage:"xml",relevance:0},{className:"bullet",begin:"^([*+-]|(\\d+\\.))\\s+"},{className:"strong",begin:"[*_]{2}.+?[*_]{2}"},{className:"emphasis",variants:[{begin:"\\*.+?\\*"},{begin:"_.+?_",relevance:0}]},{className:"quote",begin:"^>\\s+",end:"$"},{className:"code",variants:[{begin:"^```w*s*$",end:"^```s*$"},{begin:"`.+?`"},{begin:"^( {4}|\t)",
+end:"$",relevance:0}]},{begin:"^[-\\*]{3,}",end:"$"},{begin:"\\[.+?\\][\\(\\[].*?[\\)\\]]",returnBegin:!0,contains:[{className:"string",begin:"\\[",end:"\\]",excludeBegin:!0,returnEnd:!0,relevance:0},{className:"link",begin:"\\]\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0},{className:"symbol",begin:"\\]\\[",end:"\\]",excludeBegin:!0,excludeEnd:!0}],relevance:10},{begin:/^\[[^\n]+\]:/,returnBegin:!0,contains:[{className:"symbol",begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0},{className:"link",
+begin:/:\s*/,end:/$/,excludeBegin:!0}]}]}});b.registerLanguage("dart",function(a){var b={className:"subst",begin:"\\$\\{",end:"}",keywords:"true false null this is new super"},d={className:"string",variants:[{begin:"r'''",end:"'''"},{begin:'r"""',end:'"""'},{begin:"r'",end:"'",illegal:"\\n"},{begin:'r"',end:'"',illegal:"\\n"},{begin:"'''",end:"'''",contains:[a.BACKSLASH_ESCAPE,b]},{begin:'"""',end:'"""',contains:[a.BACKSLASH_ESCAPE,b]},{begin:"'",end:"'",illegal:"\\n",contains:[a.BACKSLASH_ESCAPE,
+b]},{begin:'"',end:'"',illegal:"\\n",contains:[a.BACKSLASH_ESCAPE,b]}]};b.contains=[a.C_NUMBER_MODE,d];return{keywords:{keyword:"assert async await break case catch class const continue default do else enum extends false final finally for if in is new null rethrow return super switch sync this throw true try var void while with yield abstract as dynamic export external factory get implements import library operator part set static typedef",built_in:"print Comparable DateTime Duration Function Iterable Iterator List Map Match Null Object Pattern RegExp Set Stopwatch String StringBuffer StringSink Symbol Type Uri bool double int num document window querySelector querySelectorAll Element ElementList"},
+contains:[d,a.COMMENT("/\\*\\*","\\*/",{subLanguage:"markdown"}),a.COMMENT("///","$",{subLanguage:"markdown"}),a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,{className:"class",beginKeywords:"class interface",end:"{",excludeEnd:!0,contains:[{beginKeywords:"extends implements"},a.UNDERSCORE_TITLE_MODE]},a.C_NUMBER_MODE,{className:"meta",begin:"@[A-Za-z]+"},{begin:"=>"}]}});b.registerLanguage("xml",function(a){var b={endsWithParent:!0,illegal:/</,relevance:0,contains:[{className:"attr",begin:"[A-Za-z0-9\\._:-]+",
+relevance:0},{begin:/=\s*/,relevance:0,contains:[{className:"string",endsParent:!0,variants:[{begin:/"/,end:/"/},{begin:/'/,end:/'/},{begin:/[^\s"'=<>`]+/}]}]}]};return{aliases:"html xhtml rss atom xjb xsd xsl plist".split(" "),case_insensitive:!0,contains:[{className:"meta",begin:"<!DOCTYPE",end:">",relevance:10,contains:[{begin:"\\[",end:"\\]"}]},a.COMMENT("\x3c!--","--\x3e",{relevance:10}),{begin:"<\\!\\[CDATA\\[",end:"\\]\\]>",relevance:10},{begin:/<\?(php)?/,end:/\?>/,subLanguage:"php",contains:[{begin:"/\\*",
+end:"\\*/",skip:!0}]},{className:"tag",begin:"<style(?=\\s|>|$)",end:">",keywords:{name:"style"},contains:[b],starts:{end:"</style>",returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag",begin:"<script(?=\\s|>|$)",end:">",keywords:{name:"script"},contains:[b],starts:{end:"\x3c/script>",returnEnd:!0,subLanguage:["actionscript","javascript","handlebars","xml"]}},{className:"meta",variants:[{begin:/<\?xml/,end:/\?>/,relevance:10},{begin:/<\?\w+/,end:/\?>/}]},{className:"tag",begin:"</?",end:"/?>",
+contains:[{className:"name",begin:/[^\/><\s]+/,relevance:0},b]}]}});b.registerLanguage("ruby",function(a){var b={keyword:"and then defined module in return redo if BEGIN retry end for self when next until do begin unless END rescue else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",literal:"true false nil"},d={className:"doctag",begin:"@[A-Za-z]+"},e={begin:"#<",end:">"};d=[a.COMMENT("#","$",{contains:[d]}),a.COMMENT("^\\=begin",
+"^\\=end",{contains:[d],relevance:10}),a.COMMENT("^__END__","\\n$")];var c={className:"subst",begin:"#\\{",end:"}",keywords:b},g={className:"string",contains:[a.BACKSLASH_ESCAPE,c],variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/`/,end:/`/},{begin:"%[qQwWx]?\\(",end:"\\)"},{begin:"%[qQwWx]?\\[",end:"\\]"},{begin:"%[qQwWx]?{",end:"}"},{begin:"%[qQwWx]?<",end:">"},{begin:"%[qQwWx]?/",end:"/"},{begin:"%[qQwWx]?%",end:"%"},{begin:"%[qQwWx]?-",end:"-"},{begin:"%[qQwWx]?\\|",end:"\\|"},{begin:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/},
+{begin:/<<(-?)\w+$/,end:/^\s*\w+$/}]},k={className:"params",begin:"\\(",end:"\\)",endsParent:!0,keywords:b};a=[g,e,{className:"class",beginKeywords:"class module",end:"$|;",illegal:/=/,contains:[a.inherit(a.TITLE_MODE,{begin:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{begin:"<\\s*",contains:[{begin:"("+a.IDENT_RE+"::)?"+a.IDENT_RE}]}].concat(d)},{className:"function",beginKeywords:"def",end:"$|;",contains:[a.inherit(a.TITLE_MODE,{begin:"[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?"}),
+k].concat(d)},{begin:a.IDENT_RE+"::"},{className:"symbol",begin:a.UNDERSCORE_IDENT_RE+"(\\!|\\?)?:",relevance:0},{className:"symbol",begin:":(?!\\s)",contains:[g,{begin:"[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?"}],relevance:0},{className:"number",begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{begin:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{className:"params",begin:/\|/,end:/\|/,keywords:b},{begin:"("+a.RE_STARTERS_RE+
+"|unless)\\s*",keywords:"unless",contains:[e,{className:"regexp",contains:[a.BACKSLASH_ESCAPE,c],illegal:/\n/,variants:[{begin:"/",end:"/[a-z]*"},{begin:"%r{",end:"}[a-z]*"},{begin:"%r\\(",end:"\\)[a-z]*"},{begin:"%r!",end:"![a-z]*"},{begin:"%r\\[",end:"\\][a-z]*"}]}].concat(d),relevance:0}].concat(d);c.contains=a;k.contains=a;return{aliases:["rb","gemspec","podspec","thor","irb"],keywords:b,illegal:/\/\*/,contains:d.concat([{begin:/^\s*=>/,starts:{end:"$",contains:a}},{className:"meta",begin:"^([>?]>|[\\w#]+\\(\\w+\\):\\d+:\\d+>|(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>)",
+starts:{end:"$",contains:a}}]).concat(a)}});b.registerLanguage("erb",function(a){return{subLanguage:"xml",contains:[a.COMMENT("<%#","%>"),{begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0}]}});b.registerLanguage("go",function(a){var b={keyword:"break default func interface select case map struct chan else goto package switch const fallthrough if range type continue for import return var go defer bool byte complex64 complex128 float32 float64 int8 int16 int32 int64 string uint8 uint16 uint32 uint64 int uint uintptr rune",
 literal:"true false iota nil",built_in:"append cap close complex copy imag len make new panic print println real recover delete"};return{aliases:["golang"],keywords:b,illegal:"</",contains:[a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,{className:"string",variants:[a.QUOTE_STRING_MODE,{begin:"'",end:"[^\\\\]'"},{begin:"`",end:"`"}]},{className:"number",variants:[{begin:a.C_NUMBER_RE+"[dflsi]",relevance:1},a.C_NUMBER_MODE]},{begin:/:=/},{className:"function",beginKeywords:"func",end:/\s*\{/,excludeEnd:!0,
-contains:[a.TITLE_MODE,{className:"params",begin:/\(/,end:/\)/,keywords:b,illegal:/["']/}]}]}});b.registerLanguage("haskell",function(a){var b={variants:[a.COMMENT("--","$"),a.COMMENT("{-","-}",{contains:["self"]})]},e={className:"meta",begin:"{-#",end:"#-}"},h={className:"meta",begin:"^#",end:"$"},c={className:"type",begin:"\\b[A-Z][\\w']*",relevance:0},f={begin:"\\(",end:"\\)",illegal:'"',contains:[e,h,{className:"type",begin:"\\b[A-Z][\\w]*(\\((\\.\\.|,|\\w+)\\))?"},a.inherit(a.TITLE_MODE,{begin:"[_a-z][\\w']*"}),
-b]};return{aliases:["hs"],keywords:"let in if then else case of where do module import hiding qualified type data newtype deriving class instance as default infix infixl infixr foreign export ccall stdcall cplusplus jvm dotnet safe unsafe family forall mdo proc rec",contains:[{beginKeywords:"module",end:"where",keywords:"module where",contains:[f,b],illegal:"\\W\\.|;"},{begin:"\\bimport\\b",end:"$",keywords:"import qualified as hiding",contains:[f,b],illegal:"\\W\\.|;"},{className:"class",begin:"^(\\s*)?(class|instance)\\b",
-end:"where",keywords:"class family instance where",contains:[c,f,b]},{className:"class",begin:"\\b(data|(new)?type)\\b",end:"$",keywords:"data family type newtype deriving",contains:[e,c,f,{begin:"{",end:"}",contains:f.contains},b]},{beginKeywords:"default",end:"$",contains:[c,f,b]},{beginKeywords:"infix infixl infixr",end:"$",contains:[a.C_NUMBER_MODE,b]},{begin:"\\bforeign\\b",end:"$",keywords:"foreign import export ccall stdcall cplusplus jvm dotnet safe unsafe",contains:[c,a.QUOTE_STRING_MODE,
-b]},{className:"meta",begin:"#!\\/usr\\/bin\\/env runhaskell",end:"$"},e,h,a.QUOTE_STRING_MODE,a.C_NUMBER_MODE,c,a.inherit(a.TITLE_MODE,{begin:"^[_a-z][\\w']*"}),b,{begin:"->|<-"}]}});b.registerLanguage("java",function(a){return{aliases:["jsp"],keywords:"false synchronized int abstract float private char boolean static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",
+contains:[a.TITLE_MODE,{className:"params",begin:/\(/,end:/\)/,keywords:b,illegal:/["']/}]}]}});b.registerLanguage("haskell",function(a){var b={variants:[a.COMMENT("--","$"),a.COMMENT("{-","-}",{contains:["self"]})]},d={className:"meta",begin:"{-#",end:"#-}"},e={className:"meta",begin:"^#",end:"$"},c={className:"type",begin:"\\b[A-Z][\\w']*",relevance:0},g={begin:"\\(",end:"\\)",illegal:'"',contains:[d,e,{className:"type",begin:"\\b[A-Z][\\w]*(\\((\\.\\.|,|\\w+)\\))?"},a.inherit(a.TITLE_MODE,{begin:"[_a-z][\\w']*"}),
+b]};return{aliases:["hs"],keywords:"let in if then else case of where do module import hiding qualified type data newtype deriving class instance as default infix infixl infixr foreign export ccall stdcall cplusplus jvm dotnet safe unsafe family forall mdo proc rec",contains:[{beginKeywords:"module",end:"where",keywords:"module where",contains:[g,b],illegal:"\\W\\.|;"},{begin:"\\bimport\\b",end:"$",keywords:"import qualified as hiding",contains:[g,b],illegal:"\\W\\.|;"},{className:"class",begin:"^(\\s*)?(class|instance)\\b",
+end:"where",keywords:"class family instance where",contains:[c,g,b]},{className:"class",begin:"\\b(data|(new)?type)\\b",end:"$",keywords:"data family type newtype deriving",contains:[d,c,g,{begin:"{",end:"}",contains:g.contains},b]},{beginKeywords:"default",end:"$",contains:[c,g,b]},{beginKeywords:"infix infixl infixr",end:"$",contains:[a.C_NUMBER_MODE,b]},{begin:"\\bforeign\\b",end:"$",keywords:"foreign import export ccall stdcall cplusplus jvm dotnet safe unsafe",contains:[c,a.QUOTE_STRING_MODE,
+b]},{className:"meta",begin:"#!\\/usr\\/bin\\/env runhaskell",end:"$"},d,e,a.QUOTE_STRING_MODE,a.C_NUMBER_MODE,c,a.inherit(a.TITLE_MODE,{begin:"^[_a-z][\\w']*"}),b,{begin:"->|<-"}]}});b.registerLanguage("java",function(a){return{aliases:["jsp"],keywords:"false synchronized int abstract float private char boolean static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",
 illegal:/<\/|#/,contains:[a.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{begin:/\w+@/,relevance:0},{className:"doctag",begin:"@[A-Za-z]+"}]}),a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,a.APOS_STRING_MODE,a.QUOTE_STRING_MODE,{className:"class",beginKeywords:"class interface",end:/[{;=]/,excludeEnd:!0,keywords:"class interface",illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends implements"},a.UNDERSCORE_TITLE_MODE]},{beginKeywords:"new throw return else",relevance:0},{className:"function",begin:"([\u00c0-\u02b8a-zA-Z_$][\u00c0-\u02b8a-zA-Z_$0-9]*(<[\u00c0-\u02b8a-zA-Z_$][\u00c0-\u02b8a-zA-Z_$0-9]*(\\s*,\\s*[\u00c0-\u02b8a-zA-Z_$][\u00c0-\u02b8a-zA-Z_$0-9]*)*>)?\\s+)+"+
 a.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:"false synchronized int abstract float private char boolean static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",contains:[{begin:a.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,
 contains:[a.UNDERSCORE_TITLE_MODE]},{className:"params",begin:/\(/,end:/\)/,keywords:"false synchronized int abstract float private char boolean static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",relevance:0,contains:[a.APOS_STRING_MODE,a.QUOTE_STRING_MODE,a.C_NUMBER_MODE,
 a.C_BLOCK_COMMENT_MODE]},a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE]},{className:"number",begin:"\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?",relevance:0},{className:"meta",begin:"@[A-Za-z]+"}]}});b.registerLanguage("javascript",function(a){var b={keyword:"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await static import from as",
 literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise"},
-e={className:"number",variants:[{begin:"\\b(0[bB][01]+)"},{begin:"\\b(0[oO][0-7]+)"},{begin:a.C_NUMBER_RE}],relevance:0},h={className:"subst",begin:"\\$\\{",end:"\\}",keywords:b,contains:[]},c={className:"string",begin:"`",end:"`",contains:[a.BACKSLASH_ESCAPE,h]};h.contains=[a.APOS_STRING_MODE,a.QUOTE_STRING_MODE,c,e,a.REGEXP_MODE];h=h.contains.concat([a.C_BLOCK_COMMENT_MODE,a.C_LINE_COMMENT_MODE]);return{aliases:["js","jsx"],keywords:b,contains:[{className:"meta",relevance:10,begin:/^\s*['"]use (strict|asm)['"]/},
-{className:"meta",begin:/^#!/,end:/$/},a.APOS_STRING_MODE,a.QUOTE_STRING_MODE,c,a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,e,{begin:/[{,]\s*/,relevance:0,contains:[{begin:"[A-Za-z$_][0-9A-Za-z$_]*\\s*:",returnBegin:!0,relevance:0,contains:[{className:"attr",begin:"[A-Za-z$_][0-9A-Za-z$_]*",relevance:0}]}]},{begin:"("+a.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,a.REGEXP_MODE,{className:"function",begin:"(\\(.*?\\)|[A-Za-z$_][0-9A-Za-z$_]*)\\s*=>",
-returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:"[A-Za-z$_][0-9A-Za-z$_]*"},{begin:/\(\s*\)/},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:b,contains:h}]}]},{begin:/</,end:/(\/\w+|\w+\/)>/,subLanguage:"xml",contains:[{begin:/<\w+\s*\/>/,skip:!0},{begin:/<\w+/,end:/(\/\w+|\w+\/)>/,skip:!0,contains:[{begin:/<\w+\s*\/>/,skip:!0},"self"]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/\{/,excludeEnd:!0,contains:[a.inherit(a.TITLE_MODE,{begin:"[A-Za-z$_][0-9A-Za-z$_]*"}),
-{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:h}],illegal:/\[|%/},{begin:/\$[(.]/},a.METHOD_GUARD,{className:"class",beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends"},a.UNDERSCORE_TITLE_MODE]},{beginKeywords:"constructor",end:/\{/,excludeEnd:!0}],illegal:/#(?!!)/}});b.registerLanguage("json",function(a){var b={literal:"true false null"},e=[a.QUOTE_STRING_MODE,a.C_NUMBER_MODE],h={end:",",endsWithParent:!0,excludeEnd:!0,
-contains:e,keywords:b},c={begin:"{",end:"}",contains:[{className:"attr",begin:/"/,end:/"/,contains:[a.BACKSLASH_ESCAPE],illegal:"\\n"},a.inherit(h,{begin:/:/})],illegal:"\\S"};a={begin:"\\[",end:"\\]",contains:[a.inherit(h)],illegal:"\\S"};e.splice(e.length,0,c,a);return{contains:e,keywords:b,illegal:"\\S"}});b.registerLanguage("perl",function(a){var b={className:"subst",begin:"[$@]\\{",end:"\\}",keywords:"getpwent getservent quotemeta msgrcv scalar kill dbmclose undef lc ma syswrite tr send umask sysopen shmwrite vec qx utime local oct semctl localtime readpipe do return format read sprintf dbmopen pop getpgrp not getpwnam rewinddir qqfileno qw endprotoent wait sethostent bless s|0 opendir continue each sleep endgrent shutdown dump chomp connect getsockname die socketpair close flock exists index shmgetsub for endpwent redo lstat msgctl setpgrp abs exit select print ref gethostbyaddr unshift fcntl syscall goto getnetbyaddr join gmtime symlink semget splice x|0 getpeername recv log setsockopt cos last reverse gethostbyname getgrnam study formline endhostent times chop length gethostent getnetent pack getprotoent getservbyname rand mkdir pos chmod y|0 substr endnetent printf next open msgsnd readdir use unlink getsockopt getpriority rindex wantarray hex system getservbyport endservent int chr untie rmdir prototype tell listen fork shmread ucfirst setprotoent else sysseek link getgrgid shmctl waitpid unpack getnetbyname reset chdir grep split require caller lcfirst until warn while values shift telldir getpwuid my getprotobynumber delete and sort uc defined srand accept package seekdir getprotobyname semop our rename seek if q|0 chroot sysread setpwent no crypt getc chown sqrt write setnetent setpriority foreach tie sin msgget map stat getlogin unless elsif truncate exec keys glob tied closedirioctl socket readlink eval xor readline binmode setservent eof ord bind alarm pipe atan2 getgrent exp time push setgrent gt lt or ne m|0 break given say state when"},
-e={begin:"->{",end:"}"},h={variants:[{begin:/\$\d/},{begin:/[\$%@](\^\w\b|#\w+(::\w+)*|{\w+}|\w+(::\w*)*)/},{begin:/[\$%@][^\s\w{]/,relevance:0}]},c=[a.BACKSLASH_ESCAPE,b,h];a=[h,a.HASH_COMMENT_MODE,a.COMMENT("^\\=\\w","\\=cut",{endsWithParent:!0}),e,{className:"string",contains:c,variants:[{begin:"q[qwxr]?\\s*\\(",end:"\\)",relevance:5},{begin:"q[qwxr]?\\s*\\[",end:"\\]",relevance:5},{begin:"q[qwxr]?\\s*\\{",end:"\\}",relevance:5},{begin:"q[qwxr]?\\s*\\|",end:"\\|",relevance:5},{begin:"q[qwxr]?\\s*\\<",
+d={className:"number",variants:[{begin:"\\b(0[bB][01]+)"},{begin:"\\b(0[oO][0-7]+)"},{begin:a.C_NUMBER_RE}],relevance:0},e={className:"subst",begin:"\\$\\{",end:"\\}",keywords:b,contains:[]},c={className:"string",begin:"`",end:"`",contains:[a.BACKSLASH_ESCAPE,e]};e.contains=[a.APOS_STRING_MODE,a.QUOTE_STRING_MODE,c,d,a.REGEXP_MODE];e=e.contains.concat([a.C_BLOCK_COMMENT_MODE,a.C_LINE_COMMENT_MODE]);return{aliases:["js","jsx"],keywords:b,contains:[{className:"meta",relevance:10,begin:/^\s*['"]use (strict|asm)['"]/},
+{className:"meta",begin:/^#!/,end:/$/},a.APOS_STRING_MODE,a.QUOTE_STRING_MODE,c,a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,d,{begin:/[{,]\s*/,relevance:0,contains:[{begin:"[A-Za-z$_][0-9A-Za-z$_]*\\s*:",returnBegin:!0,relevance:0,contains:[{className:"attr",begin:"[A-Za-z$_][0-9A-Za-z$_]*",relevance:0}]}]},{begin:"("+a.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,a.REGEXP_MODE,{className:"function",begin:"(\\(.*?\\)|[A-Za-z$_][0-9A-Za-z$_]*)\\s*=>",
+returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:"[A-Za-z$_][0-9A-Za-z$_]*"},{begin:/\(\s*\)/},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:b,contains:e}]}]},{begin:/</,end:/(\/\w+|\w+\/)>/,subLanguage:"xml",contains:[{begin:/<\w+\s*\/>/,skip:!0},{begin:/<\w+/,end:/(\/\w+|\w+\/)>/,skip:!0,contains:[{begin:/<\w+\s*\/>/,skip:!0},"self"]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/\{/,excludeEnd:!0,contains:[a.inherit(a.TITLE_MODE,{begin:"[A-Za-z$_][0-9A-Za-z$_]*"}),
+{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:e}],illegal:/\[|%/},{begin:/\$[(.]/},a.METHOD_GUARD,{className:"class",beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends"},a.UNDERSCORE_TITLE_MODE]},{beginKeywords:"constructor",end:/\{/,excludeEnd:!0}],illegal:/#(?!!)/}});b.registerLanguage("json",function(a){var b={literal:"true false null"},d=[a.QUOTE_STRING_MODE,a.C_NUMBER_MODE],e={end:",",endsWithParent:!0,excludeEnd:!0,
+contains:d,keywords:b},c={begin:"{",end:"}",contains:[{className:"attr",begin:/"/,end:/"/,contains:[a.BACKSLASH_ESCAPE],illegal:"\\n"},a.inherit(e,{begin:/:/})],illegal:"\\S"};a={begin:"\\[",end:"\\]",contains:[a.inherit(e)],illegal:"\\S"};d.splice(d.length,0,c,a);return{contains:d,keywords:b,illegal:"\\S"}});b.registerLanguage("kotlin",function(a){var b={keyword:"abstract as val var vararg get set class object open private protected public noinline crossinline dynamic final enum if else do while for when throw try catch finally import package is in fun override companion reified inline lateinit initinterface annotation data sealed internal infix operator out by constructor super trait volatile transient native default",
+built_in:"Byte Short Char Int Long Boolean Float Double Void Unit Nothing",literal:"true false null"},d={className:"symbol",begin:a.UNDERSCORE_IDENT_RE+"@"},e={className:"subst",begin:"\\${",end:"}",contains:[a.APOS_STRING_MODE,a.C_NUMBER_MODE]},c={className:"variable",begin:"\\$"+a.UNDERSCORE_IDENT_RE};e={className:"string",variants:[{begin:'"""',end:'"""',contains:[c,e]},{begin:"'",end:"'",illegal:/\n/,contains:[a.BACKSLASH_ESCAPE]},{begin:'"',end:'"',illegal:/\n/,contains:[a.BACKSLASH_ESCAPE,c,
+e]}]};c={className:"meta",begin:"@(?:file|property|field|get|set|receiver|param|setparam|delegate)\\s*:(?:\\s*"+a.UNDERSCORE_IDENT_RE+")?"};var g={className:"meta",begin:"@"+a.UNDERSCORE_IDENT_RE,contains:[{begin:/\(/,end:/\)/,contains:[a.inherit(e,{className:"meta-string"})]}]};return{keywords:b,contains:[a.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+"}]}),a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,{className:"keyword",begin:/\b(break|continue|return|this)\b/,
+starts:{contains:[{className:"symbol",begin:/@\w+/}]}},d,c,g,{className:"function",beginKeywords:"fun",end:"[(]|$",returnBegin:!0,excludeEnd:!0,keywords:b,illegal:/fun\s+(<.*>)?[^\s\(]+(\s+[^\s\(]+)\s*=/,relevance:5,contains:[{begin:a.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,contains:[a.UNDERSCORE_TITLE_MODE]},{className:"type",begin:/</,end:/>/,keywords:"reified",relevance:0},{className:"params",begin:/\(/,end:/\)/,endsParent:!0,keywords:b,relevance:0,contains:[{begin:/:/,end:/[=,\/]/,
+endsWithParent:!0,contains:[{className:"type",begin:a.UNDERSCORE_IDENT_RE},a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE],relevance:0},a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,c,g,e,a.C_NUMBER_MODE]},a.C_BLOCK_COMMENT_MODE]},{className:"class",beginKeywords:"class interface trait",end:/[:\{(]|$/,excludeEnd:!0,illegal:"extends implements",contains:[{beginKeywords:"public protected internal private constructor"},a.UNDERSCORE_TITLE_MODE,{className:"type",begin:/</,end:/>/,excludeBegin:!0,excludeEnd:!0,
+relevance:0},{className:"type",begin:/[,:]\s*/,end:/[<\(,]|$/,excludeBegin:!0,returnEnd:!0},c,g]},e,{className:"meta",begin:"^#!/usr/bin/env",end:"$",illegal:"\n"},a.C_NUMBER_MODE]}});b.registerLanguage("lisp",function(a){var b={className:"literal",begin:"\\b(t{1}|nil)\\b"},d={className:"number",variants:[{begin:"(\\-|\\+)?\\d+(\\.\\d+|\\/\\d+)?((d|e|f|l|s|D|E|F|L|S)(\\+|\\-)?\\d+)?",relevance:0},{begin:"#(b|B)[0-1]+(/[0-1]+)?"},{begin:"#(o|O)[0-7]+(/[0-7]+)?"},{begin:"#(x|X)[0-9a-fA-F]+(/[0-9a-fA-F]+)?"},
+{begin:"#(c|C)\\((\\-|\\+)?\\d+(\\.\\d+|\\/\\d+)?((d|e|f|l|s|D|E|F|L|S)(\\+|\\-)?\\d+)? +(\\-|\\+)?\\d+(\\.\\d+|\\/\\d+)?((d|e|f|l|s|D|E|F|L|S)(\\+|\\-)?\\d+)?",end:"\\)"}]},e=a.inherit(a.QUOTE_STRING_MODE,{illegal:null});a=a.COMMENT(";","$",{relevance:0});var c={begin:"\\*",end:"\\*"},g={className:"symbol",begin:"[:&][a-zA-Z_\\-\\+\\*\\/\\<\\=\\>\\&\\#][a-zA-Z0-9_\\-\\+\\*\\/\\<\\=\\>\\&\\#!]*"},k={begin:"[a-zA-Z_\\-\\+\\*\\/\\<\\=\\>\\&\\#][a-zA-Z0-9_\\-\\+\\*\\/\\<\\=\\>\\&\\#!]*",relevance:0},
+l={contains:[d,e,c,g,{begin:"\\(",end:"\\)",contains:["self",b,e,d,k]},k],variants:[{begin:"['`]\\(",end:"\\)"},{begin:"\\(quote ",end:"\\)",keywords:{name:"quote"}},{begin:"'\\|[^]*?\\|"}]},m={variants:[{begin:"'[a-zA-Z_\\-\\+\\*\\/\\<\\=\\>\\&\\#][a-zA-Z0-9_\\-\\+\\*\\/\\<\\=\\>\\&\\#!]*"},{begin:"#'[a-zA-Z_\\-\\+\\*\\/\\<\\=\\>\\&\\#][a-zA-Z0-9_\\-\\+\\*\\/\\<\\=\\>\\&\\#!]*(::[a-zA-Z_\\-\\+\\*\\/\\<\\=\\>\\&\\#][a-zA-Z0-9_\\-\\+\\*\\/\\<\\=\\>\\&\\#!]*)*"}]},n={begin:"\\(\\s*",end:"\\)"},h={endsWithParent:!0,
+relevance:0};n.contains=[{className:"name",variants:[{begin:"[a-zA-Z_\\-\\+\\*\\/\\<\\=\\>\\&\\#][a-zA-Z0-9_\\-\\+\\*\\/\\<\\=\\>\\&\\#!]*"},{begin:"\\|[^]*?\\|"}]},h];h.contains=[l,m,n,b,d,e,a,c,g,{begin:"\\|[^]*?\\|"},k];return{illegal:/\S/,contains:[d,{className:"meta",begin:"^#!",end:"$"},b,e,a,l,m,n,k]}});b.registerLanguage("lua",function(a){var b={begin:"\\[=*\\[",end:"\\]=*\\]",contains:["self"]},d=[a.COMMENT("--(?!\\[=*\\[)","$"),a.COMMENT("--\\[=*\\[","\\]=*\\]",{contains:[b],relevance:10})];
+return{lexemes:a.UNDERSCORE_IDENT_RE,keywords:{literal:"true false nil",keyword:"and break do else elseif end for goto if in local not or repeat return then until while",built_in:"_G _ENV _VERSION __index __newindex __mode __call __metatable __tostring __len __gc __add __sub __mul __div __mod __pow __concat __unm __eq __lt __le assert collectgarbage dofile error getfenv getmetatable ipairs load loadfile loadstringmodule next pairs pcall print rawequal rawget rawset require select setfenvsetmetatable tonumber tostring type unpack xpcall arg selfcoroutine resume yield status wrap create running debug getupvalue debug sethook getmetatable gethook setmetatable setlocal traceback setfenv getinfo setupvalue getlocal getregistry getfenv io lines write close flush open output type read stderr stdin input stdout popen tmpfile math log max acos huge ldexp pi cos tanh pow deg tan cosh sinh random randomseed frexp ceil floor rad abs sqrt modf asin min mod fmod log10 atan2 exp sin atan os exit setlocale date getenv difftime remove time clock tmpname rename execute package preload loadlib loaded loaders cpath config path seeall string sub upper len gfind rep find match char dump gmatch reverse byte format gsub lower table setn insert getn foreachi maxn foreach concat sort remove"},
+contains:d.concat([{className:"function",beginKeywords:"function",end:"\\)",contains:[a.inherit(a.TITLE_MODE,{begin:"([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*"}),{className:"params",begin:"\\(",endsWithParent:!0,contains:d}].concat(d)},a.C_NUMBER_MODE,a.APOS_STRING_MODE,a.QUOTE_STRING_MODE,{className:"string",begin:"\\[=*\\[",end:"\\]=*\\]",contains:[b],relevance:5}])}});b.registerLanguage("objectivec",function(a){var b=/[a-zA-Z@][a-zA-Z0-9_]*/;return{aliases:["mm","objc","obj-c"],keywords:{keyword:"int float while char export sizeof typedef const struct for union unsigned long volatile static bool mutable if do return goto void enum else break extern asm case short default double register explicit signed typename this switch continue wchar_t inline readonly assign readwrite self @synchronized id typeof nonatomic super unichar IBOutlet IBAction strong weak copy in out inout bycopy byref oneway __strong __weak __block __autoreleasing @private @protected @public @try @property @end @throw @catch @finally @autoreleasepool @synthesize @dynamic @selector @optional @required @encode @package @import @defs @compatibility_alias __bridge __bridge_transfer __bridge_retained __bridge_retain __covariant __contravariant __kindof _Nonnull _Nullable _Null_unspecified __FUNCTION__ __PRETTY_FUNCTION__ __attribute__ getter setter retain unsafe_unretained nonnull nullable null_unspecified null_resettable class instancetype NS_DESIGNATED_INITIALIZER NS_UNAVAILABLE NS_REQUIRES_SUPER NS_RETURNS_INNER_POINTER NS_INLINE NS_AVAILABLE NS_DEPRECATED NS_ENUM NS_OPTIONS NS_SWIFT_UNAVAILABLE NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END NS_REFINED_FOR_SWIFT NS_SWIFT_NAME NS_SWIFT_NOTHROW NS_DURING NS_HANDLER NS_ENDHANDLER NS_VALUERETURN NS_VOIDRETURN",
+literal:"false true FALSE TRUE nil YES NO NULL",built_in:"BOOL dispatch_once_t dispatch_queue_t dispatch_sync dispatch_async dispatch_once"},lexemes:b,illegal:"</",contains:[{className:"built_in",begin:"\\b(AV|CA|CF|CG|CI|CL|CM|CN|CT|MK|MP|MTK|MTL|NS|SCN|SK|UI|WK|XC)\\w+"},a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,a.C_NUMBER_MODE,a.QUOTE_STRING_MODE,{className:"string",variants:[{begin:'@"',end:'"',illegal:"\\n",contains:[a.BACKSLASH_ESCAPE]},{begin:"'",end:"[^\\\\]'",illegal:"[^\\\\][^']"}]},
+{className:"meta",begin:"#",end:"$",contains:[{className:"meta-string",variants:[{begin:'"',end:'"'},{begin:"<",end:">"}]}]},{className:"class",begin:"(@interface|@class|@protocol|@implementation)\\b",end:"({|$)",excludeEnd:!0,keywords:"@interface @class @protocol @implementation",lexemes:b,contains:[a.UNDERSCORE_TITLE_MODE]},{begin:"\\."+a.UNDERSCORE_IDENT_RE,relevance:0}]}});b.registerLanguage("ocaml",function(a){return{aliases:["ml"],keywords:{keyword:"and as assert asr begin class constraint do done downto else end exception external for fun function functor if in include inherit! inherit initializer land lazy let lor lsl lsr lxor match method!|10 method mod module mutable new object of open! open or private rec sig struct then to try type val! val virtual when while with parser value",
+built_in:"array bool bytes char exn|5 float int int32 int64 list lazy_t|5 nativeint|5 string unit in_channel out_channel ref",literal:"true false"},illegal:/\/\/|>>/,lexemes:"[a-z_]\\w*!?",contains:[{className:"literal",begin:"\\[(\\|\\|)?\\]|\\(\\)",relevance:0},a.COMMENT("\\(\\*","\\*\\)",{contains:["self"]}),{className:"symbol",begin:"'[A-Za-z_](?!')[\\w']*"},{className:"type",begin:"`[A-Z][\\w']*"},{className:"type",begin:"\\b[A-Z][\\w']*",relevance:0},{begin:"[a-z_]\\w*'[\\w']*",relevance:0},
+a.inherit(a.APOS_STRING_MODE,{className:"string",relevance:0}),a.inherit(a.QUOTE_STRING_MODE,{illegal:null}),{className:"number",begin:"\\b(0[xX][a-fA-F0-9_]+[Lln]?|0[oO][0-7_]+[Lln]?|0[bB][01_]+[Lln]?|[0-9][0-9_]*([Lln]|(\\.[0-9_]*)?([eE][-+]?[0-9_]+)?)?)",relevance:0},{begin:/[-=]>/}]}});b.registerLanguage("perl",function(a){var b={className:"subst",begin:"[$@]\\{",end:"\\}",keywords:"getpwent getservent quotemeta msgrcv scalar kill dbmclose undef lc ma syswrite tr send umask sysopen shmwrite vec qx utime local oct semctl localtime readpipe do return format read sprintf dbmopen pop getpgrp not getpwnam rewinddir qqfileno qw endprotoent wait sethostent bless s|0 opendir continue each sleep endgrent shutdown dump chomp connect getsockname die socketpair close flock exists index shmgetsub for endpwent redo lstat msgctl setpgrp abs exit select print ref gethostbyaddr unshift fcntl syscall goto getnetbyaddr join gmtime symlink semget splice x|0 getpeername recv log setsockopt cos last reverse gethostbyname getgrnam study formline endhostent times chop length gethostent getnetent pack getprotoent getservbyname rand mkdir pos chmod y|0 substr endnetent printf next open msgsnd readdir use unlink getsockopt getpriority rindex wantarray hex system getservbyport endservent int chr untie rmdir prototype tell listen fork shmread ucfirst setprotoent else sysseek link getgrgid shmctl waitpid unpack getnetbyname reset chdir grep split require caller lcfirst until warn while values shift telldir getpwuid my getprotobynumber delete and sort uc defined srand accept package seekdir getprotobyname semop our rename seek if q|0 chroot sysread setpwent no crypt getc chown sqrt write setnetent setpriority foreach tie sin msgget map stat getlogin unless elsif truncate exec keys glob tied closedirioctl socket readlink eval xor readline binmode setservent eof ord bind alarm pipe atan2 getgrent exp time push setgrent gt lt or ne m|0 break given say state when"},
+d={begin:"->{",end:"}"},e={variants:[{begin:/\$\d/},{begin:/[\$%@](\^\w\b|#\w+(::\w+)*|{\w+}|\w+(::\w*)*)/},{begin:/[\$%@][^\s\w{]/,relevance:0}]},c=[a.BACKSLASH_ESCAPE,b,e];a=[e,a.HASH_COMMENT_MODE,a.COMMENT("^\\=\\w","\\=cut",{endsWithParent:!0}),d,{className:"string",contains:c,variants:[{begin:"q[qwxr]?\\s*\\(",end:"\\)",relevance:5},{begin:"q[qwxr]?\\s*\\[",end:"\\]",relevance:5},{begin:"q[qwxr]?\\s*\\{",end:"\\}",relevance:5},{begin:"q[qwxr]?\\s*\\|",end:"\\|",relevance:5},{begin:"q[qwxr]?\\s*\\<",
 end:"\\>",relevance:5},{begin:"qw\\s+q",end:"q",relevance:5},{begin:"'",end:"'",contains:[a.BACKSLASH_ESCAPE]},{begin:'"',end:'"'},{begin:"`",end:"`",contains:[a.BACKSLASH_ESCAPE]},{begin:"{\\w+}",contains:[],relevance:0},{begin:"-?\\w+\\s*\\=\\>",contains:[],relevance:0}]},{className:"number",begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{begin:"(\\/\\/|"+a.RE_STARTERS_RE+"|\\b(split|return|print|reverse|grep)\\b)\\s*",keywords:"split return print reverse grep",
 relevance:0,contains:[a.HASH_COMMENT_MODE,{className:"regexp",begin:"(s|tr|y)/(\\\\.|[^/])*/(\\\\.|[^/])*/[a-z]*",relevance:10},{className:"regexp",begin:"(m|qr)?/",end:"/[a-z]*",contains:[a.BACKSLASH_ESCAPE],relevance:0}]},{className:"function",beginKeywords:"sub",end:"(\\s*\\(.*?\\))?[;{]",excludeEnd:!0,relevance:5,contains:[a.TITLE_MODE]},{begin:"-\\w\\b",relevance:0},{begin:"^__DATA__$",end:"^__END__$",subLanguage:"mojolicious",contains:[{begin:"^@@.*",end:"$",className:"comment"}]}];b.contains=
-a;e.contains=a;return{aliases:["pl","pm"],lexemes:/[\w\.]+/,keywords:"getpwent getservent quotemeta msgrcv scalar kill dbmclose undef lc ma syswrite tr send umask sysopen shmwrite vec qx utime local oct semctl localtime readpipe do return format read sprintf dbmopen pop getpgrp not getpwnam rewinddir qqfileno qw endprotoent wait sethostent bless s|0 opendir continue each sleep endgrent shutdown dump chomp connect getsockname die socketpair close flock exists index shmgetsub for endpwent redo lstat msgctl setpgrp abs exit select print ref gethostbyaddr unshift fcntl syscall goto getnetbyaddr join gmtime symlink semget splice x|0 getpeername recv log setsockopt cos last reverse gethostbyname getgrnam study formline endhostent times chop length gethostent getnetent pack getprotoent getservbyname rand mkdir pos chmod y|0 substr endnetent printf next open msgsnd readdir use unlink getsockopt getpriority rindex wantarray hex system getservbyport endservent int chr untie rmdir prototype tell listen fork shmread ucfirst setprotoent else sysseek link getgrgid shmctl waitpid unpack getnetbyname reset chdir grep split require caller lcfirst until warn while values shift telldir getpwuid my getprotobynumber delete and sort uc defined srand accept package seekdir getprotobyname semop our rename seek if q|0 chroot sysread setpwent no crypt getc chown sqrt write setnetent setpriority foreach tie sin msgget map stat getlogin unless elsif truncate exec keys glob tied closedirioctl socket readlink eval xor readline binmode setservent eof ord bind alarm pipe atan2 getgrent exp time push setgrent gt lt or ne m|0 break given say state when",
-contains:a}});b.registerLanguage("prolog",function(a){var b={begin:/\(/,end:/\)/,relevance:0},e={begin:/\[/,end:/\]/};a=[{begin:/[a-z][A-Za-z0-9_]*/,relevance:0},{className:"symbol",variants:[{begin:/[A-Z][a-zA-Z0-9_]*/},{begin:/_[A-Za-z0-9_]*/}],relevance:0},b,{begin:/:-/},e,{className:"comment",begin:/%/,end:/$/,contains:[a.PHRASAL_WORDS_MODE]},a.C_BLOCK_COMMENT_MODE,a.QUOTE_STRING_MODE,a.APOS_STRING_MODE,{className:"string",begin:/`/,end:/`/,contains:[a.BACKSLASH_ESCAPE]},{className:"string",begin:/0\'(\\\'|.)/},
-{className:"string",begin:/0\'\\s/},a.C_NUMBER_MODE];b.contains=a;e.contains=a;return{contains:a.concat([{begin:/\.$/}])}});b.registerLanguage("python",function(a){var b={keyword:"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda async await nonlocal|10 None True False",built_in:"Ellipsis NotImplemented"},e={className:"meta",begin:/^(>>>|\.\.\.) /},h={className:"subst",begin:/\{/,end:/\}/,
-keywords:b,illegal:/#/},c={className:"string",contains:[a.BACKSLASH_ESCAPE],variants:[{begin:/(u|b)?r?'''/,end:/'''/,contains:[e],relevance:10},{begin:/(u|b)?r?"""/,end:/"""/,contains:[e],relevance:10},{begin:/(fr|rf|f)'''/,end:/'''/,contains:[e,h]},{begin:/(fr|rf|f)"""/,end:/"""/,contains:[e,h]},{begin:/(u|r|ur)'/,end:/'/,relevance:10},{begin:/(u|r|ur)"/,end:/"/,relevance:10},{begin:/(b|br)'/,end:/'/},{begin:/(b|br)"/,end:/"/},{begin:/(fr|rf|f)'/,end:/'/,contains:[h]},{begin:/(fr|rf|f)"/,end:/"/,
-contains:[h]},a.APOS_STRING_MODE,a.QUOTE_STRING_MODE]},f={className:"number",relevance:0,variants:[{begin:a.BINARY_NUMBER_RE+"[lLjJ]?"},{begin:"\\b(0o[0-7]+)[lLjJ]?"},{begin:a.C_NUMBER_RE+"[lLjJ]?"}]},k={className:"params",begin:/\(/,end:/\)/,contains:["self",e,f,c]};h.contains=[c,f,e];return{aliases:["py","gyp"],keywords:b,illegal:/(<\/|->|\?)|=>/,contains:[e,f,c,a.HASH_COMMENT_MODE,{variants:[{className:"function",beginKeywords:"def"},{className:"class",beginKeywords:"class"}],end:/:/,illegal:/[${=;\n,]/,
-contains:[a.UNDERSCORE_TITLE_MODE,k,{begin:/->/,endsWithParent:!0,keywords:"None"}]},{className:"meta",begin:/^[\t ]*@/,end:/$/},{begin:/\b(print|exec)\(/}]}});b.registerLanguage("scala",function(a){var b={className:"subst",variants:[{begin:"\\$[A-Za-z0-9_]+"},{begin:"\\${",end:"}"}]},e={className:"type",begin:"\\b[A-Z][A-Za-z0-9_]*",relevance:0},h={className:"title",begin:/[^0-9\n\t "'(),.`{}\[\]:;][^\n\t "'(),.`{}\[\]:;]+|[^0-9\n\t "'(),.`{}\[\]:;=]/,relevance:0};return{keywords:{literal:"true false null",
-keyword:"type yield lazy override def with val var sealed abstract private trait object if forSome for while throw finally protected extends import final return else break new catch super class case package default try this match continue throws implicit"},contains:[a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,{className:"string",variants:[{begin:'"',end:'"',illegal:"\\n",contains:[a.BACKSLASH_ESCAPE]},{begin:'"""',end:'"""',relevance:10},{begin:'[a-z]+"',end:'"',illegal:"\\n",contains:[a.BACKSLASH_ESCAPE,
-b]},{className:"string",begin:'[a-z]+"""',end:'"""',contains:[b],relevance:10}]},{className:"symbol",begin:"'\\w[\\w\\d_]*(?!')"},e,{className:"function",beginKeywords:"def",end:/[:={\[(\n;]/,excludeEnd:!0,contains:[h]},{className:"class",beginKeywords:"class object trait type",end:/[:={\[\n;]/,excludeEnd:!0,contains:[{beginKeywords:"extends with",relevance:10},{begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[e]},{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,
-relevance:0,contains:[e]},h]},a.C_NUMBER_MODE,{className:"meta",begin:"@[A-Za-z]+"}]}});b.registerLanguage("sql",function(a){var b=a.COMMENT("--","$");return{case_insensitive:!0,illegal:/[<>{}*#]/,contains:[{beginKeywords:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke comment",
-end:/;/,endsWithParent:!0,lexemes:/[\w\.]+/,keywords:{keyword:"abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias allocate allow alter always analyze ancillary and any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound buffer_cache buffer_pool build bulk by byte byteordermark bytes cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain export export_set extended extent external external_1 external_2 externally extract failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour http id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner innodb input insert install instance instantiable instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists keep keep_duplicates key keys kill language large last last_day last_insert_id last_value lax lcase lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call logoff logon logs long loop low low_priority lower lpad lrtrim ltrim main make_set makedate maketime managed management manual map mapping mask master master_pos_wait match matched materialized max maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans md5 measures median medium member memcompress memory merge microsecond mid migration min minextents minimum mining minus minute minvalue missing mod mode model modification modify module monitoring month months mount move movement multiset mutex name name_const names nan national native natural nav nchar nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck noswitch not nothing notice notrim novalidate now nowait nth_value nullif nulls num numb numbe nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary out outer outfile outline output over overflow overriding package pad parallel parallel_enable parameters parent parse partial partition partitions pascal passing password password_grace_time password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction prediction_cost prediction_details prediction_probability prediction_set prepare present preserve prior priority private private_sga privileges procedural procedure procedure_analyze processlist profiles project prompt protection public publishingservername purge quarter query quick quiesce quota quotename radians raise rand range rank raw read reads readsize rebuild record records recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename repair repeat replace replicate replication required reset resetlogs resize resource respect restore restricted result result_cache resumable resume retention return returning returns reuse reverse revoke right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll sdo_georaster sdo_topo_geometry search sec_to_time second section securefile security seed segment select self sequence sequential serializable server servererror session session_user sessions_per_user set sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone standby start starting startup statement static statistics stats_binomial_test stats_crosstab stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime table tables tablespace tan tdo template temporary terminated tertiary_weights test than then thread through tier ties time time_format time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unpivot unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear wellformed when whene whenev wheneve whenever where while whitespace with within without work wrapped xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek",
+a;d.contains=a;return{aliases:["pl","pm"],lexemes:/[\w\.]+/,keywords:"getpwent getservent quotemeta msgrcv scalar kill dbmclose undef lc ma syswrite tr send umask sysopen shmwrite vec qx utime local oct semctl localtime readpipe do return format read sprintf dbmopen pop getpgrp not getpwnam rewinddir qqfileno qw endprotoent wait sethostent bless s|0 opendir continue each sleep endgrent shutdown dump chomp connect getsockname die socketpair close flock exists index shmgetsub for endpwent redo lstat msgctl setpgrp abs exit select print ref gethostbyaddr unshift fcntl syscall goto getnetbyaddr join gmtime symlink semget splice x|0 getpeername recv log setsockopt cos last reverse gethostbyname getgrnam study formline endhostent times chop length gethostent getnetent pack getprotoent getservbyname rand mkdir pos chmod y|0 substr endnetent printf next open msgsnd readdir use unlink getsockopt getpriority rindex wantarray hex system getservbyport endservent int chr untie rmdir prototype tell listen fork shmread ucfirst setprotoent else sysseek link getgrgid shmctl waitpid unpack getnetbyname reset chdir grep split require caller lcfirst until warn while values shift telldir getpwuid my getprotobynumber delete and sort uc defined srand accept package seekdir getprotobyname semop our rename seek if q|0 chroot sysread setpwent no crypt getc chown sqrt write setnetent setpriority foreach tie sin msgget map stat getlogin unless elsif truncate exec keys glob tied closedirioctl socket readlink eval xor readline binmode setservent eof ord bind alarm pipe atan2 getgrent exp time push setgrent gt lt or ne m|0 break given say state when",
+contains:a}});b.registerLanguage("php",function(a){var b={begin:"\\$+[a-zA-Z_\u007f-\u00ff][a-zA-Z0-9_\u007f-\u00ff]*"},d={className:"meta",begin:/<\?(php)?|\?>/},e={className:"string",contains:[a.BACKSLASH_ESCAPE,d],variants:[{begin:'b"',end:'"'},{begin:"b'",end:"'"},a.inherit(a.APOS_STRING_MODE,{illegal:null}),a.inherit(a.QUOTE_STRING_MODE,{illegal:null})]},c={variants:[a.BINARY_NUMBER_MODE,a.C_NUMBER_MODE]};return{aliases:["php3","php4","php5","php6"],case_insensitive:!0,keywords:"and include_once list abstract global private echo interface as static endswitch array null if endwhile or const for endforeach self var while isset public protected exit foreach throw elseif include __FILE__ empty require_once do xor return parent clone use __CLASS__ __LINE__ else break print eval new catch __METHOD__ case exception default die require __FUNCTION__ enddeclare final try switch continue endfor endif declare unset true false trait goto instanceof insteadof __DIR__ __NAMESPACE__ yield finally",
+contains:[a.HASH_COMMENT_MODE,a.COMMENT("//","$",{contains:[d]}),a.COMMENT("/\\*","\\*/",{contains:[{className:"doctag",begin:"@[A-Za-z]+"}]}),a.COMMENT("__halt_compiler.+?;",!1,{endsWithParent:!0,keywords:"__halt_compiler",lexemes:a.UNDERSCORE_IDENT_RE}),{className:"string",begin:/<<<['"]?\w+['"]?$/,end:/^\w+;?$/,contains:[a.BACKSLASH_ESCAPE,{className:"subst",variants:[{begin:/\$\w+/},{begin:/\{\$/,end:/\}/}]}]},d,{className:"keyword",begin:/\$this\b/},b,{begin:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},
+{className:"function",beginKeywords:"function",end:/[;{]/,excludeEnd:!0,illegal:"\\$|\\[|%",contains:[a.UNDERSCORE_TITLE_MODE,{className:"params",begin:"\\(",end:"\\)",contains:["self",b,a.C_BLOCK_COMMENT_MODE,e,c]}]},{className:"class",beginKeywords:"class interface",end:"{",excludeEnd:!0,illegal:/[:\(\$"]/,contains:[{beginKeywords:"extends implements"},a.UNDERSCORE_TITLE_MODE]},{beginKeywords:"namespace",end:";",illegal:/[\.']/,contains:[a.UNDERSCORE_TITLE_MODE]},{beginKeywords:"use",end:";",contains:[a.UNDERSCORE_TITLE_MODE]},
+{begin:"=>"},e,c]}});b.registerLanguage("protobuf",function(a){return{keywords:{keyword:"package import option optional required repeated group",built_in:"double float int32 int64 uint32 uint64 sint32 sint64 fixed32 fixed64 sfixed32 sfixed64 bool string bytes",literal:"true false"},contains:[a.QUOTE_STRING_MODE,a.NUMBER_MODE,a.C_LINE_COMMENT_MODE,{className:"class",beginKeywords:"message enum service",end:/\{/,illegal:/\n/,contains:[a.inherit(a.TITLE_MODE,{starts:{endsWithParent:!0,excludeEnd:!0}})]},
+{className:"function",beginKeywords:"rpc",end:/;/,excludeEnd:!0,keywords:"rpc returns"},{begin:/^\s*[A-Z_]+/,end:/\s*=/,excludeEnd:!0}]}});b.registerLanguage("puppet",function(a){var b=a.COMMENT("#","$"),d=a.inherit(a.TITLE_MODE,{begin:"([A-Za-z_]|::)(\\w|::)*"}),e={className:"variable",begin:"\\$([A-Za-z_]|::)(\\w|::)*"},c={className:"string",contains:[a.BACKSLASH_ESCAPE,e],variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/}]};return{aliases:["pp"],contains:[b,e,c,{beginKeywords:"class",end:"\\{|;",
+illegal:/=/,contains:[d,b]},{beginKeywords:"define",end:/\{/,contains:[{className:"section",begin:a.IDENT_RE,endsParent:!0}]},{begin:a.IDENT_RE+"\\s+\\{",returnBegin:!0,end:/\S/,contains:[{className:"keyword",begin:a.IDENT_RE},{begin:/\{/,end:/\}/,keywords:{keyword:"and case default else elsif false if in import enherits node or true undef unless main settings $string ",literal:"alias audit before loglevel noop require subscribe tag owner ensure group mode name|0 changes context force incl lens load_path onlyif provider returns root show_diff type_check en_address ip_address realname command environment hour monute month monthday special target weekday creates cwd ogoutput refresh refreshonly tries try_sleep umask backup checksum content ctime force ignore links mtime purge recurse recurselimit replace selinux_ignore_defaults selrange selrole seltype seluser source souirce_permissions sourceselect validate_cmd validate_replacement allowdupe attribute_membership auth_membership forcelocal gid ia_load_module members system host_aliases ip allowed_trunk_vlans description device_url duplex encapsulation etherchannel native_vlan speed principals allow_root auth_class auth_type authenticate_user k_of_n mechanisms rule session_owner shared options device fstype enable hasrestart directory present absent link atboot blockdevice device dump pass remounts poller_tag use message withpath adminfile allow_virtual allowcdrom category configfiles flavor install_options instance package_settings platform responsefile status uninstall_options vendor unless_system_user unless_uid binary control flags hasstatus manifest pattern restart running start stop allowdupe auths expiry gid groups home iterations key_membership keys managehome membership password password_max_age password_min_age profile_membership profiles project purge_ssh_keys role_membership roles salt shell uid baseurl cost descr enabled enablegroups exclude failovermethod gpgcheck gpgkey http_caching include includepkgs keepalive metadata_expire metalink mirrorlist priority protect proxy proxy_password proxy_username repo_gpgcheck s3_enabled skip_if_unavailable sslcacert sslclientcert sslclientkey sslverify mounted",
+built_in:"architecture augeasversion blockdevices boardmanufacturer boardproductname boardserialnumber cfkey dhcp_servers domain ec2_ ec2_userdata facterversion filesystems ldom fqdn gid hardwareisa hardwaremodel hostname id|0 interfaces ipaddress ipaddress_ ipaddress6 ipaddress6_ iphostnumber is_virtual kernel kernelmajversion kernelrelease kernelversion kernelrelease kernelversion lsbdistcodename lsbdistdescription lsbdistid lsbdistrelease lsbmajdistrelease lsbminordistrelease lsbrelease macaddress macaddress_ macosx_buildversion macosx_productname macosx_productversion macosx_productverson_major macosx_productversion_minor manufacturer memoryfree memorysize netmask metmask_ network_ operatingsystem operatingsystemmajrelease operatingsystemrelease osfamily partitions path physicalprocessorcount processor processorcount productname ps puppetversion rubysitedir rubyversion selinux selinux_config_mode selinux_config_policy selinux_current_mode selinux_current_mode selinux_enforced selinux_policyversion serialnumber sp_ sshdsakey sshecdsakey sshrsakey swapencrypted swapfree swapsize timezone type uniqueid uptime uptime_days uptime_hours uptime_seconds uuid virtual vlans xendomains zfs_version zonenae zones zpool_version"},
+relevance:0,contains:[c,b,{begin:"[a-zA-Z_]+\\s*=>",returnBegin:!0,end:"=>",contains:[{className:"attr",begin:a.IDENT_RE}]},{className:"number",begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},e]}],relevance:0}]}});b.registerLanguage("python",function(a){var b={keyword:"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda async await nonlocal|10 None True False",
+built_in:"Ellipsis NotImplemented"},d={className:"meta",begin:/^(>>>|\.\.\.) /},e={className:"subst",begin:/\{/,end:/\}/,keywords:b,illegal:/#/},c={className:"string",contains:[a.BACKSLASH_ESCAPE],variants:[{begin:/(u|b)?r?'''/,end:/'''/,contains:[d],relevance:10},{begin:/(u|b)?r?"""/,end:/"""/,contains:[d],relevance:10},{begin:/(fr|rf|f)'''/,end:/'''/,contains:[d,e]},{begin:/(fr|rf|f)"""/,end:/"""/,contains:[d,e]},{begin:/(u|r|ur)'/,end:/'/,relevance:10},{begin:/(u|r|ur)"/,end:/"/,relevance:10},
+{begin:/(b|br)'/,end:/'/},{begin:/(b|br)"/,end:/"/},{begin:/(fr|rf|f)'/,end:/'/,contains:[e]},{begin:/(fr|rf|f)"/,end:/"/,contains:[e]},a.APOS_STRING_MODE,a.QUOTE_STRING_MODE]},g={className:"number",relevance:0,variants:[{begin:a.BINARY_NUMBER_RE+"[lLjJ]?"},{begin:"\\b(0o[0-7]+)[lLjJ]?"},{begin:a.C_NUMBER_RE+"[lLjJ]?"}]},k={className:"params",begin:/\(/,end:/\)/,contains:["self",d,g,c]};e.contains=[c,g,d];return{aliases:["py","gyp"],keywords:b,illegal:/(<\/|->|\?)|=>/,contains:[d,g,c,a.HASH_COMMENT_MODE,
+{variants:[{className:"function",beginKeywords:"def"},{className:"class",beginKeywords:"class"}],end:/:/,illegal:/[${=;\n,]/,contains:[a.UNDERSCORE_TITLE_MODE,k,{begin:/->/,endsWithParent:!0,keywords:"None"}]},{className:"meta",begin:/^[\t ]*@/,end:/$/},{begin:/\b(print|exec)\(/}]}});b.registerLanguage("rust",function(a){return{aliases:["rs"],keywords:{keyword:"alignof as be box break const continue crate do else enum extern false fn for if impl in let loop match mod mut offsetof once priv proc pub pure ref return self Self sizeof static struct super trait true type typeof unsafe unsized use virtual while where yield move default",
+literal:"true false Some None Ok Err",built_in:"drop i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize f32 f64 str char bool Box Option Result String Vec Copy Send Sized Sync Drop Fn FnMut FnOnce ToOwned Clone Debug PartialEq PartialOrd Eq Ord AsRef AsMut Into From Default Iterator Extend IntoIterator DoubleEndedIterator ExactSizeIterator SliceConcatExt ToString assert! assert_eq! bitflags! bytes! cfg! col! concat! concat_idents! debug_assert! debug_assert_eq! env! panic! file! format! format_args! include_bin! include_str! line! local_data_key! module_path! option_env! print! println! select! stringify! try! unimplemented! unreachable! vec! write! writeln! macro_rules! assert_ne! debug_assert_ne!"},
+lexemes:a.IDENT_RE+"!?",illegal:"</",contains:[a.C_LINE_COMMENT_MODE,a.COMMENT("/\\*","\\*/",{contains:["self"]}),a.inherit(a.QUOTE_STRING_MODE,{begin:/b?"/,illegal:null}),{className:"string",variants:[{begin:/r(#*)"(.|\n)*?"\1(?!#)/},{begin:/b?'\\?(x\w{2}|u\w{4}|U\w{8}|.)'/}]},{className:"symbol",begin:/'[a-zA-Z_][a-zA-Z0-9_]*/},{className:"number",variants:[{begin:"\\b0b([01_]+)([ui](8|16|32|64|128|size)|f(32|64))?"},{begin:"\\b0o([0-7_]+)([ui](8|16|32|64|128|size)|f(32|64))?"},{begin:"\\b0x([A-Fa-f0-9_]+)([ui](8|16|32|64|128|size)|f(32|64))?"},
+{begin:"\\b(\\d[\\d_]*(\\.[0-9_]+)?([eE][+-]?[0-9_]+)?)([ui](8|16|32|64|128|size)|f(32|64))?"}],relevance:0},{className:"function",beginKeywords:"fn",end:"(\\(|<)",excludeEnd:!0,contains:[a.UNDERSCORE_TITLE_MODE]},{className:"meta",begin:"#\\!?\\[",end:"\\]",contains:[{className:"meta-string",begin:/"/,end:/"/}]},{className:"class",beginKeywords:"type",end:";",contains:[a.inherit(a.UNDERSCORE_TITLE_MODE,{endsParent:!0})],illegal:"\\S"},{className:"class",beginKeywords:"trait enum struct union",end:"{",
+contains:[a.inherit(a.UNDERSCORE_TITLE_MODE,{endsParent:!0})],illegal:"[\\w\\d]"},{begin:a.IDENT_RE+"::",keywords:{built_in:"drop i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize f32 f64 str char bool Box Option Result String Vec Copy Send Sized Sync Drop Fn FnMut FnOnce ToOwned Clone Debug PartialEq PartialOrd Eq Ord AsRef AsMut Into From Default Iterator Extend IntoIterator DoubleEndedIterator ExactSizeIterator SliceConcatExt ToString assert! assert_eq! bitflags! bytes! cfg! col! concat! concat_idents! debug_assert! debug_assert_eq! env! panic! file! format! format_args! include_bin! include_str! line! local_data_key! module_path! option_env! print! println! select! stringify! try! unimplemented! unreachable! vec! write! writeln! macro_rules! assert_ne! debug_assert_ne!"}},
+{begin:"->"}]}});b.registerLanguage("scala",function(a){var b={className:"subst",variants:[{begin:"\\$[A-Za-z0-9_]+"},{begin:"\\${",end:"}"}]},d={className:"type",begin:"\\b[A-Z][A-Za-z0-9_]*",relevance:0},e={className:"title",begin:/[^0-9\n\t "'(),.`{}\[\]:;][^\n\t "'(),.`{}\[\]:;]+|[^0-9\n\t "'(),.`{}\[\]:;=]/,relevance:0};return{keywords:{literal:"true false null",keyword:"type yield lazy override def with val var sealed abstract private trait object if forSome for while throw finally protected extends import final return else break new catch super class case package default try this match continue throws implicit"},
+contains:[a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,{className:"string",variants:[{begin:'"',end:'"',illegal:"\\n",contains:[a.BACKSLASH_ESCAPE]},{begin:'"""',end:'"""',relevance:10},{begin:'[a-z]+"',end:'"',illegal:"\\n",contains:[a.BACKSLASH_ESCAPE,b]},{className:"string",begin:'[a-z]+"""',end:'"""',contains:[b],relevance:10}]},{className:"symbol",begin:"'\\w[\\w\\d_]*(?!')"},d,{className:"function",beginKeywords:"def",end:/[:={\[(\n;]/,excludeEnd:!0,contains:[e]},{className:"class",beginKeywords:"class object trait type",
+end:/[:={\[\n;]/,excludeEnd:!0,contains:[{beginKeywords:"extends with",relevance:10},{begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[d]},{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[d]},e]},a.C_NUMBER_MODE,{className:"meta",begin:"@[A-Za-z]+"}]}});b.registerLanguage("shell",function(a){return{aliases:["console"],contains:[{className:"meta",begin:"^\\s{0,3}[\\w\\d\\[\\]()@-]*[>%$#]",starts:{end:"$",subLanguage:"bash"}}]}});b.registerLanguage("sql",
+function(a){var b=a.COMMENT("--","$");return{case_insensitive:!0,illegal:/[<>{}*#]/,contains:[{beginKeywords:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke comment",end:/;/,endsWithParent:!0,
+lexemes:/[\w\.]+/,keywords:{keyword:"abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias allocate allow alter always analyze ancillary and any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound buffer_cache buffer_pool build bulk by byte byteordermark bytes cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain export export_set extended extent external external_1 external_2 externally extract failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour http id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner innodb input insert install instance instantiable instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists keep keep_duplicates key keys kill language large last last_day last_insert_id last_value lax lcase lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call logoff logon logs long loop low low_priority lower lpad lrtrim ltrim main make_set makedate maketime managed management manual map mapping mask master master_pos_wait match matched materialized max maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans md5 measures median medium member memcompress memory merge microsecond mid migration min minextents minimum mining minus minute minvalue missing mod mode model modification modify module monitoring month months mount move movement multiset mutex name name_const names nan national native natural nav nchar nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck noswitch not nothing notice notrim novalidate now nowait nth_value nullif nulls num numb numbe nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary out outer outfile outline output over overflow overriding package pad parallel parallel_enable parameters parent parse partial partition partitions pascal passing password password_grace_time password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction prediction_cost prediction_details prediction_probability prediction_set prepare present preserve prior priority private private_sga privileges procedural procedure procedure_analyze processlist profiles project prompt protection public publishingservername purge quarter query quick quiesce quota quotename radians raise rand range rank raw read reads readsize rebuild record records recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename repair repeat replace replicate replication required reset resetlogs resize resource respect restore restricted result result_cache resumable resume retention return returning returns reuse reverse revoke right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll sdo_georaster sdo_topo_geometry search sec_to_time second section securefile security seed segment select self sequence sequential serializable server servererror session session_user sessions_per_user set sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone standby start starting startup statement static statistics stats_binomial_test stats_crosstab stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime table tables tablespace tan tdo template temporary terminated tertiary_weights test than then thread through tier ties time time_format time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unpivot unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear wellformed when whene whenev wheneve whenever where while whitespace with within without work wrapped xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek",
 literal:"true false null",built_in:"array bigint binary bit blob boolean char character date dec decimal float int int8 integer interval number numeric real record serial serial8 smallint text varchar varying void"},contains:[{className:"string",begin:"'",end:"'",contains:[a.BACKSLASH_ESCAPE,{begin:"''"}]},{className:"string",begin:'"',end:'"',contains:[a.BACKSLASH_ESCAPE,{begin:'""'}]},{className:"string",begin:"`",end:"`",contains:[a.BACKSLASH_ESCAPE]},a.C_NUMBER_MODE,a.C_BLOCK_COMMENT_MODE,b]},
-a.C_BLOCK_COMMENT_MODE,b]}});b.registerLanguage("xml",function(a){var b={endsWithParent:!0,illegal:/</,relevance:0,contains:[{className:"attr",begin:"[A-Za-z0-9\\._:-]+",relevance:0},{begin:/=\s*/,relevance:0,contains:[{className:"string",endsParent:!0,variants:[{begin:/"/,end:/"/},{begin:/'/,end:/'/},{begin:/[^\s"'=<>`]+/}]}]}]};return{aliases:"html xhtml rss atom xjb xsd xsl plist".split(" "),case_insensitive:!0,contains:[{className:"meta",begin:"<!DOCTYPE",end:">",relevance:10,contains:[{begin:"\\[",
-end:"\\]"}]},a.COMMENT("\x3c!--","--\x3e",{relevance:10}),{begin:"<\\!\\[CDATA\\[",end:"\\]\\]>",relevance:10},{begin:/<\?(php)?/,end:/\?>/,subLanguage:"php",contains:[{begin:"/\\*",end:"\\*/",skip:!0}]},{className:"tag",begin:"<style(?=\\s|>|$)",end:">",keywords:{name:"style"},contains:[b],starts:{end:"</style>",returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag",begin:"<script(?=\\s|>|$)",end:">",keywords:{name:"script"},contains:[b],starts:{end:"\x3c/script>",returnEnd:!0,subLanguage:["actionscript",
-"javascript","handlebars","xml"]}},{className:"meta",variants:[{begin:/<\?xml/,end:/\?>/,relevance:10},{begin:/<\?\w+/,end:/\?>/}]},{className:"tag",begin:"</?",end:"/?>",contains:[{className:"name",begin:/[^\/><\s]+/,relevance:0},b]}]}});return b});
+a.C_BLOCK_COMMENT_MODE,b]}});b.registerLanguage("swift",function(a){var b={keyword:"__COLUMN__ __FILE__ __FUNCTION__ __LINE__ as as! as? associativity break case catch class continue convenience default defer deinit didSet do dynamic dynamicType else enum extension fallthrough false fileprivate final for func get guard if import in indirect infix init inout internal is lazy left let mutating nil none nonmutating open operator optional override postfix precedence prefix private protocol Protocol public repeat required rethrows return right self Self set static struct subscript super switch throw throws true try try! try? Type typealias unowned var weak where while willSet",
+literal:"true false nil",built_in:"abs advance alignof alignofValue anyGenerator assert assertionFailure bridgeFromObjectiveC bridgeFromObjectiveCUnconditional bridgeToObjectiveC bridgeToObjectiveCUnconditional c contains count countElements countLeadingZeros debugPrint debugPrintln distance dropFirst dropLast dump encodeBitsAsWords enumerate equal fatalError filter find getBridgedObjectiveCType getVaList indices insertionSort isBridgedToObjectiveC isBridgedVerbatimToObjectiveC isUniquelyReferenced isUniquelyReferencedNonObjC join lazy lexicographicalCompare map max maxElement min minElement numericCast overlaps partition posix precondition preconditionFailure print println quickSort readLine reduce reflect reinterpretCast reverse roundUpToAlignment sizeof sizeofValue sort split startsWith stride strideof strideofValue swap toString transcode underestimateCount unsafeAddressOf unsafeBitCast unsafeDowncast unsafeUnwrap unsafeReflect withExtendedLifetime withObjectAtPlusZero withUnsafePointer withUnsafePointerToObject withUnsafeMutablePointer withUnsafeMutablePointers withUnsafePointer withUnsafePointers withVaList zip"},
+d=a.COMMENT("/\\*","\\*/",{contains:["self"]}),e={className:"subst",begin:/\\\(/,end:"\\)",keywords:b,contains:[]},c={className:"number",begin:"\\b([\\d_]+(\\.[\\deE_]+)?|0x[a-fA-F0-9_]+(\\.[a-fA-F0-9p_]+)?|0b[01_]+|0o[0-7_]+)\\b",relevance:0},g=a.inherit(a.QUOTE_STRING_MODE,{contains:[e,a.BACKSLASH_ESCAPE]});e.contains=[c];return{keywords:b,contains:[g,a.C_LINE_COMMENT_MODE,d,{className:"type",begin:"\\b[A-Z][\\w\u00c0-\u02b8']*",relevance:0},c,{className:"function",beginKeywords:"func",end:"{",
+excludeEnd:!0,contains:[a.inherit(a.TITLE_MODE,{begin:/[A-Za-z$_][0-9A-Za-z$_]*/}),{begin:/</,end:/>/},{className:"params",begin:/\(/,end:/\)/,endsParent:!0,keywords:b,contains:["self",c,g,a.C_BLOCK_COMMENT_MODE,{begin:":"}],illegal:/["']/}],illegal:/\[|%/},{className:"class",beginKeywords:"struct protocol class extension enum",keywords:b,end:"\\{",excludeEnd:!0,contains:[a.inherit(a.TITLE_MODE,{begin:/[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/})]},{className:"meta",begin:"(@warn_unused_result|@exported|@lazy|@noescape|@NSCopying|@NSManaged|@objc|@convention|@required|@noreturn|@IBAction|@IBDesignable|@IBInspectable|@IBOutlet|@infix|@prefix|@postfix|@autoclosure|@testable|@available|@nonobjc|@NSApplicationMain|@UIApplicationMain)"},
+{beginKeywords:"import",end:/$/,contains:[a.C_LINE_COMMENT_MODE,d]}]}});b.registerLanguage("typescript",function(a){var b={keyword:"in if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const class public private protected get set super static implements enum export import declare type namespace abstract as from extends async await",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document any number boolean string void Promise"};
+return{aliases:["ts"],keywords:b,contains:[{className:"meta",begin:/^\s*['"]use strict['"]/},a.APOS_STRING_MODE,a.QUOTE_STRING_MODE,{className:"string",begin:"`",end:"`",contains:[a.BACKSLASH_ESCAPE,{className:"subst",begin:"\\$\\{",end:"\\}"}]},a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,{className:"number",variants:[{begin:"\\b(0[bB][01]+)"},{begin:"\\b(0[oO][0-7]+)"},{begin:a.C_NUMBER_RE}],relevance:0},{begin:"("+a.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",
+contains:[a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE,a.REGEXP_MODE,{className:"function",begin:"(\\(.*?\\)|"+a.IDENT_RE+")\\s*=>",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:a.IDENT_RE},{begin:/\(\s*\)/},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:b,contains:["self",a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE]}]}]}],relevance:0},{className:"function",begin:"function",end:/[\{;]/,excludeEnd:!0,keywords:b,contains:["self",a.inherit(a.TITLE_MODE,{begin:/[A-Za-z$_][0-9A-Za-z$_]*/}),
+{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:b,contains:[a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE],illegal:/["'\(]/}],illegal:/%/,relevance:0},{beginKeywords:"constructor",end:/\{/,excludeEnd:!0,contains:["self",{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:b,contains:[a.C_LINE_COMMENT_MODE,a.C_BLOCK_COMMENT_MODE],illegal:/["'\(]/}]},{begin:/module\./,keywords:{built_in:"module"},relevance:0},{beginKeywords:"module",end:/\{/,excludeEnd:!0},
+{beginKeywords:"interface",end:/\{/,excludeEnd:!0,keywords:"interface extends"},{begin:/\$[(.]/},{begin:"\\."+a.IDENT_RE,relevance:0},{className:"meta",begin:"@[A-Za-z]+"}]}});b.registerLanguage("yaml",function(a){var b={className:"attr",variants:[{begin:"^[ \\-]*[a-zA-Z_][\\w\\-]*:"},{begin:'^[ \\-]*"[a-zA-Z_][\\w\\-]*":'},{begin:"^[ \\-]*'[a-zA-Z_][\\w\\-]*':"}]},d={className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/\S+/}],contains:[a.BACKSLASH_ESCAPE,{className:"template-variable",
+variants:[{begin:"{{",end:"}}"},{begin:"%{",end:"}"}]}]};return{case_insensitive:!0,aliases:["yml","YAML","yaml"],contains:[b,{className:"meta",begin:"^---s*$",relevance:10},{className:"string",begin:"[\\|>] *$",returnEnd:!0,contains:d.contains,end:b.variants[0].begin},{begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:"!!"+a.UNDERSCORE_IDENT_RE},{className:"meta",begin:"&"+a.UNDERSCORE_IDENT_RE+"$"},{className:"meta",begin:"\\*"+
+a.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"^ *-",relevance:0},a.HASH_COMMENT_MODE,{beginKeywords:"true false yes no null",keywords:{literal:"true false yes no null"}},a.C_NUMBER_MODE,d]}});return b});
diff --git a/lib/jgit/jgit.bzl b/lib/jgit/jgit.bzl
index adf2d4f8..be90acb 100644
--- a/lib/jgit/jgit.bzl
+++ b/lib/jgit/jgit.bzl
@@ -1,4 +1,4 @@
-load("//tools/bzl:maven_jar.bzl", "GERRIT", "MAVEN_LOCAL", "MAVEN_CENTRAL", "maven_jar")
+load("//tools/bzl:maven_jar.bzl", "GERRIT", "MAVEN_CENTRAL", "MAVEN_LOCAL", "maven_jar")
 
 _JGIT_VERS = "5.0.1.201806211838-r"
 
@@ -13,13 +13,13 @@
 LOCAL_JGIT_REPO = ""
 
 def jgit_repos():
-  if LOCAL_JGIT_REPO:
-    native.local_repository(
-        name = "jgit",
-        path = LOCAL_JGIT_REPO,
-    )
-  else:
-    jgit_maven_repos()
+    if LOCAL_JGIT_REPO:
+        native.local_repository(
+            name = "jgit",
+            path = LOCAL_JGIT_REPO,
+        )
+    else:
+        jgit_maven_repos()
 
 def jgit_maven_repos():
     maven_jar(
@@ -52,15 +52,15 @@
     )
 
 def jgit_dep(name):
-  mapping = {
-      "@jgit-junit//jar": "@jgit//org.eclipse.jgit.junit:junit",
-      "@jgit-lib//jar:src": "@jgit//org.eclipse.jgit:libjgit-src.jar",
-      "@jgit-lib//jar": "@jgit//org.eclipse.jgit:jgit",
-      "@jgit-servlet//jar":"@jgit//org.eclipse.jgit.http.server:jgit-servlet",
-      "@jgit-archive//jar": "@jgit//org.eclipse.jgit.archive:jgit-archive",
-  }
+    mapping = {
+        "@jgit-junit//jar": "@jgit//org.eclipse.jgit.junit:junit",
+        "@jgit-lib//jar:src": "@jgit//org.eclipse.jgit:libjgit-src.jar",
+        "@jgit-lib//jar": "@jgit//org.eclipse.jgit:jgit",
+        "@jgit-servlet//jar": "@jgit//org.eclipse.jgit.http.server:jgit-servlet",
+        "@jgit-archive//jar": "@jgit//org.eclipse.jgit.archive:jgit-archive",
+    }
 
-  if LOCAL_JGIT_REPO:
-    return mapping[name]
-  else:
-    return name
+    if LOCAL_JGIT_REPO:
+        return mapping[name]
+    else:
+        return name
diff --git a/lib/js/bower_archives.bzl b/lib/js/bower_archives.bzl
index 6b4e003..75c8277 100644
--- a/lib/js/bower_archives.bzl
+++ b/lib/js/bower_archives.bzl
@@ -7,138 +7,165 @@
 load("//tools/bzl:js.bzl", "bower_archive")
 
 def load_bower_archives():
-  bower_archive(
-    name = "accessibility-developer-tools",
-    package = "accessibility-developer-tools",
-    version = "2.12.0",
-    sha1 = "88ae82dcdeb6c658f76eff509d0ee425cae14d49")
-  bower_archive(
-    name = "async",
-    package = "async",
-    version = "1.5.2",
-    sha1 = "1ec975d3b3834646a7e3d4b7e68118b90ed72508")
-  bower_archive(
-    name = "chai",
-    package = "chai",
-    version = "3.5.0",
-    sha1 = "849ad3ee7c77506548b7b5db603a4e150b9431aa")
-  bower_archive(
-    name = "font-roboto",
-    package = "PolymerElements/font-roboto",
-    version = "1.1.0",
-    sha1 = "ab4218d87b9ce569d6282b01f7642e551879c3d5")
-  bower_archive(
-    name = "iron-a11y-announcer",
-    package = "PolymerElements/iron-a11y-announcer",
-    version = "1.0.6",
-    sha1 = "14aed1e1b300ea344e80362e875919ea3d104dcc")
-  bower_archive(
-    name = "iron-a11y-keys-behavior",
-    package = "PolymerElements/iron-a11y-keys-behavior",
-    version = "1.1.9",
-    sha1 = "f58358ee652c67e6e721364ba50fb77a2ece1465")
-  bower_archive(
-    name = "iron-behaviors",
-    package = "PolymerElements/iron-behaviors",
-    version = "1.0.18",
-    sha1 = "e231a1a02b090f5183db917639fdb96cdd0dca18")
-  bower_archive(
-    name = "iron-checked-element-behavior",
-    package = "PolymerElements/iron-checked-element-behavior",
-    version = "1.0.6",
-    sha1 = "93ad3554cec119d8c5732d1c722ad113e1866370")
-  bower_archive(
-    name = "iron-fit-behavior",
-    package = "PolymerElements/iron-fit-behavior",
-    version = "1.2.7",
-    sha1 = "01c485fbf898307029bbb72ac7e132db1570a842")
-  bower_archive(
-    name = "iron-flex-layout",
-    package = "PolymerElements/iron-flex-layout",
-    version = "1.3.9",
-    sha1 = "d987b924cf29fcfe4b393833e81fdc9f1e268796")
-  bower_archive(
-    name = "iron-form-element-behavior",
-    package = "PolymerElements/iron-form-element-behavior",
-    version = "1.0.7",
-    sha1 = "7b5a79e02cc32f0918725dd26925d0df1e03ed12")
-  bower_archive(
-    name = "iron-menu-behavior",
-    package = "PolymerElements/iron-menu-behavior",
-    version = "2.1.1",
-    sha1 = "1504997f6eb9aec490b855dadee473cac064f38c")
-  bower_archive(
-    name = "iron-meta",
-    package = "PolymerElements/iron-meta",
-    version = "1.1.3",
-    sha1 = "f77eba3f6f6817f10bda33918bde8f963d450041")
-  bower_archive(
-    name = "iron-resizable-behavior",
-    package = "polymerelements/iron-resizable-behavior",
-    version = "1.0.6",
-    sha1 = "719c2a8a1a784f8aefcdeef41fcc2e5a03518d9e")
-  bower_archive(
-    name = "iron-validatable-behavior",
-    package = "PolymerElements/iron-validatable-behavior",
-    version = "1.1.2",
-    sha1 = "7111f34ff32e1510131dfbdb1eaa51bfa291e8be")
-  bower_archive(
-    name = "lodash",
-    package = "lodash",
-    version = "3.10.1",
-    sha1 = "2f207a8293c4c554bf6cf071241f7a00dc513d3a")
-  bower_archive(
-    name = "mocha",
-    package = "mocha",
-    version = "3.5.3",
-    sha1 = "c14f149821e4e96241b20f85134aa757b73038f1")
-  bower_archive(
-    name = "neon-animation",
-    package = "polymerelements/neon-animation",
-    version = "1.2.5",
-    sha1 = "588d289f779d02b21ce5b676e257bbd6155649e8")
-  bower_archive(
-    name = "paper-behaviors",
-    package = "PolymerElements/paper-behaviors",
-    version = "1.0.13",
-    sha1 = "a81eab28a952e124c208430e17508d9a1aae4ee7")
-  bower_archive(
-    name = "paper-icon-button",
-    package = "PolymerElements/paper-icon-button",
-    version = "2.2.0",
-    sha1 = "9525e76ef433428bb9d6ec4fa65c4ef83156a803")
-  bower_archive(
-    name = "paper-ripple",
-    package = "PolymerElements/paper-ripple",
-    version = "1.0.10",
-    sha1 = "21199db50d02b842da54bd6f4f1d1b10b474e893")
-  bower_archive(
-    name = "paper-styles",
-    package = "PolymerElements/paper-styles",
-    version = "1.3.1",
-    sha1 = "4ee9c692366949a754e0e39f8031aa60ce66f24d")
-  bower_archive(
-    name = "sinon-chai",
-    package = "sinon-chai",
-    version = "2.14.0",
-    sha1 = "78f0dc184efe47012a2b1b9a16a4289acf8300dc")
-  bower_archive(
-    name = "sinonjs",
-    package = "sinonjs",
-    version = "1.17.1",
-    sha1 = "a26a6aab7358807de52ba738770f6ac709afd240")
-  bower_archive(
-    name = "stacky",
-    package = "stacky",
-    version = "1.3.2",
-    sha1 = "d6c07a0112ab2e9677fe085933744466a89232fb")
-  bower_archive(
-    name = "web-animations-js",
-    package = "web-animations/web-animations-js",
-    version = "2.3.1",
-    sha1 = "2ba5548d36188fe54555eaad0a576de4b027661e")
-  bower_archive(
-    name = "webcomponentsjs",
-    package = "webcomponents/webcomponentsjs",
-    version = "0.7.24",
-    sha1 = "559227f8ee9db9bfbd81989f24510cc0c1bfc65c")
+    bower_archive(
+        name = "accessibility-developer-tools",
+        package = "accessibility-developer-tools",
+        version = "2.12.0",
+        sha1 = "88ae82dcdeb6c658f76eff509d0ee425cae14d49",
+    )
+    bower_archive(
+        name = "async",
+        package = "async",
+        version = "1.5.2",
+        sha1 = "1ec975d3b3834646a7e3d4b7e68118b90ed72508",
+    )
+    bower_archive(
+        name = "chai",
+        package = "chai",
+        version = "3.5.0",
+        sha1 = "849ad3ee7c77506548b7b5db603a4e150b9431aa",
+    )
+    bower_archive(
+        name = "font-roboto",
+        package = "PolymerElements/font-roboto",
+        version = "1.1.0",
+        sha1 = "ab4218d87b9ce569d6282b01f7642e551879c3d5",
+    )
+    bower_archive(
+        name = "iron-a11y-announcer",
+        package = "PolymerElements/iron-a11y-announcer",
+        version = "1.0.6",
+        sha1 = "14aed1e1b300ea344e80362e875919ea3d104dcc",
+    )
+    bower_archive(
+        name = "iron-a11y-keys-behavior",
+        package = "PolymerElements/iron-a11y-keys-behavior",
+        version = "1.1.9",
+        sha1 = "f58358ee652c67e6e721364ba50fb77a2ece1465",
+    )
+    bower_archive(
+        name = "iron-behaviors",
+        package = "PolymerElements/iron-behaviors",
+        version = "1.0.18",
+        sha1 = "e231a1a02b090f5183db917639fdb96cdd0dca18",
+    )
+    bower_archive(
+        name = "iron-checked-element-behavior",
+        package = "PolymerElements/iron-checked-element-behavior",
+        version = "1.0.6",
+        sha1 = "93ad3554cec119d8c5732d1c722ad113e1866370",
+    )
+    bower_archive(
+        name = "iron-fit-behavior",
+        package = "PolymerElements/iron-fit-behavior",
+        version = "1.2.7",
+        sha1 = "01c485fbf898307029bbb72ac7e132db1570a842",
+    )
+    bower_archive(
+        name = "iron-flex-layout",
+        package = "PolymerElements/iron-flex-layout",
+        version = "1.3.9",
+        sha1 = "d987b924cf29fcfe4b393833e81fdc9f1e268796",
+    )
+    bower_archive(
+        name = "iron-form-element-behavior",
+        package = "PolymerElements/iron-form-element-behavior",
+        version = "1.0.7",
+        sha1 = "7b5a79e02cc32f0918725dd26925d0df1e03ed12",
+    )
+    bower_archive(
+        name = "iron-menu-behavior",
+        package = "PolymerElements/iron-menu-behavior",
+        version = "2.1.1",
+        sha1 = "1504997f6eb9aec490b855dadee473cac064f38c",
+    )
+    bower_archive(
+        name = "iron-meta",
+        package = "PolymerElements/iron-meta",
+        version = "1.1.3",
+        sha1 = "f77eba3f6f6817f10bda33918bde8f963d450041",
+    )
+    bower_archive(
+        name = "iron-resizable-behavior",
+        package = "polymerelements/iron-resizable-behavior",
+        version = "1.0.6",
+        sha1 = "719c2a8a1a784f8aefcdeef41fcc2e5a03518d9e",
+    )
+    bower_archive(
+        name = "iron-validatable-behavior",
+        package = "PolymerElements/iron-validatable-behavior",
+        version = "1.1.2",
+        sha1 = "7111f34ff32e1510131dfbdb1eaa51bfa291e8be",
+    )
+    bower_archive(
+        name = "lodash",
+        package = "lodash",
+        version = "3.10.1",
+        sha1 = "2f207a8293c4c554bf6cf071241f7a00dc513d3a",
+    )
+    bower_archive(
+        name = "mocha",
+        package = "mocha",
+        version = "3.5.3",
+        sha1 = "c14f149821e4e96241b20f85134aa757b73038f1",
+    )
+    bower_archive(
+        name = "neon-animation",
+        package = "polymerelements/neon-animation",
+        version = "1.2.5",
+        sha1 = "588d289f779d02b21ce5b676e257bbd6155649e8",
+    )
+    bower_archive(
+        name = "paper-behaviors",
+        package = "PolymerElements/paper-behaviors",
+        version = "1.0.13",
+        sha1 = "a81eab28a952e124c208430e17508d9a1aae4ee7",
+    )
+    bower_archive(
+        name = "paper-icon-button",
+        package = "PolymerElements/paper-icon-button",
+        version = "2.2.0",
+        sha1 = "9525e76ef433428bb9d6ec4fa65c4ef83156a803",
+    )
+    bower_archive(
+        name = "paper-ripple",
+        package = "PolymerElements/paper-ripple",
+        version = "1.0.10",
+        sha1 = "21199db50d02b842da54bd6f4f1d1b10b474e893",
+    )
+    bower_archive(
+        name = "paper-styles",
+        package = "PolymerElements/paper-styles",
+        version = "1.3.1",
+        sha1 = "4ee9c692366949a754e0e39f8031aa60ce66f24d",
+    )
+    bower_archive(
+        name = "sinon-chai",
+        package = "sinon-chai",
+        version = "2.14.0",
+        sha1 = "78f0dc184efe47012a2b1b9a16a4289acf8300dc",
+    )
+    bower_archive(
+        name = "sinonjs",
+        package = "sinonjs",
+        version = "1.17.1",
+        sha1 = "a26a6aab7358807de52ba738770f6ac709afd240",
+    )
+    bower_archive(
+        name = "stacky",
+        package = "stacky",
+        version = "1.3.2",
+        sha1 = "d6c07a0112ab2e9677fe085933744466a89232fb",
+    )
+    bower_archive(
+        name = "web-animations-js",
+        package = "web-animations/web-animations-js",
+        version = "2.3.1",
+        sha1 = "2ba5548d36188fe54555eaad0a576de4b027661e",
+    )
+    bower_archive(
+        name = "webcomponentsjs",
+        package = "webcomponents/webcomponentsjs",
+        version = "0.7.24",
+        sha1 = "559227f8ee9db9bfbd81989f24510cc0c1bfc65c",
+    )
diff --git a/lib/js/bower_components.bzl b/lib/js/bower_components.bzl
index dc16ccf..a540828 100644
--- a/lib/js/bower_components.bzl
+++ b/lib/js/bower_components.bzl
@@ -7,377 +7,377 @@
 load("//tools/bzl:js.bzl", "bower_component")
 
 def define_bower_components():
-  bower_component(
-    name = "accessibility-developer-tools",
-    license = "//lib:LICENSE-DO_NOT_DISTRIBUTE",
-  )
-  bower_component(
-    name = "async",
-    license = "//lib:LICENSE-DO_NOT_DISTRIBUTE",
-  )
-  bower_component(
-    name = "chai",
-    license = "//lib:LICENSE-DO_NOT_DISTRIBUTE",
-  )
-  bower_component(
-    name = "es6-promise",
-    license = "//lib:LICENSE-es6-promise",
-    seed = True,
-  )
-  bower_component(
-    name = "fetch",
-    license = "//lib:LICENSE-fetch",
-    seed = True,
-  )
-  bower_component(
-    name = "font-roboto",
-    license = "//lib:LICENSE-polymer",
-  )
-  bower_component(
-    name = "iron-a11y-announcer",
-    license = "//lib:LICENSE-polymer",
-    deps = [ ":polymer" ],
-  )
-  bower_component(
-    name = "iron-a11y-keys-behavior",
-    license = "//lib:LICENSE-polymer",
-    deps = [ ":polymer" ],
-  )
-  bower_component(
-    name = "iron-autogrow-textarea",
-    license = "//lib:LICENSE-polymer",
-    deps = [
-      ":iron-behaviors",
-      ":iron-flex-layout",
-      ":iron-validatable-behavior",
-      ":polymer",
-    ],
-    seed = True,
-  )
-  bower_component(
-    name = "iron-behaviors",
-    license = "//lib:LICENSE-polymer",
-    deps = [
-      ":iron-a11y-keys-behavior",
-      ":polymer",
-    ],
-  )
-  bower_component(
-    name = "iron-checked-element-behavior",
-    license = "//lib:LICENSE-polymer",
-    deps = [
-      ":iron-form-element-behavior",
-      ":iron-validatable-behavior",
-      ":polymer",
-    ],
-  )
-  bower_component(
-    name = "iron-dropdown",
-    license = "//lib:LICENSE-polymer",
-    deps = [
-      ":iron-behaviors",
-      ":iron-overlay-behavior",
-      ":iron-resizable-behavior",
-      ":neon-animation",
-      ":polymer",
-    ],
-    seed = True,
-  )
-  bower_component(
-    name = "iron-fit-behavior",
-    license = "//lib:LICENSE-polymer",
-    deps = [ ":polymer" ],
-  )
-  bower_component(
-    name = "iron-flex-layout",
-    license = "//lib:LICENSE-polymer",
-    deps = [ ":polymer" ],
-  )
-  bower_component(
-    name = "iron-form-element-behavior",
-    license = "//lib:LICENSE-polymer",
-    deps = [ ":polymer" ],
-  )
-  bower_component(
-    name = "iron-icon",
-    license = "//lib:LICENSE-polymer",
-    deps = [
-      ":iron-flex-layout",
-      ":iron-meta",
-      ":polymer",
-    ],
-    seed = True,
-  )
-  bower_component(
-    name = "iron-iconset-svg",
-    license = "//lib:LICENSE-polymer",
-    deps = [
-      ":iron-meta",
-      ":polymer",
-    ],
-    seed = True,
-  )
-  bower_component(
-    name = "iron-input",
-    license = "//lib:LICENSE-polymer",
-    deps = [
-      ":iron-a11y-announcer",
-      ":iron-validatable-behavior",
-      ":polymer",
-    ],
-    seed = True,
-  )
-  bower_component(
-    name = "iron-menu-behavior",
-    license = "//lib:LICENSE-polymer",
-    deps = [
-      ":iron-a11y-keys-behavior",
-      ":iron-flex-layout",
-      ":iron-selector",
-      ":polymer",
-    ],
-  )
-  bower_component(
-    name = "iron-meta",
-    license = "//lib:LICENSE-polymer",
-    deps = [ ":polymer" ],
-  )
-  bower_component(
-    name = "iron-overlay-behavior",
-    license = "//lib:LICENSE-polymer",
-    deps = [
-      ":iron-a11y-keys-behavior",
-      ":iron-fit-behavior",
-      ":iron-resizable-behavior",
-      ":polymer",
-    ],
-    seed = True,
-  )
-  bower_component(
-    name = "iron-resizable-behavior",
-    license = "//lib:LICENSE-polymer",
-    deps = [ ":polymer" ],
-  )
-  bower_component(
-    name = "iron-selector",
-    license = "//lib:LICENSE-polymer",
-    deps = [ ":polymer" ],
-    seed = True,
-  )
-  bower_component(
-    name = "iron-test-helpers",
-    license = "//lib:LICENSE-DO_NOT_DISTRIBUTE",
-    deps = [ ":polymer" ],
-    seed = True,
-  )
-  bower_component(
-    name = "iron-validatable-behavior",
-    license = "//lib:LICENSE-polymer",
-    deps = [
-      ":iron-meta",
-      ":polymer",
-    ],
-  )
-  bower_component(
-    name = "lodash",
-    license = "//lib:LICENSE-DO_NOT_DISTRIBUTE",
-  )
-  bower_component(
-    name = "mocha",
-    license = "//lib:LICENSE-DO_NOT_DISTRIBUTE",
-  )
-  bower_component(
-    name = "moment",
-    license = "//lib:LICENSE-moment",
-    seed = True,
-  )
-  bower_component(
-    name = "neon-animation",
-    license = "//lib:LICENSE-polymer",
-    deps = [
-      ":iron-meta",
-      ":iron-resizable-behavior",
-      ":iron-selector",
-      ":polymer",
-      ":web-animations-js",
-    ],
-  )
-  bower_component(
-    name = "page",
-    license = "//lib:LICENSE-page.js",
-    seed = True,
-  )
-  bower_component(
-    name = "paper-behaviors",
-    license = "//lib:LICENSE-polymer",
-    deps = [
-      ":iron-behaviors",
-      ":iron-checked-element-behavior",
-      ":paper-ripple",
-      ":polymer",
-    ],
-  )
-  bower_component(
-    name = "paper-button",
-    license = "//lib:LICENSE-polymer",
-    deps = [
-      ":iron-flex-layout",
-      ":paper-behaviors",
-      ":paper-styles",
-      ":polymer",
-    ],
-    seed = True,
-  )
-  bower_component(
-    name = "paper-icon-button",
-    license = "//lib:LICENSE-polymer",
-    deps = [
-      ":iron-icon",
-      ":paper-behaviors",
-      ":paper-styles",
-      ":polymer",
-    ],
-  )
-  bower_component(
-    name = "paper-input",
-    license = "//lib:LICENSE-polymer",
-    deps = [
-      ":iron-a11y-keys-behavior",
-      ":iron-autogrow-textarea",
-      ":iron-behaviors",
-      ":iron-form-element-behavior",
-      ":iron-input",
-      ":paper-styles",
-      ":polymer",
-    ],
-    seed = True,
-  )
-  bower_component(
-    name = "paper-item",
-    license = "//lib:LICENSE-polymer",
-    deps = [
-      ":iron-behaviors",
-      ":iron-flex-layout",
-      ":paper-styles",
-      ":polymer",
-    ],
-    seed = True,
-  )
-  bower_component(
-    name = "paper-listbox",
-    license = "//lib:LICENSE-polymer",
-    deps = [
-      ":iron-behaviors",
-      ":iron-menu-behavior",
-      ":paper-styles",
-      ":polymer",
-    ],
-    seed = True,
-  )
-  bower_component(
-    name = "paper-ripple",
-    license = "//lib:LICENSE-polymer",
-    deps = [
-      ":iron-a11y-keys-behavior",
-      ":polymer",
-    ],
-  )
-  bower_component(
-    name = "paper-styles",
-    license = "//lib:LICENSE-polymer",
-    deps = [
-      ":font-roboto",
-      ":iron-flex-layout",
-      ":polymer",
-    ],
-  )
-  bower_component(
-    name = "paper-tabs",
-    license = "//lib:LICENSE-polymer",
-    deps = [
-      ":iron-behaviors",
-      ":iron-flex-layout",
-      ":iron-icon",
-      ":iron-iconset-svg",
-      ":iron-menu-behavior",
-      ":iron-resizable-behavior",
-      ":paper-behaviors",
-      ":paper-icon-button",
-      ":paper-styles",
-      ":polymer",
-    ],
-    seed = True,
-  )
-  bower_component(
-    name = "paper-toggle-button",
-    license = "//lib:LICENSE-polymer",
-    deps = [
-      ":iron-checked-element-behavior",
-      ":paper-behaviors",
-      ":paper-styles",
-      ":polymer",
-    ],
-    seed = True,
-  )
-  bower_component(
-    name = "polymer-resin",
-    license = "//lib:LICENSE-polymer",
-    deps = [
-      ":polymer",
-      ":webcomponentsjs",
-    ],
-    seed = True,
-  )
-  bower_component(
-    name = "polymer",
-    license = "//lib:LICENSE-polymer",
-    deps = [ ":webcomponentsjs" ],
-    seed = True,
-  )
-  bower_component(
-    name = "promise-polyfill",
-    license = "//lib:LICENSE-promise-polyfill",
-    deps = [ ":polymer" ],
-    seed = True,
-  )
-  bower_component(
-    name = "sinon-chai",
-    license = "//lib:LICENSE-DO_NOT_DISTRIBUTE",
-  )
-  bower_component(
-    name = "sinonjs",
-    license = "//lib:LICENSE-DO_NOT_DISTRIBUTE",
-  )
-  bower_component(
-    name = "stacky",
-    license = "//lib:LICENSE-DO_NOT_DISTRIBUTE",
-  )
-  bower_component(
-    name = "test-fixture",
-    license = "//lib:LICENSE-DO_NOT_DISTRIBUTE",
-    seed = True,
-  )
-  bower_component(
-    name = "web-animations-js",
-    license = "//lib:LICENSE-Apache2.0",
-  )
-  bower_component(
-    name = "web-component-tester",
-    license = "//lib:LICENSE-DO_NOT_DISTRIBUTE",
-    deps = [
-      ":accessibility-developer-tools",
-      ":async",
-      ":chai",
-      ":lodash",
-      ":mocha",
-      ":sinon-chai",
-      ":sinonjs",
-      ":stacky",
-      ":test-fixture",
-    ],
-    seed = True,
-  )
-  bower_component(
-    name = "webcomponentsjs",
-    license = "//lib:LICENSE-polymer",
-  )
+    bower_component(
+        name = "accessibility-developer-tools",
+        license = "//lib:LICENSE-DO_NOT_DISTRIBUTE",
+    )
+    bower_component(
+        name = "async",
+        license = "//lib:LICENSE-DO_NOT_DISTRIBUTE",
+    )
+    bower_component(
+        name = "chai",
+        license = "//lib:LICENSE-DO_NOT_DISTRIBUTE",
+    )
+    bower_component(
+        name = "es6-promise",
+        license = "//lib:LICENSE-es6-promise",
+        seed = True,
+    )
+    bower_component(
+        name = "fetch",
+        license = "//lib:LICENSE-fetch",
+        seed = True,
+    )
+    bower_component(
+        name = "font-roboto",
+        license = "//lib:LICENSE-polymer",
+    )
+    bower_component(
+        name = "iron-a11y-announcer",
+        license = "//lib:LICENSE-polymer",
+        deps = [":polymer"],
+    )
+    bower_component(
+        name = "iron-a11y-keys-behavior",
+        license = "//lib:LICENSE-polymer",
+        deps = [":polymer"],
+    )
+    bower_component(
+        name = "iron-autogrow-textarea",
+        license = "//lib:LICENSE-polymer",
+        deps = [
+            ":iron-behaviors",
+            ":iron-flex-layout",
+            ":iron-validatable-behavior",
+            ":polymer",
+        ],
+        seed = True,
+    )
+    bower_component(
+        name = "iron-behaviors",
+        license = "//lib:LICENSE-polymer",
+        deps = [
+            ":iron-a11y-keys-behavior",
+            ":polymer",
+        ],
+    )
+    bower_component(
+        name = "iron-checked-element-behavior",
+        license = "//lib:LICENSE-polymer",
+        deps = [
+            ":iron-form-element-behavior",
+            ":iron-validatable-behavior",
+            ":polymer",
+        ],
+    )
+    bower_component(
+        name = "iron-dropdown",
+        license = "//lib:LICENSE-polymer",
+        deps = [
+            ":iron-behaviors",
+            ":iron-overlay-behavior",
+            ":iron-resizable-behavior",
+            ":neon-animation",
+            ":polymer",
+        ],
+        seed = True,
+    )
+    bower_component(
+        name = "iron-fit-behavior",
+        license = "//lib:LICENSE-polymer",
+        deps = [":polymer"],
+    )
+    bower_component(
+        name = "iron-flex-layout",
+        license = "//lib:LICENSE-polymer",
+        deps = [":polymer"],
+    )
+    bower_component(
+        name = "iron-form-element-behavior",
+        license = "//lib:LICENSE-polymer",
+        deps = [":polymer"],
+    )
+    bower_component(
+        name = "iron-icon",
+        license = "//lib:LICENSE-polymer",
+        deps = [
+            ":iron-flex-layout",
+            ":iron-meta",
+            ":polymer",
+        ],
+        seed = True,
+    )
+    bower_component(
+        name = "iron-iconset-svg",
+        license = "//lib:LICENSE-polymer",
+        deps = [
+            ":iron-meta",
+            ":polymer",
+        ],
+        seed = True,
+    )
+    bower_component(
+        name = "iron-input",
+        license = "//lib:LICENSE-polymer",
+        deps = [
+            ":iron-a11y-announcer",
+            ":iron-validatable-behavior",
+            ":polymer",
+        ],
+        seed = True,
+    )
+    bower_component(
+        name = "iron-menu-behavior",
+        license = "//lib:LICENSE-polymer",
+        deps = [
+            ":iron-a11y-keys-behavior",
+            ":iron-flex-layout",
+            ":iron-selector",
+            ":polymer",
+        ],
+    )
+    bower_component(
+        name = "iron-meta",
+        license = "//lib:LICENSE-polymer",
+        deps = [":polymer"],
+    )
+    bower_component(
+        name = "iron-overlay-behavior",
+        license = "//lib:LICENSE-polymer",
+        deps = [
+            ":iron-a11y-keys-behavior",
+            ":iron-fit-behavior",
+            ":iron-resizable-behavior",
+            ":polymer",
+        ],
+        seed = True,
+    )
+    bower_component(
+        name = "iron-resizable-behavior",
+        license = "//lib:LICENSE-polymer",
+        deps = [":polymer"],
+    )
+    bower_component(
+        name = "iron-selector",
+        license = "//lib:LICENSE-polymer",
+        deps = [":polymer"],
+        seed = True,
+    )
+    bower_component(
+        name = "iron-test-helpers",
+        license = "//lib:LICENSE-DO_NOT_DISTRIBUTE",
+        deps = [":polymer"],
+        seed = True,
+    )
+    bower_component(
+        name = "iron-validatable-behavior",
+        license = "//lib:LICENSE-polymer",
+        deps = [
+            ":iron-meta",
+            ":polymer",
+        ],
+    )
+    bower_component(
+        name = "lodash",
+        license = "//lib:LICENSE-DO_NOT_DISTRIBUTE",
+    )
+    bower_component(
+        name = "mocha",
+        license = "//lib:LICENSE-DO_NOT_DISTRIBUTE",
+    )
+    bower_component(
+        name = "moment",
+        license = "//lib:LICENSE-moment",
+        seed = True,
+    )
+    bower_component(
+        name = "neon-animation",
+        license = "//lib:LICENSE-polymer",
+        deps = [
+            ":iron-meta",
+            ":iron-resizable-behavior",
+            ":iron-selector",
+            ":polymer",
+            ":web-animations-js",
+        ],
+    )
+    bower_component(
+        name = "page",
+        license = "//lib:LICENSE-page.js",
+        seed = True,
+    )
+    bower_component(
+        name = "paper-behaviors",
+        license = "//lib:LICENSE-polymer",
+        deps = [
+            ":iron-behaviors",
+            ":iron-checked-element-behavior",
+            ":paper-ripple",
+            ":polymer",
+        ],
+    )
+    bower_component(
+        name = "paper-button",
+        license = "//lib:LICENSE-polymer",
+        deps = [
+            ":iron-flex-layout",
+            ":paper-behaviors",
+            ":paper-styles",
+            ":polymer",
+        ],
+        seed = True,
+    )
+    bower_component(
+        name = "paper-icon-button",
+        license = "//lib:LICENSE-polymer",
+        deps = [
+            ":iron-icon",
+            ":paper-behaviors",
+            ":paper-styles",
+            ":polymer",
+        ],
+    )
+    bower_component(
+        name = "paper-input",
+        license = "//lib:LICENSE-polymer",
+        deps = [
+            ":iron-a11y-keys-behavior",
+            ":iron-autogrow-textarea",
+            ":iron-behaviors",
+            ":iron-form-element-behavior",
+            ":iron-input",
+            ":paper-styles",
+            ":polymer",
+        ],
+        seed = True,
+    )
+    bower_component(
+        name = "paper-item",
+        license = "//lib:LICENSE-polymer",
+        deps = [
+            ":iron-behaviors",
+            ":iron-flex-layout",
+            ":paper-styles",
+            ":polymer",
+        ],
+        seed = True,
+    )
+    bower_component(
+        name = "paper-listbox",
+        license = "//lib:LICENSE-polymer",
+        deps = [
+            ":iron-behaviors",
+            ":iron-menu-behavior",
+            ":paper-styles",
+            ":polymer",
+        ],
+        seed = True,
+    )
+    bower_component(
+        name = "paper-ripple",
+        license = "//lib:LICENSE-polymer",
+        deps = [
+            ":iron-a11y-keys-behavior",
+            ":polymer",
+        ],
+    )
+    bower_component(
+        name = "paper-styles",
+        license = "//lib:LICENSE-polymer",
+        deps = [
+            ":font-roboto",
+            ":iron-flex-layout",
+            ":polymer",
+        ],
+    )
+    bower_component(
+        name = "paper-tabs",
+        license = "//lib:LICENSE-polymer",
+        deps = [
+            ":iron-behaviors",
+            ":iron-flex-layout",
+            ":iron-icon",
+            ":iron-iconset-svg",
+            ":iron-menu-behavior",
+            ":iron-resizable-behavior",
+            ":paper-behaviors",
+            ":paper-icon-button",
+            ":paper-styles",
+            ":polymer",
+        ],
+        seed = True,
+    )
+    bower_component(
+        name = "paper-toggle-button",
+        license = "//lib:LICENSE-polymer",
+        deps = [
+            ":iron-checked-element-behavior",
+            ":paper-behaviors",
+            ":paper-styles",
+            ":polymer",
+        ],
+        seed = True,
+    )
+    bower_component(
+        name = "polymer-resin",
+        license = "//lib:LICENSE-polymer",
+        deps = [
+            ":polymer",
+            ":webcomponentsjs",
+        ],
+        seed = True,
+    )
+    bower_component(
+        name = "polymer",
+        license = "//lib:LICENSE-polymer",
+        deps = [":webcomponentsjs"],
+        seed = True,
+    )
+    bower_component(
+        name = "promise-polyfill",
+        license = "//lib:LICENSE-promise-polyfill",
+        deps = [":polymer"],
+        seed = True,
+    )
+    bower_component(
+        name = "sinon-chai",
+        license = "//lib:LICENSE-DO_NOT_DISTRIBUTE",
+    )
+    bower_component(
+        name = "sinonjs",
+        license = "//lib:LICENSE-DO_NOT_DISTRIBUTE",
+    )
+    bower_component(
+        name = "stacky",
+        license = "//lib:LICENSE-DO_NOT_DISTRIBUTE",
+    )
+    bower_component(
+        name = "test-fixture",
+        license = "//lib:LICENSE-DO_NOT_DISTRIBUTE",
+        seed = True,
+    )
+    bower_component(
+        name = "web-animations-js",
+        license = "//lib:LICENSE-Apache2.0",
+    )
+    bower_component(
+        name = "web-component-tester",
+        license = "//lib:LICENSE-DO_NOT_DISTRIBUTE",
+        deps = [
+            ":accessibility-developer-tools",
+            ":async",
+            ":chai",
+            ":lodash",
+            ":mocha",
+            ":sinon-chai",
+            ":sinonjs",
+            ":stacky",
+            ":test-fixture",
+        ],
+        seed = True,
+    )
+    bower_component(
+        name = "webcomponentsjs",
+        license = "//lib:LICENSE-polymer",
+    )
diff --git a/lib/prolog/prolog.bzl b/lib/prolog/prolog.bzl
index d905ad8..4d4dd3a 100644
--- a/lib/prolog/prolog.bzl
+++ b/lib/prolog/prolog.bzl
@@ -13,22 +13,22 @@
 # limitations under the License.
 
 def prolog_cafe_library(
-    name,
-    srcs,
-    deps = [],
-    **kwargs):
-  native.genrule(
-    name = name + '__pl2j',
-    cmd = '$(location //lib/prolog:compiler-bin) ' +
-      '$$(dirname $@) $@ ' +
-      '$(SRCS)',
-    srcs = srcs,
-    tools = ['//lib/prolog:compiler-bin'],
-    outs = [ name + '.srcjar' ],
-  )
-  native.java_library(
-    name = name,
-    srcs = [':' + name + '__pl2j'],
-    deps = ['//lib/prolog:runtime-neverlink'] + deps,
-    **kwargs
-  )
+        name,
+        srcs,
+        deps = [],
+        **kwargs):
+    native.genrule(
+        name = name + "__pl2j",
+        cmd = "$(location //lib/prolog:compiler-bin) " +
+              "$$(dirname $@) $@ " +
+              "$(SRCS)",
+        srcs = srcs,
+        tools = ["//lib/prolog:compiler-bin"],
+        outs = [name + ".srcjar"],
+    )
+    native.java_library(
+        name = name,
+        srcs = [":" + name + "__pl2j"],
+        deps = ["//lib/prolog:runtime-neverlink"] + deps,
+        **kwargs
+    )
diff --git a/plugins/codemirror-editor b/plugins/codemirror-editor
index 53dccff..318504d 160000
--- a/plugins/codemirror-editor
+++ b/plugins/codemirror-editor
@@ -1 +1 @@
-Subproject commit 53dccff17c029459999ff70ac886b80626af634b
+Subproject commit 318504d0eb74f2f9c2967e9865ace6d5b57638c6
diff --git a/plugins/external_plugin_deps.bzl b/plugins/external_plugin_deps.bzl
index 391f920..1f7c020 100644
--- a/plugins/external_plugin_deps.bzl
+++ b/plugins/external_plugin_deps.bzl
@@ -1,2 +1,2 @@
 def external_plugin_deps():
-    pass
\ No newline at end of file
+    pass
diff --git a/plugins/reviewnotes b/plugins/reviewnotes
index c73171e..920f28b 160000
--- a/plugins/reviewnotes
+++ b/plugins/reviewnotes
@@ -1 +1 @@
-Subproject commit c73171ea9abbeb765d585a92753ce01151355a5c
+Subproject commit 920f28b46021d9c49fac09d869aa4040d13796e7
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.html b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.html
index 6ea7cf3..332121f 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.html
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.html
@@ -171,10 +171,10 @@
     </td>
     <td class="cell project"
         hidden$="[[isColumnHidden('Project', visibleChangeTableColumns)]]">
-      <a class="fullProject" href$="[[_computeProjectURL(change.project)]]">
+      <a class="fullProject" href$="[[_computeProjectURL(change)]]">
         [[change.project]]
       </a>
-      <a class="truncatedProject" href$="[[_computeProjectURL(change.project)]]">
+      <a class="truncatedProject" href$="[[_computeProjectURL(change)]]">
         [[_computeTruncatedProject(change.project)]]
       </a>
     </td>
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.js b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.js
index 259580b..53cc990 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.js
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.js
@@ -122,17 +122,19 @@
       return '';
     },
 
-    _computeProjectURL(project) {
-      return Gerrit.Nav.getUrlForProjectChanges(project, true);
+    _computeProjectURL(change) {
+      return Gerrit.Nav.getUrlForProjectChanges(change.project, true,
+          change.internalHost);
     },
 
     _computeProjectBranchURL(change) {
-      return Gerrit.Nav.getUrlForBranch(change.branch, change.project);
+      return Gerrit.Nav.getUrlForBranch(change.branch, change.project, null,
+          change.internalHost);
     },
 
     _computeTopicURL(change) {
       if (!change.topic) { return ''; }
-      return Gerrit.Nav.getUrlForTopic(change.topic);
+      return Gerrit.Nav.getUrlForTopic(change.topic, change.internalHost);
     },
 
     _computeTruncatedProject(project) {
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html
index 81e1034..4fa6ff5 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html
@@ -37,8 +37,10 @@
 <script>
   suite('gr-change-list-item tests', () => {
     let element;
+    let sandbox;
 
     setup(() => {
+      sandbox = sinon.sandbox.create();
       stub('gr-rest-api-interface', {
         getConfig() { return Promise.resolve({}); },
         getLoggedIn() { return Promise.resolve(false); },
@@ -46,6 +48,8 @@
       element = fixture('basic');
     });
 
+    teardown(() => { sandbox.restore(); });
+
     test('computed fields', () => {
       assert.equal(element._computeLabelClass({labels: {}}),
           'cell label u-gray-background');
@@ -249,5 +253,25 @@
         deletions: 999,
       }), 'XL');
     });
+
+    test('change params passed to gr-navigation', () => {
+      sandbox.stub(Gerrit.Nav);
+      const change = {
+        internalHost: 'test-host',
+        project: 'test-repo',
+        topic: 'test-topic',
+        branch: 'test-branch',
+      };
+      element.change = change;
+      flushAsynchronousOperations();
+
+      assert.deepEqual(Gerrit.Nav.getUrlForChange.lastCall.args, [change]);
+      assert.deepEqual(Gerrit.Nav.getUrlForProjectChanges.lastCall.args,
+          [change.project, true, change.internalHost]);
+      assert.deepEqual(Gerrit.Nav.getUrlForBranch.lastCall.args,
+          [change.branch, change.project, null, change.internalHost]);
+      assert.deepEqual(Gerrit.Nav.getUrlForTopic.lastCall.args,
+          [change.topic, change.internalHost]);
+    });
   });
 </script>
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
index 5a56475..1b911e0 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
@@ -273,6 +273,12 @@
         #commitMessageEditor {
           min-width: 0;
         }
+        .commitMessage {
+          margin-right: 0;
+        }
+        .mainChangeInfo {
+          padding-right: 0;
+        }
       }
       /* NOTE: If you update this breakpoint, also update the
       BREAKPOINT_RELATED_SMALL in the JS */
diff --git a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.js b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.js
index 396fed8..0682ab2 100644
--- a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.js
+++ b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.js
@@ -49,12 +49,12 @@
     },
 
     get selectedItem() {
-      if (!this._ironSelector) { return; }
+      if (!this._ironSelector) { return undefined; }
       return this._ironSelector.selectedItem;
     },
 
     get selectedValue() {
-      if (!this._ironSelector) { return; }
+      if (!this._ironSelector) { return undefined; }
       return this._ironSelector.selected;
     },
 
diff --git a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html
index e217b4b..dfe5410 100644
--- a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html
+++ b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html
@@ -177,13 +177,15 @@
        * @param {!string} project The name of the project.
        * @param {boolean=} opt_openOnly When true, only search open changes in
        *     the project.
+       * @param {string=} opt_host The host in which to search.
        * @return {string}
        */
-      getUrlForProjectChanges(project, opt_openOnly) {
+      getUrlForProjectChanges(project, opt_openOnly, opt_host) {
         return this._getUrlFor({
           view: Gerrit.Nav.View.SEARCH,
           project,
           statuses: opt_openOnly ? ['open'] : [],
+          host: opt_host,
         });
       },
 
@@ -191,26 +193,30 @@
        * @param {string} branch The name of the branch.
        * @param {string} project The name of the project.
        * @param {string=} opt_status The status to search.
+       * @param {string=} opt_host The host in which to search.
        * @return {string}
        */
-      getUrlForBranch(branch, project, opt_status) {
+      getUrlForBranch(branch, project, opt_status, opt_host) {
         return this._getUrlFor({
           view: Gerrit.Nav.View.SEARCH,
           branch,
           project,
           statuses: opt_status ? [opt_status] : undefined,
+          host: opt_host,
         });
       },
 
       /**
        * @param {string} topic The name of the topic.
+       * @param {string=} opt_host The host in which to search.
        * @return {string}
        */
-      getUrlForTopic(topic) {
+      getUrlForTopic(topic, opt_host) {
         return this._getUrlFor({
           view: Gerrit.Nav.View.SEARCH,
           topic,
           statuses: ['open', 'merged'],
+          host: opt_host,
         });
       },
 
@@ -267,6 +273,7 @@
           patchNum: opt_patchNum,
           basePatchNum: opt_basePatchNum,
           edit: opt_isEdit,
+          host: change.internalHost || undefined,
         });
       },
 
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-binary.js b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-binary.js
index 02ad67b..b47e516 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-binary.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-binary.js
@@ -22,7 +22,6 @@
 
   function GrDiffBuilderBinary(diff, comments, prefs, projectName, outputEl) {
     GrDiffBuilder.call(this, diff, comments, prefs, projectName, outputEl);
-    console.log('binary village');
   }
 
   GrDiffBuilderBinary.prototype = Object.create(GrDiffBuilder.prototype);
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js
index ea69fc2..edf9f5a 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js
@@ -457,7 +457,11 @@
         this._discardDraft();
         return;
       }
-      this._openOverlay(this.confirmDiscardOverlay);
+
+      this._openOverlay(this.confirmDiscardOverlay).then(() => {
+        this.confirmDiscardOverlay.querySelector('#confirmDiscardDialog')
+            .resetFocus();
+      });
     },
 
     _handleConfirmDiscard(e) {
@@ -626,9 +630,7 @@
 
     _openOverlay(overlay) {
       Polymer.dom(Gerrit.getRootElement()).appendChild(overlay);
-      this.async(() => {
-        overlay.open();
-      }, 1);
+      return overlay.open();
     },
 
     _closeOverlay(overlay) {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html
index b41ab23..f1ac649 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html
@@ -614,7 +614,8 @@
 
       setup(() => {
         discardStub = sandbox.stub(element, '_discardDraft');
-        overlayStub = sandbox.stub(element, '_openOverlay');
+        overlayStub = sandbox.stub(element, '_openOverlay')
+            .returns(Promise.resolve());
         mockEvent = {preventDefault: sinon.stub()};
       });
 
diff --git a/polygerrit-ui/app/rules.bzl b/polygerrit-ui/app/rules.bzl
index 199a947..293ef8b 100644
--- a/polygerrit-ui/app/rules.bzl
+++ b/polygerrit-ui/app/rules.bzl
@@ -1,107 +1,107 @@
 load("//tools/bzl:genrule2.bzl", "genrule2")
-load("@io_bazel_rules_closure//closure:defs.bzl", "closure_js_library", "closure_js_binary")
+load("@io_bazel_rules_closure//closure:defs.bzl", "closure_js_binary", "closure_js_library")
 load(
     "//tools/bzl:js.bzl",
-    "vulcanize",
     "bower_component",
     "js_component",
+    "vulcanize",
 )
 
 def polygerrit_bundle(name, srcs, outs, app):
-  appName = app.split(".html")[0].split("/").pop() # eg: gr-app
+    appName = app.split(".html")[0].split("/").pop()  # eg: gr-app
 
-  closure_js_binary(
-    name = name + "_closure_bin",
-    # Known issue: Closure compilation not compatible with Polymer behaviors.
-    # See: https://github.com/google/closure-compiler/issues/2042
-    compilation_level = "WHITESPACE_ONLY",
-    defs = [
-      "--polymer_version=1",
-      "--jscomp_off=duplicate",
-      "--force_inject_library=es6_runtime",
-    ],
-    language = "ECMASCRIPT5",
-    deps = [name + "_closure_lib"],
-  )
+    closure_js_binary(
+        name = name + "_closure_bin",
+        # Known issue: Closure compilation not compatible with Polymer behaviors.
+        # See: https://github.com/google/closure-compiler/issues/2042
+        compilation_level = "WHITESPACE_ONLY",
+        defs = [
+            "--polymer_version=1",
+            "--jscomp_off=duplicate",
+            "--force_inject_library=es6_runtime",
+        ],
+        language = "ECMASCRIPT5",
+        deps = [name + "_closure_lib"],
+    )
 
-  closure_js_library(
-    name = name + "_closure_lib",
-    srcs = [appName + ".js"],
-    convention = "GOOGLE",
-    # TODO(davido): Clean up these issues: http://paste.openstack.org/show/608548
-    # and remove this supression
-    suppress = [
-       "JSC_JSDOC_MISSING_TYPE_WARNING",
-       "JSC_UNNECESSARY_ESCAPE",
-       "JSC_UNUSED_LOCAL_ASSIGNMENT",
-    ],
-    deps = [
-      "//lib/polymer_externs:polymer_closure",
-      "@io_bazel_rules_closure//closure/library",
-    ],
-  )
+    closure_js_library(
+        name = name + "_closure_lib",
+        srcs = [appName + ".js"],
+        convention = "GOOGLE",
+        # TODO(davido): Clean up these issues: http://paste.openstack.org/show/608548
+        # and remove this supression
+        suppress = [
+            "JSC_JSDOC_MISSING_TYPE_WARNING",
+            "JSC_UNNECESSARY_ESCAPE",
+            "JSC_UNUSED_LOCAL_ASSIGNMENT",
+        ],
+        deps = [
+            "//lib/polymer_externs:polymer_closure",
+            "@io_bazel_rules_closure//closure/library",
+        ],
+    )
 
-  vulcanize(
-    name = appName,
-    srcs = srcs,
-    app = app,
-    deps = ["//polygerrit-ui:polygerrit_components.bower_components"],
-  )
+    vulcanize(
+        name = appName,
+        srcs = srcs,
+        app = app,
+        deps = ["//polygerrit-ui:polygerrit_components.bower_components"],
+    )
 
-  native.filegroup(
-    name = name + "_app_sources",
-    srcs = [
-      name + "_closure_bin.js",
-      appName + ".html",
-    ],
-  )
+    native.filegroup(
+        name = name + "_app_sources",
+        srcs = [
+            name + "_closure_bin.js",
+            appName + ".html",
+        ],
+    )
 
-  native.filegroup(
-    name = name + "_css_sources",
-    srcs = native.glob(["styles/**/*.css"]),
-  )
+    native.filegroup(
+        name = name + "_css_sources",
+        srcs = native.glob(["styles/**/*.css"]),
+    )
 
-  native.filegroup(
-    name = name + "_theme_sources",
-    srcs = native.glob(
-      ["styles/themes/*.html"],
-      # app-theme.html already included via an import in gr-app.html.
-      exclude = ["styles/themes/app-theme.html"],
-    ),
-  )
+    native.filegroup(
+        name = name + "_theme_sources",
+        srcs = native.glob(
+            ["styles/themes/*.html"],
+            # app-theme.html already included via an import in gr-app.html.
+            exclude = ["styles/themes/app-theme.html"],
+        ),
+    )
 
-  native.filegroup(
-    name = name + "_top_sources",
-    srcs = [
-        "favicon.ico",
-    ],
-  )
+    native.filegroup(
+        name = name + "_top_sources",
+        srcs = [
+            "favicon.ico",
+        ],
+    )
 
-  genrule2(
-    name = name,
-    srcs = [
-      name + "_app_sources",
-      name + "_css_sources",
-      name + "_theme_sources",
-      name + "_top_sources",
-      "//lib/fonts:robotofonts",
-      "//lib/js:highlightjs_files",
-      # we extract from the zip, but depend on the component for license checking.
-      "@webcomponentsjs//:zipfile",
-      "//lib/js:webcomponentsjs"
-    ],
-    outs = outs,
-    cmd = " && ".join([
-      "mkdir -p $$TMP/polygerrit_ui/{styles/themes,fonts,bower_components/{highlightjs,webcomponentsjs},elements}",
-      "for f in $(locations " + name + "_app_sources); do ext=$${f##*.}; cp -p $$f $$TMP/polygerrit_ui/elements/"  + appName + ".$$ext; done",
-      "cp $(locations //lib/fonts:robotofonts) $$TMP/polygerrit_ui/fonts/",
-      "for f in $(locations " + name + "_top_sources); do cp $$f $$TMP/polygerrit_ui/; done",
-      "for f in $(locations "+ name + "_css_sources); do cp $$f $$TMP/polygerrit_ui/styles; done",
-      "for f in $(locations "+ name + "_theme_sources); do cp $$f $$TMP/polygerrit_ui/styles/themes; done",
-      "for f in $(locations //lib/js:highlightjs_files); do cp $$f $$TMP/polygerrit_ui/bower_components/highlightjs/ ; done",
-      "unzip -qd $$TMP/polygerrit_ui/bower_components $(location @webcomponentsjs//:zipfile) webcomponentsjs/webcomponents-lite.js",
-      "cd $$TMP",
-      "find . -exec touch -t 198001010000 '{}' ';'",
-      "zip -qr $$ROOT/$@ *",
-    ]),
-  )
+    genrule2(
+        name = name,
+        srcs = [
+            name + "_app_sources",
+            name + "_css_sources",
+            name + "_theme_sources",
+            name + "_top_sources",
+            "//lib/fonts:robotofonts",
+            "//lib/js:highlightjs_files",
+            # we extract from the zip, but depend on the component for license checking.
+            "@webcomponentsjs//:zipfile",
+            "//lib/js:webcomponentsjs",
+        ],
+        outs = outs,
+        cmd = " && ".join([
+            "mkdir -p $$TMP/polygerrit_ui/{styles/themes,fonts,bower_components/{highlightjs,webcomponentsjs},elements}",
+            "for f in $(locations " + name + "_app_sources); do ext=$${f##*.}; cp -p $$f $$TMP/polygerrit_ui/elements/" + appName + ".$$ext; done",
+            "cp $(locations //lib/fonts:robotofonts) $$TMP/polygerrit_ui/fonts/",
+            "for f in $(locations " + name + "_top_sources); do cp $$f $$TMP/polygerrit_ui/; done",
+            "for f in $(locations " + name + "_css_sources); do cp $$f $$TMP/polygerrit_ui/styles; done",
+            "for f in $(locations " + name + "_theme_sources); do cp $$f $$TMP/polygerrit_ui/styles/themes; done",
+            "for f in $(locations //lib/js:highlightjs_files); do cp $$f $$TMP/polygerrit_ui/bower_components/highlightjs/ ; done",
+            "unzip -qd $$TMP/polygerrit_ui/bower_components $(location @webcomponentsjs//:zipfile) webcomponentsjs/webcomponents-lite.js",
+            "cd $$TMP",
+            "find . -exec touch -t 198001010000 '{}' ';'",
+            "zip -qr $$ROOT/$@ *",
+        ]),
+    )
diff --git a/polygerrit-ui/externs/BUILD b/polygerrit-ui/externs/BUILD
new file mode 100644
index 0000000..fab3954
--- /dev/null
+++ b/polygerrit-ui/externs/BUILD
@@ -0,0 +1,25 @@
+# Copyright (C) 2018 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(
+    default_visibility = ["//visibility:public"],
+)
+
+load("@io_bazel_rules_closure//closure:defs.bzl", "closure_js_library")
+
+closure_js_library(
+    name = "plugin",
+    srcs = ["plugin.js"],
+    no_closure_library = True,
+)
diff --git a/polygerrit-ui/externs/plugin.js b/polygerrit-ui/externs/plugin.js
new file mode 100644
index 0000000..edace7d
--- /dev/null
+++ b/polygerrit-ui/externs/plugin.js
@@ -0,0 +1,28 @@
+/**
+ * @license
+ * Copyright (C) 2018 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.
+ */
+
+/**
+ * @fileoverview Closure compiler externs for the Gerrit UI plugins.
+ * @externs
+ */
+
+var Gerrit = {};
+
+/**
+ * @param {!Function} callback
+ */
+Gerrit.install = function(callback) {};
diff --git a/tools/bzl/asciidoc.bzl b/tools/bzl/asciidoc.bzl
index 263b465..97d68d6 100644
--- a/tools/bzl/asciidoc.bzl
+++ b/tools/bzl/asciidoc.bzl
@@ -1,40 +1,43 @@
 def documentation_attributes():
-  return [
-    "toc2",
-    'newline="\\n"',
-    'asterisk="&#42;"',
-    'plus="&#43;"',
-    'caret="&#94;"',
-    'startsb="&#91;"',
-    'endsb="&#93;"',
-    'tilde="&#126;"',
-    "last-update-label!",
-    "source-highlighter=prettify",
-    "stylesheet=DEFAULT",
-    "linkcss=true",
-    "prettifydir=.",
-    # Just a placeholder, will be filled in asciidoctor java binary:
-    "revnumber=%s",
-  ]
+    return [
+        "toc2",
+        'newline="\\n"',
+        'asterisk="&#42;"',
+        'plus="&#43;"',
+        'caret="&#94;"',
+        'startsb="&#91;"',
+        'endsb="&#93;"',
+        'tilde="&#126;"',
+        "last-update-label!",
+        "source-highlighter=prettify",
+        "stylesheet=DEFAULT",
+        "linkcss=true",
+        "prettifydir=.",
+        # Just a placeholder, will be filled in asciidoctor java binary:
+        "revnumber=%s",
+    ]
 
 def _replace_macros_impl(ctx):
-  cmd = [
-    ctx.file._exe.path,
-    '--suffix', ctx.attr.suffix,
-    "-s", ctx.file.src.path,
-    "-o", ctx.outputs.out.path,
-  ]
-  if ctx.attr.searchbox:
-    cmd.append('--searchbox')
-  else:
-    cmd.append('--no-searchbox')
-  ctx.actions.run_shell(
-    inputs = [ctx.file._exe, ctx.file.src],
-    outputs = [ctx.outputs.out],
-    command = cmd,
-    use_default_shell_env = True,
-    progress_message = "Replacing macros in %s" % ctx.file.src.short_path,
-  )
+    cmd = [
+        ctx.file._exe.path,
+        "--suffix",
+        ctx.attr.suffix,
+        "-s",
+        ctx.file.src.path,
+        "-o",
+        ctx.outputs.out.path,
+    ]
+    if ctx.attr.searchbox:
+        cmd.append("--searchbox")
+    else:
+        cmd.append("--no-searchbox")
+    ctx.actions.run_shell(
+        inputs = [ctx.file._exe, ctx.file.src],
+        outputs = [ctx.outputs.out],
+        command = cmd,
+        use_default_shell_env = True,
+        progress_message = "Replacing macros in %s" % ctx.file.src.short_path,
+    )
 
 _replace_macros = rule(
     attrs = {
@@ -54,52 +57,55 @@
 )
 
 def _generate_asciidoc_args(ctx):
-  args = []
-  if ctx.attr.backend:
-    args.extend(["-b", ctx.attr.backend])
-  revnumber = False
-  for attribute in ctx.attr.attributes:
-    if attribute.startswith("revnumber="):
-      revnumber = True
-    else:
-      args.extend(["-a", attribute])
-  if revnumber:
-    args.extend([
-      "--revnumber-file", ctx.file.version.path,
-    ])
-  for src in ctx.files.srcs:
-    args.append(src.path)
-  return args
+    args = []
+    if ctx.attr.backend:
+        args.extend(["-b", ctx.attr.backend])
+    revnumber = False
+    for attribute in ctx.attr.attributes:
+        if attribute.startswith("revnumber="):
+            revnumber = True
+        else:
+            args.extend(["-a", attribute])
+    if revnumber:
+        args.extend([
+            "--revnumber-file",
+            ctx.file.version.path,
+        ])
+    for src in ctx.files.srcs:
+        args.append(src.path)
+    return args
 
 def _invoke_replace_macros(name, src, suffix, searchbox):
-  fn = src
-  if fn.startswith(":"):
-    fn = src[1:]
+    fn = src
+    if fn.startswith(":"):
+        fn = src[1:]
 
-  _replace_macros(
-    name = "macros_%s_%s" % (name, fn),
-    src = src,
-    out = fn + suffix,
-    suffix = suffix,
-    searchbox = searchbox,
-  )
+    _replace_macros(
+        name = "macros_%s_%s" % (name, fn),
+        src = src,
+        out = fn + suffix,
+        suffix = suffix,
+        searchbox = searchbox,
+    )
 
-  return ":" + fn + suffix, fn.replace(".txt", ".html")
+    return ":" + fn + suffix, fn.replace(".txt", ".html")
 
 def _asciidoc_impl(ctx):
-  args = [
-    "--bazel",
-    "--in-ext", ".txt" + ctx.attr.suffix,
-    "--out-ext", ".html",
-  ]
-  args.extend(_generate_asciidoc_args(ctx))
-  ctx.actions.run(
-    inputs = ctx.files.srcs + [ctx.executable._exe, ctx.file.version],
-    outputs = ctx.outputs.outs,
-    executable = ctx.executable._exe,
-    arguments = args,
-    progress_message = "Rendering asciidoctor files for %s" % ctx.label.name,
-  )
+    args = [
+        "--bazel",
+        "--in-ext",
+        ".txt" + ctx.attr.suffix,
+        "--out-ext",
+        ".html",
+    ]
+    args.extend(_generate_asciidoc_args(ctx))
+    ctx.actions.run(
+        inputs = ctx.files.srcs + [ctx.executable._exe, ctx.file.version],
+        outputs = ctx.outputs.outs,
+        executable = ctx.executable._exe,
+        arguments = args,
+        progress_message = "Rendering asciidoctor files for %s" % ctx.label.name,
+    )
 
 _asciidoc_attrs = {
     "_exe": attr.label(
@@ -129,82 +135,85 @@
 )
 
 def _genasciidoc_htmlonly(
-    name,
-    srcs = [],
-    attributes = [],
-    backend = None,
-    searchbox = True,
-    **kwargs):
-  SUFFIX = "." + name + "_macros"
-  new_srcs = []
-  outs = ["asciidoctor.css"]
+        name,
+        srcs = [],
+        attributes = [],
+        backend = None,
+        searchbox = True,
+        **kwargs):
+    SUFFIX = "." + name + "_macros"
+    new_srcs = []
+    outs = ["asciidoctor.css"]
 
-  for src in srcs:
-    new_src, html_name = _invoke_replace_macros(name, src, SUFFIX, searchbox)
-    new_srcs.append(new_src)
-    outs.append(html_name)
+    for src in srcs:
+        new_src, html_name = _invoke_replace_macros(name, src, SUFFIX, searchbox)
+        new_srcs.append(new_src)
+        outs.append(html_name)
 
-  _asciidoc(
-    name = name + "_gen",
-    srcs = new_srcs,
-    suffix = SUFFIX,
-    backend = backend,
-    attributes = attributes,
-    outs = outs,
-  )
-
-  native.filegroup(
-    name = name,
-    data = outs,
-    **kwargs
-  )
-
-def genasciidoc(
-    name,
-    srcs = [],
-    attributes = [],
-    backend = None,
-    searchbox = True,
-    resources = True,
-    **kwargs):
-  SUFFIX = "_htmlonly"
-
-  _genasciidoc_htmlonly(
-    name = name + SUFFIX if resources else name,
-    srcs = srcs,
-    attributes = attributes,
-    backend = backend,
-    searchbox = searchbox,
-    **kwargs
-  )
-
-  if resources:
-    htmlonly = ":" + name + SUFFIX
-    native.filegroup(
-      name = name,
-      srcs = [
-        htmlonly,
-        "//Documentation:resources",
-      ],
-      **kwargs
+    _asciidoc(
+        name = name + "_gen",
+        srcs = new_srcs,
+        suffix = SUFFIX,
+        backend = backend,
+        attributes = attributes,
+        outs = outs,
     )
 
+    native.filegroup(
+        name = name,
+        data = outs,
+        **kwargs
+    )
+
+def genasciidoc(
+        name,
+        srcs = [],
+        attributes = [],
+        backend = None,
+        searchbox = True,
+        resources = True,
+        **kwargs):
+    SUFFIX = "_htmlonly"
+
+    _genasciidoc_htmlonly(
+        name = name + SUFFIX if resources else name,
+        srcs = srcs,
+        attributes = attributes,
+        backend = backend,
+        searchbox = searchbox,
+        **kwargs
+    )
+
+    if resources:
+        htmlonly = ":" + name + SUFFIX
+        native.filegroup(
+            name = name,
+            srcs = [
+                htmlonly,
+                "//Documentation:resources",
+            ],
+            **kwargs
+        )
+
 def _asciidoc_html_zip_impl(ctx):
-  args = [
-    "--mktmp",
-    "-z", ctx.outputs.out.path,
-    "--in-ext", ".txt" + ctx.attr.suffix,
-    "--out-ext", ".html",
-  ]
-  args.extend(_generate_asciidoc_args(ctx))
-  ctx.actions.run(
-    inputs = ctx.files.srcs + [ctx.file.version],
-    outputs = [ctx.outputs.out],
-    tools = [ctx.executable._exe],
-    executable = ctx.executable._exe,
-    arguments = args,
-    progress_message = "Rendering asciidoctor files for %s" % ctx.label.name,
-  )
+    args = [
+        "--mktmp",
+        "-z",
+        ctx.outputs.out.path,
+        "--in-ext",
+        ".txt" + ctx.attr.suffix,
+        "--out-ext",
+        ".html",
+    ]
+    args.extend(_generate_asciidoc_args(ctx))
+    ctx.actions.run(
+        inputs = ctx.files.srcs + [ctx.file.version],
+        outputs = [ctx.outputs.out],
+        tools = [ctx.executable._exe],
+        executable = ctx.executable._exe,
+        arguments = args,
+        progress_message = "Rendering asciidoctor files for %s" % ctx.label.name,
+    )
 
 _asciidoc_html_zip = rule(
     attrs = _asciidoc_attrs,
@@ -215,53 +224,54 @@
 )
 
 def _genasciidoc_htmlonly_zip(
-    name,
-    srcs = [],
-    attributes = [],
-    backend = None,
-    searchbox = True,
-    **kwargs):
-  SUFFIX = "." + name + "_expn"
-  new_srcs = []
+        name,
+        srcs = [],
+        attributes = [],
+        backend = None,
+        searchbox = True,
+        **kwargs):
+    SUFFIX = "." + name + "_expn"
+    new_srcs = []
 
-  for src in srcs:
-    new_src, _ = _invoke_replace_macros(name, src, SUFFIX, searchbox)
-    new_srcs.append(new_src)
+    for src in srcs:
+        new_src, _ = _invoke_replace_macros(name, src, SUFFIX, searchbox)
+        new_srcs.append(new_src)
 
-  _asciidoc_html_zip(
-    name = name,
-    srcs = new_srcs,
-    suffix = SUFFIX,
-    backend = backend,
-    attributes = attributes,
-  )
+    _asciidoc_html_zip(
+        name = name,
+        srcs = new_srcs,
+        suffix = SUFFIX,
+        backend = backend,
+        attributes = attributes,
+    )
 
 def _asciidoc_zip_impl(ctx):
-  tmpdir = ctx.outputs.out.path + "_tmpdir"
-  cmd = [
-    "p=$PWD",
-    "rm -rf %s" % tmpdir,
-    "mkdir -p %s/%s/" % (tmpdir, ctx.attr.directory),
-    "unzip -q %s -d %s/%s/" % (ctx.file.src.path, tmpdir, ctx.attr.directory),
-  ]
-  for r in ctx.files.resources:
-    if r.path == r.short_path:
-      cmd.append("tar -cf- %s | tar -C %s -xf-" % (r.short_path, tmpdir))
-    else:
-      parent = r.path[:-len(r.short_path)]
-      cmd.append(
-        "tar -C %s -cf- %s | tar -C %s -xf-" % (parent, r.short_path, tmpdir))
-  cmd.extend([
-    "cd %s" % tmpdir,
-    "zip -qr $p/%s *" % ctx.outputs.out.path,
-  ])
-  ctx.actions.run_shell(
-    inputs = [ctx.file.src] + ctx.files.resources,
-    outputs = [ctx.outputs.out],
-    command = " && ".join(cmd),
-    progress_message =
-        "Generating asciidoctor zip file %s" % ctx.outputs.out.short_path,
-  )
+    tmpdir = ctx.outputs.out.path + "_tmpdir"
+    cmd = [
+        "p=$PWD",
+        "rm -rf %s" % tmpdir,
+        "mkdir -p %s/%s/" % (tmpdir, ctx.attr.directory),
+        "unzip -q %s -d %s/%s/" % (ctx.file.src.path, tmpdir, ctx.attr.directory),
+    ]
+    for r in ctx.files.resources:
+        if r.path == r.short_path:
+            cmd.append("tar -cf- %s | tar -C %s -xf-" % (r.short_path, tmpdir))
+        else:
+            parent = r.path[:-len(r.short_path)]
+            cmd.append(
+                "tar -C %s -cf- %s | tar -C %s -xf-" % (parent, r.short_path, tmpdir),
+            )
+    cmd.extend([
+        "cd %s" % tmpdir,
+        "zip -qr $p/%s *" % ctx.outputs.out.path,
+    ])
+    ctx.actions.run_shell(
+        inputs = [ctx.file.src] + ctx.files.resources,
+        outputs = [ctx.outputs.out],
+        command = " && ".join(cmd),
+        progress_message =
+            "Generating asciidoctor zip file %s" % ctx.outputs.out.short_path,
+    )
 
 _asciidoc_zip = rule(
     attrs = {
@@ -282,30 +292,30 @@
 )
 
 def genasciidoc_zip(
-    name,
-    srcs = [],
-    attributes = [],
-    directory = None,
-    backend = None,
-    searchbox = True,
-    resources = True,
-    **kwargs):
-  SUFFIX = "_htmlonly"
+        name,
+        srcs = [],
+        attributes = [],
+        directory = None,
+        backend = None,
+        searchbox = True,
+        resources = True,
+        **kwargs):
+    SUFFIX = "_htmlonly"
 
-  _genasciidoc_htmlonly_zip(
-    name = name + SUFFIX if resources else name,
-    srcs = srcs,
-    attributes = attributes,
-    backend = backend,
-    searchbox = searchbox,
-    **kwargs
-  )
-
-  if resources:
-    htmlonly = ":" + name + SUFFIX
-    _asciidoc_zip(
-      name = name,
-      src = htmlonly,
-      resources = ["//Documentation:resources"],
-      directory = directory,
+    _genasciidoc_htmlonly_zip(
+        name = name + SUFFIX if resources else name,
+        srcs = srcs,
+        attributes = attributes,
+        backend = backend,
+        searchbox = searchbox,
+        **kwargs
     )
+
+    if resources:
+        htmlonly = ":" + name + SUFFIX
+        _asciidoc_zip(
+            name = name,
+            src = htmlonly,
+            resources = ["//Documentation:resources"],
+            directory = directory,
+        )
diff --git a/tools/bzl/classpath.bzl b/tools/bzl/classpath.bzl
index 9448ed1..afdd907 100644
--- a/tools/bzl/classpath.bzl
+++ b/tools/bzl/classpath.bzl
@@ -1,15 +1,17 @@
 def _classpath_collector(ctx):
     all = depset()
     for d in ctx.attr.deps:
-        if hasattr(d, 'java'):
+        if hasattr(d, "java"):
             all += d.java.transitive_runtime_deps
             all += d.java.compilation_info.runtime_classpath
-        elif hasattr(d, 'files'):
+        elif hasattr(d, "files"):
             all += d.files
 
     as_strs = [c.path for c in all]
-    ctx.file_action(output= ctx.outputs.runtime,
-                    content="\n".join(sorted(as_strs)))
+    ctx.file_action(
+        output = ctx.outputs.runtime,
+        content = "\n".join(sorted(as_strs)),
+    )
 
 classpath_collector = rule(
     attrs = {
diff --git a/tools/bzl/genrule2.bzl b/tools/bzl/genrule2.bzl
index 563a9ef..3113022 100644
--- a/tools/bzl/genrule2.bzl
+++ b/tools/bzl/genrule2.bzl
@@ -17,11 +17,12 @@
 #   expose TMP shell variable
 
 def genrule2(cmd, **kwargs):
-  cmd = ' && '.join([
-    'ROOT=$$PWD',
-    'TMP=$$(mktemp -d || mktemp -d -t bazel-tmp)',
-    '(' + cmd + ')',
-  ])
-  native.genrule(
-    cmd = cmd,
-    **kwargs)
+    cmd = " && ".join([
+        "ROOT=$$PWD",
+        "TMP=$$(mktemp -d || mktemp -d -t bazel-tmp)",
+        "(" + cmd + ")",
+    ])
+    native.genrule(
+        cmd = cmd,
+        **kwargs
+    )
diff --git a/tools/bzl/gwt.bzl b/tools/bzl/gwt.bzl
index 71ba750..2adb7dd 100644
--- a/tools/bzl/gwt.bzl
+++ b/tools/bzl/gwt.bzl
@@ -90,117 +90,122 @@
 </module>
 """
 
-def gwt_module(gwt_xml=None, resources=[], srcs=[], **kwargs):
-  if gwt_xml:
-    resources = resources + [gwt_xml]
+def gwt_module(gwt_xml = None, resources = [], srcs = [], **kwargs):
+    if gwt_xml:
+        resources = resources + [gwt_xml]
 
-  java_library2(
-    srcs = srcs,
-    resources = resources,
-    **kwargs)
+    java_library2(
+        srcs = srcs,
+        resources = resources,
+        **kwargs
+    )
 
 def _gwt_user_agent_module(ctx):
-  """Generate user agent specific GWT module."""
-  if not ctx.attr.user_agent:
-    return None
+    """Generate user agent specific GWT module."""
+    if not ctx.attr.user_agent:
+        return None
 
-  ua = ctx.attr.user_agent
-  impl = ua
-  if ua in ALIASES:
-    impl = ALIASES[ua]
+    ua = ctx.attr.user_agent
+    impl = ua
+    if ua in ALIASES:
+        impl = ALIASES[ua]
 
-  # intermediate artifact: user agent speific GWT xml file
-  gwt_user_agent_xml = ctx.new_file(ctx.label.name + "_gwt.xml")
-  ctx.file_action(output = gwt_user_agent_xml,
-                  content=USER_AGENT_XML % (MODULE, impl))
+    # intermediate artifact: user agent speific GWT xml file
+    gwt_user_agent_xml = ctx.new_file(ctx.label.name + "_gwt.xml")
+    ctx.file_action(
+        output = gwt_user_agent_xml,
+        content = USER_AGENT_XML % (MODULE, impl),
+    )
 
-  # intermediate artifact: user agent specific zip with GWT module
-  gwt_user_agent_zip = ctx.new_file(ctx.label.name + "_gwt.zip")
-  gwt = '%s_%s.gwt.xml' % (MODULE.replace('.', '/'), ua)
-  dir = gwt_user_agent_zip.path + ".dir"
-  cmd = " && ".join([
-    "p=$PWD",
-    "mkdir -p %s" % dir,
-    "cd %s" % dir,
-    "mkdir -p $(dirname %s)" % gwt,
-    "cp $p/%s %s" % (gwt_user_agent_xml.path, gwt),
-    "$p/%s cC $p/%s $(find . | sed 's|^./||')" % (ctx.executable._zip.path, gwt_user_agent_zip.path)
-  ])
-  ctx.actions.run_shell(
-    inputs = [gwt_user_agent_xml] + ctx.files._zip,
-    outputs = [gwt_user_agent_zip],
-    command = cmd,
-    mnemonic = "GenerateUserAgentGWTModule")
+    # intermediate artifact: user agent specific zip with GWT module
+    gwt_user_agent_zip = ctx.new_file(ctx.label.name + "_gwt.zip")
+    gwt = "%s_%s.gwt.xml" % (MODULE.replace(".", "/"), ua)
+    dir = gwt_user_agent_zip.path + ".dir"
+    cmd = " && ".join([
+        "p=$PWD",
+        "mkdir -p %s" % dir,
+        "cd %s" % dir,
+        "mkdir -p $(dirname %s)" % gwt,
+        "cp $p/%s %s" % (gwt_user_agent_xml.path, gwt),
+        "$p/%s cC $p/%s $(find . | sed 's|^./||')" % (ctx.executable._zip.path, gwt_user_agent_zip.path),
+    ])
+    ctx.actions.run_shell(
+        inputs = [gwt_user_agent_xml] + ctx.files._zip,
+        outputs = [gwt_user_agent_zip],
+        command = cmd,
+        mnemonic = "GenerateUserAgentGWTModule",
+    )
 
-  return struct(
-    zip=gwt_user_agent_zip,
-    module=MODULE + '_' + ua
-  )
+    return struct(
+        zip = gwt_user_agent_zip,
+        module = MODULE + "_" + ua,
+    )
 
 def _gwt_binary_impl(ctx):
-  module = ctx.attr.module[0]
-  output_zip = ctx.outputs.output
-  output_dir = output_zip.path + '.gwt_output'
-  deploy_dir = output_zip.path + '.gwt_deploy'
+    module = ctx.attr.module[0]
+    output_zip = ctx.outputs.output
+    output_dir = output_zip.path + ".gwt_output"
+    deploy_dir = output_zip.path + ".gwt_deploy"
 
-  deps = _get_transitive_closure(ctx)
+    deps = _get_transitive_closure(ctx)
 
-  paths = []
-  for dep in deps:
-    paths.append(dep.path)
+    paths = []
+    for dep in deps:
+        paths.append(dep.path)
 
-  gwt_user_agent_modules = []
-  ua = _gwt_user_agent_module(ctx)
-  if ua:
-    paths.append(ua.zip.path)
-    gwt_user_agent_modules.append(ua.zip)
-    module = ua.module
+    gwt_user_agent_modules = []
+    ua = _gwt_user_agent_module(ctx)
+    if ua:
+        paths.append(ua.zip.path)
+        gwt_user_agent_modules.append(ua.zip)
+        module = ua.module
 
-  cmd = "%s %s -Dgwt.normalizeTimestamps=true -cp %s %s -war %s -deploy %s " % (
-    ctx.attr._jdk[java_common.JavaRuntimeInfo].java_executable_exec_path,
-    " ".join(ctx.attr.jvm_args),
-    ":".join(paths),
-    GWT_COMPILER,
-    output_dir,
-    deploy_dir,
-  )
-  # TODO(davido): clean up command concatenation
-  cmd += " ".join([
-    "-style %s" % ctx.attr.style,
-    "-optimize %s" % ctx.attr.optimize,
-    "-strict",
-    " ".join(ctx.attr.compiler_args),
-    module + "\n",
-    "rm -rf %s/gwt-unitCache\n" % output_dir,
-    "root=`pwd`\n",
-    "cd %s; $root/%s Cc ../%s $(find .)\n" % (
-      output_dir,
-      ctx.executable._zip.path,
-      output_zip.basename,
+    cmd = "%s %s -Dgwt.normalizeTimestamps=true -cp %s %s -war %s -deploy %s " % (
+        ctx.attr._jdk[java_common.JavaRuntimeInfo].java_executable_exec_path,
+        " ".join(ctx.attr.jvm_args),
+        ":".join(paths),
+        GWT_COMPILER,
+        output_dir,
+        deploy_dir,
     )
-  ])
 
-  ctx.actions.run_shell(
-    inputs = list(deps) + gwt_user_agent_modules,
-    outputs = [output_zip],
-    tools = ctx.files._jdk + ctx.files._zip,
-    mnemonic = "GwtBinary",
-    progress_message = "GWT compiling " + output_zip.short_path,
-    command = "set -e\n" + cmd,
-  )
+    # TODO(davido): clean up command concatenation
+    cmd += " ".join([
+        "-style %s" % ctx.attr.style,
+        "-optimize %s" % ctx.attr.optimize,
+        "-strict",
+        " ".join(ctx.attr.compiler_args),
+        module + "\n",
+        "rm -rf %s/gwt-unitCache\n" % output_dir,
+        "root=`pwd`\n",
+        "cd %s; $root/%s Cc ../%s $(find .)\n" % (
+            output_dir,
+            ctx.executable._zip.path,
+            output_zip.basename,
+        ),
+    ])
+
+    ctx.actions.run_shell(
+        inputs = list(deps) + gwt_user_agent_modules,
+        outputs = [output_zip],
+        tools = ctx.files._jdk + ctx.files._zip,
+        mnemonic = "GwtBinary",
+        progress_message = "GWT compiling " + output_zip.short_path,
+        command = "set -e\n" + cmd,
+    )
 
 def _get_transitive_closure(ctx):
-  deps = depset()
-  for dep in ctx.attr.module_deps:
-    deps += dep.java.transitive_runtime_deps
-    deps += dep.java.transitive_source_jars
-  for dep in ctx.attr.deps:
-    if hasattr(dep, 'java'):
-      deps += dep.java.transitive_runtime_deps
-    elif hasattr(dep, 'files'):
-      deps += dep.files
+    deps = depset()
+    for dep in ctx.attr.module_deps:
+        deps += dep.java.transitive_runtime_deps
+        deps += dep.java.transitive_source_jars
+    for dep in ctx.attr.deps:
+        if hasattr(dep, "java"):
+            deps += dep.java.transitive_runtime_deps
+        elif hasattr(dep, "files"):
+            deps += dep.files
 
-  return deps
+    return deps
 
 gwt_binary = rule(
     attrs = {
@@ -230,77 +235,78 @@
 )
 
 def gwt_genrule(suffix = ""):
-  dbg = 'ui_dbg' + suffix
-  opt = 'ui_opt' + suffix
-  module_dep = ':ui_module' + suffix
-  args = GWT_COMPILER_ARGS_RELEASE_MODE if suffix == "_r" else GWT_COMPILER_ARGS
+    dbg = "ui_dbg" + suffix
+    opt = "ui_opt" + suffix
+    module_dep = ":ui_module" + suffix
+    args = GWT_COMPILER_ARGS_RELEASE_MODE if suffix == "_r" else GWT_COMPILER_ARGS
 
-  genrule2(
-    name = 'ui_optdbg' + suffix,
-    srcs = [
-      ':' + dbg,
-      ':' + opt,
-     ],
-    cmd = 'cd $$TMP;' +
-      'unzip -q $$ROOT/$(location :%s);' % dbg +
-      'mv' +
-      ' gerrit_ui/gerrit_ui.nocache.js' +
-      ' gerrit_ui/dbg_gerrit_ui.nocache.js;' +
-      'unzip -qo $$ROOT/$(location :%s);' % opt +
-      'mkdir -p $$(dirname $@);' +
-      'zip -qrD $$ROOT/$@ .',
-    outs = ['ui_optdbg' + suffix + '.zip'],
-    visibility = ['//visibility:public'],
-   )
+    genrule2(
+        name = "ui_optdbg" + suffix,
+        srcs = [
+            ":" + dbg,
+            ":" + opt,
+        ],
+        cmd = "cd $$TMP;" +
+              "unzip -q $$ROOT/$(location :%s);" % dbg +
+              "mv" +
+              " gerrit_ui/gerrit_ui.nocache.js" +
+              " gerrit_ui/dbg_gerrit_ui.nocache.js;" +
+              "unzip -qo $$ROOT/$(location :%s);" % opt +
+              "mkdir -p $$(dirname $@);" +
+              "zip -qrD $$ROOT/$@ .",
+        outs = ["ui_optdbg" + suffix + ".zip"],
+        visibility = ["//visibility:public"],
+    )
 
-  gwt_binary(
-    name = opt,
-    module = [MODULE],
-    module_deps = [module_dep],
-    deps = DEPS,
-    compiler_args = args,
-    jvm_args = GWT_JVM_ARGS,
-  )
+    gwt_binary(
+        name = opt,
+        module = [MODULE],
+        module_deps = [module_dep],
+        deps = DEPS,
+        compiler_args = args,
+        jvm_args = GWT_JVM_ARGS,
+    )
 
-  gwt_binary(
-    name = dbg,
-    style = 'PRETTY',
-    optimize = "0",
-    module_deps = [module_dep],
-    deps = DEPS,
-    compiler_args = GWT_COMPILER_ARGS,
-    jvm_args = GWT_JVM_ARGS,
-  )
+    gwt_binary(
+        name = dbg,
+        style = "PRETTY",
+        optimize = "0",
+        module_deps = [module_dep],
+        deps = DEPS,
+        compiler_args = GWT_COMPILER_ARGS,
+        jvm_args = GWT_JVM_ARGS,
+    )
 
 def gen_ui_module(name, suffix = ""):
-  gwt_module(
-    name = name + suffix,
-    srcs = native.glob(['src/main/java/**/*.java']),
-    gwt_xml = 'src/main/java/%s.gwt.xml' % MODULE.replace('.', '/'),
-    resources = native.glob(
-        ['src/main/java/**/*'],
-        exclude = ['src/main/java/**/*.java'] +
-        ['src/main/java/%s.gwt.xml' % MODULE.replace('.', '/')]),
-    deps = [
-      '//gerrit-gwtui-common:diffy_logo',
-      '//gerrit-gwtui-common:client',
-      '//java/com/google/gwtexpui/css',
-      '//lib/codemirror:codemirror' + suffix,
-      '//lib/gwt:user',
-    ],
-    visibility = ['//visibility:public'],
-  )
+    gwt_module(
+        name = name + suffix,
+        srcs = native.glob(["src/main/java/**/*.java"]),
+        gwt_xml = "src/main/java/%s.gwt.xml" % MODULE.replace(".", "/"),
+        resources = native.glob(
+            ["src/main/java/**/*"],
+            exclude = ["src/main/java/**/*.java"] +
+                      ["src/main/java/%s.gwt.xml" % MODULE.replace(".", "/")],
+        ),
+        deps = [
+            "//gerrit-gwtui-common:diffy_logo",
+            "//gerrit-gwtui-common:client",
+            "//java/com/google/gwtexpui/css",
+            "//lib/codemirror:codemirror" + suffix,
+            "//lib/gwt:user",
+        ],
+        visibility = ["//visibility:public"],
+    )
 
 def gwt_user_agent_permutations():
-  for ua in BROWSERS:
-    gwt_binary(
-      name = "ui_%s" % ua,
-      user_agent = ua,
-      style = 'PRETTY',
-      optimize = "0",
-      module = [MODULE],
-      module_deps = [':ui_module'],
-      deps = DEPS,
-      compiler_args = GWT_COMPILER_ARGS,
-      jvm_args = GWT_JVM_ARGS,
-    )
+    for ua in BROWSERS:
+        gwt_binary(
+            name = "ui_%s" % ua,
+            user_agent = ua,
+            style = "PRETTY",
+            optimize = "0",
+            module = [MODULE],
+            module_deps = [":ui_module"],
+            deps = DEPS,
+            compiler_args = GWT_COMPILER_ARGS,
+            jvm_args = GWT_JVM_ARGS,
+        )
diff --git a/tools/bzl/java.bzl b/tools/bzl/java.bzl
index 5fca724..7c41fbe 100644
--- a/tools/bzl/java.bzl
+++ b/tools/bzl/java.bzl
@@ -15,11 +15,12 @@
 # Syntactic sugar for native java_library() rule:
 #   accept exported_deps attributes
 
-def java_library2(deps=[], exported_deps=[], exports=[], **kwargs):
-  if exported_deps:
-    deps = deps + exported_deps
-    exports = exports + exported_deps
-  native.java_library(
-    deps = deps,
-    exports = exports,
-    **kwargs)
+def java_library2(deps = [], exported_deps = [], exports = [], **kwargs):
+    if exported_deps:
+        deps = deps + exported_deps
+        exports = exports + exported_deps
+    native.java_library(
+        deps = deps,
+        exports = exports,
+        **kwargs
+    )
diff --git a/tools/bzl/javadoc.bzl b/tools/bzl/javadoc.bzl
index f49c881..34ae9d2 100644
--- a/tools/bzl/javadoc.bzl
+++ b/tools/bzl/javadoc.bzl
@@ -15,49 +15,51 @@
 # Javadoc rule.
 
 def _impl(ctx):
-  zip_output = ctx.outputs.zip
+    zip_output = ctx.outputs.zip
 
-  transitive_jar_set = depset()
-  source_jars = depset()
-  for l in ctx.attr.libs:
-    source_jars += l.java.source_jars
-    transitive_jar_set += l.java.transitive_deps
+    transitive_jar_set = depset()
+    source_jars = depset()
+    for l in ctx.attr.libs:
+        source_jars += l.java.source_jars
+        transitive_jar_set += l.java.transitive_deps
 
-  transitive_jar_paths = [j.path for j in transitive_jar_set]
-  dir = ctx.outputs.zip.path + ".dir"
-  source = ctx.outputs.zip.path + ".source"
-  external_docs = ["http://docs.oracle.com/javase/8/docs/api"] + ctx.attr.external_docs
-  cmd = [
-      "TZ=UTC",
-      "export TZ",
-      "rm -rf %s" % source,
-      "mkdir %s" % source,
-      " && ".join(["unzip -qud %s %s" % (source, j.path) for j in source_jars]),
-      "rm -rf %s" % dir,
-      "mkdir %s" % dir,
-      " ".join([
-        ctx.file._javadoc.path,
-        "-Xdoclint:-missing",
-        "-protected",
-        "-encoding UTF-8",
-        "-charset UTF-8",
-        "-notimestamp",
-        "-quiet",
-        "-windowtitle '%s'" % ctx.attr.title,
-        " ".join(['-link %s' % url for url in external_docs]),
-        "-sourcepath %s" % source,
-        "-subpackages ",
-        ":".join(ctx.attr.pkgs),
-        " -classpath ",
-        ":".join(transitive_jar_paths),
-        "-d %s" % dir]),
-    "find %s -exec touch -t 198001010000 '{}' ';'" % dir,
-    "(cd %s && zip -Xqr ../%s *)" % (dir, ctx.outputs.zip.basename),
-  ]
-  ctx.actions.run_shell(
-      inputs = list(transitive_jar_set) + list(source_jars) + ctx.files._jdk,
-      outputs = [zip_output],
-      command = " && ".join(cmd))
+    transitive_jar_paths = [j.path for j in transitive_jar_set]
+    dir = ctx.outputs.zip.path + ".dir"
+    source = ctx.outputs.zip.path + ".source"
+    external_docs = ["http://docs.oracle.com/javase/8/docs/api"] + ctx.attr.external_docs
+    cmd = [
+        "TZ=UTC",
+        "export TZ",
+        "rm -rf %s" % source,
+        "mkdir %s" % source,
+        " && ".join(["unzip -qud %s %s" % (source, j.path) for j in source_jars]),
+        "rm -rf %s" % dir,
+        "mkdir %s" % dir,
+        " ".join([
+            ctx.file._javadoc.path,
+            "-Xdoclint:-missing",
+            "-protected",
+            "-encoding UTF-8",
+            "-charset UTF-8",
+            "-notimestamp",
+            "-quiet",
+            "-windowtitle '%s'" % ctx.attr.title,
+            " ".join(["-link %s" % url for url in external_docs]),
+            "-sourcepath %s" % source,
+            "-subpackages ",
+            ":".join(ctx.attr.pkgs),
+            " -classpath ",
+            ":".join(transitive_jar_paths),
+            "-d %s" % dir,
+        ]),
+        "find %s -exec touch -t 198001010000 '{}' ';'" % dir,
+        "(cd %s && zip -Xqr ../%s *)" % (dir, ctx.outputs.zip.basename),
+    ]
+    ctx.actions.run_shell(
+        inputs = list(transitive_jar_set) + list(source_jars) + ctx.files._jdk,
+        outputs = [zip_output],
+        command = " && ".join(cmd),
+    )
 
 java_doc = rule(
     attrs = {
diff --git a/tools/bzl/js.bzl b/tools/bzl/js.bzl
index 2796f64..03ee87c 100644
--- a/tools/bzl/js.bzl
+++ b/tools/bzl/js.bzl
@@ -2,37 +2,38 @@
 
 GERRIT = "GERRIT:"
 
-load("//lib/js:npm.bzl", "NPM_VERSIONS", "NPM_SHA1S")
+load("//lib/js:npm.bzl", "NPM_SHA1S", "NPM_VERSIONS")
+load("@io_bazel_rules_closure//closure:defs.bzl", "closure_js_binary", "closure_js_library")
 
 def _npm_tarball(name):
-  return "%s@%s.npm_binary.tgz" % (name, NPM_VERSIONS[name])
+    return "%s@%s.npm_binary.tgz" % (name, NPM_VERSIONS[name])
 
 def _npm_binary_impl(ctx):
-  """rule to download a NPM archive."""
-  name = ctx.name
-  version= NPM_VERSIONS[name]
-  sha1 = NPM_SHA1S[name]
+    """rule to download a NPM archive."""
+    name = ctx.name
+    version = NPM_VERSIONS[name]
+    sha1 = NPM_SHA1S[name]
 
-  dir = '%s-%s' % (name, version)
-  filename = '%s.tgz' % dir
-  base =  '%s@%s.npm_binary.tgz' % (name, version)
-  dest = ctx.path(base)
-  repository = ctx.attr.repository
-  if repository == GERRIT:
-    url = 'http://gerrit-maven.storage.googleapis.com/npm-packages/%s' % filename
-  elif repository == NPMJS:
-    url = 'http://registry.npmjs.org/%s/-/%s' % (name, filename)
-  else:
-    fail('repository %s not in {%s,%s}' % (repository, GERRIT, NPMJS))
+    dir = "%s-%s" % (name, version)
+    filename = "%s.tgz" % dir
+    base = "%s@%s.npm_binary.tgz" % (name, version)
+    dest = ctx.path(base)
+    repository = ctx.attr.repository
+    if repository == GERRIT:
+        url = "http://gerrit-maven.storage.googleapis.com/npm-packages/%s" % filename
+    elif repository == NPMJS:
+        url = "http://registry.npmjs.org/%s/-/%s" % (name, filename)
+    else:
+        fail("repository %s not in {%s,%s}" % (repository, GERRIT, NPMJS))
 
-  python = ctx.which("python")
-  script = ctx.path(ctx.attr._download_script)
+    python = ctx.which("python")
+    script = ctx.path(ctx.attr._download_script)
 
-  args = [python, script, "-o", dest, "-u", url, "-v", sha1]
-  out = ctx.execute(args)
-  if out.return_code:
-    fail("failed %s: %s" % (args, out.stderr))
-  ctx.file("BUILD", "package(default_visibility=['//visibility:public'])\nfilegroup(name='tarball', srcs=['%s'])" % base, False)
+    args = [python, script, "-o", dest, "-u", url, "-v", sha1]
+    out = ctx.execute(args)
+    if out.return_code:
+        fail("failed %s: %s" % (args, out.stderr))
+    ctx.file("BUILD", "package(default_visibility=['//visibility:public'])\nfilegroup(name='tarball', srcs=['%s'])" % base, False)
 
 npm_binary = repository_rule(
     attrs = {
@@ -46,64 +47,75 @@
 
 # for use in repo rules.
 def _run_npm_binary_str(ctx, tarball, args):
-  python_bin = ctx.which("python")
-  return " ".join([
-    python_bin,
-    ctx.path(ctx.attr._run_npm),
-    ctx.path(tarball)] + args)
+    python_bin = ctx.which("python")
+    return " ".join([
+        python_bin,
+        ctx.path(ctx.attr._run_npm),
+        ctx.path(tarball),
+    ] + args)
 
 def _bower_archive(ctx):
-  """Download a bower package."""
-  download_name = '%s__download_bower.zip' % ctx.name
-  renamed_name = '%s__renamed.zip' % ctx.name
-  version_name = '%s__version.json' % ctx.name
+    """Download a bower package."""
+    download_name = "%s__download_bower.zip" % ctx.name
+    renamed_name = "%s__renamed.zip" % ctx.name
+    version_name = "%s__version.json" % ctx.name
 
-  cmd = [
-      ctx.which("python"),
-      ctx.path(ctx.attr._download_bower),
-      '-b', '%s' % _run_npm_binary_str(ctx, ctx.attr._bower_archive, []),
-      '-n', ctx.name,
-      '-p', ctx.attr.package,
-      '-v', ctx.attr.version,
-      '-s', ctx.attr.sha1,
-      '-o', download_name,
+    cmd = [
+        ctx.which("python"),
+        ctx.path(ctx.attr._download_bower),
+        "-b",
+        "%s" % _run_npm_binary_str(ctx, ctx.attr._bower_archive, []),
+        "-n",
+        ctx.name,
+        "-p",
+        ctx.attr.package,
+        "-v",
+        ctx.attr.version,
+        "-s",
+        ctx.attr.sha1,
+        "-o",
+        download_name,
     ]
 
-  out = ctx.execute(cmd)
-  if out.return_code:
-    fail("failed %s: %s" % (" ".join(cmd), out.stderr))
+    out = ctx.execute(cmd)
+    if out.return_code:
+        fail("failed %s: %s" % (" ".join(cmd), out.stderr))
 
-  _bash(ctx, " && " .join([
-    "TMP=$(mktemp -d || mktemp -d -t bazel-tmp)",
-    "TZ=UTC",
-    "export UTC",
-    "cd $TMP",
-    "mkdir bower_components",
-    "cd bower_components",
-    "unzip %s" % ctx.path(download_name),
-    "cd ..",
-    "find . -exec touch -t 198001010000 '{}' ';'",
-    "zip -Xr %s bower_components" % renamed_name,
-    "cd ..",
-    "rm -rf ${TMP}",
-  ]))
+    _bash(ctx, " && ".join([
+        "TMP=$(mktemp -d || mktemp -d -t bazel-tmp)",
+        "TZ=UTC",
+        "export UTC",
+        "cd $TMP",
+        "mkdir bower_components",
+        "cd bower_components",
+        "unzip %s" % ctx.path(download_name),
+        "cd ..",
+        "find . -exec touch -t 198001010000 '{}' ';'",
+        "zip -Xr %s bower_components" % renamed_name,
+        "cd ..",
+        "rm -rf ${TMP}",
+    ]))
 
-  dep_version = ctx.attr.semver if ctx.attr.semver else ctx.attr.version
-  ctx.file(version_name,
-           '"%s":"%s#%s"' % (ctx.name, ctx.attr.package, dep_version))
-  ctx.file(
-    "BUILD",
-    "\n".join([
-      "package(default_visibility=['//visibility:public'])",
-      "filegroup(name = 'zipfile', srcs = ['%s'], )" % download_name,
-      "filegroup(name = 'version_json', srcs = ['%s'], visibility=['//visibility:public'])" % version_name,
-    ]), False)
+    dep_version = ctx.attr.semver if ctx.attr.semver else ctx.attr.version
+    ctx.file(
+        version_name,
+        '"%s":"%s#%s"' % (ctx.name, ctx.attr.package, dep_version),
+    )
+    ctx.file(
+        "BUILD",
+        "\n".join([
+            "package(default_visibility=['//visibility:public'])",
+            "filegroup(name = 'zipfile', srcs = ['%s'], )" % download_name,
+            "filegroup(name = 'version_json', srcs = ['%s'], visibility=['//visibility:public'])" % version_name,
+        ]),
+        False,
+    )
 
 def _bash(ctx, cmd):
-  cmd_list = ["bash", "-c", cmd]
-  out = ctx.execute(cmd_list)
-  if out.return_code:
-    fail("failed %s: %s" % (" ".join(cmd_list), out.stderr))
+    cmd_list = ["bash", "-c", cmd]
+    out = ctx.execute(cmd_list)
+    if out.return_code:
+        fail("failed %s: %s" % (" ".join(cmd_list), out.stderr))
 
 bower_archive = repository_rule(
     _bower_archive,
@@ -119,26 +131,26 @@
 )
 
 def _bower_component_impl(ctx):
-  transitive_zipfiles = depset([ctx.file.zipfile])
-  for d in ctx.attr.deps:
-    transitive_zipfiles += d.transitive_zipfiles
+    transitive_zipfiles = depset([ctx.file.zipfile])
+    for d in ctx.attr.deps:
+        transitive_zipfiles += d.transitive_zipfiles
 
-  transitive_licenses = depset()
-  if ctx.file.license:
-    transitive_licenses += depset([ctx.file.license])
+    transitive_licenses = depset()
+    if ctx.file.license:
+        transitive_licenses += depset([ctx.file.license])
 
-  for d in ctx.attr.deps:
-    transitive_licenses += d.transitive_licenses
+    for d in ctx.attr.deps:
+        transitive_licenses += d.transitive_licenses
 
-  transitive_versions = depset(ctx.files.version_json)
-  for d in ctx.attr.deps:
-    transitive_versions += d.transitive_versions
+    transitive_versions = depset(ctx.files.version_json)
+    for d in ctx.attr.deps:
+        transitive_versions += d.transitive_versions
 
-  return struct(
-    transitive_zipfiles=transitive_zipfiles,
-    transitive_versions=transitive_versions,
-    transitive_licenses=transitive_licenses,
-  )
+    return struct(
+        transitive_licenses = transitive_licenses,
+        transitive_versions = transitive_versions,
+        transitive_zipfiles = transitive_zipfiles,
+    )
 
 _common_attrs = {
     "deps": attr.label_list(providers = [
@@ -149,35 +161,37 @@
 }
 
 def _js_component(ctx):
-  dir = ctx.outputs.zip.path + ".dir"
-  name = ctx.outputs.zip.basename
-  if name.endswith(".zip"):
-    name = name[:-4]
-  dest = "%s/%s" % (dir, name)
-  cmd = " && ".join([
-    "TZ=UTC",
-    "export TZ",
-    "mkdir -p %s" % dest,
-    "cp %s %s/" % (' '.join([s.path for s in ctx.files.srcs]), dest),
-    "cd %s" % dir,
-    "find . -exec touch -t 198001010000 '{}' ';'",
-    "zip -Xqr ../%s *" %  ctx.outputs.zip.basename
-  ])
+    dir = ctx.outputs.zip.path + ".dir"
+    name = ctx.outputs.zip.basename
+    if name.endswith(".zip"):
+        name = name[:-4]
+    dest = "%s/%s" % (dir, name)
+    cmd = " && ".join([
+        "TZ=UTC",
+        "export TZ",
+        "mkdir -p %s" % dest,
+        "cp %s %s/" % (" ".join([s.path for s in ctx.files.srcs]), dest),
+        "cd %s" % dir,
+        "find . -exec touch -t 198001010000 '{}' ';'",
+        "zip -Xqr ../%s *" % ctx.outputs.zip.basename,
+    ])
 
-  ctx.actions.run_shell(
-    inputs = ctx.files.srcs,
-    outputs = [ctx.outputs.zip],
-    command = cmd,
-    mnemonic = "GenBowerZip")
+    ctx.actions.run_shell(
+        inputs = ctx.files.srcs,
+        outputs = [ctx.outputs.zip],
+        command = cmd,
+        mnemonic = "GenBowerZip",
+    )
 
-  licenses = depset()
-  if ctx.file.license:
-    licenses += depset([ctx.file.license])
+    licenses = depset()
+    if ctx.file.license:
+        licenses += depset([ctx.file.license])
 
-  return struct(
-    transitive_zipfiles=list([ctx.outputs.zip]),
-    transitive_versions=depset(),
-    transitive_licenses=licenses)
+    return struct(
+        transitive_licenses = licenses,
+        transitive_versions = depset(),
+        transitive_zipfiles = list([ctx.outputs.zip]),
+    )
 
 js_component = rule(
     _js_component,
@@ -203,61 +217,65 @@
 )
 
 # TODO(hanwen): make license mandatory.
-def bower_component(name, license=None, **kwargs):
-  prefix = "//lib:LICENSE-"
-  if license and not license.startswith(prefix):
-    license = prefix + license
-  _bower_component(
-    name=name,
-    license=license,
-    zipfile="@%s//:zipfile"% name,
-    version_json="@%s//:version_json" % name,
-    **kwargs)
+def bower_component(name, license = None, **kwargs):
+    prefix = "//lib:LICENSE-"
+    if license and not license.startswith(prefix):
+        license = prefix + license
+    _bower_component(
+        name = name,
+        license = license,
+        zipfile = "@%s//:zipfile" % name,
+        version_json = "@%s//:version_json" % name,
+        **kwargs
+    )
 
 def _bower_component_bundle_impl(ctx):
-  """A bunch of bower components zipped up."""
-  zips = depset()
-  for d in ctx.attr.deps:
-    zips += d.transitive_zipfiles
+    """A bunch of bower components zipped up."""
+    zips = depset()
+    for d in ctx.attr.deps:
+        zips += d.transitive_zipfiles
 
-  versions = depset()
-  for d in ctx.attr.deps:
-    versions += d.transitive_versions
+    versions = depset()
+    for d in ctx.attr.deps:
+        versions += d.transitive_versions
 
-  licenses = depset()
-  for d in ctx.attr.deps:
-    licenses += d.transitive_versions
+    licenses = depset()
+    for d in ctx.attr.deps:
+        licenses += d.transitive_versions
 
-  out_zip = ctx.outputs.zip
-  out_versions = ctx.outputs.version_json
+    out_zip = ctx.outputs.zip
+    out_versions = ctx.outputs.version_json
 
-  ctx.actions.run_shell(
-    inputs=list(zips),
-    outputs=[out_zip],
-    command=" && ".join([
-      "p=$PWD",
-      "TZ=UTC",
-      "export TZ",
-      "rm -rf %s.dir" % out_zip.path,
-      "mkdir -p %s.dir/bower_components" % out_zip.path,
-      "cd %s.dir/bower_components" % out_zip.path,
-      "for z in %s; do unzip -q $p/$z ; done" % " ".join(sorted([z.path for z in zips])),
-      "cd ..",
-      "find . -exec touch -t 198001010000 '{}' ';'",
-      "zip -Xqr $p/%s bower_components/*" % out_zip.path,
-    ]),
-    mnemonic="BowerCombine")
+    ctx.actions.run_shell(
+        inputs = list(zips),
+        outputs = [out_zip],
+        command = " && ".join([
+            "p=$PWD",
+            "TZ=UTC",
+            "export TZ",
+            "rm -rf %s.dir" % out_zip.path,
+            "mkdir -p %s.dir/bower_components" % out_zip.path,
+            "cd %s.dir/bower_components" % out_zip.path,
+            "for z in %s; do unzip -q $p/$z ; done" % " ".join(sorted([z.path for z in zips])),
+            "cd ..",
+            "find . -exec touch -t 198001010000 '{}' ';'",
+            "zip -Xqr $p/%s bower_components/*" % out_zip.path,
+        ]),
+        mnemonic = "BowerCombine",
+    )
 
-  ctx.actions.run_shell(
-    inputs=list(versions),
-    outputs=[out_versions],
-    mnemonic="BowerVersions",
-    command="(echo '{' ; for j in  %s ; do cat $j; echo ',' ; done ; echo \\\"\\\":\\\"\\\"; echo '}') > %s" % (" ".join([v.path for v in versions]), out_versions.path))
+    ctx.actions.run_shell(
+        inputs = list(versions),
+        outputs = [out_versions],
+        mnemonic = "BowerVersions",
+        command = "(echo '{' ; for j in  %s ; do cat $j; echo ',' ; done ; echo \\\"\\\":\\\"\\\"; echo '}') > %s" % (" ".join([v.path for v in versions]), out_versions.path),
+    )
 
-  return struct(
-    transitive_zipfiles=zips,
-    transitive_versions=versions,
-    transitive_licenses=licenses)
+    return struct(
+        transitive_licenses = licenses,
+        transitive_versions = versions,
+        transitive_zipfiles = zips,
+    )
 
 bower_component_bundle = rule(
     _bower_component_bundle_impl,
@@ -279,80 +297,99 @@
 """
 
 def _vulcanize_impl(ctx):
-  # intermediate artifact if split is wanted.
-  if ctx.attr.split:
-    vulcanized = ctx.new_file(
-      ctx.configuration.genfiles_dir, ctx.outputs.html, ".vulcanized.html")
-  else:
-    vulcanized = ctx.outputs.html
-  destdir = ctx.outputs.html.path + ".dir"
-  zips =  [z for d in ctx.attr.deps for z in d.transitive_zipfiles ]
+    # intermediate artifact if split is wanted.
+    if ctx.attr.split:
+        vulcanized = ctx.new_file(
+            ctx.configuration.genfiles_dir,
+            ctx.outputs.html,
+            ".vulcanized.html",
+        )
+    else:
+        vulcanized = ctx.outputs.html
+    destdir = ctx.outputs.html.path + ".dir"
+    zips = [z for d in ctx.attr.deps for z in d.transitive_zipfiles]
 
-  hermetic_npm_binary = " ".join([
-    'python',
-    "$p/" + ctx.file._run_npm.path,
-    "$p/" + ctx.file._vulcanize_archive.path,
-    '--inline-scripts',
-    '--inline-css',
-    '--strip-comments',
-    '--out-html', "$p/" + vulcanized.path,
-    ctx.file.app.path
-  ])
+    hermetic_npm_binary = " ".join([
+        "python",
+        "$p/" + ctx.file._run_npm.path,
+        "$p/" + ctx.file._vulcanize_archive.path,
+        "--inline-scripts",
+        "--inline-css",
+        "--strip-comments",
+        "--out-html",
+        "$p/" + vulcanized.path,
+        ctx.file.app.path,
+    ])
 
-  pkg_dir = ctx.attr.pkg.lstrip("/")
-  cmd = " && ".join([
-    # unpack dependencies.
-    "export PATH",
-    "p=$PWD",
-    "rm -rf %s" % destdir,
-    "mkdir -p %s/%s/bower_components" % (destdir, pkg_dir),
-    "for z in %s; do unzip -qd %s/%s/bower_components/ $z; done" % (
-      ' '.join([z.path for z in zips]), destdir, pkg_dir),
-    "tar -cf - %s | tar -C %s -xf -" % (" ".join([s.path for s in ctx.files.srcs]), destdir),
-    "cd %s" % destdir,
-    hermetic_npm_binary,
-  ])
+    pkg_dir = ctx.attr.pkg.lstrip("/")
+    cmd = " && ".join([
+        # unpack dependencies.
+        "export PATH",
+        "p=$PWD",
+        "rm -rf %s" % destdir,
+        "mkdir -p %s/%s/bower_components" % (destdir, pkg_dir),
+        "for z in %s; do unzip -qd %s/%s/bower_components/ $z; done" % (
+            " ".join([z.path for z in zips]),
+            destdir,
+            pkg_dir,
+        ),
+        "tar -cf - %s | tar -C %s -xf -" % (" ".join([s.path for s in ctx.files.srcs]), destdir),
+        "cd %s" % destdir,
+        hermetic_npm_binary,
+    ])
 
-  # Node/NPM is not (yet) hermeticized, so we have to get the binary
-  # from the environment, and it may be under $HOME, so we can't run
-  # in the sandbox.
-  node_tweaks = dict(
-    use_default_shell_env = True,
-    execution_requirements = {"local": "1"},
-  )
-  ctx.actions.run_shell(
-    mnemonic = "Vulcanize",
-    inputs = [ctx.file._run_npm, ctx.file.app,
-              ctx.file._vulcanize_archive
-    ] + list(zips) + ctx.files.srcs,
-    outputs = [vulcanized],
-    command = cmd,
-    **node_tweaks)
-
-  if ctx.attr.split:
-    hermetic_npm_command = "export PATH && " + " ".join([
-      'python',
-      ctx.file._run_npm.path,
-      ctx.file._crisper_archive.path,
-      "--always-write-script",
-      "--source", vulcanized.path,
-      "--html", ctx.outputs.html.path,
-      "--js", ctx.outputs.js.path])
-
+    # Node/NPM is not (yet) hermeticized, so we have to get the binary
+    # from the environment, and it may be under $HOME, so we can't run
+    # in the sandbox.
+    node_tweaks = dict(
+        execution_requirements = {"local": "1"},
+        use_default_shell_env = True,
+    )
     ctx.actions.run_shell(
-      mnemonic = "Crisper",
-      inputs = [ctx.file._run_npm, ctx.file.app,
-                ctx.file._crisper_archive, vulcanized],
-      outputs = [ctx.outputs.js, ctx.outputs.html],
-      command = hermetic_npm_command,
-      **node_tweaks)
+        mnemonic = "Vulcanize",
+        inputs = [
+            ctx.file._run_npm,
+            ctx.file.app,
+            ctx.file._vulcanize_archive,
+        ] + list(zips) + ctx.files.srcs,
+        outputs = [vulcanized],
+        command = cmd,
+        **node_tweaks
+    )
+
+    if ctx.attr.split:
+        hermetic_npm_command = "export PATH && " + " ".join([
+            "python",
+            ctx.file._run_npm.path,
+            ctx.file._crisper_archive.path,
+            "--always-write-script",
+            "--source",
+            vulcanized.path,
+            "--html",
+            ctx.outputs.html.path,
+            "--js",
+            ctx.outputs.js.path,
+        ])
+
+        ctx.actions.run_shell(
+            mnemonic = "Crisper",
+            inputs = [
+                ctx.file._run_npm,
+                ctx.file.app,
+                ctx.file._crisper_archive,
+                vulcanized,
+            ],
+            outputs = [ctx.outputs.js, ctx.outputs.html],
+            command = hermetic_npm_command,
+            **node_tweaks
+        )
 
 def _vulcanize_output_func(name, split):
-  _ignore = [name]  # unused.
-  out = {"html": "%{name}.html"}
-  if split:
-    out["js"] = "%{name}.js"
-  return out
+    _ignore = [name]  # unused.
+    out = {"html": "%{name}.html"}
+    if split:
+        out["js"] = "%{name}.js"
+    return out
 
 _vulcanize_rule = rule(
     _vulcanize_impl,
@@ -388,9 +425,90 @@
 )
 
 def vulcanize(*args, **kwargs):
-  """Vulcanize runs vulcanize and (optionally) crisper on a set of sources."""
-  _vulcanize_rule(*args, pkg=PACKAGE_NAME, **kwargs)
+    """Vulcanize runs vulcanize and (optionally) crisper on a set of sources."""
+    _vulcanize_rule(*args, pkg = PACKAGE_NAME, **kwargs)
 
-def polygerrit_plugin(*args, **kwargs):
-  """Bundles plugin dependencies for deployment."""
-  _vulcanize_rule(*args, pkg=PACKAGE_NAME, **kwargs)
+def polygerrit_plugin(name, app, srcs = [], assets = None, **kwargs):
+    """Bundles plugin dependencies for deployment.
+
+    This rule bundles all Polymer elements and JS dependencies into .html and .js files.
+    Run-time dependencies (e.g. JS libraries loaded after plugin starts) should be provided using "assets" property.
+    Output of this rule is a FileSet with "${name}_fs", with deploy artifacts in "plugins/${name}/static".
+
+    Args:
+      name: String, plugin name.
+      app: String, the main or root source file.
+      assets: Fileset, additional files to be used by plugin in runtime, exported to "plugins/${name}/static".
+      srcs: Source files required for combining.
+    """
+
+    # Combines all .js and .html files into foo_combined.js and foo_combined.html
+    _vulcanize_rule(name = name + "_combined", app = app, srcs = srcs + [app], pkg = PACKAGE_NAME, **kwargs)
+
+    closure_js_binary(
+        name = name + "_bin",
+        compilation_level = "SIMPLE",
+        defs = [
+            "--polymer_version=1",
+            "--language_out=ECMASCRIPT6",
+            "--rewrite_polyfills=false",
+        ],
+        deps = [
+            name + "_closure_lib",
+        ],
+    )
+
+    closure_js_library(
+        name = name + "_closure_lib",
+        srcs = [name + "_combined.js"],
+        convention = "GOOGLE",
+        no_closure_library = True,
+        deps = [
+            "//lib/polymer_externs:polymer_closure",
+            "//polygerrit-ui/externs:plugin",
+        ],
+    )
+
+    native.genrule(
+        name = name + "_rename_html",
+        srcs = [name + "_combined.html"],
+        outs = [name + ".html"],
+        cmd = "sed 's/<script src=\"" + name + "_combined.js\"/<script src=\"" + name + ".js\"/g' $(SRCS) > $(OUTS)",
+        output_to_bindir = True,
+    )
+
+    native.genrule(
+        name = name + "_rename_js",
+        srcs = [name + "_bin.js"],
+        outs = [name + ".js"],
+        cmd = "cp $< $@",
+        output_to_bindir = True,
+    )
+
+    static_files = [
+        name + ".js",
+        name + ".html",
+    ]
+
+    if assets:
+        nested, direct = [], []
+        for x in assets:
+            target = nested if "/" in x else direct
+            target.append(x)
+
+        static_files += direct
+
+        if nested:
+            native.genrule(
+                name = name + "_copy_assets",
+                srcs = assets,
+                outs = [f.split("/")[-1] for f in nested],
+                cmd = "cp $(SRCS) $(@D)",
+                output_to_bindir = True,
+            )
+            static_files += [":" + name + "_copy_assets"]
+
+    native.filegroup(
+        name = name,
+        srcs = static_files,
+    )
diff --git a/tools/bzl/junit.bzl b/tools/bzl/junit.bzl
index 1abcc23..8076059 100644
--- a/tools/bzl/junit.bzl
+++ b/tools/bzl/junit.bzl
@@ -43,15 +43,21 @@
         if findex != -1:
             break
     if findex == -1:
-        fail("%s does not contain any of %s",
-                         fname, _PREFIXES)
+        fail(
+            "%s does not contain any of %s",
+            fname,
+            _PREFIXES,
+        )
     return ".".join(toks[findex:]) + ".class"
 
 def _impl(ctx):
     classes = ",".join(
-        [_AsClassName(x) for x in ctx.attr.srcs])
-    ctx.file_action(output=ctx.outputs.out, content=_OUTPUT % (
-            classes, ctx.attr.outname))
+        [_AsClassName(x) for x in ctx.attr.srcs],
+    )
+    ctx.file_action(output = ctx.outputs.out, content = _OUTPUT % (
+        classes,
+        ctx.attr.outname,
+    ))
 
 _GenSuite = rule(
     attrs = {
@@ -64,9 +70,11 @@
 
 def junit_tests(name, srcs, **kwargs):
     s_name = name + "TestSuite"
-    _GenSuite(name = s_name,
-              srcs = srcs,
-              outname = s_name)
+    _GenSuite(
+        name = s_name,
+        srcs = srcs,
+        outname = s_name,
+    )
     jvm_flags = kwargs.get("jvm_flags", [])
     jvm_flags = jvm_flags + select({
         "//:java9": [
@@ -78,7 +86,9 @@
         ],
         "//conditions:default": [],
     })
-    native.java_test(name = name,
-                     test_class = s_name,
-                     srcs = srcs + [":"+s_name],
-                     **dict(kwargs, jvm_flags=jvm_flags))
+    native.java_test(
+        name = name,
+        test_class = s_name,
+        srcs = srcs + [":" + s_name],
+        **dict(kwargs, jvm_flags = jvm_flags)
+    )
diff --git a/tools/bzl/license.bzl b/tools/bzl/license.bzl
index 38dfbe5..f011446 100644
--- a/tools/bzl/license.bzl
+++ b/tools/bzl/license.bzl
@@ -1,57 +1,57 @@
 def normalize_target_name(target):
-  return target.replace("//", "").replace("/", "__").replace(":", "___")
+    return target.replace("//", "").replace("/", "__").replace(":", "___")
 
 def license_map(name, targets = [], opts = [], **kwargs):
-  """Generate XML for all targets that depend directly on a LICENSE file"""
-  xmls = []
-  tools = [ "//tools/bzl:license-map.py", "//lib:all-licenses" ]
-  for target in targets:
-    subname = name + "_" + normalize_target_name(target) + ".xml"
-    xmls.append("$(location :%s)" % subname)
-    tools.append(subname)
-    native.genquery(
-      name = subname,
-      scope = [ target ],
+    """Generate XML for all targets that depend directly on a LICENSE file"""
+    xmls = []
+    tools = ["//tools/bzl:license-map.py", "//lib:all-licenses"]
+    for target in targets:
+        subname = name + "_" + normalize_target_name(target) + ".xml"
+        xmls.append("$(location :%s)" % subname)
+        tools.append(subname)
+        native.genquery(
+            name = subname,
+            scope = [target],
 
-      # Find everything that depends on a license file, but remove
-      # the license files themselves from this list.
-      expression = 'rdeps(%s, filter("//lib:LICENSE.*", deps(%s)),1) - filter("//lib:LICENSE.*", deps(%s))' % (target, target, target),
+            # Find everything that depends on a license file, but remove
+            # the license files themselves from this list.
+            expression = 'rdeps(%s, filter("//lib:LICENSE.*", deps(%s)),1) - filter("//lib:LICENSE.*", deps(%s))' % (target, target, target),
 
-      # We are interested in the edges of the graph ({java_library,
-      # license-file} tuples).  'query' provides this in the XML output.
-      opts = [ "--output=xml", ],
+            # We are interested in the edges of the graph ({java_library,
+            # license-file} tuples).  'query' provides this in the XML output.
+            opts = ["--output=xml"],
+        )
+
+    # post process the XML into our favorite format.
+    native.genrule(
+        name = "gen_license_txt_" + name,
+        cmd = "python $(location //tools/bzl:license-map.py) %s %s > $@" % (" ".join(opts), " ".join(xmls)),
+        outs = [name + ".txt"],
+        tools = tools,
+        **kwargs
     )
 
-  # post process the XML into our favorite format.
-  native.genrule(
-    name = "gen_license_txt_" + name,
-    cmd = "python $(location //tools/bzl:license-map.py) %s %s > $@" % (" ".join(opts), " ".join(xmls)),
-    outs = [ name + ".txt" ],
-    tools = tools,
-    **kwargs
-  )
-
 def license_test(name, target):
-  """Make sure a target doesn't depend on DO_NOT_DISTRIBUTE license"""
-  txt = name + "-forbidden.txt"
+    """Make sure a target doesn't depend on DO_NOT_DISTRIBUTE license"""
+    txt = name + "-forbidden.txt"
 
-  # fully qualify target name.
-  if target[0] not in ":/":
-    target = ":" + target
-  if target[0] != "/":
-    target = "//" + PACKAGE_NAME + target
+    # fully qualify target name.
+    if target[0] not in ":/":
+        target = ":" + target
+    if target[0] != "/":
+        target = "//" + PACKAGE_NAME + target
 
-  forbidden = "//lib:LICENSE-DO_NOT_DISTRIBUTE"
-  native.genquery(
-    name = txt,
-    scope = [ target, forbidden ],
-    # Find everything that depends on a license file, but remove
-    # the license files themselves from this list.
-    expression = 'rdeps(%s, "%s", 1) - rdeps(%s, "%s", 0)' % (target, forbidden, target, forbidden),
-  )
-  native.sh_test(
-    name = name,
-    srcs = [ "//tools/bzl:test_license.sh" ],
-    args  = [ "$(location :%s)" % txt ],
-    data = [ txt ],
-  )
+    forbidden = "//lib:LICENSE-DO_NOT_DISTRIBUTE"
+    native.genquery(
+        name = txt,
+        scope = [target, forbidden],
+        # Find everything that depends on a license file, but remove
+        # the license files themselves from this list.
+        expression = 'rdeps(%s, "%s", 1) - rdeps(%s, "%s", 0)' % (target, forbidden, target, forbidden),
+    )
+    native.sh_test(
+        name = name,
+        srcs = ["//tools/bzl:test_license.sh"],
+        args = ["$(location :%s)" % txt],
+        data = [txt],
+    )
diff --git a/tools/bzl/maven.bzl b/tools/bzl/maven.bzl
index c255c0c..71aa91c 100644
--- a/tools/bzl/maven.bzl
+++ b/tools/bzl/maven.bzl
@@ -15,18 +15,18 @@
 # Merge maven files
 
 def cmd(jars):
-  return ('$(location //tools:merge_jars) $@ '
-          + ' '.join(['$(location %s)' % j for j in jars]))
+    return ("$(location //tools:merge_jars) $@ " +
+            " ".join(["$(location %s)" % j for j in jars]))
 
 def merge_maven_jars(name, srcs, **kwargs):
-  native.genrule(
-    name = '%s__merged_bin' % name,
-    cmd = cmd(srcs),
-    tools = srcs + ['//tools:merge_jars'],
-    outs = ['%s__merged.jar' % name],
-  )
-  native.java_import(
-    name = name,
-    jars = [':%s__merged_bin' % name],
-    **kwargs
-  )
+    native.genrule(
+        name = "%s__merged_bin" % name,
+        cmd = cmd(srcs),
+        tools = srcs + ["//tools:merge_jars"],
+        outs = ["%s__merged.jar" % name],
+    )
+    native.java_import(
+        name = name,
+        jars = [":%s__merged_bin" % name],
+        **kwargs
+    )
diff --git a/tools/bzl/maven_jar.bzl b/tools/bzl/maven_jar.bzl
index 55bfae1..2ebb2c2 100644
--- a/tools/bzl/maven_jar.bzl
+++ b/tools/bzl/maven_jar.bzl
@@ -7,69 +7,70 @@
 MAVEN_LOCAL = "MAVEN_LOCAL:"
 
 def _maven_release(ctx, parts):
-  """induce jar and url name from maven coordinates."""
-  if len(parts) not in [3, 4]:
-    fail('%s:\nexpected id="groupId:artifactId:version[:classifier]"'
-         % ctx.attr.artifact)
-  if len(parts) == 4:
-    group, artifact, version, classifier = parts
-    file_version = version + '-' + classifier
-  else:
-    group, artifact, version = parts
-    file_version = version
+    """induce jar and url name from maven coordinates."""
+    if len(parts) not in [3, 4]:
+        fail('%s:\nexpected id="groupId:artifactId:version[:classifier]"' %
+             ctx.attr.artifact)
+    if len(parts) == 4:
+        group, artifact, version, classifier = parts
+        file_version = version + "-" + classifier
+    else:
+        group, artifact, version = parts
+        file_version = version
 
-  jar = artifact.lower() + '-' + file_version
-  url = '/'.join([
-    ctx.attr.repository,
-    group.replace('.', '/'),
-    artifact,
-    version,
-    artifact + '-' + file_version])
+    jar = artifact.lower() + "-" + file_version
+    url = "/".join([
+        ctx.attr.repository,
+        group.replace(".", "/"),
+        artifact,
+        version,
+        artifact + "-" + file_version,
+    ])
 
-  return jar, url
+    return jar, url
 
 # Creates a struct containing the different parts of an artifact's FQN
 def _create_coordinates(fully_qualified_name):
-  parts = fully_qualified_name.split(":")
-  packaging = None
-  classifier = None
+    parts = fully_qualified_name.split(":")
+    packaging = None
+    classifier = None
 
-  if len(parts) == 3:
-    group_id, artifact_id, version = parts
-  elif len(parts) == 4:
-    group_id, artifact_id, version, packaging = parts
-  elif len(parts) == 5:
-    group_id, artifact_id, version, packaging, classifier = parts
-  else:
-    fail("Invalid fully qualified name for artifact: %s" % fully_qualified_name)
+    if len(parts) == 3:
+        group_id, artifact_id, version = parts
+    elif len(parts) == 4:
+        group_id, artifact_id, version, packaging = parts
+    elif len(parts) == 5:
+        group_id, artifact_id, version, packaging, classifier = parts
+    else:
+        fail("Invalid fully qualified name for artifact: %s" % fully_qualified_name)
 
-  return struct(
-      fully_qualified_name = fully_qualified_name,
-      group_id = group_id,
-      artifact_id = artifact_id,
-      packaging = packaging,
-      classifier = classifier,
-      version = version,
-  )
+    return struct(
+        fully_qualified_name = fully_qualified_name,
+        group_id = group_id,
+        artifact_id = artifact_id,
+        packaging = packaging,
+        classifier = classifier,
+        version = version,
+    )
 
 def _format_deps(attr, deps):
-  formatted_deps = ""
-  if deps:
-    if len(deps) == 1:
-      formatted_deps += "%s = [\'%s\']," % (attr, deps[0])
-    else:
-      formatted_deps += "%s = [\n" % attr
-      for dep in deps:
-        formatted_deps += "        \'%s\',\n" % dep
-      formatted_deps += "    ],"
-  return formatted_deps
+    formatted_deps = ""
+    if deps:
+        if len(deps) == 1:
+            formatted_deps += "%s = [\'%s\']," % (attr, deps[0])
+        else:
+            formatted_deps += "%s = [\n" % attr
+            for dep in deps:
+                formatted_deps += "        \'%s\',\n" % dep
+            formatted_deps += "    ],"
+    return formatted_deps
 
 def _generate_build_files(ctx, binjar, srcjar):
-  header = "# DO NOT EDIT: automatically generated BUILD file for maven_jar rule %s" % ctx.name
-  srcjar_attr = ""
-  if srcjar:
-    srcjar_attr = 'srcjar = "%s",' % srcjar
-  contents = """
+    header = "# DO NOT EDIT: automatically generated BUILD file for maven_jar rule %s" % ctx.name
+    srcjar_attr = ""
+    if srcjar:
+        srcjar_attr = 'srcjar = "%s",' % srcjar
+    contents = """
 {header}
 package(default_visibility = ['//visibility:public'])
 java_import(
@@ -86,22 +87,24 @@
     {deps}
     {exports}
 )
-\n""".format(srcjar_attr = srcjar_attr,
-             header = header,
-             binjar = binjar,
-             deps = _format_deps("deps", ctx.attr.deps),
-             exports = _format_deps("exports", ctx.attr.exports))
-  if srcjar:
-    contents += """
+\n""".format(
+        srcjar_attr = srcjar_attr,
+        header = header,
+        binjar = binjar,
+        deps = _format_deps("deps", ctx.attr.deps),
+        exports = _format_deps("exports", ctx.attr.exports),
+    )
+    if srcjar:
+        contents += """
 java_import(
     name = 'src',
     jars = ['{srcjar}'],
 )
 """.format(srcjar = srcjar)
-  ctx.file('%s/BUILD' % ctx.path("jar"), contents, False)
+    ctx.file("%s/BUILD" % ctx.path("jar"), contents, False)
 
-  # Compatibility layer for java_import_external from rules_closure
-  contents = """
+    # Compatibility layer for java_import_external from rules_closure
+    contents = """
 {header}
 package(default_visibility = ['//visibility:public'])
 
@@ -110,52 +113,53 @@
     actual = "@{rule_name}//jar",
 )
 \n""".format(rule_name = ctx.name, header = header)
-  ctx.file("BUILD", contents, False)
+    ctx.file("BUILD", contents, False)
 
 def _maven_jar_impl(ctx):
-  """rule to download a Maven archive."""
-  coordinates = _create_coordinates(ctx.attr.artifact)
+    """rule to download a Maven archive."""
+    coordinates = _create_coordinates(ctx.attr.artifact)
 
-  name = ctx.name
-  sha1 = ctx.attr.sha1
+    name = ctx.name
+    sha1 = ctx.attr.sha1
 
-  parts = ctx.attr.artifact.split(':')
-  # TODO(davido): Only releases for now, implement handling snapshots
-  jar, url = _maven_release(ctx, parts)
+    parts = ctx.attr.artifact.split(":")
 
-  binjar = jar + '.jar'
-  binjar_path = ctx.path('/'.join(['jar', binjar]))
-  binurl = url + '.jar'
+    # TODO(davido): Only releases for now, implement handling snapshots
+    jar, url = _maven_release(ctx, parts)
 
-  python = ctx.which("python")
-  script = ctx.path(ctx.attr._download_script)
+    binjar = jar + ".jar"
+    binjar_path = ctx.path("/".join(["jar", binjar]))
+    binurl = url + ".jar"
 
-  args = [python, script, "-o", binjar_path, "-u", binurl]
-  if ctx.attr.sha1:
-    args.extend(["-v", sha1])
-  if ctx.attr.unsign:
-    args.append('--unsign')
-  for x in ctx.attr.exclude:
-    args.extend(['-x', x])
+    python = ctx.which("python")
+    script = ctx.path(ctx.attr._download_script)
 
-  out = ctx.execute(args)
+    args = [python, script, "-o", binjar_path, "-u", binurl]
+    if ctx.attr.sha1:
+        args.extend(["-v", sha1])
+    if ctx.attr.unsign:
+        args.append("--unsign")
+    for x in ctx.attr.exclude:
+        args.extend(["-x", x])
 
-  if out.return_code:
-    fail("failed %s: %s" % (' '.join(args), out.stderr))
-
-  srcjar = None
-  if ctx.attr.src_sha1 or ctx.attr.attach_source:
-    srcjar = jar + '-src.jar'
-    srcurl = url + '-sources.jar'
-    srcjar_path = ctx.path('jar/' + srcjar)
-    args = [python, script, "-o", srcjar_path, "-u", srcurl]
-    if ctx.attr.src_sha1:
-      args.extend(['-v', ctx.attr.src_sha1])
     out = ctx.execute(args)
-    if out.return_code:
-      fail("failed %s: %s" % (args, out.stderr))
 
-  _generate_build_files(ctx, binjar, srcjar)
+    if out.return_code:
+        fail("failed %s: %s" % (" ".join(args), out.stderr))
+
+    srcjar = None
+    if ctx.attr.src_sha1 or ctx.attr.attach_source:
+        srcjar = jar + "-src.jar"
+        srcurl = url + "-sources.jar"
+        srcjar_path = ctx.path("jar/" + srcjar)
+        args = [python, script, "-o", srcjar_path, "-u", srcurl]
+        if ctx.attr.src_sha1:
+            args.extend(["-v", ctx.attr.src_sha1])
+        out = ctx.execute(args)
+        if out.return_code:
+            fail("failed %s: %s" % (args, out.stderr))
+
+    _generate_build_files(ctx, binjar, srcjar)
 
 maven_jar = repository_rule(
     attrs = {
diff --git a/tools/bzl/pkg_war.bzl b/tools/bzl/pkg_war.bzl
index 46a4f9b..1a376e9 100644
--- a/tools/bzl/pkg_war.bzl
+++ b/tools/bzl/pkg_war.bzl
@@ -32,93 +32,93 @@
 ]
 
 def _add_context(in_file, output):
-  input_path = in_file.path
-  return [
-    'unzip -qd %s %s' % (output, input_path)
-  ]
+    input_path = in_file.path
+    return [
+        "unzip -qd %s %s" % (output, input_path),
+    ]
 
 def _add_file(in_file, output):
-  output_path = output
-  input_path = in_file.path
-  short_path = in_file.short_path
-  n = in_file.basename
+    output_path = output
+    input_path = in_file.path
+    short_path = in_file.short_path
+    n = in_file.basename
 
-  if short_path.startswith('gerrit-'):
-    n = short_path.split('/')[0] + '-' + n
-  elif short_path.startswith('java/'):
-    n = short_path[5:].replace('/', '_')
-  output_path += n
-  return [
-    'test -L %s || ln -s $(pwd)/%s %s' % (output_path, input_path, output_path)
-  ]
+    if short_path.startswith("gerrit-"):
+        n = short_path.split("/")[0] + "-" + n
+    elif short_path.startswith("java/"):
+        n = short_path[5:].replace("/", "_")
+    output_path += n
+    return [
+        "test -L %s || ln -s $(pwd)/%s %s" % (output_path, input_path, output_path),
+    ]
 
 def _make_war(input_dir, output):
-  return '(%s)' % ' && '.join([
-    'root=$(pwd)',
-    'TZ=UTC',
-    'export TZ',
-    'cd %s' % input_dir,
-    "find . -exec touch -t 198001010000 '{}' ';' 2> /dev/null",
-    'zip -X -9qr ${root}/%s .' % (output.path),
-  ])
+    return "(%s)" % " && ".join([
+        "root=$(pwd)",
+        "TZ=UTC",
+        "export TZ",
+        "cd %s" % input_dir,
+        "find . -exec touch -t 198001010000 '{}' ';' 2> /dev/null",
+        "zip -X -9qr ${root}/%s ." % (output.path),
+    ])
 
 def _war_impl(ctx):
-  war = ctx.outputs.war
-  build_output = war.path + '.build_output'
-  inputs = []
+    war = ctx.outputs.war
+    build_output = war.path + ".build_output"
+    inputs = []
 
-  # Create war layout
-  cmd = [
-    'set -e;rm -rf ' + build_output,
-    'mkdir -p ' + build_output,
-    'mkdir -p %s/WEB-INF/lib' % build_output,
-    'mkdir -p %s/WEB-INF/pgm-lib' % build_output,
-  ]
+    # Create war layout
+    cmd = [
+        "set -e;rm -rf " + build_output,
+        "mkdir -p " + build_output,
+        "mkdir -p %s/WEB-INF/lib" % build_output,
+        "mkdir -p %s/WEB-INF/pgm-lib" % build_output,
+    ]
 
-  # Add lib
-  transitive_lib_deps = depset()
-  for l in ctx.attr.libs:
-    if hasattr(l, 'java'):
-      transitive_lib_deps += l.java.transitive_runtime_deps
-    elif hasattr(l, 'files'):
-      transitive_lib_deps += l.files
+    # Add lib
+    transitive_lib_deps = depset()
+    for l in ctx.attr.libs:
+        if hasattr(l, "java"):
+            transitive_lib_deps += l.java.transitive_runtime_deps
+        elif hasattr(l, "files"):
+            transitive_lib_deps += l.files
 
-  for dep in transitive_lib_deps:
-    cmd += _add_file(dep, build_output + '/WEB-INF/lib/')
-    inputs.append(dep)
+    for dep in transitive_lib_deps:
+        cmd += _add_file(dep, build_output + "/WEB-INF/lib/")
+        inputs.append(dep)
 
-  # Add pgm lib
-  transitive_pgmlib_deps = depset()
-  for l in ctx.attr.pgmlibs:
-    transitive_pgmlib_deps += l.java.transitive_runtime_deps
+    # Add pgm lib
+    transitive_pgmlib_deps = depset()
+    for l in ctx.attr.pgmlibs:
+        transitive_pgmlib_deps += l.java.transitive_runtime_deps
 
-  for dep in transitive_pgmlib_deps:
-    if dep not in inputs:
-      cmd += _add_file(dep, build_output + '/WEB-INF/pgm-lib/')
-      inputs.append(dep)
+    for dep in transitive_pgmlib_deps:
+        if dep not in inputs:
+            cmd += _add_file(dep, build_output + "/WEB-INF/pgm-lib/")
+            inputs.append(dep)
 
-  # Add context
-  transitive_context_deps = depset()
-  if ctx.attr.context:
-    for jar in ctx.attr.context:
-      if hasattr(jar, 'java'):
-        transitive_context_deps += jar.java.transitive_runtime_deps
-      elif hasattr(jar, 'files'):
-        transitive_context_deps += jar.files
-  for dep in transitive_context_deps:
-    cmd += _add_context(dep, build_output)
-    inputs.append(dep)
+    # Add context
+    transitive_context_deps = depset()
+    if ctx.attr.context:
+        for jar in ctx.attr.context:
+            if hasattr(jar, "java"):
+                transitive_context_deps += jar.java.transitive_runtime_deps
+            elif hasattr(jar, "files"):
+                transitive_context_deps += jar.files
+    for dep in transitive_context_deps:
+        cmd += _add_context(dep, build_output)
+        inputs.append(dep)
 
-  # Add zip war
-  cmd.append(_make_war(build_output, war))
+    # Add zip war
+    cmd.append(_make_war(build_output, war))
 
-  ctx.actions.run_shell(
-    inputs = inputs,
-    outputs = [war],
-    mnemonic = 'WAR',
-    command = '\n'.join(cmd),
-    use_default_shell_env = True,
-  )
+    ctx.actions.run_shell(
+        inputs = inputs,
+        outputs = [war],
+        mnemonic = "WAR",
+        command = "\n".join(cmd),
+        use_default_shell_env = True,
+    )
 
 # context: go to the root directory
 # libs: go to the WEB-INF/lib directory
@@ -133,25 +133,25 @@
     implementation = _war_impl,
 )
 
-def pkg_war(name, ui = 'ui_optdbg', context = [], doc = False, **kwargs):
-  doc_ctx = []
-  doc_lib = []
-  ui_deps = []
-  if ui == 'polygerrit' or ui == 'ui_optdbg' or ui == 'ui_optdbg_r':
-    ui_deps.append('//polygerrit-ui/app:polygerrit_ui')
-  if ui and ui != 'polygerrit':
-    ui_deps.append('//gerrit-gwtui:%s' % ui)
-  if doc:
-    doc_ctx.append('//Documentation:html')
-    doc_lib.append('//Documentation:index')
+def pkg_war(name, ui = "ui_optdbg", context = [], doc = False, **kwargs):
+    doc_ctx = []
+    doc_lib = []
+    ui_deps = []
+    if ui == "polygerrit" or ui == "ui_optdbg" or ui == "ui_optdbg_r":
+        ui_deps.append("//polygerrit-ui/app:polygerrit_ui")
+    if ui and ui != "polygerrit":
+        ui_deps.append("//gerrit-gwtui:%s" % ui)
+    if doc:
+        doc_ctx.append("//Documentation:html")
+        doc_lib.append("//Documentation:index")
 
-  _pkg_war(
-    name = name,
-    libs = LIBS + doc_lib,
-    pgmlibs = PGMLIBS,
-    context = doc_ctx + context + ui_deps + [
-      '//java:gerrit-main-class_deploy.jar',
-      '//webapp:assets',
-    ],
-    **kwargs
-  )
+    _pkg_war(
+        name = name,
+        libs = LIBS + doc_lib,
+        pgmlibs = PGMLIBS,
+        context = doc_ctx + context + ui_deps + [
+            "//java:gerrit-main-class_deploy.jar",
+            "//webapp:assets",
+        ],
+        **kwargs
+    )
diff --git a/tools/bzl/plugin.bzl b/tools/bzl/plugin.bzl
index 23f88df..5ae7dd9 100644
--- a/tools/bzl/plugin.bzl
+++ b/tools/bzl/plugin.bzl
@@ -1,11 +1,11 @@
 load("//tools/bzl:genrule2.bzl", "genrule2")
 load(
     "//tools/bzl:gwt.bzl",
+    "GWT_COMPILER_ARGS",
+    "GWT_JVM_ARGS",
     "GWT_PLUGIN_DEPS",
     "GWT_PLUGIN_DEPS_NEVERLINK",
     "GWT_TRANSITIVE_DEPS",
-    "GWT_COMPILER_ARGS",
-    "GWT_JVM_ARGS",
     "gwt_binary",
 )
 
@@ -21,82 +21,84 @@
 ]
 
 def gerrit_plugin(
-    name,
-    deps = [],
-    provided_deps = [],
-    srcs = [],
-    gwt_module = [],
-    resources = [],
-    manifest_entries = [],
-    dir_name = None,
-    target_suffix = "",
-    **kwargs):
-  native.java_library(
-    name = name + '__plugin',
-    srcs = srcs,
-    resources = resources,
-    deps = provided_deps + deps + GWT_PLUGIN_DEPS_NEVERLINK + PLUGIN_DEPS_NEVERLINK,
-    visibility = ['//visibility:public'],
-    **kwargs
-  )
-
-  static_jars = []
-  if gwt_module:
-    static_jars = [':%s-static' % name]
-
-  if not dir_name:
-    dir_name = name
-
-  native.java_binary(
-    name = '%s__non_stamped' % name,
-    deploy_manifest_lines = manifest_entries + ["Gerrit-ApiType: plugin"],
-    main_class = 'Dummy',
-    runtime_deps = [
-      ':%s__plugin' % name,
-    ] + static_jars,
-    visibility = ['//visibility:public'],
-    **kwargs
-  )
-
-  if gwt_module:
+        name,
+        deps = [],
+        provided_deps = [],
+        srcs = [],
+        gwt_module = [],
+        resources = [],
+        manifest_entries = [],
+        dir_name = None,
+        target_suffix = "",
+        **kwargs):
     native.java_library(
-      name = name + '__gwt_module',
-      resources = depset(srcs + resources).to_list(),
-      runtime_deps = deps + GWT_PLUGIN_DEPS,
-      visibility = ['//visibility:public'],
-      **kwargs
-    )
-    genrule2(
-      name = '%s-static' % name,
-      cmd = ' && '.join([
-        'mkdir -p $$TMP/static',
-        'unzip -qd $$TMP/static $(location %s__gwt_application)' % name,
-        'cd $$TMP',
-        'zip -qr $$ROOT/$@ .']),
-      tools = [':%s__gwt_application' % name],
-      outs = ['%s-static.jar' % name],
-    )
-    gwt_binary(
-      name = name + '__gwt_application',
-      module = [gwt_module],
-      deps = GWT_PLUGIN_DEPS + GWT_TRANSITIVE_DEPS + ['//lib/gwt:dev'],
-      module_deps = [':%s__gwt_module' % name],
-      compiler_args = GWT_COMPILER_ARGS,
-      jvm_args = GWT_JVM_ARGS,
+        name = name + "__plugin",
+        srcs = srcs,
+        resources = resources,
+        deps = provided_deps + deps + GWT_PLUGIN_DEPS_NEVERLINK + PLUGIN_DEPS_NEVERLINK,
+        visibility = ["//visibility:public"],
+        **kwargs
     )
 
-  # TODO(davido): Remove manual merge of manifest file when this feature
-  # request is implemented: https://github.com/bazelbuild/bazel/issues/2009
-  genrule2(
-    name = name + target_suffix,
-    stamp = 1,
-    srcs = ['%s__non_stamped_deploy.jar' % name],
-    cmd = " && ".join([
-      "GEN_VERSION=$$(cat bazel-out/stable-status.txt | grep -w STABLE_BUILD_%s_LABEL | cut -d ' ' -f 2)" % dir_name.upper(),
-      "cd $$TMP",
-      "unzip -q $$ROOT/$<",
-      "echo \"Implementation-Version: $$GEN_VERSION\n$$(cat META-INF/MANIFEST.MF)\" > META-INF/MANIFEST.MF",
-      "zip -qr $$ROOT/$@ ."]),
-    outs = ['%s%s.jar' % (name, target_suffix)],
-    visibility = ['//visibility:public'],
-  )
+    static_jars = []
+    if gwt_module:
+        static_jars = [":%s-static" % name]
+
+    if not dir_name:
+        dir_name = name
+
+    native.java_binary(
+        name = "%s__non_stamped" % name,
+        deploy_manifest_lines = manifest_entries + ["Gerrit-ApiType: plugin"],
+        main_class = "Dummy",
+        runtime_deps = [
+            ":%s__plugin" % name,
+        ] + static_jars,
+        visibility = ["//visibility:public"],
+        **kwargs
+    )
+
+    if gwt_module:
+        native.java_library(
+            name = name + "__gwt_module",
+            resources = depset(srcs + resources).to_list(),
+            runtime_deps = deps + GWT_PLUGIN_DEPS,
+            visibility = ["//visibility:public"],
+            **kwargs
+        )
+        genrule2(
+            name = "%s-static" % name,
+            cmd = " && ".join([
+                "mkdir -p $$TMP/static",
+                "unzip -qd $$TMP/static $(location %s__gwt_application)" % name,
+                "cd $$TMP",
+                "zip -qr $$ROOT/$@ .",
+            ]),
+            tools = [":%s__gwt_application" % name],
+            outs = ["%s-static.jar" % name],
+        )
+        gwt_binary(
+            name = name + "__gwt_application",
+            module = [gwt_module],
+            deps = GWT_PLUGIN_DEPS + GWT_TRANSITIVE_DEPS + ["//lib/gwt:dev"],
+            module_deps = [":%s__gwt_module" % name],
+            compiler_args = GWT_COMPILER_ARGS,
+            jvm_args = GWT_JVM_ARGS,
+        )
+
+    # TODO(davido): Remove manual merge of manifest file when this feature
+    # request is implemented: https://github.com/bazelbuild/bazel/issues/2009
+    genrule2(
+        name = name + target_suffix,
+        stamp = 1,
+        srcs = ["%s__non_stamped_deploy.jar" % name],
+        cmd = " && ".join([
+            "GEN_VERSION=$$(cat bazel-out/stable-status.txt | grep -w STABLE_BUILD_%s_LABEL | cut -d ' ' -f 2)" % dir_name.upper(),
+            "cd $$TMP",
+            "unzip -q $$ROOT/$<",
+            "echo \"Implementation-Version: $$GEN_VERSION\n$$(cat META-INF/MANIFEST.MF)\" > META-INF/MANIFEST.MF",
+            "zip -qr $$ROOT/$@ .",
+        ]),
+        outs = ["%s%s.jar" % (name, target_suffix)],
+        visibility = ["//visibility:public"],
+    )
diff --git a/tools/maven/package.bzl b/tools/maven/package.bzl
index 3c32bb2..5b497f8 100644
--- a/tools/maven/package.bzl
+++ b/tools/maven/package.bzl
@@ -25,73 +25,86 @@
 ]))
 
 def maven_package(
-    version,
-    repository = None,
-    url = None,
-    jar = {},
-    src = {},
-    doc = {},
-    war = {}):
+        version,
+        repository = None,
+        url = None,
+        jar = {},
+        src = {},
+        doc = {},
+        war = {}):
+    build_cmd = ["bazel", "build"]
+    mvn_cmd = ["python", "tools/maven/mvn.py", "-v", version]
+    api_cmd = mvn_cmd[:]
+    api_targets = []
+    for type, d in [("jar", jar), ("java-source", src), ("javadoc", doc)]:
+        for a, t in sorted(d.items()):
+            api_cmd.append("-s %s:%s:$(location %s)" % (a, type, t))
+            api_targets.append(t)
 
-  build_cmd = ['bazel', 'build']
-  mvn_cmd = ['python', 'tools/maven/mvn.py', '-v', version]
-  api_cmd = mvn_cmd[:]
-  api_targets = []
-  for type,d in [('jar', jar), ('java-source', src), ('javadoc', doc)]:
-    for a,t in sorted(d.items()):
-      api_cmd.append('-s %s:%s:$(location %s)' % (a,type,t))
-      api_targets.append(t)
-
-  native.genrule(
-    name = 'gen_api_install',
-    cmd = sh_bang_template % (
-      ' '.join(build_cmd + api_targets),
-      ' '.join(api_cmd + ['-a', 'install'])),
-    srcs = api_targets,
-    outs = ['api_install.sh'],
-    executable = True,
-    testonly = 1,
-  )
-
-  if repository and url:
     native.genrule(
-      name = 'gen_api_deploy',
-      cmd = sh_bang_template % (
-        ' '.join(build_cmd + api_targets),
-        ' '.join(api_cmd + ['-a', 'deploy',
-                            '--repository', repository,
-                            '--url', url])),
-      srcs = api_targets,
-      outs = ['api_deploy.sh'],
-      executable = True,
-      testonly = 1,
+        name = "gen_api_install",
+        cmd = sh_bang_template % (
+            " ".join(build_cmd + api_targets),
+            " ".join(api_cmd + ["-a", "install"]),
+        ),
+        srcs = api_targets,
+        outs = ["api_install.sh"],
+        executable = True,
+        testonly = 1,
     )
 
-  war_cmd = mvn_cmd[:]
-  war_targets = []
-  for a,t in sorted(war.items()):
-    war_cmd.append('-s %s:war:$(location %s)' % (a,t))
-    war_targets.append(t)
+    if repository and url:
+        native.genrule(
+            name = "gen_api_deploy",
+            cmd = sh_bang_template % (
+                " ".join(build_cmd + api_targets),
+                " ".join(api_cmd + [
+                    "-a",
+                    "deploy",
+                    "--repository",
+                    repository,
+                    "--url",
+                    url,
+                ]),
+            ),
+            srcs = api_targets,
+            outs = ["api_deploy.sh"],
+            executable = True,
+            testonly = 1,
+        )
 
-  native.genrule(
-    name = 'gen_war_install',
-    cmd = sh_bang_template % (' '.join(build_cmd + war_targets),
-                              ' '.join(war_cmd + ['-a', 'install'])),
-    srcs = war_targets,
-    outs = ['war_install.sh'],
-    executable = True,
-  )
+    war_cmd = mvn_cmd[:]
+    war_targets = []
+    for a, t in sorted(war.items()):
+        war_cmd.append("-s %s:war:$(location %s)" % (a, t))
+        war_targets.append(t)
 
-  if repository and url:
     native.genrule(
-      name = 'gen_war_deploy',
-      cmd = sh_bang_template % (
-          ' '.join(build_cmd + war_targets),
-          ' '.join(war_cmd + [
-        '-a', 'deploy',
-        '--repository', repository,
-        '--url', url])),
-      srcs = war_targets,
-      outs = ['war_deploy.sh'],
-      executable = True,
+        name = "gen_war_install",
+        cmd = sh_bang_template % (
+            " ".join(build_cmd + war_targets),
+            " ".join(war_cmd + ["-a", "install"]),
+        ),
+        srcs = war_targets,
+        outs = ["war_install.sh"],
+        executable = True,
     )
+
+    if repository and url:
+        native.genrule(
+            name = "gen_war_deploy",
+            cmd = sh_bang_template % (
+                " ".join(build_cmd + war_targets),
+                " ".join(war_cmd + [
+                    "-a",
+                    "deploy",
+                    "--repository",
+                    repository,
+                    "--url",
+                    url,
+                ]),
+            ),
+            srcs = war_targets,
+            outs = ["war_deploy.sh"],
+            executable = True,
+        )