Merge changes from topic "boolcond"

* changes:
  UiActions: defer GlobalPermission checks on views
  CherryPick: use BooleanCondition for visible
  PermissionBackend: support bulk evaluation of UiAction
  PermissionBackend: support testCond for UiAction
  Delay UiAction visible and enabled with BooleanCondition
diff --git a/Documentation/index.txt b/Documentation/index.txt
index 0f51926..e2df077 100644
--- a/Documentation/index.txt
+++ b/Documentation/index.txt
@@ -12,7 +12,7 @@
 == Guides
 . link:intro-user.html[User Guide]
 . link:intro-project-owner.html[Project Owner Guide]
-. link:http://source.android.com/submit-patches/workflow[Default Android Workflow] (external)
+. link:https://source.android.com/source/developing[Default Android Workflow] (external)
 
 == Tutorials
 . Web
diff --git a/Documentation/rest-api-plugins.txt b/Documentation/rest-api-plugins.txt
index 0f687bf..938d101 100644
--- a/Documentation/rest-api-plugins.txt
+++ b/Documentation/rest-api-plugins.txt
@@ -47,6 +47,7 @@
     "delete-project": {
       "id": "delete-project",
       "index_url": "plugins/delete-project/",
+      "filename": "delete-project.jar",
       "version": "2.9-SNAPSHOT"
     }
   }
@@ -73,11 +74,13 @@
     "delete-project": {
       "id": "delete-project",
       "index_url": "plugins/delete-project/",
+      "filename": "delete-project.jar",
       "version": "2.9-SNAPSHOT"
     },
     "reviewers-by-blame": {
       "id": "reviewers-by-blame",
       "index_url": "plugins/reviewers-by-blame/",
+      "filename": "reviewers-by-blame.jar",
       "version": "2.9-SNAPSHOT",
       "disabled": true
     }
@@ -105,6 +108,7 @@
     "delete-project": {
       "id": "delete-project",
       "index_url": "plugins/delete-project/",
+      "filename": "delete-project.jar",
       "version": "2.9-SNAPSHOT"
     }
   }
@@ -134,6 +138,7 @@
     "delete-project": {
       "id": "delete-project",
       "index_url": "plugins/delete-project/",
+      "filename": "delete-project.jar",
       "version": "2.9-SNAPSHOT"
     }
   }
@@ -168,11 +173,13 @@
     "some-plugin": {
       "id": "some-plugin",
       "index_url": "plugins/some-plugin/",
+      "filename": "some-plugin.jar",
       "version": "2.9-SNAPSHOT"
     },
     "some-other-plugin": {
       "id": "some-other-plugin",
       "index_url": "plugins/some-other-plugin/",
+      "filename": "some-other-plugin.jar",
       "version": "2.9-SNAPSHOT"
     }
   }
@@ -200,6 +207,7 @@
     "reviewers-by-blame": {
       "id": "reviewers-by-blame",
       "index_url": "plugins/reviewers-by-blame/",
+      "filename": "reviewers-by-blame.jar",
       "version": "2.9-SNAPSHOT",
       "disabled": true
     }
@@ -229,6 +237,7 @@
     "delete-project": {
       "id": "delete-project",
       "index_url": "plugins/delete-project/",
+      "filename": "delete-project.jar",
       "version": "2.9-SNAPSHOT"
     }
   }
@@ -429,6 +438,7 @@
 |`id`       ||The ID of the plugin.
 |`version`  ||The version of the plugin.
 |`index_url`|optional|URL of the plugin's default page.
+|`filename` |optional|The plugin's filename.
 |`disabled` |not set if `false`|Whether the plugin is disabled.
 |=======================
 
diff --git a/gerrit-acceptance-framework/BUILD b/gerrit-acceptance-framework/BUILD
index 465dcc6..5a7f3b7 100644
--- a/gerrit-acceptance-framework/BUILD
+++ b/gerrit-acceptance-framework/BUILD
@@ -7,9 +7,11 @@
     "//gerrit-common:server",
     "//gerrit-extension-api:api",
     "//gerrit-httpd:httpd",
+    "//gerrit-index:index",
     "//gerrit-lucene:lucene",
     "//gerrit-pgm:init",
     "//gerrit-reviewdb:server",
+    "//gerrit-server:metrics",
     "//gerrit-server:receive",
     "//gerrit-server:server",
     "//lib:gson",
@@ -32,8 +34,8 @@
     testonly = 1,
     srcs = SRCS,
     exported_deps = [
-        "//gerrit-antlr:query_exception",
         "//gerrit-gpg:gpg",
+        "//gerrit-index:query_exception",
         "//gerrit-launcher:launcher",
         "//gerrit-openid:openid",
         "//gerrit-pgm:daemon",
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/ReadOnlyChangeIndex.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/ReadOnlyChangeIndex.java
index 6b054c8..7809ae0 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/ReadOnlyChangeIndex.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/ReadOnlyChangeIndex.java
@@ -14,13 +14,13 @@
 
 package com.google.gerrit.acceptance;
 
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.query.DataSource;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.Change.Id;
-import com.google.gerrit.server.index.QueryOptions;
-import com.google.gerrit.server.index.Schema;
 import com.google.gerrit.server.index.change.ChangeIndex;
-import com.google.gerrit.server.query.DataSource;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gerrit.server.query.change.ChangeData;
 import java.io.IOException;
 
diff --git a/gerrit-acceptance-tests/BUILD b/gerrit-acceptance-tests/BUILD
index 91b90e3..ebc7c9b 100644
--- a/gerrit-acceptance-tests/BUILD
+++ b/gerrit-acceptance-tests/BUILD
@@ -19,6 +19,7 @@
         "//gerrit-pgm:pgm",
         "//gerrit-pgm:util",
         "//gerrit-reviewdb:server",
+        "//gerrit-server:metrics",
         "//gerrit-server:prolog-common",
         "//gerrit-server:receive",
         "//gerrit-server:server",
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
index 6c1fb39..48aa0bb 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
@@ -152,7 +152,7 @@
 
   @Inject private Sequences seq;
 
-  @Inject private InternalAccountQuery accountQuery;
+  @Inject private Provider<InternalAccountQuery> accountQueryProvider;
 
   @Inject protected Emails emails;
 
@@ -1033,7 +1033,7 @@
     }
 
     assertThat(accountCache.getOrNull(admin.id)).isNull();
-    assertThat(accountQuery.byDefault(admin.id.toString())).isEmpty();
+    assertThat(accountQueryProvider.get().byDefault(admin.id.toString())).isEmpty();
   }
 
   @Test
@@ -1257,7 +1257,7 @@
   @Test
   public void internalQueryFindActiveAndInactiveAccounts() throws Exception {
     String name = name("foo");
-    assertThat(accountQuery.byDefault(name)).isEmpty();
+    assertThat(accountQueryProvider.get().byDefault(name)).isEmpty();
 
     TestAccount foo1 = accountCreator.create(name + "-1");
     assertThat(gApi.accounts().id(foo1.username).getActive()).isTrue();
@@ -1266,7 +1266,7 @@
     gApi.accounts().id(foo2.username).setActive(false);
     assertThat(gApi.accounts().id(foo2.username).getActive()).isFalse();
 
-    assertThat(accountQuery.byDefault(name)).hasSize(2);
+    assertThat(accountQueryProvider.get().byDefault(name)).hasSize(2);
   }
 
   private void assertSequenceNumbers(List<SshKeyInfo> sshKeys) {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/plugin/PluginIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/plugin/PluginIT.java
index 3e1b2cb..0fa09af 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/plugin/PluginIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/plugin/PluginIT.java
@@ -68,6 +68,7 @@
       assertThat(info.id).isEqualTo(name);
       assertThat(info.version).isEqualTo(pluginVersion(plugin));
       assertThat(info.indexUrl).isEqualTo(String.format("plugins/%s/", name));
+      assertThat(info.filename).isEqualTo(plugin);
       assertThat(info.disabled).isNull();
     }
     assertPlugins(list().get(), PLUGINS);
diff --git a/gerrit-antlr/BUILD b/gerrit-antlr/BUILD
deleted file mode 100644
index f4ce4c7..0000000
--- a/gerrit-antlr/BUILD
+++ /dev/null
@@ -1,32 +0,0 @@
-load("//tools/bzl:genrule2.bzl", "genrule2")
-
-java_library(
-    name = "query_exception",
-    srcs = ["src/main/java/com/google/gerrit/server/query/QueryParseException.java"],
-    visibility = ["//visibility:public"],
-)
-
-genrule2(
-    name = "query_antlr",
-    srcs = ["src/main/antlr3/com/google/gerrit/server/query/Query.g"],
-    outs = ["query_antlr.srcjar"],
-    cmd = " && ".join([
-        "$(location //lib/antlr:antlr-tool) -o $$TMP $<",
-        "cd $$TMP",
-        "$$ROOT/$(location @bazel_tools//tools/zip:zipper) cC $$ROOT/$@ $$(find *)",
-    ]),
-    tools = [
-        "//lib/antlr:antlr-tool",
-        "@bazel_tools//tools/zip:zipper",
-    ],
-)
-
-java_library(
-    name = "query_parser",
-    srcs = [":query_antlr"],
-    visibility = ["//visibility:public"],
-    deps = [
-        ":query_exception",
-        "//lib/antlr:java_runtime",
-    ],
-)
diff --git a/gerrit-elasticsearch/BUILD b/gerrit-elasticsearch/BUILD
index ccaee55..fb86aaf 100644
--- a/gerrit-elasticsearch/BUILD
+++ b/gerrit-elasticsearch/BUILD
@@ -3,8 +3,9 @@
     srcs = glob(["src/main/java/**/*.java"]),
     visibility = ["//visibility:public"],
     deps = [
-        "//gerrit-antlr:query_exception",
         "//gerrit-extension-api:api",
+        "//gerrit-index:index",
+        "//gerrit-index:query_exception",
         "//gerrit-reviewdb:server",
         "//gerrit-server:server",
         "//lib:gson",
@@ -35,6 +36,7 @@
     deps = [
         ":elasticsearch",
         "//gerrit-extension-api:api",
+        "//gerrit-index:index",
         "//gerrit-reviewdb:server",
         "//gerrit-server:server",
         "//lib:gson",
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
index 3462eeb..8f0ea8f 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
@@ -23,12 +23,12 @@
 import com.google.common.collect.FluentIterable;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Streams;
+import com.google.gerrit.index.Index;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.Schema.Values;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.index.Index;
 import com.google.gerrit.server.index.IndexUtils;
-import com.google.gerrit.server.index.Schema;
-import com.google.gerrit.server.index.Schema.Values;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
 import com.google.gson.JsonArray;
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java
index 29d787a..18eb660 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java
@@ -20,19 +20,19 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
 import com.google.gerrit.elasticsearch.ElasticMapping.MappingProperties;
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.query.DataSource;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 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.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.index.IndexUtils;
-import com.google.gerrit.server.index.QueryOptions;
-import com.google.gerrit.server.index.Schema;
 import com.google.gerrit.server.index.account.AccountField;
 import com.google.gerrit.server.index.account.AccountIndex;
-import com.google.gerrit.server.query.DataSource;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gson.JsonArray;
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
index 53b16b1..b99f296 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
@@ -30,6 +30,10 @@
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import com.google.gerrit.elasticsearch.ElasticMapping.MappingProperties;
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Change.Id;
@@ -40,14 +44,10 @@
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.index.IndexUtils;
-import com.google.gerrit.server.index.QueryOptions;
-import com.google.gerrit.server.index.Schema;
 import com.google.gerrit.server.index.change.ChangeField;
 import com.google.gerrit.server.index.change.ChangeIndex;
 import com.google.gerrit.server.index.change.ChangeIndexRewriter;
 import com.google.gerrit.server.project.SubmitRuleOptions;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.ChangeDataSource;
 import com.google.gson.JsonArray;
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticGroupIndex.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticGroupIndex.java
index 51c2a67..38c4e23 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticGroupIndex.java
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticGroupIndex.java
@@ -18,18 +18,18 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
 import com.google.gerrit.elasticsearch.ElasticMapping.MappingProperties;
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.query.DataSource;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.account.GroupCache;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.index.IndexUtils;
-import com.google.gerrit.server.index.QueryOptions;
-import com.google.gerrit.server.index.Schema;
 import com.google.gerrit.server.index.group.GroupField;
 import com.google.gerrit.server.index.group.GroupIndex;
-import com.google.gerrit.server.query.DataSource;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gson.JsonArray;
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java
index a690136..7868443 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java
@@ -16,9 +16,9 @@
 
 import static com.google.common.base.Preconditions.checkArgument;
 
+import com.google.gerrit.index.IndexConfig;
 import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.index.IndexConfig;
 import com.google.gerrit.server.index.IndexModule;
 import com.google.gerrit.server.index.OnlineUpgrader;
 import com.google.gerrit.server.index.SingleVersionModule;
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticMapping.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticMapping.java
index af74daf..9e1c729 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticMapping.java
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticMapping.java
@@ -15,9 +15,9 @@
 package com.google.gerrit.elasticsearch;
 
 import com.google.common.collect.ImmutableMap;
-import com.google.gerrit.server.index.FieldDef;
-import com.google.gerrit.server.index.FieldType;
-import com.google.gerrit.server.index.Schema;
+import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.FieldType;
+import com.google.gerrit.index.Schema;
 import java.util.Map;
 
 class ElasticMapping {
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticQueryBuilder.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticQueryBuilder.java
index d493497..a470696 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticQueryBuilder.java
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticQueryBuilder.java
@@ -14,17 +14,17 @@
 
 package com.google.gerrit.elasticsearch;
 
-import com.google.gerrit.server.index.FieldDef;
-import com.google.gerrit.server.index.FieldType;
-import com.google.gerrit.server.index.IndexPredicate;
-import com.google.gerrit.server.index.IntegerRangePredicate;
-import com.google.gerrit.server.index.RegexPredicate;
-import com.google.gerrit.server.index.TimestampRangePredicate;
-import com.google.gerrit.server.query.AndPredicate;
-import com.google.gerrit.server.query.NotPredicate;
-import com.google.gerrit.server.query.OrPredicate;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
+import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.FieldType;
+import com.google.gerrit.index.query.AndPredicate;
+import com.google.gerrit.index.query.IndexPredicate;
+import com.google.gerrit.index.query.IntegerRangePredicate;
+import com.google.gerrit.index.query.NotPredicate;
+import com.google.gerrit.index.query.OrPredicate;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
+import com.google.gerrit.index.query.RegexPredicate;
+import com.google.gerrit.index.query.TimestampRangePredicate;
 import com.google.gerrit.server.query.change.AfterPredicate;
 import java.time.Instant;
 import org.apache.lucene.search.BooleanQuery;
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticVersionManager.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticVersionManager.java
index 609c4d9..b2b241f 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticVersionManager.java
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticVersionManager.java
@@ -17,13 +17,13 @@
 import com.google.common.base.MoreObjects;
 import com.google.common.primitives.Ints;
 import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.index.Index;
+import com.google.gerrit.index.IndexDefinition;
+import com.google.gerrit.index.Schema;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.index.GerritIndexStatus;
-import com.google.gerrit.server.index.Index;
-import com.google.gerrit.server.index.IndexDefinition;
 import com.google.gerrit.server.index.OnlineUpgradeListener;
-import com.google.gerrit.server.index.Schema;
 import com.google.gerrit.server.index.VersionManager;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
diff --git a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticTestUtils.java b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticTestUtils.java
index ece9edd..694348f 100644
--- a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticTestUtils.java
+++ b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticTestUtils.java
@@ -26,10 +26,10 @@
 import com.google.gerrit.elasticsearch.ElasticAccountIndex.AccountMapping;
 import com.google.gerrit.elasticsearch.ElasticChangeIndex.ChangeMapping;
 import com.google.gerrit.elasticsearch.ElasticGroupIndex.GroupMapping;
+import com.google.gerrit.index.Schema;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.index.IndexModule.IndexType;
-import com.google.gerrit.server.index.Schema;
 import com.google.gerrit.server.index.account.AccountSchemaDefinitions;
 import com.google.gerrit.server.index.change.ChangeSchemaDefinitions;
 import com.google.gerrit.server.index.group.GroupSchemaDefinitions;
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/PluginInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/PluginInfo.java
index bcb957e..0df6235 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/PluginInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/PluginInfo.java
@@ -18,12 +18,14 @@
   public final String id;
   public final String version;
   public final String indexUrl;
+  public final String filename;
   public final Boolean disabled;
 
-  public PluginInfo(String id, String version, String indexUrl, Boolean disabled) {
+  public PluginInfo(String id, String version, String indexUrl, String filename, Boolean disabled) {
     this.id = id;
     this.version = version;
     this.indexUrl = indexUrl;
+    this.filename = filename;
     this.disabled = disabled;
   }
 }
diff --git a/gerrit-httpd/BUILD b/gerrit-httpd/BUILD
index 57ecd26..dbca10c 100644
--- a/gerrit-httpd/BUILD
+++ b/gerrit-httpd/BUILD
@@ -16,16 +16,17 @@
     resources = RESOURCES,
     visibility = ["//visibility:public"],
     deps = [
-        "//gerrit-antlr:query_exception",
         "//gerrit-common:annotations",
         "//gerrit-common:server",
         "//gerrit-extension-api:api",
         "//gerrit-gwtexpui:linker_server",
         "//gerrit-gwtexpui:server",
+        "//gerrit-index:query_exception",
         "//gerrit-launcher:launcher",
         "//gerrit-patch-jgit:server",
         "//gerrit-prettify:server",
         "//gerrit-reviewdb:server",
+        "//gerrit-server:metrics",
         "//gerrit-server:receive",
         "//gerrit-server:server",
         "//gerrit-util-cli:cli",
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
index 365789c..4f7a5ba 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
@@ -38,6 +38,7 @@
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.SchemaFactory;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 import com.google.inject.Singleton;
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -63,7 +64,7 @@
   private final AccountCache accountCache;
   private final AccountManager accountManager;
   private final SiteHeaderFooter headers;
-  private final InternalAccountQuery accountQuery;
+  private final Provider<InternalAccountQuery> queryProvider;
 
   @Inject
   BecomeAnyAccountLoginServlet(
@@ -73,14 +74,14 @@
       AccountCache ac,
       AccountManager am,
       SiteHeaderFooter shf,
-      InternalAccountQuery aq) {
+      Provider<InternalAccountQuery> qp) {
     webSession = ws;
     schema = sf;
     accounts = a;
     accountCache = ac;
     accountManager = am;
     headers = shf;
-    accountQuery = aq;
+    queryProvider = qp;
   }
 
   @Override
@@ -198,7 +199,8 @@
 
   private AuthResult byUserName(String userName) {
     try {
-      List<AccountState> accountStates = accountQuery.byExternalId(SCHEME_USERNAME, userName);
+      List<AccountState> accountStates =
+          queryProvider.get().byExternalId(SCHEME_USERNAME, userName);
       if (accountStates.isEmpty()) {
         getServletContext().log("No accounts with username " + userName + " found");
         return null;
@@ -217,7 +219,8 @@
   private AuthResult byPreferredEmail(String email) {
     try (ReviewDb db = schema.open()) {
       Optional<Account> match =
-          accountQuery
+          queryProvider
+              .get()
               .byPreferredEmail(email)
               .stream()
               // the index query also matches prefixes, filter those out
diff --git a/gerrit-index/BUILD b/gerrit-index/BUILD
new file mode 100644
index 0000000..41eed60
--- /dev/null
+++ b/gerrit-index/BUILD
@@ -0,0 +1,74 @@
+load("//tools/bzl:genrule2.bzl", "genrule2")
+load("//tools/bzl:junit.bzl", "junit_tests")
+
+QUERY_PARSE_EXCEPTION_SRCS = ["src/main/java/com/google/gerrit/index/query/QueryParseException.java"]
+
+java_library(
+    name = "query_exception",
+    srcs = QUERY_PARSE_EXCEPTION_SRCS,
+    visibility = ["//visibility:public"],
+)
+
+genrule2(
+    name = "query_antlr",
+    srcs = ["src/main/antlr3/com/google/gerrit/index/query/Query.g"],
+    outs = ["query_antlr.srcjar"],
+    cmd = " && ".join([
+        "$(location //lib/antlr:antlr-tool) -o $$TMP $<",
+        "cd $$TMP",
+        "$$ROOT/$(location @bazel_tools//tools/zip:zipper) cC $$ROOT/$@ $$(find *)",
+    ]),
+    tools = [
+        "//lib/antlr:antlr-tool",
+        "@bazel_tools//tools/zip:zipper",
+    ],
+)
+
+java_library(
+    name = "query_parser",
+    srcs = [":query_antlr"],
+    visibility = ["//gerrit-plugin-api:__pkg__"],
+    deps = [
+        ":query_exception",
+        "//lib/antlr:java_runtime",
+    ],
+)
+
+java_library(
+    name = "index",
+    srcs = glob(
+        ["src/main/java/**/*.java"],
+        exclude = QUERY_PARSE_EXCEPTION_SRCS,
+    ),
+    visibility = ["//visibility:public"],
+    deps = [
+        ":query_exception",
+        ":query_parser",
+        "//gerrit-common:annotations",
+        "//gerrit-extension-api:api",
+        "//gerrit-server:metrics",
+        "//lib:guava",
+        "//lib:gwtjsonrpc",
+        "//lib:gwtorm",
+        "//lib/antlr:java_runtime",
+        "//lib/auto:auto-value",
+        "//lib/jgit/org.eclipse.jgit:jgit",
+        "//lib/log:api",
+    ],
+)
+
+junit_tests(
+    name = "index_tests",
+    size = "small",
+    srcs = glob(["src/test/java/**/*.java"]),
+    visibility = ["//visibility:public"],
+    deps = [
+        ":index",
+        ":query_exception",
+        ":query_parser",
+        "//lib:junit",
+        "//lib:truth",
+        "//lib/antlr:java_runtime",
+        "//lib/jgit/org.eclipse.jgit:jgit",
+    ],
+)
diff --git a/gerrit-antlr/src/main/antlr3/com/google/gerrit/server/query/Query.g b/gerrit-index/src/main/antlr3/com/google/gerrit/index/query/Query.g
similarity index 96%
rename from gerrit-antlr/src/main/antlr3/com/google/gerrit/server/query/Query.g
rename to gerrit-index/src/main/antlr3/com/google/gerrit/index/query/Query.g
index d0b5875..953a473 100644
--- a/gerrit-antlr/src/main/antlr3/com/google/gerrit/server/query/Query.g
+++ b/gerrit-index/src/main/antlr3/com/google/gerrit/index/query/Query.g
@@ -26,7 +26,7 @@
 }
 
 @header {
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 }
 @members {
   static class QueryParseInternalException extends RuntimeException {
@@ -55,7 +55,7 @@
     }
   }
 
-  static boolean isSingleWord(final String value) {
+  public static boolean isSingleWord(final String value) {
     try {
       final QueryLexer lexer = new QueryLexer(new ANTLRStringStream(value));
       lexer.mSINGLE_WORD();
@@ -77,7 +77,7 @@
 }
 
 @lexer::header {
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 }
 @lexer::members {
   @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldDef.java b/gerrit-index/src/main/java/com/google/gerrit/index/FieldDef.java
similarity index 98%
rename from gerrit-server/src/main/java/com/google/gerrit/server/index/FieldDef.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/FieldDef.java
index 5e226e2..b1ffac1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldDef.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/FieldDef.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.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldType.java b/gerrit-index/src/main/java/com/google/gerrit/index/FieldType.java
similarity index 97%
rename from gerrit-server/src/main/java/com/google/gerrit/server/index/FieldType.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/FieldType.java
index 820b62a..0db0284 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/FieldType.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/FieldType.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 java.sql.Timestamp;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/Index.java b/gerrit-index/src/main/java/com/google/gerrit/index/Index.java
similarity index 91%
rename from gerrit-server/src/main/java/com/google/gerrit/server/index/Index.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/Index.java
index 3be201a..739c358 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/Index.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/Index.java
@@ -12,11 +12,12 @@
 // 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 com.google.gerrit.server.query.DataSource;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
+import com.google.gerrit.index.query.DataSource;
+import com.google.gerrit.index.query.IndexPredicate;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gwtorm.server.OrmException;
 import java.io.IOException;
 import java.util.List;
@@ -26,8 +27,8 @@
  * Secondary index implementation for arbitrary documents.
  *
  * <p>Documents are inserted into the index and are queried by converting special {@link
- * com.google.gerrit.server.query.Predicate} instances into index-aware predicates that use the
- * index search results as a source.
+ * com.google.gerrit.index.query.Predicate} instances into index-aware predicates that use the index
+ * search results as a source.
  *
  * <p>Implementations must be thread-safe and should batch inserts/updates where appropriate.
  */
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexCollection.java b/gerrit-index/src/main/java/com/google/gerrit/index/IndexCollection.java
similarity index 98%
rename from gerrit-server/src/main/java/com/google/gerrit/server/index/IndexCollection.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/IndexCollection.java
index a887852..77cabd1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexCollection.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/IndexCollection.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 com.google.common.collect.Lists;
 import com.google.gerrit.extensions.events.LifecycleListener;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexConfig.java b/gerrit-index/src/main/java/com/google/gerrit/index/IndexConfig.java
similarity index 98%
rename from gerrit-server/src/main/java/com/google/gerrit/server/index/IndexConfig.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/IndexConfig.java
index 5c3cdf2..b53b59b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexConfig.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/IndexConfig.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.Preconditions.checkArgument;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexDefinition.java b/gerrit-index/src/main/java/com/google/gerrit/index/IndexDefinition.java
similarity index 97%
rename from gerrit-server/src/main/java/com/google/gerrit/server/index/IndexDefinition.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/IndexDefinition.java
index 0d42ee5..f283bf1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexDefinition.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/IndexDefinition.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 com.google.common.collect.ImmutableSortedMap;
 import com.google.gerrit.common.Nullable;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexRewriter.java b/gerrit-index/src/main/java/com/google/gerrit/index/IndexRewriter.java
similarity index 82%
rename from gerrit-server/src/main/java/com/google/gerrit/server/index/IndexRewriter.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/IndexRewriter.java
index 5c1d838..4d6a35b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexRewriter.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/IndexRewriter.java
@@ -12,10 +12,10 @@
 // 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 com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 
 public interface IndexRewriter<T> {
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexedQuery.java b/gerrit-index/src/main/java/com/google/gerrit/index/IndexedQuery.java
similarity index 92%
rename from gerrit-server/src/main/java/com/google/gerrit/server/index/IndexedQuery.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/IndexedQuery.java
index b8f21f5..050b4a9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexedQuery.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/IndexedQuery.java
@@ -12,14 +12,15 @@
 // 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 com.google.common.base.MoreObjects;
 import com.google.common.collect.ImmutableList;
-import com.google.gerrit.server.query.DataSource;
-import com.google.gerrit.server.query.Paginated;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
+import com.google.gerrit.index.query.DataSource;
+import com.google.gerrit.index.query.IndexPredicate;
+import com.google.gerrit.index.query.Paginated;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.ResultSet;
 import java.util.Collection;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/QueryOptions.java b/gerrit-index/src/main/java/com/google/gerrit/index/QueryOptions.java
similarity index 97%
rename from gerrit-server/src/main/java/com/google/gerrit/server/index/QueryOptions.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/QueryOptions.java
index a26b0ac..b57fb5f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/QueryOptions.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/QueryOptions.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.Preconditions.checkArgument;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/Schema.java b/gerrit-index/src/main/java/com/google/gerrit/index/Schema.java
similarity index 98%
rename from gerrit-server/src/main/java/com/google/gerrit/server/index/Schema.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/Schema.java
index d14a37f..3070951 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/Schema.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/Schema.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.Preconditions.checkState;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/SchemaDefinitions.java b/gerrit-index/src/main/java/com/google/gerrit/index/SchemaDefinitions.java
similarity index 97%
rename from gerrit-server/src/main/java/com/google/gerrit/server/index/SchemaDefinitions.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/SchemaDefinitions.java
index 261734d..f9c690c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/SchemaDefinitions.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/SchemaDefinitions.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.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/SchemaUtil.java b/gerrit-index/src/main/java/com/google/gerrit/index/SchemaUtil.java
similarity index 98%
rename from gerrit-server/src/main/java/com/google/gerrit/server/index/SchemaUtil.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/SchemaUtil.java
index ea33190..c59f251 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/SchemaUtil.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/SchemaUtil.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.Preconditions.checkArgument;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/SiteIndexer.java b/gerrit-index/src/main/java/com/google/gerrit/index/SiteIndexer.java
similarity index 98%
rename from gerrit-server/src/main/java/com/google/gerrit/server/index/SiteIndexer.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/SiteIndexer.java
index 0d84be7..4ad0827 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/SiteIndexer.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/SiteIndexer.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.Preconditions.checkNotNull;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/AndPredicate.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/AndPredicate.java
similarity index 98%
rename from gerrit-server/src/main/java/com/google/gerrit/server/query/AndPredicate.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/query/AndPredicate.java
index 7d99052..7fba05f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/AndPredicate.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/AndPredicate.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 
 import static com.google.common.base.Preconditions.checkState;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/AndSource.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/AndSource.java
similarity index 98%
rename from gerrit-server/src/main/java/com/google/gerrit/server/query/AndSource.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/query/AndSource.java
index dcd8a66..16620b3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/AndSource.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/AndSource.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 
 import static com.google.common.base.Preconditions.checkArgument;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/DataSource.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/DataSource.java
similarity index 95%
rename from gerrit-server/src/main/java/com/google/gerrit/server/query/DataSource.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/query/DataSource.java
index 8a1718d..77dcca2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/DataSource.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/DataSource.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.ResultSet;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexPredicate.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/IndexPredicate.java
similarity index 90%
rename from gerrit-server/src/main/java/com/google/gerrit/server/index/IndexPredicate.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/query/IndexPredicate.java
index ff9ff03..7811a32 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexPredicate.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/IndexPredicate.java
@@ -12,9 +12,10 @@
 // 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.query;
 
-import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.FieldType;
 
 /** Index-aware predicate that includes a field type annotation. */
 public abstract class IndexPredicate<I> extends OperatorPredicate<I> {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/IntPredicate.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/IntPredicate.java
similarity index 97%
rename from gerrit-server/src/main/java/com/google/gerrit/server/query/IntPredicate.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/query/IntPredicate.java
index 42dcff8..16e59e7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/IntPredicate.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/IntPredicate.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 
 /** Predicate to filter a field by matching integer value. */
 public abstract class IntPredicate<T> extends OperatorPredicate<T> {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IntegerRangePredicate.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/IntegerRangePredicate.java
similarity index 88%
rename from gerrit-server/src/main/java/com/google/gerrit/server/index/IntegerRangePredicate.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/query/IntegerRangePredicate.java
index 5832694..66351a8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IntegerRangePredicate.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/IntegerRangePredicate.java
@@ -12,11 +12,10 @@
 // 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.query;
 
-import com.google.gerrit.server.query.QueryParseException;
-import com.google.gerrit.server.util.RangeUtil;
-import com.google.gerrit.server.util.RangeUtil.Range;
+import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.query.RangeUtil.Range;
 import com.google.gwtorm.server.OrmException;
 
 public abstract class IntegerRangePredicate<T> extends IndexPredicate<T> {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/InternalQuery.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/InternalQuery.java
similarity index 88%
rename from gerrit-server/src/main/java/com/google/gerrit/server/query/InternalQuery.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/query/InternalQuery.java
index 87772d2..0f8948b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/InternalQuery.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/InternalQuery.java
@@ -12,14 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
-import com.google.gerrit.server.index.Index;
-import com.google.gerrit.server.index.IndexCollection;
-import com.google.gerrit.server.index.IndexConfig;
-import com.google.gerrit.server.index.Schema;
+import com.google.gerrit.index.Index;
+import com.google.gerrit.index.IndexCollection;
+import com.google.gerrit.index.IndexConfig;
+import com.google.gerrit.index.Schema;
 import com.google.gwtorm.server.OrmException;
 import java.util.List;
 import java.util.Set;
@@ -30,6 +30,9 @@
  * <p>By default, visibility of returned entities is not enforced (unlike in {@link
  * QueryProcessor}). The methods in this class are not typically used by user-facing paths, but
  * rather by internal callers that need to process all matching results.
+ *
+ * <p>Instances are one-time-use. Other singleton classes should inject a Provider rather than
+ * holding on to a single instance.
  */
 public class InternalQuery<T> {
   private final QueryProcessor<T> queryProcessor;
@@ -47,7 +50,7 @@
   }
 
   public InternalQuery<T> setLimit(int n) {
-    queryProcessor.setLimit(n);
+    queryProcessor.setUserProvidedLimit(n);
     return this;
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexRewriter.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/IsVisibleToPredicate.java
similarity index 68%
copy from gerrit-server/src/main/java/com/google/gerrit/server/index/IndexRewriter.java
copy to gerrit-index/src/main/java/com/google/gerrit/index/query/IsVisibleToPredicate.java
index 5c1d838..9cc6f50 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexRewriter.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/IsVisibleToPredicate.java
@@ -12,12 +12,10 @@
 // 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.query;
 
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
-
-public interface IndexRewriter<T> {
-
-  Predicate<T> rewrite(Predicate<T> in, QueryOptions opts) throws QueryParseException;
+public abstract class IsVisibleToPredicate<T> extends OperatorPredicate<T> implements Matchable<T> {
+  public IsVisibleToPredicate(String name, String value) {
+    super(name, value);
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/LimitPredicate.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/LimitPredicate.java
similarity index 96%
rename from gerrit-server/src/main/java/com/google/gerrit/server/query/LimitPredicate.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/query/LimitPredicate.java
index 7c38e5a8..23e0f6d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/LimitPredicate.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/LimitPredicate.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 
 public class LimitPredicate<T> extends IntPredicate<T> implements Matchable<T> {
   @SuppressWarnings("unchecked")
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/Matchable.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/Matchable.java
similarity index 95%
rename from gerrit-server/src/main/java/com/google/gerrit/server/query/Matchable.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/query/Matchable.java
index b37e112..3d07943 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/Matchable.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/Matchable.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 
 import com.google.gwtorm.server.OrmException;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/NotPredicate.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/NotPredicate.java
similarity index 98%
rename from gerrit-server/src/main/java/com/google/gerrit/server/query/NotPredicate.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/query/NotPredicate.java
index 306b4cb..750759d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/NotPredicate.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/NotPredicate.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 
 import static com.google.common.base.Preconditions.checkState;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/OperatorPredicate.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/OperatorPredicate.java
similarity index 97%
rename from gerrit-server/src/main/java/com/google/gerrit/server/query/OperatorPredicate.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/query/OperatorPredicate.java
index 254aa99..368ee24 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/OperatorPredicate.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/OperatorPredicate.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 
 import java.util.Collection;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/OrPredicate.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/OrPredicate.java
similarity index 98%
rename from gerrit-server/src/main/java/com/google/gerrit/server/query/OrPredicate.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/query/OrPredicate.java
index f357344..8c3ed1c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/OrPredicate.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/OrPredicate.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 
 import static com.google.common.base.Preconditions.checkState;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/Paginated.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/Paginated.java
similarity index 89%
rename from gerrit-server/src/main/java/com/google/gerrit/server/query/Paginated.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/query/Paginated.java
index a51555e..20f65dc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/Paginated.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/Paginated.java
@@ -12,9 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 
-import com.google.gerrit.server.index.QueryOptions;
+import com.google.gerrit.index.QueryOptions;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.ResultSet;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/Predicate.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/Predicate.java
similarity index 98%
rename from gerrit-server/src/main/java/com/google/gerrit/server/query/Predicate.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/query/Predicate.java
index c5b2b96..ca74a52 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/Predicate.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/Predicate.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 
 import static com.google.common.base.Preconditions.checkState;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryBuilder.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/QueryBuilder.java
similarity index 93%
rename from gerrit-server/src/main/java/com/google/gerrit/server/query/QueryBuilder.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/query/QueryBuilder.java
index c1fecba..c6c39c3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryBuilder.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/QueryBuilder.java
@@ -12,18 +12,18 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 
-import static com.google.gerrit.server.query.Predicate.and;
-import static com.google.gerrit.server.query.Predicate.not;
-import static com.google.gerrit.server.query.Predicate.or;
-import static com.google.gerrit.server.query.QueryParser.AND;
-import static com.google.gerrit.server.query.QueryParser.DEFAULT_FIELD;
-import static com.google.gerrit.server.query.QueryParser.EXACT_PHRASE;
-import static com.google.gerrit.server.query.QueryParser.FIELD_NAME;
-import static com.google.gerrit.server.query.QueryParser.NOT;
-import static com.google.gerrit.server.query.QueryParser.OR;
-import static com.google.gerrit.server.query.QueryParser.SINGLE_WORD;
+import static com.google.gerrit.index.query.Predicate.and;
+import static com.google.gerrit.index.query.Predicate.not;
+import static com.google.gerrit.index.query.Predicate.or;
+import static com.google.gerrit.index.query.QueryParser.AND;
+import static com.google.gerrit.index.query.QueryParser.DEFAULT_FIELD;
+import static com.google.gerrit.index.query.QueryParser.EXACT_PHRASE;
+import static com.google.gerrit.index.query.QueryParser.FIELD_NAME;
+import static com.google.gerrit.index.query.QueryParser.NOT;
+import static com.google.gerrit.index.query.QueryParser.OR;
+import static com.google.gerrit.index.query.QueryParser.SINGLE_WORD;
 
 import com.google.common.base.Strings;
 import java.lang.annotation.ElementType;
diff --git a/gerrit-antlr/src/main/java/com/google/gerrit/server/query/QueryParseException.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/QueryParseException.java
similarity index 95%
rename from gerrit-antlr/src/main/java/com/google/gerrit/server/query/QueryParseException.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/query/QueryParseException.java
index 9495d19..6a62b9e 100644
--- a/gerrit-antlr/src/main/java/com/google/gerrit/server/query/QueryParseException.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/QueryParseException.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 
 /**
  * Exception thrown when a search query is invalid.
diff --git a/gerrit-index/src/main/java/com/google/gerrit/index/query/QueryProcessor.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/QueryProcessor.java
new file mode 100644
index 0000000..b318199
--- /dev/null
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/QueryProcessor.java
@@ -0,0 +1,335 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.index.query;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.collect.ImmutableList.toImmutableList;
+
+import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Ordering;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.index.Index;
+import com.google.gerrit.index.IndexCollection;
+import com.google.gerrit.index.IndexConfig;
+import com.google.gerrit.index.IndexRewriter;
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.SchemaDefinitions;
+import com.google.gerrit.metrics.Description;
+import com.google.gerrit.metrics.Field;
+import com.google.gerrit.metrics.MetricMaker;
+import com.google.gerrit.metrics.Timer1;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.OrmRuntimeException;
+import com.google.gwtorm.server.ResultSet;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.IntSupplier;
+import java.util.stream.IntStream;
+
+/**
+ * Lower-level implementation for executing a single query over a secondary index.
+ *
+ * <p>Instances are one-time-use. Other singleton classes should inject a Provider rather than
+ * holding on to a single instance.
+ */
+public abstract class QueryProcessor<T> {
+  protected static class Metrics {
+    final Timer1<String> executionTime;
+
+    Metrics(MetricMaker metricMaker) {
+      Field<String> index = Field.ofString("index", "index name");
+      executionTime =
+          metricMaker.newTimer(
+              "query/query_latency",
+              new Description("Successful query latency, accumulated over the life of the process")
+                  .setCumulative()
+                  .setUnit(Description.Units.MILLISECONDS),
+              index);
+    }
+  }
+
+  private final Metrics metrics;
+  private final SchemaDefinitions<T> schemaDef;
+  private final IndexConfig indexConfig;
+  private final IndexCollection<?, T, ? extends Index<?, T>> indexes;
+  private final IndexRewriter<T> rewriter;
+  private final String limitField;
+  private final IntSupplier permittedLimit;
+
+  // This class is not generally thread-safe, but programmer error may result in it being shared
+  // across threads. At least ensure the bit for checking if it's been used is threadsafe.
+  private final AtomicBoolean used;
+
+  protected int start;
+
+  private boolean enforceVisibility = true;
+  private int userProvidedLimit;
+  private Set<String> requestedFields;
+
+  protected QueryProcessor(
+      MetricMaker metricMaker,
+      SchemaDefinitions<T> schemaDef,
+      IndexConfig indexConfig,
+      IndexCollection<?, T, ? extends Index<?, T>> indexes,
+      IndexRewriter<T> rewriter,
+      String limitField,
+      IntSupplier permittedLimit) {
+    this.metrics = new Metrics(metricMaker);
+    this.schemaDef = schemaDef;
+    this.indexConfig = indexConfig;
+    this.indexes = indexes;
+    this.rewriter = rewriter;
+    this.limitField = limitField;
+    this.permittedLimit = permittedLimit;
+    this.used = new AtomicBoolean(false);
+  }
+
+  public QueryProcessor<T> setStart(int n) {
+    start = n;
+    return this;
+  }
+
+  /**
+   * Specify whether to enforce visibility by filtering out results that are not visible to the
+   * user.
+   *
+   * <p>Enforcing visibility may have performance consequences, as the index system may need to
+   * post-filter a large number of results to fill even a modest limit.
+   *
+   * <p>If visibility is enforced, the user's {@code queryLimit} global capability is also used to
+   * bound the total number of results. If this capability is non-positive, this results in the
+   * entire query processor being {@link #isDisabled() disabled}.
+   *
+   * @param enforce whether to enforce visibility.
+   * @return this.
+   */
+  public QueryProcessor<T> enforceVisibility(boolean enforce) {
+    enforceVisibility = enforce;
+    return this;
+  }
+
+  /**
+   * Set an end-user-provided limit on the number of results returned.
+   *
+   * <p>Since this limit is provided by an end user, it may exceed the limit that they are
+   * authorized to use. This is allowed; the processor will take multiple possible limits into
+   * account and choose the one that makes the most sense.
+   *
+   * @param n limit; zero or negative means no limit.
+   * @return this.
+   */
+  public QueryProcessor<T> setUserProvidedLimit(int n) {
+    userProvidedLimit = n;
+    return this;
+  }
+
+  public QueryProcessor<T> setRequestedFields(Set<String> fields) {
+    requestedFields = fields;
+    return this;
+  }
+
+  /**
+   * Query for entities that match a structured query.
+   *
+   * @see #query(List)
+   * @param query the query.
+   * @return results of the query.
+   */
+  public QueryResult<T> query(Predicate<T> query) throws OrmException, QueryParseException {
+    return query(ImmutableList.of(query)).get(0);
+  }
+
+  /**
+   * Perform multiple queries in parallel.
+   *
+   * <p>If querying is disabled, short-circuits the index and returns empty results. Callers that
+   * wish to distinguish this case from a query returning no results from the index may call {@link
+   * #isDisabled()} themselves.
+   *
+   * @param queries list of queries.
+   * @return results of the queries, one QueryResult per input query, in the same order as the
+   *     input.
+   */
+  public List<QueryResult<T>> query(List<Predicate<T>> queries)
+      throws OrmException, QueryParseException {
+    try {
+      return query(null, queries);
+    } catch (OrmRuntimeException e) {
+      throw new OrmException(e.getMessage(), e);
+    } catch (OrmException e) {
+      if (e.getCause() != null) {
+        Throwables.throwIfInstanceOf(e.getCause(), QueryParseException.class);
+      }
+      throw e;
+    }
+  }
+
+  private List<QueryResult<T>> query(
+      @Nullable List<String> queryStrings, List<Predicate<T>> queries)
+      throws OrmException, QueryParseException {
+    long startNanos = System.nanoTime();
+    checkState(!used.getAndSet(true), "%s has already been used", getClass().getSimpleName());
+    int cnt = queries.size();
+    if (queryStrings != null) {
+      int qs = queryStrings.size();
+      checkArgument(qs == cnt, "got %s query strings but %s predicates", qs, cnt);
+    }
+    if (cnt == 0) {
+      return ImmutableList.of();
+    }
+    if (isDisabled()) {
+      return disabledResults(queryStrings, queries);
+    }
+
+    // Parse and rewrite all queries.
+    List<Integer> limits = new ArrayList<>(cnt);
+    List<Predicate<T>> predicates = new ArrayList<>(cnt);
+    List<DataSource<T>> sources = new ArrayList<>(cnt);
+    for (Predicate<T> q : queries) {
+      int limit = getEffectiveLimit(q);
+      limits.add(limit);
+
+      if (limit == getBackendSupportedLimit()) {
+        limit--;
+      }
+
+      int page = (start / limit) + 1;
+      if (page > indexConfig.maxPages()) {
+        throw new QueryParseException(
+            "Cannot go beyond page " + indexConfig.maxPages() + " of results");
+      }
+
+      // Always bump limit by 1, even if this results in exceeding the permitted
+      // max for this user. The only way to see if there are more entities is to
+      // ask for one more result from the query.
+      QueryOptions opts = createOptions(indexConfig, start, limit + 1, getRequestedFields());
+      Predicate<T> pred = rewriter.rewrite(q, opts);
+      if (enforceVisibility) {
+        pred = enforceVisibility(pred);
+      }
+      predicates.add(pred);
+
+      @SuppressWarnings("unchecked")
+      DataSource<T> s = (DataSource<T>) pred;
+      sources.add(s);
+    }
+
+    // Run each query asynchronously, if supported.
+    List<ResultSet<T>> matches = new ArrayList<>(cnt);
+    for (DataSource<T> s : sources) {
+      matches.add(s.read());
+    }
+
+    List<QueryResult<T>> out = new ArrayList<>(cnt);
+    for (int i = 0; i < cnt; i++) {
+      out.add(
+          QueryResult.create(
+              queryStrings != null ? queryStrings.get(i) : null,
+              predicates.get(i),
+              limits.get(i),
+              matches.get(i).toList()));
+    }
+
+    // Only measure successful queries that actually touched the index.
+    metrics.executionTime.record(
+        schemaDef.getName(), System.nanoTime() - startNanos, TimeUnit.NANOSECONDS);
+    return out;
+  }
+
+  private static <T> ImmutableList<QueryResult<T>> disabledResults(
+      List<String> queryStrings, List<Predicate<T>> queries) {
+    return IntStream.range(0, queries.size())
+        .mapToObj(
+            i ->
+                QueryResult.create(
+                    queryStrings != null ? queryStrings.get(i) : null,
+                    queries.get(i),
+                    0,
+                    ImmutableList.of()))
+        .collect(toImmutableList());
+  }
+
+  protected QueryOptions createOptions(
+      IndexConfig indexConfig, int start, int limit, Set<String> requestedFields) {
+    return QueryOptions.create(indexConfig, start, limit, requestedFields);
+  }
+
+  /**
+   * Invoked after the query was rewritten. Subclasses must overwrite this method to filter out
+   * results that are not visible to the calling user.
+   *
+   * @param pred the query
+   * @return the modified query
+   */
+  protected abstract Predicate<T> enforceVisibility(Predicate<T> pred);
+
+  private Set<String> getRequestedFields() {
+    if (requestedFields != null) {
+      return requestedFields;
+    }
+    Index<?, T> index = indexes.getSearchIndex();
+    return index != null ? index.getSchema().getStoredFields().keySet() : ImmutableSet.<String>of();
+  }
+
+  /**
+   * Check whether querying should be disabled.
+   *
+   * <p>Currently, the only condition that can disable the whole query processor is if both {@link
+   * #enforceVisibility(boolean) visibility is enforced} and the user has a non-positive maximum
+   * value for the {@code queryLimit} capability.
+   *
+   * <p>If querying is disabled, all calls to {@link #query(Predicate)} and {@link #query(List)}
+   * will return empty results. This method can be used if callers wish to distinguish this case
+   * from a query returning no results from the index.
+   *
+   * @return true if querying should be disabled.
+   */
+  public boolean isDisabled() {
+    return enforceVisibility && getPermittedLimit() <= 0;
+  }
+
+  private int getPermittedLimit() {
+    return enforceVisibility ? permittedLimit.getAsInt() : Integer.MAX_VALUE;
+  }
+
+  private int getBackendSupportedLimit() {
+    return indexConfig.maxLimit();
+  }
+
+  private int getEffectiveLimit(Predicate<T> p) {
+    List<Integer> possibleLimits = new ArrayList<>(4);
+    possibleLimits.add(getBackendSupportedLimit());
+    possibleLimits.add(getPermittedLimit());
+    if (userProvidedLimit > 0) {
+      possibleLimits.add(userProvidedLimit);
+    }
+    if (limitField != null) {
+      Integer limitFromPredicate = LimitPredicate.getLimit(limitField, p);
+      if (limitFromPredicate != null) {
+        possibleLimits.add(limitFromPredicate);
+      }
+    }
+    int result = Ordering.natural().min(possibleLimits);
+    // Should have short-circuited from #query or thrown some other exception before getting here.
+    checkState(result > 0, "effective limit should be positive");
+    return result;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryResult.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/QueryResult.java
similarity index 85%
rename from gerrit-server/src/main/java/com/google/gerrit/server/query/QueryResult.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/query/QueryResult.java
index f86eb707..341e2b6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryResult.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/QueryResult.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 
 import com.google.auto.value.AutoValue;
 import com.google.gerrit.common.Nullable;
@@ -21,7 +21,7 @@
 /** Results of a query over entities. */
 @AutoValue
 public abstract class QueryResult<T> {
-  static <T> QueryResult<T> create(
+  public static <T> QueryResult<T> create(
       @Nullable String query, Predicate<T> predicate, int limit, List<T> entites) {
     boolean more;
     if (entites.size() > limit) {
@@ -44,8 +44,8 @@
   public abstract List<T> entities();
 
   /**
-   * @return whether the query could be retried with {@link QueryProcessor#setStart(int)} to produce
-   *     more results. Never true if {@link #entities()} is empty.
+   * @return whether the query could be retried with a higher start/limit to produce more results.
+   *     Never true if {@link #entities()} is empty.
    */
   public abstract boolean more();
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/RangeUtil.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/RangeUtil.java
similarity index 98%
rename from gerrit-server/src/main/java/com/google/gerrit/server/util/RangeUtil.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/query/RangeUtil.java
index f7f2cff..1f22f36 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/util/RangeUtil.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/RangeUtil.java
@@ -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.index.query;
 
 import com.google.common.primitives.Ints;
 import com.google.gerrit.common.Nullable;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/RegexPredicate.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/RegexPredicate.java
similarity index 91%
rename from gerrit-server/src/main/java/com/google/gerrit/server/index/RegexPredicate.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/query/RegexPredicate.java
index b73674d..60a2a9e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/RegexPredicate.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/RegexPredicate.java
@@ -12,7 +12,9 @@
 // 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.query;
+
+import com.google.gerrit.index.FieldDef;
 
 public abstract class RegexPredicate<I> extends IndexPredicate<I> {
   protected RegexPredicate(FieldDef<I, ?> def, String value) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/TimestampRangePredicate.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/TimestampRangePredicate.java
similarity index 92%
rename from gerrit-server/src/main/java/com/google/gerrit/server/index/TimestampRangePredicate.java
rename to gerrit-index/src/main/java/com/google/gerrit/index/query/TimestampRangePredicate.java
index 7e194b7..edc2120 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/TimestampRangePredicate.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/TimestampRangePredicate.java
@@ -12,9 +12,9 @@
 // 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.query;
 
-import com.google.gerrit.server.query.QueryParseException;
+import com.google.gerrit.index.FieldDef;
 import com.google.gwtjsonrpc.common.JavaSqlTimestampHelper;
 import java.sql.Timestamp;
 import java.util.Date;
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/index/SchemaUtilTest.java b/gerrit-index/src/test/java/com/google/gerrit/index/SchemaUtilTest.java
similarity index 87%
rename from gerrit-server/src/test/java/com/google/gerrit/server/index/SchemaUtilTest.java
rename to gerrit-index/src/test/java/com/google/gerrit/index/SchemaUtilTest.java
index 1dc6a469..3c0bbe0 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/index/SchemaUtilTest.java
+++ b/gerrit-index/src/test/java/com/google/gerrit/index/SchemaUtilTest.java
@@ -12,19 +12,22 @@
 // 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.truth.Truth.assertThat;
-import static com.google.gerrit.server.index.SchemaUtil.getNameParts;
-import static com.google.gerrit.server.index.SchemaUtil.getPersonParts;
-import static com.google.gerrit.server.index.SchemaUtil.schema;
+import static com.google.gerrit.index.SchemaUtil.getNameParts;
+import static com.google.gerrit.index.SchemaUtil.getPersonParts;
+import static com.google.gerrit.index.SchemaUtil.schema;
 
-import com.google.gerrit.testutil.GerritBaseTests;
 import java.util.Map;
 import org.eclipse.jgit.lib.PersonIdent;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
 
-public class SchemaUtilTest extends GerritBaseTests {
+public class SchemaUtilTest {
+  @Rule public ExpectedException exception = ExpectedException.none();
+
   static class TestSchemas {
     static final Schema<String> V1 = schema();
     static final Schema<String> V2 = schema();
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/AndPredicateTest.java b/gerrit-index/src/test/java/com/google/gerrit/index/query/AndPredicateTest.java
similarity index 97%
rename from gerrit-server/src/test/java/com/google/gerrit/server/query/AndPredicateTest.java
rename to gerrit-index/src/test/java/com/google/gerrit/index/query/AndPredicateTest.java
index cc59081..21098b3 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/AndPredicateTest.java
+++ b/gerrit-index/src/test/java/com/google/gerrit/index/query/AndPredicateTest.java
@@ -12,10 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 
 import static com.google.common.collect.ImmutableList.of;
-import static com.google.gerrit.server.query.Predicate.and;
+import static com.google.gerrit.index.query.Predicate.and;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotSame;
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/FieldPredicateTest.java b/gerrit-index/src/test/java/com/google/gerrit/index/query/FieldPredicateTest.java
similarity index 97%
rename from gerrit-server/src/test/java/com/google/gerrit/server/query/FieldPredicateTest.java
rename to gerrit-index/src/test/java/com/google/gerrit/index/query/FieldPredicateTest.java
index 6a72fce..8fe90fc 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/FieldPredicateTest.java
+++ b/gerrit-index/src/test/java/com/google/gerrit/index/query/FieldPredicateTest.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/NotPredicateTest.java b/gerrit-index/src/test/java/com/google/gerrit/index/query/NotPredicateTest.java
similarity index 95%
rename from gerrit-server/src/test/java/com/google/gerrit/server/query/NotPredicateTest.java
rename to gerrit-index/src/test/java/com/google/gerrit/index/query/NotPredicateTest.java
index f70b8fc..88d8349 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/NotPredicateTest.java
+++ b/gerrit-index/src/test/java/com/google/gerrit/index/query/NotPredicateTest.java
@@ -12,10 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 
-import static com.google.gerrit.server.query.Predicate.and;
-import static com.google.gerrit.server.query.Predicate.not;
+import static com.google.gerrit.index.query.Predicate.and;
+import static com.google.gerrit.index.query.Predicate.not;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotSame;
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/OrPredicateTest.java b/gerrit-index/src/test/java/com/google/gerrit/index/query/OrPredicateTest.java
similarity index 97%
rename from gerrit-server/src/test/java/com/google/gerrit/server/query/OrPredicateTest.java
rename to gerrit-index/src/test/java/com/google/gerrit/index/query/OrPredicateTest.java
index 7d97a0d..255a3f8 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/OrPredicateTest.java
+++ b/gerrit-index/src/test/java/com/google/gerrit/index/query/OrPredicateTest.java
@@ -12,10 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 
 import static com.google.common.collect.ImmutableList.of;
-import static com.google.gerrit.server.query.Predicate.or;
+import static com.google.gerrit.index.query.Predicate.or;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotSame;
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/PredicateTest.java b/gerrit-index/src/test/java/com/google/gerrit/index/query/PredicateTest.java
similarity index 81%
rename from gerrit-server/src/test/java/com/google/gerrit/server/query/PredicateTest.java
rename to gerrit-index/src/test/java/com/google/gerrit/index/query/PredicateTest.java
index 2d13876..6979d82 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/PredicateTest.java
+++ b/gerrit-index/src/test/java/com/google/gerrit/index/query/PredicateTest.java
@@ -12,13 +12,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 
-import com.google.gerrit.testutil.GerritBaseTests;
 import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.rules.ExpectedException;
 
 @Ignore
-public abstract class PredicateTest extends GerritBaseTests {
+public abstract class PredicateTest {
+  @Rule public ExpectedException exception = ExpectedException.none();
+
   protected static final class TestPredicate extends OperatorPredicate<String> {
     protected TestPredicate(String name, String value) {
       super(name, value);
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/QueryParserTest.java b/gerrit-index/src/test/java/com/google/gerrit/index/query/QueryParserTest.java
similarity index 96%
rename from gerrit-server/src/test/java/com/google/gerrit/server/query/QueryParserTest.java
rename to gerrit-index/src/test/java/com/google/gerrit/index/query/QueryParserTest.java
index 3a38a50..448f292 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/QueryParserTest.java
+++ b/gerrit-index/src/test/java/com/google/gerrit/index/query/QueryParserTest.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.query;
+package com.google.gerrit.index.query;
 
 import static org.junit.Assert.assertEquals;
 
diff --git a/gerrit-lucene/BUILD b/gerrit-lucene/BUILD
index 5f87b4e..aae5000 100644
--- a/gerrit-lucene/BUILD
+++ b/gerrit-lucene/BUILD
@@ -7,7 +7,8 @@
     srcs = QUERY_BUILDER,
     visibility = ["//visibility:public"],
     deps = [
-        "//gerrit-antlr:query_exception",
+        "//gerrit-index:index",
+        "//gerrit-index:query_exception",
         "//gerrit-reviewdb:server",
         "//gerrit-server:server",
         "//lib:guava",
@@ -25,10 +26,11 @@
     visibility = ["//visibility:public"],
     deps = [
         ":query_builder",
-        "//gerrit-antlr:query_exception",
         "//gerrit-common:annotations",
         "//gerrit-common:server",
         "//gerrit-extension-api:api",
+        "//gerrit-index:index",
+        "//gerrit-index:query_exception",
         "//gerrit-reviewdb:server",
         "//gerrit-server:server",
         "//lib:guava",
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/AbstractLuceneIndex.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/AbstractLuceneIndex.java
index ad72f70..9d474dd 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/AbstractLuceneIndex.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/AbstractLuceneIndex.java
@@ -25,13 +25,13 @@
 import com.google.common.util.concurrent.ListeningExecutorService;
 import com.google.common.util.concurrent.MoreExecutors;
 import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.FieldType;
+import com.google.gerrit.index.Index;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.Schema.Values;
 import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.index.FieldDef;
-import com.google.gerrit.server.index.FieldType;
-import com.google.gerrit.server.index.Index;
 import com.google.gerrit.server.index.IndexUtils;
-import com.google.gerrit.server.index.Schema;
-import com.google.gerrit.server.index.Schema.Values;
 import java.io.IOException;
 import java.sql.Timestamp;
 import java.util.Set;
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/ChangeSubIndex.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/ChangeSubIndex.java
index 58117b8..126c79f 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/ChangeSubIndex.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/ChangeSubIndex.java
@@ -19,17 +19,17 @@
 import static com.google.gerrit.lucene.LuceneChangeIndex.UPDATED_SORT_FIELD;
 import static com.google.gerrit.server.index.change.ChangeSchemaDefinitions.NAME;
 
+import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.Schema.Values;
+import com.google.gerrit.index.query.DataSource;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.index.FieldDef;
-import com.google.gerrit.server.index.QueryOptions;
-import com.google.gerrit.server.index.Schema;
-import com.google.gerrit.server.index.Schema.Values;
 import com.google.gerrit.server.index.change.ChangeField;
 import com.google.gerrit.server.index.change.ChangeIndex;
-import com.google.gerrit.server.query.DataSource;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gerrit.server.query.change.ChangeData;
 import java.io.IOException;
 import java.nio.file.Path;
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneAccountIndex.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneAccountIndex.java
index b08301f..7a4cd40 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneAccountIndex.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneAccountIndex.java
@@ -16,18 +16,18 @@
 
 import static com.google.gerrit.server.index.account.AccountField.ID;
 
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.query.DataSource;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 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.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.index.IndexUtils;
-import com.google.gerrit.server.index.QueryOptions;
-import com.google.gerrit.server.index.Schema;
 import com.google.gerrit.server.index.account.AccountIndex;
-import com.google.gerrit.server.query.DataSource;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.ResultSet;
 import com.google.inject.Inject;
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
index 5bb811e..80078dc 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneChangeIndex.java
@@ -36,6 +36,10 @@
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListeningExecutorService;
 import com.google.common.util.concurrent.MoreExecutors;
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
@@ -46,14 +50,10 @@
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.index.IndexExecutor;
 import com.google.gerrit.server.index.IndexUtils;
-import com.google.gerrit.server.index.QueryOptions;
-import com.google.gerrit.server.index.Schema;
 import com.google.gerrit.server.index.change.ChangeField;
 import com.google.gerrit.server.index.change.ChangeIndex;
 import com.google.gerrit.server.index.change.ChangeIndexRewriter;
 import com.google.gerrit.server.project.SubmitRuleOptions;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.ChangeDataSource;
 import com.google.gwtorm.protobuf.ProtobufCodec;
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneGroupIndex.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneGroupIndex.java
index 21371e2..f08b3df 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneGroupIndex.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneGroupIndex.java
@@ -16,17 +16,17 @@
 
 import static com.google.gerrit.server.index.group.GroupField.UUID;
 
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.query.DataSource;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.account.GroupCache;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.index.IndexUtils;
-import com.google.gerrit.server.index.QueryOptions;
-import com.google.gerrit.server.index.Schema;
 import com.google.gerrit.server.index.group.GroupIndex;
-import com.google.gerrit.server.query.DataSource;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.ResultSet;
 import com.google.inject.Inject;
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java
index b5531d5..d738540 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java
@@ -17,9 +17,9 @@
 import static com.google.common.base.Preconditions.checkArgument;
 
 import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.index.IndexConfig;
 import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.index.IndexConfig;
 import com.google.gerrit.server.index.IndexModule;
 import com.google.gerrit.server.index.OnlineUpgrader;
 import com.google.gerrit.server.index.SingleVersionModule;
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneVersionManager.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneVersionManager.java
index 9e8007c..c7c802f 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneVersionManager.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneVersionManager.java
@@ -16,13 +16,13 @@
 
 import com.google.common.primitives.Ints;
 import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.index.Index;
+import com.google.gerrit.index.IndexDefinition;
+import com.google.gerrit.index.Schema;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.index.GerritIndexStatus;
-import com.google.gerrit.server.index.Index;
-import com.google.gerrit.server.index.IndexDefinition;
 import com.google.gerrit.server.index.OnlineUpgradeListener;
-import com.google.gerrit.server.index.Schema;
 import com.google.gerrit.server.index.VersionManager;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/QueryBuilder.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/QueryBuilder.java
index 35033be..2f2a1cd 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/QueryBuilder.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/QueryBuilder.java
@@ -21,17 +21,17 @@
 import static org.apache.lucene.search.BooleanClause.Occur.SHOULD;
 
 import com.google.common.collect.Lists;
-import com.google.gerrit.server.index.FieldType;
-import com.google.gerrit.server.index.IndexPredicate;
-import com.google.gerrit.server.index.IntegerRangePredicate;
-import com.google.gerrit.server.index.RegexPredicate;
-import com.google.gerrit.server.index.Schema;
-import com.google.gerrit.server.index.TimestampRangePredicate;
-import com.google.gerrit.server.query.AndPredicate;
-import com.google.gerrit.server.query.NotPredicate;
-import com.google.gerrit.server.query.OrPredicate;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
+import com.google.gerrit.index.FieldType;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.query.AndPredicate;
+import com.google.gerrit.index.query.IndexPredicate;
+import com.google.gerrit.index.query.IntegerRangePredicate;
+import com.google.gerrit.index.query.NotPredicate;
+import com.google.gerrit.index.query.OrPredicate;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
+import com.google.gerrit.index.query.RegexPredicate;
+import com.google.gerrit.index.query.TimestampRangePredicate;
 import java.util.Date;
 import java.util.List;
 import org.apache.lucene.analysis.Analyzer;
diff --git a/gerrit-pgm/BUILD b/gerrit-pgm/BUILD
index 24a19d4..1fd3165 100644
--- a/gerrit-pgm/BUILD
+++ b/gerrit-pgm/BUILD
@@ -29,6 +29,7 @@
 
 DEPS = BASE_JETTY_DEPS + [
     "//gerrit-reviewdb:server",
+    "//gerrit-server:metrics",
     "//gerrit-server:module",
     "//gerrit-server:receive",
     "//lib:gwtorm",
@@ -50,6 +51,7 @@
     deps = DEPS + [
         ":init-api",
         ":util",
+        "//gerrit-index:index",
         "//gerrit-elasticsearch:elasticsearch",
         "//gerrit-launcher:launcher",  # We want this dep to be provided_deps
         "//gerrit-lucene:lucene",
@@ -116,6 +118,7 @@
     "//gerrit-cache-h2:cache-h2",
     "//gerrit-elasticsearch:elasticsearch",
     "//gerrit-gpg:gpg",
+    "//gerrit-index:index",
     "//gerrit-lucene:lucene",
     "//gerrit-oauth:oauth",
     "//gerrit-openid:openid",
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java
index 232d71b..bee9928 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java
@@ -22,6 +22,9 @@
 import com.google.gerrit.common.Die;
 import com.google.gerrit.elasticsearch.ElasticIndexModule;
 import com.google.gerrit.extensions.config.FactoryModule;
+import com.google.gerrit.index.Index;
+import com.google.gerrit.index.IndexDefinition;
+import com.google.gerrit.index.SiteIndexer;
 import com.google.gerrit.lifecycle.LifecycleManager;
 import com.google.gerrit.lucene.LuceneIndexModule;
 import com.google.gerrit.pgm.util.BatchProgramModule;
@@ -29,11 +32,8 @@
 import com.google.gerrit.pgm.util.ThreadLimiter;
 import com.google.gerrit.server.change.ChangeResource;
 import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.index.Index;
-import com.google.gerrit.server.index.IndexDefinition;
 import com.google.gerrit.server.index.IndexModule;
 import com.google.gerrit.server.index.IndexModule.IndexType;
-import com.google.gerrit.server.index.SiteIndexer;
 import com.google.gerrit.server.index.change.ChangeSchemaDefinitions;
 import com.google.inject.Inject;
 import com.google.inject.Injector;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/BaseInit.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/BaseInit.java
index 49fd1f9..f750fdb 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/BaseInit.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/BaseInit.java
@@ -73,6 +73,7 @@
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Set;
 import javax.sql.DataSource;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -387,14 +388,25 @@
       schemaUpdater.update(
           new UpdateUI() {
             @Override
-            public void message(String msg) {
-              System.err.println(msg);
+            public void message(String message) {
+              System.err.println(message);
               System.err.flush();
             }
 
             @Override
-            public boolean yesno(boolean def, String msg) {
-              return ui.yesno(def, msg);
+            public boolean yesno(boolean defaultValue, String message) {
+              return ui.yesno(defaultValue, message);
+            }
+
+            @Override
+            public void waitForUser() {
+              ui.waitForUser();
+            }
+
+            @Override
+            public String readString(
+                String defaultValue, Set<String> allowedValues, String message) {
+              return ui.readString(defaultValue, allowedValues, message);
             }
 
             @Override
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitIndex.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitIndex.java
index 0d9a822..6ad0a6b 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitIndex.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitIndex.java
@@ -16,6 +16,7 @@
 
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
+import com.google.gerrit.index.SchemaDefinitions;
 import com.google.gerrit.pgm.init.api.ConsoleUI;
 import com.google.gerrit.pgm.init.api.InitFlags;
 import com.google.gerrit.pgm.init.api.InitStep;
@@ -24,7 +25,6 @@
 import com.google.gerrit.server.index.IndexModule;
 import com.google.gerrit.server.index.IndexModule.IndexType;
 import com.google.gerrit.server.index.IndexUtils;
-import com.google.gerrit.server.index.SchemaDefinitions;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import java.io.IOException;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/index/IndexManagerOnInit.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/index/IndexManagerOnInit.java
index 5273dfb..6b1ee26 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/index/IndexManagerOnInit.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/index/IndexManagerOnInit.java
@@ -15,7 +15,7 @@
 package com.google.gerrit.pgm.init.index;
 
 import com.google.gerrit.extensions.events.LifecycleListener;
-import com.google.gerrit.server.index.IndexDefinition;
+import com.google.gerrit.index.IndexDefinition;
 import com.google.inject.Inject;
 import com.google.inject.name.Named;
 import java.util.Collection;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/index/IndexModuleOnInit.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/index/IndexModuleOnInit.java
index 0358f13..b417d05 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/index/IndexModuleOnInit.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/index/IndexModuleOnInit.java
@@ -19,10 +19,10 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.index.IndexDefinition;
+import com.google.gerrit.index.SchemaDefinitions;
 import com.google.gerrit.server.account.AccountCache;
 import com.google.gerrit.server.account.GroupCache;
-import com.google.gerrit.server.index.IndexDefinition;
-import com.google.gerrit.server.index.SchemaDefinitions;
 import com.google.gerrit.server.index.SingleVersionModule;
 import com.google.gerrit.server.index.SingleVersionModule.SingleVersionListener;
 import com.google.gerrit.server.index.account.AccountIndexCollection;
diff --git a/gerrit-plugin-api/BUILD b/gerrit-plugin-api/BUILD
index d8d6838..51b1486 100644
--- a/gerrit-plugin-api/BUILD
+++ b/gerrit-plugin-api/BUILD
@@ -12,12 +12,14 @@
 ]
 
 EXPORTS = [
-    "//gerrit-antlr:query_exception",
-    "//gerrit-antlr:query_parser",
+    "//gerrit-index:index",
+    "//gerrit-index:query_exception",
+    "//gerrit-index:query_parser",
     "//gerrit-common:annotations",
     "//gerrit-common:server",
     "//gerrit-extension-api:api",
     "//gerrit-gwtexpui:server",
+    "//gerrit-server:metrics",
     "//gerrit-reviewdb:server",
     "//gerrit-server:prolog-common",
     "//lib/commons:dbcp",
@@ -81,12 +83,12 @@
     main_class = "Dummy",
     visibility = ["//visibility:public"],
     runtime_deps = [
-        "//gerrit-antlr:libquery_exception-src.jar",
-        "//gerrit-antlr:libquery_parser-src.jar",
         "//gerrit-common:libannotations-src.jar",
         "//gerrit-extension-api:libapi-src.jar",
         "//gerrit-gwtexpui:libserver-src.jar",
         "//gerrit-httpd:libhttpd-src.jar",
+        "//gerrit-index:libquery_exception-src.jar",
+        "//gerrit-index:libquery_parser-src.jar",
         "//gerrit-pgm:libinit-api-src.jar",
         "//gerrit-reviewdb:libserver-src.jar",
         "//gerrit-server:libserver-src.jar",
@@ -99,8 +101,8 @@
 java_doc(
     name = "plugin-api-javadoc",
     libs = PLUGIN_API + [
-        "//gerrit-antlr:query_exception",
-        "//gerrit-antlr:query_parser",
+        "//gerrit-index:query_exception",
+        "//gerrit-index:query_parser",
         "//gerrit-common:annotations",
         "//gerrit-common:server",
         "//gerrit-extension-api:api",
diff --git a/gerrit-server/BUILD b/gerrit-server/BUILD
index 567ec6c..e124e89 100644
--- a/gerrit-server/BUILD
+++ b/gerrit-server/BUILD
@@ -9,11 +9,14 @@
     "src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java",
 ]
 
+# Non-recursive glob; dropwizard implementation is in a subpackage.
+METRICS_SRCS = glob(["src/main/java/com/google/gerrit/metrics/*.java"])
+
 RECEIVE_SRCS = glob(["src/main/java/com/google/gerrit/server/git/receive/**/*.java"])
 
 SRCS = glob(
     ["src/main/java/**/*.java"],
-    exclude = CONSTANTS_SRC + GERRIT_GLOBAL_MODULE_SRC + RECEIVE_SRCS,
+    exclude = CONSTANTS_SRC + GERRIT_GLOBAL_MODULE_SRC + METRICS_SRCS + RECEIVE_SRCS,
 )
 
 RESOURCES = glob(["src/main/resources/**/*"])
@@ -44,11 +47,12 @@
     visibility = ["//visibility:public"],
     deps = [
         ":constants",
-        "//gerrit-antlr:query_exception",
-        "//gerrit-antlr:query_parser",
+        ":metrics",
         "//gerrit-common:annotations",
         "//gerrit-common:server",
         "//gerrit-extension-api:api",
+        "//gerrit-index:index",
+        "//gerrit-index:query_exception",
         "//gerrit-patch-commonsnet:commons-net",
         "//gerrit-patch-jgit:server",
         "//gerrit-prettify:server",
@@ -73,7 +77,6 @@
         "//lib:soy",
         "//lib:tukaani-xz",
         "//lib:velocity",
-        "//lib/antlr:java_runtime",
         "//lib/auto:auto-value",
         "//lib/bouncycastle:bcpkix-neverlink",
         "//lib/bouncycastle:bcprov-neverlink",
@@ -147,7 +150,20 @@
     ],
 )
 
+# TODO(dborowitz): Move to a different top-level directory to avoid inbound
+# dependencies on gerrit-server.
+java_library(
+    name = "metrics",
+    srcs = METRICS_SRCS,
+    visibility = ["//visibility:public"],
+    deps = [
+        "//gerrit-extension-api:api",
+        "//lib:guava",
+    ],
+)
+
 TESTUTIL_DEPS = [
+    ":metrics",
     ":module",
     ":server",
     "//gerrit-common:annotations",
@@ -155,6 +171,7 @@
     "//gerrit-cache-h2:cache-h2",
     "//gerrit-extension-api:api",
     "//gerrit-gpg:gpg",
+    "//gerrit-index:index",
     "//gerrit-lucene:lucene",
     "//gerrit-reviewdb:server",
     "//lib:gwtorm",
@@ -266,9 +283,6 @@
     deps = TESTUTIL_DEPS + [
         ":prolog-common",
         ":testutil",
-        "//gerrit-antlr:query_exception",
-        "//gerrit-antlr:query_parser",
-        "//lib/antlr:java_runtime",
     ],
 )
 
@@ -280,9 +294,6 @@
     deps = TESTUTIL_DEPS + [
         ":prolog-common",
         ":testutil",
-        "//gerrit-antlr:query_exception",
-        "//gerrit-antlr:query_parser",
-        "//lib/antlr:java_runtime",
     ],
 )
 
@@ -299,7 +310,7 @@
         ":custom-truth-subjects",
         ":prolog-common",
         ":testutil",
-        "//gerrit-antlr:query_exception",
+        "//gerrit-index:query_exception",
         "//gerrit-patch-jgit:server",
         "//gerrit-test-util:test_util",
         "//lib:args4j",
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeFinder.java b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeFinder.java
index 731d156..2e0fe2b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeFinder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeFinder.java
@@ -19,12 +19,12 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Sets;
 import com.google.common.primitives.Ints;
+import com.google.gerrit.index.IndexConfig;
 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.cache.CacheModule;
 import com.google.gerrit.server.change.ChangeTriplet;
-import com.google.gerrit.server.index.IndexConfig;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.query.change.ChangeData;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ReviewerRecommender.java b/gerrit-server/src/main/java/com/google/gerrit/server/ReviewerRecommender.java
index 2fad708..7374833 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ReviewerRecommender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ReviewerRecommender.java
@@ -23,6 +23,8 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -36,8 +38,6 @@
 import com.google.gerrit.server.index.change.ChangeField;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.project.ProjectControl;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.ChangeQueryBuilder;
 import com.google.gerrit.server.query.change.InternalChangeQuery;
@@ -80,7 +80,7 @@
   private final ChangeQueryBuilder changeQueryBuilder;
   private final Config config;
   private final DynamicMap<ReviewerSuggestion> reviewerSuggestionPluginMap;
-  private final InternalChangeQuery internalChangeQuery;
+  private final Provider<InternalChangeQuery> queryProvider;
   private final WorkQueue workQueue;
   private final Provider<ReviewDb> dbProvider;
   private final ApprovalsUtil approvalsUtil;
@@ -89,7 +89,7 @@
   ReviewerRecommender(
       ChangeQueryBuilder changeQueryBuilder,
       DynamicMap<ReviewerSuggestion> reviewerSuggestionPluginMap,
-      InternalChangeQuery internalChangeQuery,
+      Provider<InternalChangeQuery> queryProvider,
       WorkQueue workQueue,
       Provider<ReviewDb> dbProvider,
       ApprovalsUtil approvalsUtil,
@@ -98,7 +98,7 @@
     fillOptions.addAll(AccountLoader.DETAILED_OPTIONS);
     this.changeQueryBuilder = changeQueryBuilder;
     this.config = config;
-    this.internalChangeQuery = internalChangeQuery;
+    this.queryProvider = queryProvider;
     this.reviewerSuggestionPluginMap = reviewerSuggestionPluginMap;
     this.workQueue = workQueue;
     this.dbProvider = dbProvider;
@@ -202,7 +202,8 @@
     // Get the user's last 25 changes, check approvals
     try {
       List<ChangeData> result =
-          internalChangeQuery
+          queryProvider
+              .get()
               .setLimit(25)
               .setRequestedFields(ImmutableSet.of(ChangeField.REVIEWER.getName()))
               .query(changeQueryBuilder.owner("self"));
@@ -268,7 +269,7 @@
     }
 
     List<List<ChangeData>> result =
-        internalChangeQuery.setLimit(25).setRequestedFields(ImmutableSet.of()).query(predicates);
+        queryProvider.get().setLimit(25).setRequestedFields(ImmutableSet.of()).query(predicates);
 
     Iterator<List<ChangeData>> queryResultIterator = result.iterator();
     Iterator<Account.Id> reviewersIterator = reviewers.keySet().iterator();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ReviewersUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ReviewersUtil.java
index 57a81c8..0c1f871 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ReviewersUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ReviewersUtil.java
@@ -25,6 +25,8 @@
 import com.google.gerrit.extensions.common.GroupBaseInfo;
 import com.google.gerrit.extensions.common.SuggestedReviewerInfo;
 import com.google.gerrit.extensions.restapi.Url;
+import com.google.gerrit.index.query.QueryParseException;
+import com.google.gerrit.index.query.QueryResult;
 import com.google.gerrit.metrics.Description;
 import com.google.gerrit.metrics.Description.Units;
 import com.google.gerrit.metrics.MetricMaker;
@@ -41,8 +43,6 @@
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.gerrit.server.project.ProjectControl;
-import com.google.gerrit.server.query.QueryParseException;
-import com.google.gerrit.server.query.QueryResult;
 import com.google.gerrit.server.query.account.AccountPredicates;
 import com.google.gerrit.server.query.account.AccountQueryBuilder;
 import com.google.gerrit.server.query.account.AccountQueryProcessor;
@@ -109,7 +109,7 @@
 
   private final AccountLoader accountLoader;
   private final AccountQueryBuilder accountQueryBuilder;
-  private final AccountQueryProcessor accountQueryProcessor;
+  private final Provider<AccountQueryProcessor> queryProvider;
   private final GroupBackend groupBackend;
   private final GroupMembers.Factory groupMembersFactory;
   private final Provider<CurrentUser> currentUser;
@@ -120,7 +120,7 @@
   ReviewersUtil(
       AccountLoader.Factory accountLoaderFactory,
       AccountQueryBuilder accountQueryBuilder,
-      AccountQueryProcessor accountQueryProcessor,
+      Provider<AccountQueryProcessor> queryProvider,
       GroupBackend groupBackend,
       GroupMembers.Factory groupMembersFactory,
       Provider<CurrentUser> currentUser,
@@ -130,7 +130,7 @@
     fillOptions.addAll(AccountLoader.DETAILED_OPTIONS);
     this.accountLoader = accountLoaderFactory.create(fillOptions);
     this.accountQueryBuilder = accountQueryBuilder;
-    this.accountQueryProcessor = accountQueryProcessor;
+    this.queryProvider = queryProvider;
     this.currentUser = currentUser;
     this.groupBackend = groupBackend;
     this.groupMembersFactory = groupMembersFactory;
@@ -199,8 +199,9 @@
     try (Timer0.Context ctx = metrics.queryAccountsLatency.start()) {
       try {
         QueryResult<AccountState> result =
-            accountQueryProcessor
-                .setLimit(suggestReviewers.getLimit() * CANDIDATE_LIST_MULTIPLIER)
+            queryProvider
+                .get()
+                .setUserProvidedLimit(suggestReviewers.getLimit() * CANDIDATE_LIST_MULTIPLIER)
                 .query(
                     AccountPredicates.andActive(
                         accountQueryBuilder.defaultQuery(suggestReviewers.getQuery())));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountLimits.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountLimits.java
index e8e6d9e..4d1d1b8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountLimits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountLimits.java
@@ -17,6 +17,7 @@
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.common.data.PermissionRange;
 import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.index.query.QueryProcessor;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.git.QueueProvider;
 import com.google.gerrit.server.group.SystemGroupBackend;
@@ -89,6 +90,15 @@
     return QueueProvider.QueueType.INTERACTIVE;
   }
 
+  /**
+   * Get the limit on a {@link QueryProcessor} for a given user.
+   *
+   * @return limit according to {@link GlobalCapability#QUERY_LIMIT}.
+   */
+  public int getQueryLimit() {
+    return getRange(GlobalCapability.QUERY_LIMIT).getMax();
+  }
+
   /** @return true if the user has a permission rule specifying the range. */
   public boolean hasExplicitRange(String permission) {
     return GlobalCapability.hasRange(permission) && !getRules(permission).isEmpty();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/Emails.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/Emails.java
index 15f4509..db707a8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/Emails.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/Emails.java
@@ -24,6 +24,7 @@
 import com.google.gerrit.server.query.account.InternalAccountQuery;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 import com.google.inject.Singleton;
 import java.io.IOException;
 import java.util.List;
@@ -32,12 +33,12 @@
 @Singleton
 public class Emails {
   private final ExternalIds externalIds;
-  private final InternalAccountQuery accountQuery;
+  private final Provider<InternalAccountQuery> queryProvider;
 
   @Inject
-  public Emails(ExternalIds externalIds, InternalAccountQuery accountQuery) {
+  public Emails(ExternalIds externalIds, Provider<InternalAccountQuery> queryProvider) {
     this.externalIds = externalIds;
-    this.accountQuery = accountQuery;
+    this.queryProvider = queryProvider;
   }
 
   /**
@@ -60,7 +61,7 @@
    * @see #getAccountsFor(String...)
    */
   public ImmutableSet<Account.Id> getAccountFor(String email) throws IOException, OrmException {
-    List<AccountState> byPreferredEmail = accountQuery.byPreferredEmail(email);
+    List<AccountState> byPreferredEmail = queryProvider.get().byPreferredEmail(email);
     return Streams.concat(
             externalIds.byEmail(email).stream().map(e -> e.accountId()),
             byPreferredEmail
@@ -85,7 +86,8 @@
         .entries()
         .stream()
         .forEach(e -> builder.put(e.getKey(), e.getValue().accountId()));
-    accountQuery
+    queryProvider
+        .get()
         .byPreferredEmail(emails)
         .entries()
         .stream()
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/IncludingGroupMembership.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/IncludingGroupMembership.java
index 70801c3..165cbb6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/IncludingGroupMembership.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/IncludingGroupMembership.java
@@ -40,13 +40,16 @@
     IncludingGroupMembership create(IdentifiedUser user);
   }
 
+  private final GroupCache groupCache;
   private final GroupIncludeCache includeCache;
   private final IdentifiedUser user;
   private final Map<AccountGroup.UUID, Boolean> memberOf;
   private Set<AccountGroup.UUID> knownGroups;
 
   @Inject
-  IncludingGroupMembership(GroupIncludeCache includeCache, @Assisted IdentifiedUser user) {
+  IncludingGroupMembership(
+      GroupCache groupCache, GroupIncludeCache includeCache, @Assisted IdentifiedUser user) {
+    this.groupCache = groupCache;
     this.includeCache = includeCache;
     this.user = user;
 
@@ -88,6 +91,10 @@
         }
 
         memberOf.put(id, false);
+        AccountGroup group = groupCache.get(id);
+        if (group == null) {
+          continue;
+        }
         if (search(includeCache.subgroupsOf(id))) {
           memberOf.put(id, true);
           return true;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/QueryAccounts.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/QueryAccounts.java
index f270b3c..cbfb7f5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/QueryAccounts.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/QueryAccounts.java
@@ -22,13 +22,13 @@
 import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
+import com.google.gerrit.index.query.QueryResult;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.account.AccountDirectory.FillOptions;
 import com.google.gerrit.server.api.accounts.AccountInfoComparator;
 import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
-import com.google.gerrit.server.query.QueryResult;
 import com.google.gerrit.server.query.account.AccountPredicates;
 import com.google.gerrit.server.query.account.AccountQueryBuilder;
 import com.google.gerrit.server.query.account.AccountQueryProcessor;
@@ -71,7 +71,7 @@
     usage = "maximum number of users to return"
   )
   public void setLimit(int n) {
-    queryProcessor.setLimit(n);
+    queryProcessor.setUserProvidedLimit(n);
 
     if (n < 0) {
       suggestLimit = 10;
@@ -177,7 +177,7 @@
       Predicate<AccountState> queryPred;
       if (suggest) {
         queryPred = queryBuilder.defaultQuery(query);
-        queryProcessor.setLimit(suggestLimit);
+        queryProcessor.setUserProvidedLimit(suggestLimit);
       } else {
         queryPred = queryBuilder.parse(query);
       }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/plugins/PluginsImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/plugins/PluginsImpl.java
index 75fb350..a955abe 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/plugins/PluginsImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/plugins/PluginsImpl.java
@@ -66,7 +66,7 @@
         list.setMatchPrefix(this.getPrefix());
         list.setMatchSubstring(this.getSubstring());
         list.setMatchRegex(this.getRegex());
-        return list.apply();
+        return list.apply(TopLevelResource.INSTANCE);
       }
     };
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/AbandonUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/AbandonUtil.java
index 02ad66e..58a908d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/AbandonUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/AbandonUtil.java
@@ -16,17 +16,18 @@
 
 import com.google.common.collect.ImmutableListMultimap;
 import com.google.common.collect.ListMultimap;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.InternalUser;
 import com.google.gerrit.server.config.ChangeCleanupConfig;
 import com.google.gerrit.server.project.ChangeControl;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.ChangeQueryBuilder;
 import com.google.gerrit.server.query.change.ChangeQueryProcessor;
 import com.google.gerrit.server.update.BatchUpdate;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 import com.google.inject.Singleton;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -40,7 +41,7 @@
   private static final Logger log = LoggerFactory.getLogger(AbandonUtil.class);
 
   private final ChangeCleanupConfig cfg;
-  private final ChangeQueryProcessor queryProcessor;
+  private final Provider<ChangeQueryProcessor> queryProvider;
   private final ChangeQueryBuilder queryBuilder;
   private final Abandon abandon;
   private final InternalUser internalUser;
@@ -49,11 +50,11 @@
   AbandonUtil(
       ChangeCleanupConfig cfg,
       InternalUser.Factory internalUserFactory,
-      ChangeQueryProcessor queryProcessor,
+      Provider<ChangeQueryProcessor> queryProvider,
       ChangeQueryBuilder queryBuilder,
       Abandon abandon) {
     this.cfg = cfg;
-    this.queryProcessor = queryProcessor;
+    this.queryProvider = queryProvider;
     this.queryBuilder = queryBuilder;
     this.abandon = abandon;
     internalUser = internalUserFactory.create();
@@ -72,7 +73,7 @@
       }
 
       List<ChangeData> changesToAbandon =
-          queryProcessor.enforceVisibility(false).query(queryBuilder.parse(query)).entities();
+          queryProvider.get().enforceVisibility(false).query(queryBuilder.parse(query)).entities();
       ImmutableListMultimap.Builder<Project.NameKey, ChangeControl> builder =
           ImmutableListMultimap.builder();
       for (ChangeData cd : changesToAbandon) {
@@ -110,7 +111,11 @@
     for (ChangeControl cc : changeControls) {
       String newQuery = query + " change:" + cc.getId();
       List<ChangeData> changesToAbandon =
-          queryProcessor.enforceVisibility(false).query(queryBuilder.parse(newQuery)).entities();
+          queryProvider
+              .get()
+              .enforceVisibility(false)
+              .query(queryBuilder.parse(newQuery))
+              .entities();
       if (!changesToAbandon.isEmpty()) {
         validChanges.add(cc);
       } else {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
index 6c66e50..4e6bb5b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
@@ -81,6 +81,7 @@
 import com.google.gerrit.extensions.config.DownloadScheme;
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.restapi.Url;
+import com.google.gerrit.index.query.QueryResult;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.ChangeMessage;
@@ -117,7 +118,6 @@
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.SubmitRuleOptions;
-import com.google.gerrit.server.query.QueryResult;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.ChangeData.ChangedLines;
 import com.google.gerrit.server.query.change.PluginDefinedAttributesFactory;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
index d3ddbd9..8823e6d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
@@ -228,7 +228,7 @@
   private final InternalUser.Factory internalUserFactory;
   private final MergeSuperSet mergeSuperSet;
   private final MergeValidators.Factory mergeValidatorsFactory;
-  private final InternalChangeQuery internalChangeQuery;
+  private final Provider<InternalChangeQuery> queryProvider;
   private final SubmitStrategyFactory submitStrategyFactory;
   private final SubmoduleOp.Factory subOpFactory;
   private final Provider<MergeOpRepoManager> ormProvider;
@@ -255,7 +255,7 @@
       InternalUser.Factory internalUserFactory,
       MergeSuperSet mergeSuperSet,
       MergeValidators.Factory mergeValidatorsFactory,
-      InternalChangeQuery internalChangeQuery,
+      Provider<InternalChangeQuery> queryProvider,
       SubmitStrategyFactory submitStrategyFactory,
       SubmoduleOp.Factory subOpFactory,
       Provider<MergeOpRepoManager> ormProvider,
@@ -267,7 +267,7 @@
     this.internalUserFactory = internalUserFactory;
     this.mergeSuperSet = mergeSuperSet;
     this.mergeValidatorsFactory = mergeValidatorsFactory;
-    this.internalChangeQuery = internalChangeQuery;
+    this.queryProvider = queryProvider;
     this.submitStrategyFactory = submitStrategyFactory;
     this.subOpFactory = subOpFactory;
     this.ormProvider = ormProvider;
@@ -860,7 +860,7 @@
 
   private void abandonAllOpenChangeForDeletedProject(Project.NameKey destProject) {
     try {
-      for (ChangeData cd : internalChangeQuery.byProjectOpen(destProject)) {
+      for (ChangeData cd : queryProvider.get().byProjectOpen(destProject)) {
         try (BatchUpdate bu =
             batchUpdateFactory.create(db, destProject, internalUserFactory.create(), ts)) {
           bu.setRequestId(submissionId);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseSorter.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseSorter.java
index d596987..6fc5eaa 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseSorter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseSorter.java
@@ -21,6 +21,7 @@
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.InternalChangeQuery;
 import com.google.gwtorm.server.OrmException;
+import com.google.inject.Provider;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -41,7 +42,7 @@
   private final RevFlag canMergeFlag;
   private final RevCommit initialTip;
   private final Set<RevCommit> alreadyAccepted;
-  private final InternalChangeQuery internalChangeQuery;
+  private final Provider<InternalChangeQuery> queryProvider;
   private final Set<CodeReviewCommit> incoming;
 
   public RebaseSorter(
@@ -49,13 +50,13 @@
       RevCommit initialTip,
       Set<RevCommit> alreadyAccepted,
       RevFlag canMergeFlag,
-      InternalChangeQuery internalChangeQuery,
+      Provider<InternalChangeQuery> queryProvider,
       Set<CodeReviewCommit> incoming) {
     this.rw = rw;
     this.canMergeFlag = canMergeFlag;
     this.initialTip = initialTip;
     this.alreadyAccepted = alreadyAccepted;
-    this.internalChangeQuery = internalChangeQuery;
+    this.queryProvider = queryProvider;
     this.incoming = incoming;
   }
 
@@ -116,7 +117,7 @@
       }
 
       // check if the commit associated change is merged in the same branch
-      List<ChangeData> changes = internalChangeQuery.byCommit(commit);
+      List<ChangeData> changes = queryProvider.get().byCommit(commit);
       for (ChangeData change : changes) {
         if (change.change().getStatus() == Status.MERGED
             && change.change().getDest().equals(dest)) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java
index 9892b6e..2a22c1c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java
@@ -58,6 +58,7 @@
 import com.google.gerrit.server.util.RequestId;
 import com.google.inject.Inject;
 import com.google.inject.Module;
+import com.google.inject.Provider;
 import com.google.inject.assistedinject.Assisted;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -119,7 +120,7 @@
     final RebaseChangeOp.Factory rebaseFactory;
     final OnSubmitValidators.Factory onSubmitValidatorsFactory;
     final TagCache tagCache;
-    final InternalChangeQuery internalChangeQuery;
+    final Provider<InternalChangeQuery> queryProvider;
 
     final Branch.NameKey destBranch;
     final CodeReviewRevWalk rw;
@@ -159,7 +160,7 @@
         RebaseChangeOp.Factory rebaseFactory,
         OnSubmitValidators.Factory onSubmitValidatorsFactory,
         TagCache tagCache,
-        InternalChangeQuery internalChangeQuery,
+        Provider<InternalChangeQuery> queryProvider,
         @Assisted Branch.NameKey destBranch,
         @Assisted CommitStatus commitStatus,
         @Assisted CodeReviewRevWalk rw,
@@ -188,7 +189,7 @@
       this.projectCache = projectCache;
       this.rebaseFactory = rebaseFactory;
       this.tagCache = tagCache;
-      this.internalChangeQuery = internalChangeQuery;
+      this.queryProvider = queryProvider;
 
       this.serverIdent = serverIdent;
       this.destBranch = destBranch;
@@ -214,12 +215,7 @@
       this.mergeSorter = new MergeSorter(rw, alreadyAccepted, canMergeFlag, incoming);
       this.rebaseSorter =
           new RebaseSorter(
-              rw,
-              mergeTip.getInitialTip(),
-              alreadyAccepted,
-              canMergeFlag,
-              internalChangeQuery,
-              incoming);
+              rw, mergeTip.getInitialTip(), alreadyAccepted, canMergeFlag, queryProvider, incoming);
       this.mergeUtil = mergeUtilFactory.create(project);
       this.onSubmitValidatorsFactory = onSubmitValidatorsFactory;
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidators.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidators.java
index 4b96578..17303f8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidators.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/CommitValidators.java
@@ -610,9 +610,15 @@
           && author.getName().equals(gerritIdent.getName())
           && author.getEmailAddress().equals(gerritIdent.getEmailAddress())) {
         try {
+          // Stop authors from amending the merge commits that Gerrit itself creates.
           perm.check(RefPermission.FORGE_SERVER);
         } catch (AuthException denied) {
-          throw new CommitValidationException("do not amend merges not made by you");
+          throw new CommitValidationException(
+              String.format(
+                  "pushing merge commit %s by %s requires '%s' permission",
+                  receiveEvent.commit.getId(),
+                  gerritIdent.getEmailAddress(),
+                  RefPermission.FORGE_SERVER.name()));
         } catch (PermissionBackendException e) {
           log.error("cannot check FORGE_SERVER", e);
           throw new CommitValidationException("internal auth error");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/QueryGroups.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/QueryGroups.java
index abf464e..32ea4e4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/QueryGroups.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/QueryGroups.java
@@ -23,11 +23,11 @@
 import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.gerrit.index.query.QueryParseException;
+import com.google.gerrit.index.query.QueryResult;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.index.group.GroupIndex;
 import com.google.gerrit.server.index.group.GroupIndexCollection;
-import com.google.gerrit.server.query.QueryParseException;
-import com.google.gerrit.server.query.QueryResult;
 import com.google.gerrit.server.query.group.GroupQueryBuilder;
 import com.google.gerrit.server.query.group.GroupQueryProcessor;
 import com.google.gwtorm.server.OrmException;
@@ -119,7 +119,7 @@
     }
 
     if (limit != 0) {
-      queryProcessor.setLimit(limit);
+      queryProcessor.setUserProvidedLimit(limit);
     }
 
     try {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/DummyIndexModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/DummyIndexModule.java
index 1706761..51ef634 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/DummyIndexModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/DummyIndexModule.java
@@ -14,6 +14,9 @@
 
 package com.google.gerrit.server.index;
 
+import com.google.gerrit.index.Index;
+import com.google.gerrit.index.IndexConfig;
+import com.google.gerrit.index.Schema;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.index.account.AccountIndex;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java
index 636cce6..6854a87 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java
@@ -23,6 +23,8 @@
 import com.google.common.util.concurrent.ListeningExecutorService;
 import com.google.common.util.concurrent.MoreExecutors;
 import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.index.IndexDefinition;
+import com.google.gerrit.index.SchemaDefinitions;
 import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.git.WorkQueue;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexUtils.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexUtils.java
index 7000e04..ea9900b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexUtils.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexUtils.java
@@ -21,9 +21,12 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.index.account.AccountField;
 import com.google.gerrit.server.index.group.GroupField;
+import com.google.gerrit.server.query.change.SingleGroupUser;
 import java.io.IOException;
 import java.util.Set;
 import org.eclipse.jgit.errors.ConfigInvalidException;
@@ -81,6 +84,16 @@
         : Sets.union(fs, ImmutableSet.of(GroupField.UUID.getName()));
   }
 
+  public static String describe(CurrentUser user) {
+    if (user.isIdentifiedUser()) {
+      return user.getAccountId().toString();
+    }
+    if (user instanceof SingleGroupUser) {
+      return "group:" + user.getEffectiveGroups().getKnownGroups().iterator().next().toString();
+    }
+    return user.toString();
+  }
+
   private IndexUtils() {
     // hide default constructor
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/OnlineReindexer.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/OnlineReindexer.java
index 8d14931..bb6b427 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/OnlineReindexer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/OnlineReindexer.java
@@ -18,6 +18,10 @@
 
 import com.google.common.collect.Lists;
 import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.index.Index;
+import com.google.gerrit.index.IndexCollection;
+import com.google.gerrit.index.IndexDefinition;
+import com.google.gerrit.index.SiteIndexer;
 import java.io.IOException;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicBoolean;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/SingleVersionModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/SingleVersionModule.java
index bf28d7d..e3f9d7a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/SingleVersionModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/SingleVersionModule.java
@@ -16,6 +16,9 @@
 
 import com.google.common.collect.ImmutableSet;
 import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.index.Index;
+import com.google.gerrit.index.IndexDefinition;
+import com.google.gerrit.index.Schema;
 import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.inject.Inject;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/VersionManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/VersionManager.java
index 697c9c2..5284117 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/VersionManager.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/VersionManager.java
@@ -22,8 +22,12 @@
 import com.google.common.collect.Maps;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.index.Index;
+import com.google.gerrit.index.IndexCollection;
+import com.google.gerrit.index.IndexDefinition;
+import com.google.gerrit.index.IndexDefinition.IndexFactory;
+import com.google.gerrit.index.Schema;
 import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.index.IndexDefinition.IndexFactory;
 import com.google.inject.ProvisionException;
 import java.io.IOException;
 import java.util.Collection;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountField.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountField.java
index 9258913..b7c5e77 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountField.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountField.java
@@ -14,19 +14,19 @@
 
 package com.google.gerrit.server.index.account;
 
-import static com.google.gerrit.server.index.FieldDef.exact;
-import static com.google.gerrit.server.index.FieldDef.integer;
-import static com.google.gerrit.server.index.FieldDef.prefix;
-import static com.google.gerrit.server.index.FieldDef.timestamp;
+import static com.google.gerrit.index.FieldDef.exact;
+import static com.google.gerrit.index.FieldDef.integer;
+import static com.google.gerrit.index.FieldDef.prefix;
+import static com.google.gerrit.index.FieldDef.timestamp;
 
 import com.google.common.base.Predicates;
 import com.google.common.base.Strings;
 import com.google.common.collect.FluentIterable;
 import com.google.common.collect.Iterables;
+import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.SchemaUtil;
 import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.account.externalids.ExternalId;
-import com.google.gerrit.server.index.FieldDef;
-import com.google.gerrit.server.index.SchemaUtil;
 import java.sql.Timestamp;
 import java.util.Collections;
 import java.util.Locale;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndex.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndex.java
index ffa94ec..5c1b3dc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndex.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndex.java
@@ -14,11 +14,11 @@
 
 package com.google.gerrit.server.index.account;
 
+import com.google.gerrit.index.Index;
+import com.google.gerrit.index.IndexDefinition;
+import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.account.AccountState;
-import com.google.gerrit.server.index.Index;
-import com.google.gerrit.server.index.IndexDefinition;
-import com.google.gerrit.server.query.Predicate;
 import com.google.gerrit.server.query.account.AccountPredicates;
 
 public interface AccountIndex extends Index<Account.Id, AccountState> {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndexCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndexCollection.java
index 2eb8235..67b507d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndexCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndexCollection.java
@@ -15,9 +15,9 @@
 package com.google.gerrit.server.index.account;
 
 import com.google.common.annotations.VisibleForTesting;
+import com.google.gerrit.index.IndexCollection;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.account.AccountState;
-import com.google.gerrit.server.index.IndexCollection;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndexDefinition.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndexDefinition.java
index 25bf541..af23b52 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndexDefinition.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndexDefinition.java
@@ -15,9 +15,9 @@
 package com.google.gerrit.server.index.account;
 
 import com.google.gerrit.common.Nullable;
+import com.google.gerrit.index.IndexDefinition;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.account.AccountState;
-import com.google.gerrit.server.index.IndexDefinition;
 import com.google.inject.Inject;
 
 public class AccountIndexDefinition
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndexRewriter.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndexRewriter.java
index c6b2b45..bc0970e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndexRewriter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndexRewriter.java
@@ -16,11 +16,11 @@
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
+import com.google.gerrit.index.IndexRewriter;
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.server.account.AccountState;
-import com.google.gerrit.server.index.IndexRewriter;
-import com.google.gerrit.server.index.QueryOptions;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndexerImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndexerImpl.java
index 8796360..6ec1260 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndexerImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountIndexerImpl.java
@@ -18,10 +18,10 @@
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.extensions.events.AccountIndexedListener;
 import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.index.Index;
 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.index.Index;
 import com.google.inject.assistedinject.Assisted;
 import com.google.inject.assistedinject.AssistedInject;
 import java.io.IOException;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountSchemaDefinitions.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountSchemaDefinitions.java
index b89256d..8f9b443 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountSchemaDefinitions.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountSchemaDefinitions.java
@@ -14,11 +14,11 @@
 
 package com.google.gerrit.server.index.account;
 
-import static com.google.gerrit.server.index.SchemaUtil.schema;
+import static com.google.gerrit.index.SchemaUtil.schema;
 
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.SchemaDefinitions;
 import com.google.gerrit.server.account.AccountState;
-import com.google.gerrit.server.index.Schema;
-import com.google.gerrit.server.index.SchemaDefinitions;
 
 public class AccountSchemaDefinitions extends SchemaDefinitions<AccountState> {
   @Deprecated
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AllAccountsIndexer.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AllAccountsIndexer.java
index a8cc8aa..b6a95b7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AllAccountsIndexer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AllAccountsIndexer.java
@@ -20,12 +20,12 @@
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.gerrit.index.SiteIndexer;
 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.account.Accounts;
 import com.google.gerrit.server.index.IndexExecutor;
-import com.google.gerrit.server.index.SiteIndexer;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import java.io.IOException;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/IndexedAccountQuery.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/IndexedAccountQuery.java
index 8c29266..e8b1861 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/IndexedAccountQuery.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/IndexedAccountQuery.java
@@ -14,14 +14,14 @@
 
 package com.google.gerrit.server.index.account;
 
+import com.google.gerrit.index.Index;
+import com.google.gerrit.index.IndexedQuery;
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.query.DataSource;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.account.AccountState;
-import com.google.gerrit.server.index.Index;
-import com.google.gerrit.server.index.IndexedQuery;
-import com.google.gerrit.server.index.QueryOptions;
-import com.google.gerrit.server.query.DataSource;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
 
 public class IndexedAccountQuery extends IndexedQuery<Account.Id, AccountState>
     implements DataSource<AccountState> {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/AllChangesIndexer.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/AllChangesIndexer.java
index 35953b0..02fd609 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/AllChangesIndexer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/AllChangesIndexer.java
@@ -28,6 +28,7 @@
 import com.google.common.collect.MultimapBuilder;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.gerrit.index.SiteIndexer;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RefNames;
@@ -36,7 +37,6 @@
 import com.google.gerrit.server.git.MultiProgressMonitor;
 import com.google.gerrit.server.git.MultiProgressMonitor.Task;
 import com.google.gerrit.server.index.IndexExecutor;
-import com.google.gerrit.server.index.SiteIndexer;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.query.change.ChangeData;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java
index c780d19..cc8f9be 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java
@@ -16,13 +16,13 @@
 
 import static com.google.common.base.MoreObjects.firstNonNull;
 import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.gerrit.server.index.FieldDef.exact;
-import static com.google.gerrit.server.index.FieldDef.fullText;
-import static com.google.gerrit.server.index.FieldDef.intRange;
-import static com.google.gerrit.server.index.FieldDef.integer;
-import static com.google.gerrit.server.index.FieldDef.prefix;
-import static com.google.gerrit.server.index.FieldDef.storedOnly;
-import static com.google.gerrit.server.index.FieldDef.timestamp;
+import static com.google.gerrit.index.FieldDef.exact;
+import static com.google.gerrit.index.FieldDef.fullText;
+import static com.google.gerrit.index.FieldDef.intRange;
+import static com.google.gerrit.index.FieldDef.integer;
+import static com.google.gerrit.index.FieldDef.prefix;
+import static com.google.gerrit.index.FieldDef.storedOnly;
+import static com.google.gerrit.index.FieldDef.timestamp;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.stream.Collectors.toList;
 import static java.util.stream.Collectors.toSet;
@@ -36,6 +36,8 @@
 import com.google.common.collect.Lists;
 import com.google.common.collect.Table;
 import com.google.gerrit.common.data.SubmitRecord;
+import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.SchemaUtil;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.ChangeMessage;
@@ -48,8 +50,6 @@
 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.FieldDef;
-import com.google.gerrit.server.index.SchemaUtil;
 import com.google.gerrit.server.index.change.StalenessChecker.RefState;
 import com.google.gerrit.server.index.change.StalenessChecker.RefStatePattern;
 import com.google.gerrit.server.mail.Address;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndex.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndex.java
index 27b0c26..855bfe9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndex.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndex.java
@@ -14,10 +14,10 @@
 
 package com.google.gerrit.server.index.change;
 
+import com.google.gerrit.index.Index;
+import com.google.gerrit.index.IndexDefinition;
+import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.server.index.Index;
-import com.google.gerrit.server.index.IndexDefinition;
-import com.google.gerrit.server.query.Predicate;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.LegacyChangeIdPredicate;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexCollection.java
index f8acb74..5ce361f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexCollection.java
@@ -15,8 +15,8 @@
 package com.google.gerrit.server.index.change;
 
 import com.google.common.annotations.VisibleForTesting;
+import com.google.gerrit.index.IndexCollection;
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.server.index.IndexCollection;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexDefinition.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexDefinition.java
index 8b63a1d..7945429 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexDefinition.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexDefinition.java
@@ -15,8 +15,8 @@
 package com.google.gerrit.server.index.change;
 
 import com.google.gerrit.common.Nullable;
+import com.google.gerrit.index.IndexDefinition;
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.server.index.IndexDefinition;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.inject.Inject;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexRewriter.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexRewriter.java
index 11c9d83..28843c9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexRewriter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexRewriter.java
@@ -18,20 +18,20 @@
 
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
+import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.IndexConfig;
+import com.google.gerrit.index.IndexRewriter;
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.query.AndPredicate;
+import com.google.gerrit.index.query.IndexPredicate;
+import com.google.gerrit.index.query.LimitPredicate;
+import com.google.gerrit.index.query.NotPredicate;
+import com.google.gerrit.index.query.OrPredicate;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Change.Status;
-import com.google.gerrit.server.index.FieldDef;
-import com.google.gerrit.server.index.IndexConfig;
-import com.google.gerrit.server.index.IndexPredicate;
-import com.google.gerrit.server.index.IndexRewriter;
-import com.google.gerrit.server.index.QueryOptions;
-import com.google.gerrit.server.index.Schema;
-import com.google.gerrit.server.query.AndPredicate;
-import com.google.gerrit.server.query.LimitPredicate;
-import com.google.gerrit.server.query.NotPredicate;
-import com.google.gerrit.server.query.OrPredicate;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gerrit.server.query.change.AndChangeSource;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.ChangeDataSource;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexer.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexer.java
index e4919c8..f62b662 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeIndexer.java
@@ -24,12 +24,12 @@
 import com.google.common.util.concurrent.ListeningExecutorService;
 import com.google.gerrit.extensions.events.ChangeIndexedListener;
 import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.index.Index;
 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.CurrentUser;
 import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.index.Index;
 import com.google.gerrit.server.index.IndexExecutor;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.notedb.NotesMigration;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
index 2f03779..129c8ac 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
@@ -14,10 +14,10 @@
 
 package com.google.gerrit.server.index.change;
 
-import static com.google.gerrit.server.index.SchemaUtil.schema;
+import static com.google.gerrit.index.SchemaUtil.schema;
 
-import com.google.gerrit.server.index.Schema;
-import com.google.gerrit.server.index.SchemaDefinitions;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.SchemaDefinitions;
 import com.google.gerrit.server.query.change.ChangeData;
 
 public class ChangeSchemaDefinitions extends SchemaDefinitions<ChangeData> {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/DummyChangeIndex.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/DummyChangeIndex.java
index 6cbc1cb..f6cee6d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/DummyChangeIndex.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/DummyChangeIndex.java
@@ -14,10 +14,10 @@
 
 package com.google.gerrit.server.index.change;
 
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.server.index.QueryOptions;
-import com.google.gerrit.server.index.Schema;
-import com.google.gerrit.server.query.Predicate;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.ChangeDataSource;
 import java.io.IOException;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/IndexedChangeQuery.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/IndexedChangeQuery.java
index f99f3b4..66f8df2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/IndexedChangeQuery.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/IndexedChangeQuery.java
@@ -21,15 +21,15 @@
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
+import com.google.gerrit.index.IndexConfig;
+import com.google.gerrit.index.IndexedQuery;
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.query.DataSource;
+import com.google.gerrit.index.query.IndexPredicate;
+import com.google.gerrit.index.query.Matchable;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.server.index.IndexConfig;
-import com.google.gerrit.server.index.IndexPredicate;
-import com.google.gerrit.server.index.IndexedQuery;
-import com.google.gerrit.server.index.QueryOptions;
-import com.google.gerrit.server.query.DataSource;
-import com.google.gerrit.server.query.Matchable;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.ChangeDataSource;
 import com.google.gwtorm.server.OrmException;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/StalenessChecker.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/StalenessChecker.java
index 63d5f9a..df92379 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/StalenessChecker.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/StalenessChecker.java
@@ -30,11 +30,11 @@
 import com.google.common.collect.Sets;
 import com.google.common.collect.Streams;
 import com.google.gerrit.common.Nullable;
+import com.google.gerrit.index.IndexConfig;
 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.git.GitRepositoryManager;
-import com.google.gerrit.server.index.IndexConfig;
 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/gerrit-server/src/main/java/com/google/gerrit/server/index/group/AllGroupsIndexer.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/AllGroupsIndexer.java
index 0f03435..7c4074a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/group/AllGroupsIndexer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/AllGroupsIndexer.java
@@ -21,12 +21,12 @@
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.gerrit.index.SiteIndexer;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.account.GroupCache;
 import com.google.gerrit.server.group.Groups;
 import com.google.gerrit.server.index.IndexExecutor;
-import com.google.gerrit.server.index.SiteIndexer;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.SchemaFactory;
 import com.google.inject.Inject;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupField.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupField.java
index 70bdb3f..3d4c92f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupField.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupField.java
@@ -14,15 +14,15 @@
 
 package com.google.gerrit.server.index.group;
 
-import static com.google.gerrit.server.index.FieldDef.exact;
-import static com.google.gerrit.server.index.FieldDef.fullText;
-import static com.google.gerrit.server.index.FieldDef.integer;
-import static com.google.gerrit.server.index.FieldDef.prefix;
-import static com.google.gerrit.server.index.FieldDef.timestamp;
+import static com.google.gerrit.index.FieldDef.exact;
+import static com.google.gerrit.index.FieldDef.fullText;
+import static com.google.gerrit.index.FieldDef.integer;
+import static com.google.gerrit.index.FieldDef.prefix;
+import static com.google.gerrit.index.FieldDef.timestamp;
 
+import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.SchemaUtil;
 import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.server.index.FieldDef;
-import com.google.gerrit.server.index.SchemaUtil;
 import java.sql.Timestamp;
 
 /** Secondary index schemas for groups. */
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndex.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndex.java
index 48480f8..1e56837 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndex.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndex.java
@@ -14,10 +14,10 @@
 
 package com.google.gerrit.server.index.group;
 
+import com.google.gerrit.index.Index;
+import com.google.gerrit.index.IndexDefinition;
+import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.server.index.Index;
-import com.google.gerrit.server.index.IndexDefinition;
-import com.google.gerrit.server.query.Predicate;
 import com.google.gerrit.server.query.group.GroupPredicates;
 
 public interface GroupIndex extends Index<AccountGroup.UUID, AccountGroup> {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexCollection.java
index 2f0d8e0..5c49ee5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexCollection.java
@@ -15,8 +15,8 @@
 package com.google.gerrit.server.index.group;
 
 import com.google.common.annotations.VisibleForTesting;
+import com.google.gerrit.index.IndexCollection;
 import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.server.index.IndexCollection;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexDefinition.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexDefinition.java
index 8e15b5e..61c3445 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexDefinition.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexDefinition.java
@@ -15,8 +15,8 @@
 package com.google.gerrit.server.index.group;
 
 import com.google.gerrit.common.Nullable;
+import com.google.gerrit.index.IndexDefinition;
 import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.server.index.IndexDefinition;
 import com.google.inject.Inject;
 
 public class GroupIndexDefinition
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexRewriter.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexRewriter.java
index 82f55ed..9ef4ba1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexRewriter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexRewriter.java
@@ -16,11 +16,11 @@
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
+import com.google.gerrit.index.IndexRewriter;
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.server.index.IndexRewriter;
-import com.google.gerrit.server.index.QueryOptions;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexerImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexerImpl.java
index b137fb3..8c2eec9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexerImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupIndexerImpl.java
@@ -18,9 +18,9 @@
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.extensions.events.GroupIndexedListener;
 import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.index.Index;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.account.GroupCache;
-import com.google.gerrit.server.index.Index;
 import com.google.inject.assistedinject.Assisted;
 import com.google.inject.assistedinject.AssistedInject;
 import java.io.IOException;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupSchemaDefinitions.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupSchemaDefinitions.java
index cebde7e..ecd4168 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupSchemaDefinitions.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/GroupSchemaDefinitions.java
@@ -14,11 +14,11 @@
 
 package com.google.gerrit.server.index.group;
 
-import static com.google.gerrit.server.index.SchemaUtil.schema;
+import static com.google.gerrit.index.SchemaUtil.schema;
 
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.SchemaDefinitions;
 import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.server.index.Schema;
-import com.google.gerrit.server.index.SchemaDefinitions;
 
 public class GroupSchemaDefinitions extends SchemaDefinitions<AccountGroup> {
   @Deprecated
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/group/IndexedGroupQuery.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/IndexedGroupQuery.java
index 1ea4478..5f31dd7d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/group/IndexedGroupQuery.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/IndexedGroupQuery.java
@@ -14,13 +14,13 @@
 
 package com.google.gerrit.server.index.group;
 
+import com.google.gerrit.index.Index;
+import com.google.gerrit.index.IndexedQuery;
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.query.DataSource;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.server.index.Index;
-import com.google.gerrit.server.index.IndexedQuery;
-import com.google.gerrit.server.index.QueryOptions;
-import com.google.gerrit.server.query.DataSource;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
 
 public class IndexedGroupQuery extends IndexedQuery<AccountGroup.UUID, AccountGroup>
     implements DataSource<AccountGroup> {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/ProjectWatch.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/ProjectWatch.java
index 6363afa..278cd86 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/ProjectWatch.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/ProjectWatch.java
@@ -19,6 +19,8 @@
 import com.google.gerrit.common.data.GroupDescriptions;
 import com.google.gerrit.common.data.GroupReference;
 import com.google.gerrit.common.errors.NoSuchGroupException;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
@@ -31,8 +33,6 @@
 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.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.ChangeQueryBuilder;
 import com.google.gerrit.server.query.change.SingleGroupUser;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java
index 0e514d6..97d728d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java
@@ -15,11 +15,8 @@
 package com.google.gerrit.server.plugins;
 
 import static java.util.Comparator.comparing;
-import static java.util.stream.Collectors.toList;
 
-import com.google.common.base.Strings;
 import com.google.common.collect.Streams;
-import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
 import com.google.gerrit.extensions.common.PluginInfo;
@@ -27,16 +24,12 @@
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.extensions.restapi.TopLevelResource;
 import com.google.gerrit.extensions.restapi.Url;
-import com.google.gerrit.server.OutputFormat;
-import com.google.gson.reflect.TypeToken;
 import com.google.inject.Inject;
-import java.io.PrintWriter;
-import java.util.List;
 import java.util.Locale;
-import java.util.Map;
 import java.util.SortedMap;
 import java.util.TreeMap;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import org.kohsuke.args4j.Option;
 
@@ -52,10 +45,6 @@
   private String matchSubstring;
   private String matchRegex;
 
-  @Deprecated
-  @Option(name = "--format", usage = "(deprecated) output format")
-  private OutputFormat format = OutputFormat.TEXT;
-
   @Option(
     name = "--all",
     aliases = {"-a"},
@@ -115,29 +104,8 @@
     this.pluginLoader = pluginLoader;
   }
 
-  public OutputFormat getFormat() {
-    return format;
-  }
-
-  public ListPlugins setFormat(OutputFormat fmt) {
-    this.format = fmt;
-    return this;
-  }
-
   @Override
-  public Object apply(TopLevelResource resource) throws BadRequestException {
-    format = OutputFormat.JSON;
-    return display(null);
-  }
-
-  public SortedMap<String, PluginInfo> apply() throws BadRequestException {
-    format = OutputFormat.JSON;
-    return display(null);
-  }
-
-  public SortedMap<String, PluginInfo> display(@Nullable PrintWriter stdout)
-      throws BadRequestException {
-    SortedMap<String, PluginInfo> output = new TreeMap<>();
+  public SortedMap<String, PluginInfo> apply(TopLevelResource resource) throws BadRequestException {
     Stream<Plugin> s = Streams.stream(pluginLoader.getPlugins(all));
     if (matchPrefix != null) {
       checkMatchOptions(matchSubstring == null && matchRegex == null);
@@ -158,38 +126,7 @@
     if (limit > 0) {
       s = s.limit(limit);
     }
-    List<Plugin> plugins = s.collect(toList());
-
-    if (!format.isJson()) {
-      stdout.format("%-30s %-10s %-8s %s\n", "Name", "Version", "Status", "File");
-      stdout.print(
-          "-------------------------------------------------------------------------------\n");
-    }
-
-    for (Plugin p : plugins) {
-      PluginInfo info = toPluginInfo(p);
-      if (format.isJson()) {
-        output.put(p.getName(), info);
-      } else {
-        stdout.format(
-            "%-30s %-10s %-8s %s\n",
-            p.getName(),
-            Strings.nullToEmpty(info.version),
-            p.isDisabled() ? "DISABLED" : "ENABLED",
-            p.getSrcFile().getFileName());
-      }
-    }
-
-    if (stdout == null) {
-      return output;
-    } else if (format.isJson()) {
-      format
-          .newGson()
-          .toJson(output, new TypeToken<Map<String, PluginInfo>>() {}.getType(), stdout);
-      stdout.print('\n');
-    }
-    stdout.flush();
-    return null;
+    return new TreeMap<>(s.collect(Collectors.toMap(p -> p.getName(), p -> toPluginInfo(p))));
   }
 
   private void checkMatchOptions(boolean cond) throws BadRequestException {
@@ -202,13 +139,20 @@
     String id;
     String version;
     String indexUrl;
+    String filename;
     Boolean disabled;
 
     id = Url.encode(p.getName());
     version = p.getVersion();
     disabled = p.isDisabled() ? true : null;
-    indexUrl = p.getSrcFile() != null ? String.format("plugins/%s/", p.getName()) : null;
+    if (p.getSrcFile() != null) {
+      indexUrl = String.format("plugins/%s/", p.getName());
+      filename = p.getSrcFile().getFileName().toString();
+    } else {
+      indexUrl = null;
+      filename = null;
+    }
 
-    return new PluginInfo(id, version, indexUrl, disabled);
+    return new PluginInfo(id, version, indexUrl, filename, disabled);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/IsVisibleToPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/IsVisibleToPredicate.java
deleted file mode 100644
index 9295eb9..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/IsVisibleToPredicate.java
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright (C) 2016 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.query;
-
-import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.query.change.SingleGroupUser;
-
-public abstract class IsVisibleToPredicate<T> extends OperatorPredicate<T> implements Matchable<T> {
-  public IsVisibleToPredicate(String name, String value) {
-    super(name, value);
-  }
-
-  protected static String describe(CurrentUser user) {
-    if (user.isIdentifiedUser()) {
-      return user.getAccountId().toString();
-    }
-    if (user instanceof SingleGroupUser) {
-      return "group:" + user.getEffectiveGroups().getKnownGroups().iterator().next().toString();
-    }
-    return user.toString();
-  }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryProcessor.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryProcessor.java
deleted file mode 100644
index 7176d80..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/QueryProcessor.java
+++ /dev/null
@@ -1,265 +0,0 @@
-// Copyright (C) 2016 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.query;
-
-import com.google.common.base.Throwables;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Ordering;
-import com.google.gerrit.common.data.GlobalCapability;
-import com.google.gerrit.metrics.Description;
-import com.google.gerrit.metrics.Field;
-import com.google.gerrit.metrics.MetricMaker;
-import com.google.gerrit.metrics.Timer1;
-import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.account.AccountLimits;
-import com.google.gerrit.server.index.Index;
-import com.google.gerrit.server.index.IndexCollection;
-import com.google.gerrit.server.index.IndexConfig;
-import com.google.gerrit.server.index.IndexRewriter;
-import com.google.gerrit.server.index.QueryOptions;
-import com.google.gerrit.server.index.SchemaDefinitions;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.OrmRuntimeException;
-import com.google.gwtorm.server.ResultSet;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.google.inject.Singleton;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-
-public abstract class QueryProcessor<T> {
-  @Singleton
-  protected static class Metrics {
-    final Timer1<String> executionTime;
-
-    @Inject
-    Metrics(MetricMaker metricMaker) {
-      Field<String> index = Field.ofString("index", "index name");
-      executionTime =
-          metricMaker.newTimer(
-              "query/query_latency",
-              new Description("Successful query latency, accumulated over the life of the process")
-                  .setCumulative()
-                  .setUnit(Description.Units.MILLISECONDS),
-              index);
-    }
-  }
-
-  protected final Provider<CurrentUser> userProvider;
-
-  private final AccountLimits.Factory limitsFactory;
-  private final Metrics metrics;
-  private final SchemaDefinitions<T> schemaDef;
-  private final IndexConfig indexConfig;
-  private final IndexCollection<?, T, ? extends Index<?, T>> indexes;
-  private final IndexRewriter<T> rewriter;
-  private final String limitField;
-
-  protected int start;
-
-  private boolean enforceVisibility = true;
-  private int limitFromCaller;
-  private Set<String> requestedFields;
-
-  protected QueryProcessor(
-      Provider<CurrentUser> userProvider,
-      AccountLimits.Factory limitsFactory,
-      Metrics metrics,
-      SchemaDefinitions<T> schemaDef,
-      IndexConfig indexConfig,
-      IndexCollection<?, T, ? extends Index<?, T>> indexes,
-      IndexRewriter<T> rewriter,
-      String limitField) {
-    this.userProvider = userProvider;
-    this.limitsFactory = limitsFactory;
-    this.metrics = metrics;
-    this.schemaDef = schemaDef;
-    this.indexConfig = indexConfig;
-    this.indexes = indexes;
-    this.rewriter = rewriter;
-    this.limitField = limitField;
-  }
-
-  public QueryProcessor<T> setStart(int n) {
-    start = n;
-    return this;
-  }
-
-  public QueryProcessor<T> enforceVisibility(boolean enforce) {
-    enforceVisibility = enforce;
-    return this;
-  }
-
-  public QueryProcessor<T> setLimit(int n) {
-    limitFromCaller = n;
-    return this;
-  }
-
-  public QueryProcessor<T> setRequestedFields(Set<String> fields) {
-    requestedFields = fields;
-    return this;
-  }
-
-  /**
-   * Query for entities that match a structured query.
-   *
-   * @see #query(List)
-   * @param query the query.
-   * @return results of the query.
-   */
-  public QueryResult<T> query(Predicate<T> query) throws OrmException, QueryParseException {
-    return query(ImmutableList.of(query)).get(0);
-  }
-
-  /**
-   * Perform multiple queries in parallel.
-   *
-   * @param queries list of queries.
-   * @return results of the queries, one QueryResult per input query, in the same order as the
-   *     input.
-   */
-  public List<QueryResult<T>> query(List<Predicate<T>> queries)
-      throws OrmException, QueryParseException {
-    try {
-      return query(null, queries);
-    } catch (OrmRuntimeException e) {
-      throw new OrmException(e.getMessage(), e);
-    } catch (OrmException e) {
-      if (e.getCause() != null) {
-        Throwables.throwIfInstanceOf(e.getCause(), QueryParseException.class);
-      }
-      throw e;
-    }
-  }
-
-  private List<QueryResult<T>> query(List<String> queryStrings, List<Predicate<T>> queries)
-      throws OrmException, QueryParseException {
-    long startNanos = System.nanoTime();
-
-    int cnt = queries.size();
-    // Parse and rewrite all queries.
-    List<Integer> limits = new ArrayList<>(cnt);
-    List<Predicate<T>> predicates = new ArrayList<>(cnt);
-    List<DataSource<T>> sources = new ArrayList<>(cnt);
-    for (Predicate<T> q : queries) {
-      int limit = getEffectiveLimit(q);
-      limits.add(limit);
-
-      if (limit == getBackendSupportedLimit()) {
-        limit--;
-      }
-
-      int page = (start / limit) + 1;
-      if (page > indexConfig.maxPages()) {
-        throw new QueryParseException(
-            "Cannot go beyond page " + indexConfig.maxPages() + " of results");
-      }
-
-      // Always bump limit by 1, even if this results in exceeding the permitted
-      // max for this user. The only way to see if there are more entities is to
-      // ask for one more result from the query.
-      QueryOptions opts = createOptions(indexConfig, start, limit + 1, getRequestedFields());
-      Predicate<T> pred = rewriter.rewrite(q, opts);
-      if (enforceVisibility) {
-        pred = enforceVisibility(pred);
-      }
-      predicates.add(pred);
-
-      @SuppressWarnings("unchecked")
-      DataSource<T> s = (DataSource<T>) pred;
-      sources.add(s);
-    }
-
-    // Run each query asynchronously, if supported.
-    List<ResultSet<T>> matches = new ArrayList<>(cnt);
-    for (DataSource<T> s : sources) {
-      matches.add(s.read());
-    }
-
-    List<QueryResult<T>> out = new ArrayList<>(cnt);
-    for (int i = 0; i < cnt; i++) {
-      out.add(
-          QueryResult.create(
-              queryStrings != null ? queryStrings.get(i) : null,
-              predicates.get(i),
-              limits.get(i),
-              matches.get(i).toList()));
-    }
-
-    // only measure successful queries
-    metrics.executionTime.record(
-        schemaDef.getName(), System.nanoTime() - startNanos, TimeUnit.NANOSECONDS);
-    return out;
-  }
-
-  protected QueryOptions createOptions(
-      IndexConfig indexConfig, int start, int limit, Set<String> requestedFields) {
-    return QueryOptions.create(indexConfig, start, limit, requestedFields);
-  }
-
-  /**
-   * Invoked after the query was rewritten. Subclasses must overwrite this method to filter out
-   * results that are not visible to the calling user.
-   *
-   * @param pred the query
-   * @return the modified query
-   */
-  protected abstract Predicate<T> enforceVisibility(Predicate<T> pred);
-
-  private Set<String> getRequestedFields() {
-    if (requestedFields != null) {
-      return requestedFields;
-    }
-    Index<?, T> index = indexes.getSearchIndex();
-    return index != null ? index.getSchema().getStoredFields().keySet() : ImmutableSet.<String>of();
-  }
-
-  public boolean isDisabled() {
-    return getPermittedLimit() <= 0;
-  }
-
-  private int getPermittedLimit() {
-    if (enforceVisibility) {
-      return limitsFactory
-          .create(userProvider.get())
-          .getRange(GlobalCapability.QUERY_LIMIT)
-          .getMax();
-    }
-    return Integer.MAX_VALUE;
-  }
-
-  private int getBackendSupportedLimit() {
-    return indexConfig.maxLimit();
-  }
-
-  private int getEffectiveLimit(Predicate<T> p) {
-    List<Integer> possibleLimits = new ArrayList<>(4);
-    possibleLimits.add(getBackendSupportedLimit());
-    possibleLimits.add(getPermittedLimit());
-    if (limitFromCaller > 0) {
-      possibleLimits.add(limitFromCaller);
-    }
-    if (limitField != null) {
-      Integer limitFromPredicate = LimitPredicate.getLimit(limitField, p);
-      if (limitFromPredicate != null) {
-        possibleLimits.add(limitFromPredicate);
-      }
-    }
-    return Ordering.natural().min(possibleLimits);
-  }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountIsVisibleToPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountIsVisibleToPredicate.java
index e5ed44d..cc9fc0d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountIsVisibleToPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountIsVisibleToPredicate.java
@@ -14,16 +14,17 @@
 
 package com.google.gerrit.server.query.account;
 
+import com.google.gerrit.index.query.IsVisibleToPredicate;
 import com.google.gerrit.server.account.AccountControl;
 import com.google.gerrit.server.account.AccountState;
-import com.google.gerrit.server.query.IsVisibleToPredicate;
+import com.google.gerrit.server.index.IndexUtils;
 import com.google.gwtorm.server.OrmException;
 
 public class AccountIsVisibleToPredicate extends IsVisibleToPredicate<AccountState> {
   protected final AccountControl accountControl;
 
   public AccountIsVisibleToPredicate(AccountControl accountControl) {
-    super(AccountQueryBuilder.FIELD_VISIBLETO, describe(accountControl.getUser()));
+    super(AccountQueryBuilder.FIELD_VISIBLETO, IndexUtils.describe(accountControl.getUser()));
     this.accountControl = accountControl;
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountPredicates.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountPredicates.java
index e13bd0f..d6552e2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountPredicates.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountPredicates.java
@@ -16,14 +16,14 @@
 
 import com.google.common.collect.Lists;
 import com.google.common.primitives.Ints;
+import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.query.IndexPredicate;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryBuilder;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.account.AccountState;
-import com.google.gerrit.server.index.FieldDef;
-import com.google.gerrit.server.index.IndexPredicate;
 import com.google.gerrit.server.index.account.AccountField;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryBuilder;
 import java.util.List;
 
 public class AccountPredicates {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java
index 6122277..9358a7a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java
@@ -18,14 +18,14 @@
 import com.google.common.collect.Lists;
 import com.google.common.primitives.Ints;
 import com.google.gerrit.common.errors.NotSignedInException;
+import com.google.gerrit.index.query.LimitPredicate;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryBuilder;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.AccountState;
-import com.google.gerrit.server.query.LimitPredicate;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryBuilder;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.ProvisionException;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountQueryProcessor.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountQueryProcessor.java
index 75213e7..a33118d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountQueryProcessor.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountQueryProcessor.java
@@ -17,21 +17,28 @@
 import static com.google.common.base.Preconditions.checkState;
 import static com.google.gerrit.server.query.account.AccountQueryBuilder.FIELD_LIMIT;
 
+import com.google.gerrit.index.IndexConfig;
+import com.google.gerrit.index.query.AndSource;
+import com.google.gerrit.index.query.IndexPredicate;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryProcessor;
+import com.google.gerrit.metrics.MetricMaker;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.account.AccountControl;
 import com.google.gerrit.server.account.AccountLimits;
 import com.google.gerrit.server.account.AccountState;
-import com.google.gerrit.server.index.IndexConfig;
-import com.google.gerrit.server.index.IndexPredicate;
 import com.google.gerrit.server.index.account.AccountIndexCollection;
 import com.google.gerrit.server.index.account.AccountIndexRewriter;
 import com.google.gerrit.server.index.account.AccountSchemaDefinitions;
-import com.google.gerrit.server.query.AndSource;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryProcessor;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
+/**
+ * Query processor for the account index.
+ *
+ * <p>Instances are one-time-use. Other singleton classes should inject a Provider rather than
+ * holding on to a single instance.
+ */
 public class AccountQueryProcessor extends QueryProcessor<AccountState> {
   private final AccountControl.Factory accountControlFactory;
 
@@ -46,20 +53,19 @@
   protected AccountQueryProcessor(
       Provider<CurrentUser> userProvider,
       AccountLimits.Factory limitsFactory,
-      Metrics metrics,
+      MetricMaker metricMaker,
       IndexConfig indexConfig,
       AccountIndexCollection indexes,
       AccountIndexRewriter rewriter,
       AccountControl.Factory accountControlFactory) {
     super(
-        userProvider,
-        limitsFactory,
-        metrics,
+        metricMaker,
         AccountSchemaDefinitions.INSTANCE,
         indexConfig,
         indexes,
         rewriter,
-        FIELD_LIMIT);
+        FIELD_LIMIT,
+        () -> limitsFactory.create(userProvider.get()).getQueryLimit());
     this.accountControlFactory = accountControlFactory;
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/account/InternalAccountQuery.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/account/InternalAccountQuery.java
index 8fa29753..4821e6f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/account/InternalAccountQuery.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/account/InternalAccountQuery.java
@@ -20,12 +20,12 @@
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Multimap;
+import com.google.gerrit.index.IndexConfig;
+import com.google.gerrit.index.query.InternalQuery;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.account.externalids.ExternalId;
-import com.google.gerrit.server.index.IndexConfig;
 import com.google.gerrit.server.index.account.AccountIndexCollection;
-import com.google.gerrit.server.query.InternalQuery;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import java.util.Arrays;
@@ -34,6 +34,12 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+/**
+ * Query wrapper for the account index.
+ *
+ * <p>Instances are one-time-use. Other singleton classes should inject a Provider rather than
+ * holding on to a single instance.
+ */
 public class InternalAccountQuery extends InternalQuery<AccountState> {
   private static final Logger log = LoggerFactory.getLogger(InternalAccountQuery.class);
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AddedPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AddedPredicate.java
index 06cccf4..099e841 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AddedPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AddedPredicate.java
@@ -14,8 +14,8 @@
 
 package com.google.gerrit.server.query.change;
 
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.server.index.change.ChangeField;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gwtorm.server.OrmException;
 
 public class AddedPredicate extends IntegerRangeChangePredicate {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AfterPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AfterPredicate.java
index b9c4694..de57b3b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AfterPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AfterPredicate.java
@@ -14,8 +14,8 @@
 
 package com.google.gerrit.server.query.change;
 
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.server.index.change.ChangeField;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gwtorm.server.OrmException;
 import java.util.Date;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AndChangeSource.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AndChangeSource.java
index b0fcfd1..ff1ab23 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AndChangeSource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AndChangeSource.java
@@ -14,9 +14,9 @@
 
 package com.google.gerrit.server.query.change;
 
-import com.google.gerrit.server.query.AndSource;
-import com.google.gerrit.server.query.IsVisibleToPredicate;
-import com.google.gerrit.server.query.Predicate;
+import com.google.gerrit.index.query.AndSource;
+import com.google.gerrit.index.query.IsVisibleToPredicate;
+import com.google.gerrit.index.query.Predicate;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.OrmRuntimeException;
 import java.util.Collection;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BeforePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BeforePredicate.java
index bc57f15..4d6ed69 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BeforePredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BeforePredicate.java
@@ -14,8 +14,8 @@
 
 package com.google.gerrit.server.query.change;
 
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.server.index.change.ChangeField;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gwtorm.server.OrmException;
 import java.util.Date;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BooleanPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BooleanPredicate.java
index 50d9c90..5930b74 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BooleanPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/BooleanPredicate.java
@@ -14,7 +14,7 @@
 
 package com.google.gerrit.server.query.change;
 
-import com.google.gerrit.server.index.FieldDef;
+import com.google.gerrit.index.FieldDef;
 import com.google.gwtorm.server.OrmException;
 
 public class BooleanPredicate extends ChangeIndexPredicate {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeDataSource.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeDataSource.java
index c32ff0d..34579a9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeDataSource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeDataSource.java
@@ -14,7 +14,7 @@
 
 package com.google.gerrit.server.query.change;
 
-import com.google.gerrit.server.query.DataSource;
+import com.google.gerrit.index.query.DataSource;
 
 public interface ChangeDataSource extends DataSource<ChangeData> {
   /** @return true if all returned ChangeData.hasChange() will be true. */
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIndexPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIndexPredicate.java
index 0362c85..db3b94c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIndexPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIndexPredicate.java
@@ -14,11 +14,11 @@
 
 package com.google.gerrit.server.query.change;
 
+import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.query.IndexPredicate;
+import com.google.gerrit.index.query.Matchable;
+import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.server.index.FieldDef;
-import com.google.gerrit.server.index.IndexPredicate;
-import com.google.gerrit.server.query.Matchable;
-import com.google.gerrit.server.query.Predicate;
 import com.google.gerrit.server.query.change.ChangeQueryBuilder.Arguments;
 
 public abstract class ChangeIndexPredicate extends IndexPredicate<ChangeData>
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIsVisibleToPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIsVisibleToPredicate.java
index 15fd190..7bbb27b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIsVisibleToPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIsVisibleToPredicate.java
@@ -14,23 +14,24 @@
 
 package com.google.gerrit.server.query.change;
 
+import com.google.gerrit.index.query.IsVisibleToPredicate;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.index.IndexUtils;
 import com.google.gerrit.server.notedb.ChangeNotes;
 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.project.ChangeControl;
 import com.google.gerrit.server.project.NoSuchChangeException;
-import com.google.gerrit.server.query.IsVisibleToPredicate;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Provider;
 
 public class ChangeIsVisibleToPredicate extends IsVisibleToPredicate<ChangeData> {
   protected final Provider<ReviewDb> db;
   protected final ChangeNotes.Factory notesFactory;
-  protected final ChangeControl.GenericFactory changeControl;
+  protected final ChangeControl.GenericFactory changeControlFactory;
   protected final CurrentUser user;
   protected final PermissionBackend permissionBackend;
 
@@ -40,10 +41,10 @@
       ChangeControl.GenericFactory changeControlFactory,
       CurrentUser user,
       PermissionBackend permissionBackend) {
-    super(ChangeQueryBuilder.FIELD_VISIBLETO, describe(user));
+    super(ChangeQueryBuilder.FIELD_VISIBLETO, IndexUtils.describe(user));
     this.db = db;
     this.notesFactory = notesFactory;
-    this.changeControl = changeControlFactory;
+    this.changeControlFactory = changeControlFactory;
     this.user = user;
     this.permissionBackend = permissionBackend;
   }
@@ -53,19 +54,20 @@
     if (cd.fastIsVisibleTo(user)) {
       return true;
     }
-    Change change;
+    Change change = cd.change();
+    if (change == null) {
+      return false;
+    }
+
+    ChangeControl changeControl;
+    ChangeNotes notes = notesFactory.createFromIndexedChange(change);
     try {
-      change = cd.change();
-      if (change == null) {
-        return false;
-      }
+      changeControl = changeControlFactory.controlFor(notes, user);
     } catch (NoSuchChangeException e) {
       // Ignored
       return false;
     }
 
-    ChangeNotes notes = notesFactory.createFromIndexedChange(change);
-    ChangeControl cc = changeControl.controlFor(notes, user);
     boolean visible;
     try {
       visible =
@@ -78,10 +80,9 @@
       throw new OrmException("unable to check permissions", e);
     }
     if (visible) {
-      cd.cacheVisibleTo(cc);
+      cd.cacheVisibleTo(changeControl);
       return true;
     }
-
     return false;
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeOperatorPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeOperatorPredicate.java
index 242592e..8b08536 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeOperatorPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeOperatorPredicate.java
@@ -14,8 +14,8 @@
 
 package com.google.gerrit.server.query.change;
 
-import com.google.gerrit.server.query.Matchable;
-import com.google.gerrit.server.query.OperatorPredicate;
+import com.google.gerrit.index.query.Matchable;
+import com.google.gerrit.index.query.OperatorPredicate;
 
 public abstract class ChangeOperatorPredicate extends OperatorPredicate<ChangeData>
     implements Matchable<ChangeData> {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index e997a82..5cd2a01 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -29,6 +29,13 @@
 import com.google.gerrit.common.data.SubmitRecord;
 import com.google.gerrit.common.errors.NotSignedInException;
 import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.index.IndexConfig;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.SchemaUtil;
+import com.google.gerrit.index.query.LimitPredicate;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryBuilder;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Branch;
@@ -52,9 +59,6 @@
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.strategy.SubmitDryRun;
 import com.google.gerrit.server.group.ListMembers;
-import com.google.gerrit.server.index.IndexConfig;
-import com.google.gerrit.server.index.Schema;
-import com.google.gerrit.server.index.SchemaUtil;
 import com.google.gerrit.server.index.change.ChangeField;
 import com.google.gerrit.server.index.change.ChangeIndex;
 import com.google.gerrit.server.index.change.ChangeIndexCollection;
@@ -68,10 +72,6 @@
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.ListChildProjects;
 import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.query.LimitPredicate;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryBuilder;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryProcessor.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryProcessor.java
index eeb6d01..eb6cf77 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryProcessor.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryProcessor.java
@@ -19,12 +19,15 @@
 
 import com.google.gerrit.extensions.common.PluginDefinedInfo;
 import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.index.IndexConfig;
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.query.IndexPredicate;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryProcessor;
+import com.google.gerrit.metrics.MetricMaker;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.account.AccountLimits;
-import com.google.gerrit.server.index.IndexConfig;
-import com.google.gerrit.server.index.IndexPredicate;
-import com.google.gerrit.server.index.QueryOptions;
 import com.google.gerrit.server.index.change.ChangeIndexCollection;
 import com.google.gerrit.server.index.change.ChangeIndexRewriter;
 import com.google.gerrit.server.index.change.ChangeSchemaDefinitions;
@@ -32,14 +35,18 @@
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.permissions.PermissionBackend;
 import com.google.gerrit.server.project.ChangeControl;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryProcessor;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
 
+/**
+ * Query processor for the change index.
+ *
+ * <p>Instances are one-time-use. Other singleton classes should inject a Provider rather than
+ * holding on to a single instance.
+ */
 public class ChangeQueryProcessor extends QueryProcessor<ChangeData>
     implements PluginDefinedAttributesFactory {
   /**
@@ -53,6 +60,7 @@
   }
 
   private final Provider<ReviewDb> db;
+  private final Provider<CurrentUser> userProvider;
   private final ChangeControl.GenericFactory changeControlFactory;
   private final ChangeNotes.Factory notesFactory;
   private final DynamicMap<ChangeAttributeFactory> attributeFactories;
@@ -69,7 +77,7 @@
   ChangeQueryProcessor(
       Provider<CurrentUser> userProvider,
       AccountLimits.Factory limitsFactory,
-      Metrics metrics,
+      MetricMaker metricMaker,
       IndexConfig indexConfig,
       ChangeIndexCollection indexes,
       ChangeIndexRewriter rewriter,
@@ -79,15 +87,15 @@
       DynamicMap<ChangeAttributeFactory> attributeFactories,
       PermissionBackend permissionBackend) {
     super(
-        userProvider,
-        limitsFactory,
-        metrics,
+        metricMaker,
         ChangeSchemaDefinitions.INSTANCE,
         indexConfig,
         indexes,
         rewriter,
-        FIELD_LIMIT);
+        FIELD_LIMIT,
+        () -> limitsFactory.create(userProvider.get()).getQueryLimit());
     this.db = db;
+    this.userProvider = userProvider;
     this.changeControlFactory = changeControlFactory;
     this.notesFactory = notesFactory;
     this.attributeFactories = attributeFactories;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeRegexPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeRegexPredicate.java
index f421985..24b8b7a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeRegexPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeRegexPredicate.java
@@ -14,9 +14,9 @@
 
 package com.google.gerrit.server.query.change;
 
-import com.google.gerrit.server.index.FieldDef;
-import com.google.gerrit.server.index.RegexPredicate;
-import com.google.gerrit.server.query.Matchable;
+import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.query.Matchable;
+import com.google.gerrit.index.query.RegexPredicate;
 
 public abstract class ChangeRegexPredicate extends RegexPredicate<ChangeData>
     implements Matchable<ChangeData> {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeStatusPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeStatusPredicate.java
index e9564bd..d2065cb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeStatusPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeStatusPredicate.java
@@ -14,11 +14,11 @@
 
 package com.google.gerrit.server.query.change;
 
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Change.Status;
 import com.google.gerrit.server.index.change.ChangeField;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gwtorm.server.OrmException;
 import java.util.ArrayList;
 import java.util.List;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommentPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommentPredicate.java
index 85efe90..5a6d186 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommentPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommentPredicate.java
@@ -14,11 +14,11 @@
 
 package com.google.gerrit.server.query.change;
 
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.server.index.change.ChangeField;
 import com.google.gerrit.server.index.change.ChangeIndex;
 import com.google.gerrit.server.index.change.IndexedChangeQuery;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gwtorm.server.OrmException;
 
 public class CommentPredicate extends ChangeIndexPredicate {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPredicate.java
index d2537ca..d1ae529 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/CommitPredicate.java
@@ -18,8 +18,8 @@
 import static com.google.gerrit.server.index.change.ChangeField.EXACT_COMMIT;
 import static org.eclipse.jgit.lib.Constants.OBJECT_ID_STRING_LENGTH;
 
+import com.google.gerrit.index.FieldDef;
 import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.server.index.FieldDef;
 import com.google.gwtorm.server.OrmException;
 
 public class CommitPredicate extends ChangeIndexPredicate {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsPredicate.java
index 8212d64..7890edd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ConflictsPredicate.java
@@ -15,6 +15,8 @@
 package com.google.gerrit.server.query.change;
 
 import com.google.gerrit.common.data.SubmitTypeRecord;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.git.CodeReviewCommit;
@@ -24,8 +26,6 @@
 import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectState;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gerrit.server.query.change.ChangeQueryBuilder.Arguments;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Provider;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/DeletedPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/DeletedPredicate.java
index 24c37e3..6232fc5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/DeletedPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/DeletedPredicate.java
@@ -14,8 +14,8 @@
 
 package com.google.gerrit.server.query.change;
 
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.server.index.change.ChangeField;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gwtorm.server.OrmException;
 
 public class DeletedPredicate extends IntegerRangeChangePredicate {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/DeltaPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/DeltaPredicate.java
index dac3730..aae0a20 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/DeltaPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/DeltaPredicate.java
@@ -14,8 +14,8 @@
 
 package com.google.gerrit.server.query.change;
 
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.server.index.change.ChangeField;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gwtorm.server.OrmException;
 
 public class DeltaPredicate extends IntegerRangeChangePredicate {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsFilePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsFilePredicate.java
index 66958695..b5a2d05 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsFilePredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsFilePredicate.java
@@ -14,8 +14,8 @@
 
 package com.google.gerrit.server.query.change;
 
+import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.server.index.change.ChangeField;
-import com.google.gerrit.server.query.Predicate;
 import com.google.gerrit.server.query.change.ChangeQueryBuilder.Arguments;
 import com.google.gwtorm.server.OrmException;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/FuzzyTopicPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/FuzzyTopicPredicate.java
index c6908dc..545b668 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/FuzzyTopicPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/FuzzyTopicPredicate.java
@@ -17,11 +17,11 @@
 import static com.google.gerrit.server.index.change.ChangeField.FUZZY_TOPIC;
 
 import com.google.common.collect.Iterables;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.server.index.change.ChangeIndex;
 import com.google.gerrit.server.index.change.IndexedChangeQuery;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gwtorm.server.OrmException;
 
 public class FuzzyTopicPredicate extends ChangeIndexPredicate {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IntegerRangeChangePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IntegerRangeChangePredicate.java
index d4f5620..312c04e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IntegerRangeChangePredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IntegerRangeChangePredicate.java
@@ -14,10 +14,10 @@
 
 package com.google.gerrit.server.query.change;
 
-import com.google.gerrit.server.index.FieldDef;
-import com.google.gerrit.server.index.IntegerRangePredicate;
-import com.google.gerrit.server.query.Matchable;
-import com.google.gerrit.server.query.QueryParseException;
+import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.query.IntegerRangePredicate;
+import com.google.gerrit.index.query.Matchable;
+import com.google.gerrit.index.query.QueryParseException;
 
 public abstract class IntegerRangeChangePredicate extends IntegerRangePredicate<ChangeData>
     implements Matchable<ChangeData> {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
index 42e60a4..2316bd0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
@@ -15,25 +15,25 @@
 package com.google.gerrit.server.query.change;
 
 import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.gerrit.server.query.Predicate.and;
-import static com.google.gerrit.server.query.Predicate.not;
-import static com.google.gerrit.server.query.Predicate.or;
+import static com.google.gerrit.index.query.Predicate.and;
+import static com.google.gerrit.index.query.Predicate.not;
+import static com.google.gerrit.index.query.Predicate.or;
 import static com.google.gerrit.server.query.change.ChangeStatusPredicate.open;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Strings;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
+import com.google.gerrit.index.IndexConfig;
+import com.google.gerrit.index.query.InternalQuery;
+import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.index.IndexConfig;
 import com.google.gerrit.server.index.change.ChangeIndexCollection;
 import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gerrit.server.query.InternalQuery;
-import com.google.gerrit.server.query.Predicate;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import java.io.IOException;
@@ -46,6 +46,12 @@
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
 
+/**
+ * Query wrapper for the change index.
+ *
+ * <p>Instances are one-time-use. Other singleton classes should inject a Provider rather than
+ * holding on to a single instance.
+ */
 public class InternalChangeQuery extends InternalQuery<ChangeData> {
   private static Predicate<ChangeData> ref(Branch.NameKey branch) {
     return new RefPredicate(branch.get());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsReviewedPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsReviewedPredicate.java
index 8b6c8e6..7ff5a28 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsReviewedPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsReviewedPredicate.java
@@ -16,9 +16,9 @@
 
 import static com.google.gerrit.server.index.change.ChangeField.REVIEWEDBY;
 
+import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.index.change.ChangeField;
-import com.google.gerrit.server.query.Predicate;
 import com.google.gwtorm.server.OrmException;
 import java.util.ArrayList;
 import java.util.Collection;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsUnresolvedPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsUnresolvedPredicate.java
index c319546..225dc454 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsUnresolvedPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsUnresolvedPredicate.java
@@ -14,8 +14,8 @@
 
 package com.google.gerrit.server.query.change;
 
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.server.index.change.ChangeField;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gwtorm.server.OrmException;
 
 public class IsUnresolvedPredicate extends IntegerRangeChangePredicate {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java
index a1a5070..90eb8e4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java
@@ -15,12 +15,12 @@
 package com.google.gerrit.server.query.change;
 
 import com.google.common.collect.ImmutableList;
+import com.google.gerrit.index.query.AndPredicate;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryBuilder;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.account.WatchConfig.ProjectWatchKey;
-import com.google.gerrit.server.query.AndPredicate;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryBuilder;
-import com.google.gerrit.server.query.QueryParseException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java
index bd342d7..c9ddfb7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java
@@ -15,6 +15,10 @@
 package com.google.gerrit.server.query.change;
 
 import com.google.common.collect.Lists;
+import com.google.gerrit.index.query.OrPredicate;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.RangeUtil;
+import com.google.gerrit.index.query.RangeUtil.Range;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -22,11 +26,7 @@
 import com.google.gerrit.server.permissions.PermissionBackend;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.query.OrPredicate;
-import com.google.gerrit.server.query.Predicate;
 import com.google.gerrit.server.util.LabelVote;
-import com.google.gerrit.server.util.RangeUtil;
-import com.google.gerrit.server.util.RangeUtil.Range;
 import com.google.inject.Provider;
 import java.util.ArrayList;
 import java.util.List;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/MessagePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/MessagePredicate.java
index 92d1ed3..0cfcedb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/MessagePredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/MessagePredicate.java
@@ -14,11 +14,11 @@
 
 package com.google.gerrit.server.query.change;
 
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.server.index.change.ChangeField;
 import com.google.gerrit.server.index.change.ChangeIndex;
 import com.google.gerrit.server.index.change.IndexedChangeQuery;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gwtorm.server.OrmException;
 
 /** Predicate to match changes that contains specified text in commit messages body. */
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OrSource.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OrSource.java
index 90c2fb3..a703852 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OrSource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OrSource.java
@@ -14,9 +14,9 @@
 
 package com.google.gerrit.server.query.change;
 
+import com.google.gerrit.index.query.OrPredicate;
+import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.server.query.OrPredicate;
-import com.google.gerrit.server.query.Predicate;
 import com.google.gwtorm.server.ListResultSet;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.ResultSet;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OutputStreamQuery.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
index a69727d7..879d34b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
@@ -19,6 +19,8 @@
 
 import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.data.LabelTypes;
+import com.google.gerrit.index.query.QueryParseException;
+import com.google.gerrit.index.query.QueryResult;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -34,8 +36,6 @@
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.SubmitRuleEvaluator;
-import com.google.gerrit.server.query.QueryParseException;
-import com.google.gerrit.server.query.QueryResult;
 import com.google.gson.Gson;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -59,7 +59,12 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-/** Change query implementation that outputs to a stream in the style of an SSH command. */
+/**
+ * Change query implementation that outputs to a stream in the style of an SSH command.
+ *
+ * <p>Instances are one-time-use. Other singleton classes should inject a Provider rather than
+ * holding on to a single instance.
+ */
 public class OutputStreamQuery {
   private static final Logger log = LoggerFactory.getLogger(OutputStreamQuery.class);
 
@@ -120,7 +125,7 @@
   }
 
   void setLimit(int n) {
-    queryProcessor.setLimit(n);
+    queryProcessor.setUserProvidedLimit(n);
   }
 
   public void setStart(int n) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ParentProjectPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ParentProjectPredicate.java
index 3b00c0a..f5e8d69 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ParentProjectPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ParentProjectPredicate.java
@@ -15,6 +15,8 @@
 package com.google.gerrit.server.query.change;
 
 import com.google.gerrit.extensions.common.ProjectInfo;
+import com.google.gerrit.index.query.OrPredicate;
+import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -22,8 +24,6 @@
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectResource;
 import com.google.gerrit.server.project.ProjectState;
-import com.google.gerrit.server.query.OrPredicate;
-import com.google.gerrit.server.query.Predicate;
 import com.google.inject.Provider;
 import java.util.ArrayList;
 import java.util.Collections;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/PredicateArgs.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/PredicateArgs.java
index 1fbc1aa..ad7a57d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/PredicateArgs.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/PredicateArgs.java
@@ -14,7 +14,7 @@
 
 package com.google.gerrit.server.query.change;
 
-import com.google.gerrit.server.query.QueryParseException;
+import com.google.gerrit.index.query.QueryParseException;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
index f0ef40d..c1fc0ec 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
@@ -25,10 +25,10 @@
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.gerrit.index.query.QueryParseException;
+import com.google.gerrit.index.query.QueryResult;
 import com.google.gerrit.server.change.ChangeJson;
 import com.google.gerrit.server.index.change.ChangeField;
-import com.google.gerrit.server.query.QueryParseException;
-import com.google.gerrit.server.query.QueryResult;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import java.util.ArrayList;
@@ -60,7 +60,7 @@
     usage = "Maximum number of results to return"
   )
   public void setLimit(int limit) {
-    imp.setLimit(limit);
+    imp.setUserProvidedLimit(limit);
   }
 
   @Option(name = "-o", usage = "Output options per change")
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerByEmailPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerByEmailPredicate.java
index a040e18..16feed9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerByEmailPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerByEmailPredicate.java
@@ -16,10 +16,10 @@
 
 import static com.google.common.base.Preconditions.checkArgument;
 
+import com.google.gerrit.index.query.Predicate;
 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.gerrit.server.query.Predicate;
 import com.google.gerrit.server.query.change.ChangeQueryBuilder.Arguments;
 import com.google.gwtorm.server.OrmException;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerPredicate.java
index f3a8619..7bea4a4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ReviewerPredicate.java
@@ -17,10 +17,10 @@
 import static com.google.common.base.Preconditions.checkArgument;
 import static java.util.stream.Collectors.toList;
 
+import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.index.change.ChangeField;
 import com.google.gerrit.server.notedb.ReviewerStateInternal;
-import com.google.gerrit.server.query.Predicate;
 import com.google.gerrit.server.query.change.ChangeQueryBuilder.Arguments;
 import com.google.gwtorm.server.OrmException;
 import java.util.stream.Stream;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SubmitRecordPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SubmitRecordPredicate.java
index 81d64e0e..17034df 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SubmitRecordPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/SubmitRecordPredicate.java
@@ -17,9 +17,9 @@
 import static java.util.stream.Collectors.toList;
 
 import com.google.gerrit.common.data.SubmitRecord;
+import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.index.change.ChangeField;
-import com.google.gerrit.server.query.Predicate;
 import com.google.gwtorm.server.OrmException;
 import java.util.Set;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TimestampRangeChangePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TimestampRangeChangePredicate.java
index f0ac127..abbd0c9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TimestampRangeChangePredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/TimestampRangeChangePredicate.java
@@ -14,9 +14,9 @@
 
 package com.google.gerrit.server.query.change;
 
-import com.google.gerrit.server.index.FieldDef;
-import com.google.gerrit.server.index.TimestampRangePredicate;
-import com.google.gerrit.server.query.Matchable;
+import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.query.Matchable;
+import com.google.gerrit.index.query.TimestampRangePredicate;
 import java.sql.Timestamp;
 
 public abstract class TimestampRangeChangePredicate extends TimestampRangePredicate<ChangeData>
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupIsVisibleToPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupIsVisibleToPredicate.java
index 3ac9c39..63138cb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupIsVisibleToPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupIsVisibleToPredicate.java
@@ -15,10 +15,11 @@
 package com.google.gerrit.server.query.group;
 
 import com.google.gerrit.common.errors.NoSuchGroupException;
+import com.google.gerrit.index.query.IsVisibleToPredicate;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.account.GroupControl;
-import com.google.gerrit.server.query.IsVisibleToPredicate;
+import com.google.gerrit.server.index.IndexUtils;
 import com.google.gerrit.server.query.account.AccountQueryBuilder;
 import com.google.gwtorm.server.OrmException;
 
@@ -28,7 +29,7 @@
 
   public GroupIsVisibleToPredicate(
       GroupControl.GenericFactory groupControlFactory, CurrentUser user) {
-    super(AccountQueryBuilder.FIELD_VISIBLETO, describe(user));
+    super(AccountQueryBuilder.FIELD_VISIBLETO, IndexUtils.describe(user));
     this.groupControlFactory = groupControlFactory;
     this.user = user;
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupPredicates.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupPredicates.java
index 650024c..6d3d9ba 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupPredicates.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupPredicates.java
@@ -14,11 +14,11 @@
 
 package com.google.gerrit.server.query.group;
 
+import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.query.IndexPredicate;
+import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.server.index.FieldDef;
-import com.google.gerrit.server.index.IndexPredicate;
 import com.google.gerrit.server.index.group.GroupField;
-import com.google.gerrit.server.query.Predicate;
 import java.util.Locale;
 
 public class GroupPredicates {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupQueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupQueryBuilder.java
index 3197ab7..1cba96c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupQueryBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupQueryBuilder.java
@@ -18,14 +18,14 @@
 import com.google.common.collect.Lists;
 import com.google.common.primitives.Ints;
 import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.index.query.LimitPredicate;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryBuilder;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.account.GroupBackend;
 import com.google.gerrit.server.account.GroupBackends;
 import com.google.gerrit.server.account.GroupCache;
-import com.google.gerrit.server.query.LimitPredicate;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryBuilder;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.inject.Inject;
 import java.util.List;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupQueryProcessor.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupQueryProcessor.java
index e096656..a1f4a31 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupQueryProcessor.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/group/GroupQueryProcessor.java
@@ -17,22 +17,30 @@
 import static com.google.common.base.Preconditions.checkState;
 import static com.google.gerrit.server.query.group.GroupQueryBuilder.FIELD_LIMIT;
 
+import com.google.gerrit.index.IndexConfig;
+import com.google.gerrit.index.query.AndSource;
+import com.google.gerrit.index.query.IndexPredicate;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryProcessor;
+import com.google.gerrit.metrics.MetricMaker;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.account.AccountLimits;
 import com.google.gerrit.server.account.GroupControl;
-import com.google.gerrit.server.index.IndexConfig;
-import com.google.gerrit.server.index.IndexPredicate;
 import com.google.gerrit.server.index.group.GroupIndexCollection;
 import com.google.gerrit.server.index.group.GroupIndexRewriter;
 import com.google.gerrit.server.index.group.GroupSchemaDefinitions;
-import com.google.gerrit.server.query.AndSource;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryProcessor;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
+/**
+ * Query processor for the group index.
+ *
+ * <p>Instances are one-time-use. Other singleton classes should inject a Provider rather than
+ * holding on to a single instance.
+ */
 public class GroupQueryProcessor extends QueryProcessor<AccountGroup> {
+  private final Provider<CurrentUser> userProvider;
   private final GroupControl.GenericFactory groupControlFactory;
 
   static {
@@ -46,20 +54,20 @@
   protected GroupQueryProcessor(
       Provider<CurrentUser> userProvider,
       AccountLimits.Factory limitsFactory,
-      Metrics metrics,
+      MetricMaker metricMaker,
       IndexConfig indexConfig,
       GroupIndexCollection indexes,
       GroupIndexRewriter rewriter,
       GroupControl.GenericFactory groupControlFactory) {
     super(
-        userProvider,
-        limitsFactory,
-        metrics,
+        metricMaker,
         GroupSchemaDefinitions.INSTANCE,
         indexConfig,
         indexes,
         rewriter,
-        FIELD_LIMIT);
+        FIELD_LIMIT,
+        () -> limitsFactory.create(userProvider.get()).getQueryLimit());
+    this.userProvider = userProvider;
     this.groupControlFactory = groupControlFactory;
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/JdbcAccountPatchReviewStore.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/JdbcAccountPatchReviewStore.java
index 5a3e76d..70fbf5c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/JdbcAccountPatchReviewStore.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/JdbcAccountPatchReviewStore.java
@@ -41,6 +41,12 @@
 
 public abstract class JdbcAccountPatchReviewStore
     implements AccountPatchReviewStore, LifecycleListener {
+  private static final String ACCOUNT_PATCH_REVIEW_DB = "accountPatchReviewDb";
+  private static final String H2_DB = "h2";
+  private static final String MARIADB = "mariadb";
+  private static final String MYSQL = "mysql";
+  private static final String POSTGRESQL = "postgresql";
+  private static final String URL = "url";
   private static final Logger log = LoggerFactory.getLogger(JdbcAccountPatchReviewStore.class);
 
   public static class Module extends LifecycleModule {
@@ -52,20 +58,20 @@
 
     @Override
     protected void configure() {
-      String url = cfg.getString("accountPatchReviewDb", null, "url");
-      if (url == null || url.contains("h2")) {
+      String url = cfg.getString(ACCOUNT_PATCH_REVIEW_DB, null, URL);
+      if (url == null || url.contains(H2_DB)) {
         DynamicItem.bind(binder(), AccountPatchReviewStore.class)
             .to(H2AccountPatchReviewStore.class);
         listener().to(H2AccountPatchReviewStore.class);
-      } else if (url.contains("postgresql")) {
+      } else if (url.contains(POSTGRESQL)) {
         DynamicItem.bind(binder(), AccountPatchReviewStore.class)
             .to(PostgresqlAccountPatchReviewStore.class);
         listener().to(PostgresqlAccountPatchReviewStore.class);
-      } else if (url.contains("mysql")) {
+      } else if (url.contains(MYSQL)) {
         DynamicItem.bind(binder(), AccountPatchReviewStore.class)
             .to(MysqlAccountPatchReviewStore.class);
         listener().to(MysqlAccountPatchReviewStore.class);
-      } else if (url.contains("mariadb")) {
+      } else if (url.contains(MARIADB)) {
         DynamicItem.bind(binder(), AccountPatchReviewStore.class)
             .to(MariaDBAccountPatchReviewStore.class);
         listener().to(MariaDBAccountPatchReviewStore.class);
@@ -80,19 +86,21 @@
 
   public static JdbcAccountPatchReviewStore createAccountPatchReviewStore(
       Config cfg, SitePaths sitePaths) {
-    String url = cfg.getString("accountPatchReviewDb", null, "url");
-    if (url == null || url.contains("h2")) {
+    String url = cfg.getString(ACCOUNT_PATCH_REVIEW_DB, null, URL);
+    if (url == null || url.contains(H2_DB)) {
       return new H2AccountPatchReviewStore(cfg, sitePaths);
-    } else if (url.contains("postgresql")) {
-      return new PostgresqlAccountPatchReviewStore(cfg, sitePaths);
-    } else if (url.contains("mysql")) {
-      return new MysqlAccountPatchReviewStore(cfg, sitePaths);
-    } else if (url.contains("mariadb")) {
-      return new MariaDBAccountPatchReviewStore(cfg, sitePaths);
-    } else {
-      throw new IllegalArgumentException(
-          "unsupported driver type for account patch reviews db: " + url);
     }
+    if (url.contains(POSTGRESQL)) {
+      return new PostgresqlAccountPatchReviewStore(cfg, sitePaths);
+    }
+    if (url.contains(MYSQL)) {
+      return new MysqlAccountPatchReviewStore(cfg, sitePaths);
+    }
+    if (url.contains(MARIADB)) {
+      return new MariaDBAccountPatchReviewStore(cfg, sitePaths);
+    }
+    throw new IllegalArgumentException(
+        "unsupported driver type for account patch reviews db: " + url);
   }
 
   protected JdbcAccountPatchReviewStore(Config cfg, SitePaths sitePaths) {
@@ -104,7 +112,7 @@
   }
 
   private static String getUrl(@GerritServerConfig Config cfg, SitePaths sitePaths) {
-    String url = cfg.getString("accountPatchReviewDb", null, "url");
+    String url = cfg.getString(ACCOUNT_PATCH_REVIEW_DB, null, URL);
     if (url == null) {
       return H2.createUrl(sitePaths.db_dir.resolve("account_patch_reviews"));
     }
@@ -113,13 +121,13 @@
 
   protected static DataSource createDataSource(String url) {
     BasicDataSource datasource = new BasicDataSource();
-    if (url.contains("postgresql")) {
+    if (url.contains(POSTGRESQL)) {
       datasource.setDriverClassName("org.postgresql.Driver");
-    } else if (url.contains("h2")) {
+    } else if (url.contains(H2_DB)) {
       datasource.setDriverClassName("org.h2.Driver");
-    } else if (url.contains("mysql")) {
+    } else if (url.contains(MYSQL)) {
       datasource.setDriverClassName("com.mysql.jdbc.Driver");
-    } else if (url.contains("mariadb")) {
+    } else if (url.contains(MARIADB)) {
       datasource.setDriverClassName("org.mariadb.jdbc.Driver");
     }
     datasource.setUrl(url);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/UpdateUI.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/UpdateUI.java
index b43aaa6..0c02607 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/UpdateUI.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/UpdateUI.java
@@ -17,11 +17,24 @@
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.StatementExecutor;
 import java.util.List;
+import java.util.Set;
 
 public interface UpdateUI {
-  void message(String msg);
 
-  boolean yesno(boolean def, String msg);
+  void message(String message);
+
+  /** Requests the user to answer a yes/no question. */
+  boolean yesno(boolean defaultValue, String message);
+
+  /** Prints a message asking the user to let us know when it's safe to continue. */
+  void waitForUser();
+
+  /**
+   * Prompts the user for a string, suggesting a default.
+   *
+   * @return the chosen string from the list of allowed values.
+   */
+  String readString(String defaultValue, Set<String> allowedValues, String message);
 
   boolean isBatch();
 
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/index/change/ChangeIndexRewriterTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/index/change/ChangeIndexRewriterTest.java
index b80e31e..b589289 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/index/change/ChangeIndexRewriterTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/index/change/ChangeIndexRewriterTest.java
@@ -16,21 +16,21 @@
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.gerrit.common.data.GlobalCapability.DEFAULT_MAX_QUERY_LIMIT;
+import static com.google.gerrit.index.query.Predicate.and;
+import static com.google.gerrit.index.query.Predicate.or;
 import static com.google.gerrit.reviewdb.client.Change.Status.ABANDONED;
 import static com.google.gerrit.reviewdb.client.Change.Status.DRAFT;
 import static com.google.gerrit.reviewdb.client.Change.Status.MERGED;
 import static com.google.gerrit.reviewdb.client.Change.Status.NEW;
 import static com.google.gerrit.server.index.change.IndexedChangeQuery.convertOptions;
-import static com.google.gerrit.server.query.Predicate.and;
-import static com.google.gerrit.server.query.Predicate.or;
 import static org.junit.Assert.assertEquals;
 
 import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.index.IndexConfig;
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.server.index.IndexConfig;
-import com.google.gerrit.server.index.QueryOptions;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gerrit.server.query.change.AndChangeSource;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.ChangeQueryBuilder;
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/index/change/FakeChangeIndex.java b/gerrit-server/src/test/java/com/google/gerrit/server/index/change/FakeChangeIndex.java
index 8189c81..74e1c09 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/index/change/FakeChangeIndex.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/index/change/FakeChangeIndex.java
@@ -15,12 +15,12 @@
 package com.google.gerrit.server.index.change;
 
 import com.google.common.collect.ImmutableList;
+import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.server.index.FieldDef;
-import com.google.gerrit.server.index.QueryOptions;
-import com.google.gerrit.server.index.Schema;
-import com.google.gerrit.server.query.Predicate;
-import com.google.gerrit.server.query.QueryParseException;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.ChangeDataSource;
 import com.google.gwtorm.server.OrmException;
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/index/change/FakeQueryBuilder.java b/gerrit-server/src/test/java/com/google/gerrit/server/index/change/FakeQueryBuilder.java
index edc221c..a194336 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/index/change/FakeQueryBuilder.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/index/change/FakeQueryBuilder.java
@@ -14,8 +14,8 @@
 
 package com.google.gerrit.server.index.change;
 
-import com.google.gerrit.server.query.OperatorPredicate;
-import com.google.gerrit.server.query.Predicate;
+import com.google.gerrit.index.query.OperatorPredicate;
+import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.ChangeQueryBuilder;
 import org.junit.Ignore;
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java
index 8323051..c5eea0f 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java
@@ -104,7 +104,7 @@
 
   @Inject protected OneOffRequestContext oneOffRequestContext;
 
-  @Inject protected InternalAccountQuery internalAccountQuery;
+  @Inject protected Provider<InternalAccountQuery> queryProvider;
 
   @Inject protected AllProjectsName allProjects;
 
@@ -294,19 +294,19 @@
     AccountInfo user2 = newAccountWithFullName("jroe", "Jane Roe");
     AccountInfo user3 = newAccountWithFullName("user3", "Mr Selfish");
 
-    assertThat(internalAccountQuery.byWatchedProject(p)).isEmpty();
+    assertThat(queryProvider.get().byWatchedProject(p)).isEmpty();
 
     watch(user1, p, null);
-    assertAccounts(internalAccountQuery.byWatchedProject(p), user1);
+    assertAccounts(queryProvider.get().byWatchedProject(p), user1);
 
     watch(user2, p, "keyword");
-    assertAccounts(internalAccountQuery.byWatchedProject(p), user1, user2);
+    assertAccounts(queryProvider.get().byWatchedProject(p), user1, user2);
 
     watch(user3, p2, "keyword");
     watch(user3, allProjects, "keyword");
-    assertAccounts(internalAccountQuery.byWatchedProject(p), user1, user2);
-    assertAccounts(internalAccountQuery.byWatchedProject(p2), user3);
-    assertAccounts(internalAccountQuery.byWatchedProject(allProjects), user3);
+    assertAccounts(queryProvider.get().byWatchedProject(p), user1, user2);
+    assertAccounts(queryProvider.get().byWatchedProject(p2), user3);
+    assertAccounts(queryProvider.get().byWatchedProject(allProjects), user3);
   }
 
   @Test
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index 044dbbe..9ab4db0 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -57,6 +57,10 @@
 import com.google.gerrit.extensions.common.CommentInfo;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.IndexConfig;
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.Schema;
 import com.google.gerrit.lifecycle.LifecycleManager;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Branch;
@@ -84,10 +88,6 @@
 import com.google.gerrit.server.change.PatchSetInserter;
 import com.google.gerrit.server.config.AllUsersName;
 import com.google.gerrit.server.git.MetaDataUpdate;
-import com.google.gerrit.server.index.FieldDef;
-import com.google.gerrit.server.index.IndexConfig;
-import com.google.gerrit.server.index.QueryOptions;
-import com.google.gerrit.server.index.Schema;
 import com.google.gerrit.server.index.change.ChangeField;
 import com.google.gerrit.server.index.change.ChangeIndexCollection;
 import com.google.gerrit.server.index.change.ChangeIndexer;
@@ -167,13 +167,13 @@
   @Inject protected ChangeIndexer indexer;
   @Inject protected IndexConfig indexConfig;
   @Inject protected InMemoryRepositoryManager repoManager;
-  @Inject protected InternalChangeQuery internalChangeQuery;
+  @Inject protected Provider<InternalChangeQuery> queryProvider;
   @Inject protected ChangeNotes.Factory notesFactory;
   @Inject protected OneOffRequestContext oneOffRequestContext;
   @Inject protected PatchSetInserter.Factory patchSetFactory;
   @Inject protected PatchSetUtil psUtil;
   @Inject protected ChangeControl.GenericFactory changeControlFactory;
-  @Inject protected ChangeQueryProcessor queryProcessor;
+  @Inject protected Provider<ChangeQueryProcessor> queryProcessorProvider;
   @Inject protected SchemaCreator schemaCreator;
   @Inject protected SchemaFactory<ReviewDb> schemaFactory;
   @Inject protected Sequences seq;
@@ -2011,7 +2011,7 @@
 
     for (int i = 1; i <= 11; i++) {
       Iterable<ChangeData> cds =
-          internalChangeQuery.byCommitsOnBranchNotMerged(repo.getRepository(), db, dest, shas, i);
+          queryProvider.get().byCommitsOnBranchNotMerged(repo.getRepository(), db, dest, shas, i);
       Iterable<Integer> ids = FluentIterable.from(cds).transform(in -> in.getId().get());
       String name = "limit " + i;
       assertThat(ids).named(name).hasSize(n);
@@ -2029,7 +2029,10 @@
     requestContext.setContext(newRequestContext(userId));
     // Use QueryProcessor directly instead of API so we get ChangeDatas back.
     List<ChangeData> cds =
-        queryProcessor.query(queryBuilder.parse(change.getId().toString())).entities();
+        queryProcessorProvider
+            .get()
+            .query(queryBuilder.parse(change.getId().toString()))
+            .entities();
     assertThat(cds).hasSize(1);
 
     ChangeData cd = cds.get(0);
@@ -2059,7 +2062,8 @@
     requestContext.setContext(newRequestContext(userId));
     // Use QueryProcessor directly instead of API so we get ChangeDatas back.
     List<ChangeData> cds =
-        queryProcessor
+        queryProcessorProvider
+            .get()
             .setRequestedFields(
                 ImmutableSet.of(ChangeField.PATCH_SET.getName(), ChangeField.CHANGE.getName()))
             .query(queryBuilder.parse(change.getId().toString()))
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java
index 96f573f..db89eda 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java
@@ -40,7 +40,6 @@
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.group.GroupsUpdate;
 import com.google.gerrit.server.group.ServerInitiated;
-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;
@@ -94,8 +93,6 @@
 
   @Inject protected OneOffRequestContext oneOffRequestContext;
 
-  @Inject protected InternalAccountQuery internalAccountQuery;
-
   @Inject protected AllProjectsName allProjects;
 
   @Inject protected GroupCache groupCache;
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaUpdaterTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaUpdaterTest.java
index d9fc7e5..5b86f46 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaUpdaterTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaUpdaterTest.java
@@ -34,9 +34,9 @@
 import com.google.gerrit.testutil.InMemoryDatabase;
 import com.google.gerrit.testutil.InMemoryH2Type;
 import com.google.gerrit.testutil.InMemoryRepositoryManager;
+import com.google.gerrit.testutil.TestUpdateUI;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.SchemaFactory;
-import com.google.gwtorm.server.StatementExecutor;
 import com.google.inject.Guice;
 import com.google.inject.Key;
 import com.google.inject.ProvisionException;
@@ -45,7 +45,6 @@
 import java.io.IOException;
 import java.nio.file.Path;
 import java.nio.file.Paths;
-import java.util.List;
 import java.util.UUID;
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.PersonIdent;
@@ -133,28 +132,7 @@
       }
     }
 
-    u.update(
-        new UpdateUI() {
-          @Override
-          public void message(String msg) {}
-
-          @Override
-          public boolean yesno(boolean def, String msg) {
-            return def;
-          }
-
-          @Override
-          public boolean isBatch() {
-            return true;
-          }
-
-          @Override
-          public void pruneSchema(StatementExecutor e, List<String> pruneList) throws OrmException {
-            for (String sql : pruneList) {
-              e.execute(sql);
-            }
-          }
-        });
+    u.update(new TestUpdateUI());
 
     db.assertSchemaVersion();
     final SystemConfig sc = db.getSystemConfig();
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java
index 0db22d3..41647db 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java
@@ -23,6 +23,7 @@
 import com.google.gerrit.extensions.config.FactoryModule;
 import com.google.gerrit.extensions.systemstatus.ServerInformation;
 import com.google.gerrit.gpg.GpgModule;
+import com.google.gerrit.index.SchemaDefinitions;
 import com.google.gerrit.metrics.DisabledMetricMaker;
 import com.google.gerrit.metrics.MetricMaker;
 import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -50,7 +51,6 @@
 import com.google.gerrit.server.git.SearchingChangeCacheImpl;
 import com.google.gerrit.server.git.SendEmailExecutor;
 import com.google.gerrit.server.index.IndexModule.IndexType;
-import com.google.gerrit.server.index.SchemaDefinitions;
 import com.google.gerrit.server.index.account.AccountSchemaDefinitions;
 import com.google.gerrit.server.index.account.AllAccountsIndexer;
 import com.google.gerrit.server.index.change.AllChangesIndexer;
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/IndexVersions.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/IndexVersions.java
index 825cd7b..c2ba740 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/IndexVersions.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/IndexVersions.java
@@ -22,8 +22,8 @@
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
 import com.google.common.primitives.Ints;
-import com.google.gerrit.server.index.Schema;
-import com.google.gerrit.server.index.SchemaDefinitions;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.SchemaDefinitions;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/TestUpdateUI.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/TestUpdateUI.java
index 644f8e2..d4acbcb 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/TestUpdateUI.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/TestUpdateUI.java
@@ -18,21 +18,34 @@
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.StatementExecutor;
 import java.util.List;
+import java.util.Set;
 
 public class TestUpdateUI implements UpdateUI {
   @Override
-  public void message(String msg) {}
+  public void message(String message) {}
 
   @Override
-  public boolean yesno(boolean def, String msg) {
-    return false;
+  public boolean yesno(boolean defaultValue, String message) {
+    return defaultValue;
+  }
+
+  @Override
+  public void waitForUser() {}
+
+  @Override
+  public String readString(String defaultValue, Set<String> allowedValues, String message) {
+    return defaultValue;
   }
 
   @Override
   public boolean isBatch() {
-    return false;
+    return true;
   }
 
   @Override
-  public void pruneSchema(StatementExecutor e, List<String> pruneList) throws OrmException {}
+  public void pruneSchema(StatementExecutor e, List<String> pruneList) throws OrmException {
+    for (String sql : pruneList) {
+      e.execute(sql);
+    }
+  }
 }
diff --git a/gerrit-sshd/BUILD b/gerrit-sshd/BUILD
index 1ae0376..6dd0d5f 100644
--- a/gerrit-sshd/BUILD
+++ b/gerrit-sshd/BUILD
@@ -14,6 +14,7 @@
         "//gerrit-lucene:lucene",
         "//gerrit-patch-jgit:server",
         "//gerrit-reviewdb:server",
+        "//gerrit-server:metrics",
         "//gerrit-server:receive",
         "//gerrit-server:server",
         "//gerrit-util-cli:cli",
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginLsCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginLsCommand.java
index 78c9526..0fdd105 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginLsCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginLsCommand.java
@@ -16,25 +16,68 @@
 
 import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
 
+import com.google.common.base.Strings;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.extensions.common.PluginInfo;
+import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.gerrit.server.OutputFormat;
 import com.google.gerrit.server.plugins.ListPlugins;
 import com.google.gerrit.sshd.CommandMetaData;
 import com.google.gerrit.sshd.SshCommand;
+import com.google.gson.reflect.TypeToken;
 import com.google.inject.Inject;
+import java.util.Map;
+import org.kohsuke.args4j.Option;
 
 @RequiresCapability(GlobalCapability.VIEW_PLUGINS)
 @CommandMetaData(name = "ls", description = "List the installed plugins", runsAt = MASTER_OR_SLAVE)
 final class PluginLsCommand extends SshCommand {
-  @Inject private ListPlugins impl;
+  @Inject private ListPlugins list;
+
+  @Option(
+    name = "--all",
+    aliases = {"-a"},
+    usage = "List all plugins, including disabled plugins"
+  )
+  private boolean all;
+
+  @Option(name = "--format", usage = "output format")
+  private OutputFormat format = OutputFormat.TEXT;
 
   @Override
   public void run() throws Exception {
-    impl.display(stdout);
+    list.setAll(all);
+    Map<String, PluginInfo> output = list.apply(TopLevelResource.INSTANCE);
+
+    if (format.isJson()) {
+      format
+          .newGson()
+          .toJson(output, new TypeToken<Map<String, PluginInfo>>() {}.getType(), stdout);
+      stdout.print('\n');
+    } else {
+      stdout.format("%-30s %-10s %-8s %s\n", "Name", "Version", "Status", "File");
+      stdout.print(
+          "-------------------------------------------------------------------------------\n");
+      for (Map.Entry<String, PluginInfo> p : output.entrySet()) {
+        PluginInfo info = p.getValue();
+        stdout.format(
+            "%-30s %-10s %-8s %s\n",
+            p.getKey(),
+            Strings.nullToEmpty(info.version),
+            status(info.disabled),
+            Strings.nullToEmpty(info.filename));
+      }
+    }
+    stdout.flush();
+  }
+
+  private String status(Boolean disabled) {
+    return disabled != null && disabled.booleanValue() ? "DISABLED" : "ENABLED";
   }
 
   @Override
   protected void parseCommandLine() throws UnloggedFailure {
-    parseCommandLine(impl);
+    parseCommandLine(this);
   }
 }
diff --git a/lib/antlr/BUILD b/lib/antlr/BUILD
index 6afe7b8..fff886fe 100644
--- a/lib/antlr/BUILD
+++ b/lib/antlr/BUILD
@@ -1,3 +1,5 @@
+package(default_visibility = ["//gerrit-index:__pkg__"])
+
 [java_library(
     name = n,
     data = ["//lib:LICENSE-antlr"],
@@ -17,7 +19,7 @@
 java_binary(
     name = "antlr-tool",
     main_class = "org.antlr.Tool",
-    visibility = ["//gerrit-antlr:__pkg__"],
+    visibility = ["//gerrit-index:__pkg__"],
     runtime_deps = [":tool"],
 )
 
diff --git a/plugins/singleusergroup b/plugins/singleusergroup
index 54030a4..50d04dd 160000
--- a/plugins/singleusergroup
+++ b/plugins/singleusergroup
@@ -1 +1 @@
-Subproject commit 54030a46ed17e097f09fdc586ebe1859569e1383
+Subproject commit 50d04dd09a747868cd2f2707d1aac3d9b2a2d630
diff --git a/polygerrit-ui/README.md b/polygerrit-ui/README.md
index 01f61d9..5aa918c 100644
--- a/polygerrit-ui/README.md
+++ b/polygerrit-ui/README.md
@@ -161,4 +161,25 @@
 ```sh
 bazel test //polygerrit-ui/app:polylint_test
 ```
+## Template Type Safety
+Polymer elements are not type checked against the element definition, making it trivial to break the display when refactoring or moving code. We now run additional tests to help ensure that template types are checked.
 
+A few notes to ensure that these tests pass
+- Any functions with optional parameters will need closure annotations.
+- Any Polymer parameters that are nullable or can be multiple types (other than the one explicitly delared) will need type annotations.
+
+A few dependencies are necessary to run these tests:
+``` sh
+npm install -g typescript fried-twinkie
+```
+
+To run on all files, execute the following command:
+
+```sh
+bazel test //polygerrit-ui/app:template_test
+```
+
+To run on a specific file (ex: gr-list-view), execute the following command:
+```sh
+bazel test //polygerrit-ui/app:template_test --test_arg=gr-list-view
+```
\ No newline at end of file
diff --git a/polygerrit-ui/app/BUILD b/polygerrit-ui/app/BUILD
index 03ecf0b..59a9a6d 100644
--- a/polygerrit-ui/app/BUILD
+++ b/polygerrit-ui/app/BUILD
@@ -125,6 +125,22 @@
     ],
 )
 
+sh_test(
+    name = "template_test",
+    size = "large",
+    srcs = ["template_test.sh"],
+    data = [
+        ":pg_code",
+        ":template_test_srcs",
+        "//polygerrit-ui:polygerrit_components.bower_components.zip",
+    ],
+    # Should not run sandboxed.
+    tags = [
+        "local",
+        "manual",
+    ],
+)
+
 # Embed bundle
 polygerrit_bundle(
     name = "polygerrit_embed_ui",
@@ -153,6 +169,14 @@
     ),
 )
 
+filegroup(
+    name = "template_test_srcs",
+    srcs = [
+        "template_test_srcs/convert_for_template_tests.py",
+        "template_test_srcs/template_test.js",
+    ],
+)
+
 sh_test(
     name = "embed_test",
     size = "small",
diff --git a/polygerrit-ui/app/behaviors/base-url-behavior/base-url-behavior.html b/polygerrit-ui/app/behaviors/base-url-behavior/base-url-behavior.html
index fce06c9..cda8c530 100644
--- a/polygerrit-ui/app/behaviors/base-url-behavior/base-url-behavior.html
+++ b/polygerrit-ui/app/behaviors/base-url-behavior/base-url-behavior.html
@@ -22,6 +22,7 @@
 
   /** @polymerBehavior Gerrit.BaseUrlBehavior */
   Gerrit.BaseUrlBehavior = {
+    /** @return {string} */
     getBaseUrl() {
       return window.CANONICAL_PATH || '';
     },
diff --git a/polygerrit-ui/app/behaviors/base-url-behavior/base-url-behavior_test.html b/polygerrit-ui/app/behaviors/base-url-behavior/base-url-behavior_test.html
index 706d499..b7c29dc 100644
--- a/polygerrit-ui/app/behaviors/base-url-behavior/base-url-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/base-url-behavior/base-url-behavior_test.html
@@ -22,7 +22,7 @@
 <script src="../../bower_components/web-component-tester/browser.js"></script>
 <link rel="import" href="../../test/common-test-setup.html"/>
 <script>
-  /** @type {String} */
+  /** @type {string} */
   window.CANONICAL_PATH = '/r';
 </script>
 <link rel="import" href="base-url-behavior.html">
diff --git a/polygerrit-ui/app/behaviors/docs-url-behavior/docs-url-behavior.html b/polygerrit-ui/app/behaviors/docs-url-behavior/docs-url-behavior.html
index 394ec74..07ce55e 100644
--- a/polygerrit-ui/app/behaviors/docs-url-behavior/docs-url-behavior.html
+++ b/polygerrit-ui/app/behaviors/docs-url-behavior/docs-url-behavior.html
@@ -32,7 +32,7 @@
      * Get the docs base URL from either the server config or by probing.
      * @param {Object} config The server config.
      * @param {!Object} restApi A REST API instance
-     * @return {!Promise<String>} A promise that resolves with the docs base
+     * @return {!Promise<string>} A promise that resolves with the docs base
      *     URL.
      */
     getDocsBaseUrl(config, restApi) {
diff --git a/polygerrit-ui/app/behaviors/gr-anonymous-name-behavior/gr-anonymous-name-behavior.html b/polygerrit-ui/app/behaviors/gr-anonymous-name-behavior/gr-anonymous-name-behavior.html
index 329611f..10065af 100644
--- a/polygerrit-ui/app/behaviors/gr-anonymous-name-behavior/gr-anonymous-name-behavior.html
+++ b/polygerrit-ui/app/behaviors/gr-anonymous-name-behavior/gr-anonymous-name-behavior.html
@@ -24,8 +24,16 @@
 
   /** @polymerBehavior Gerrit.AnonymousNameBehavior */
   Gerrit.AnonymousNameBehavior = {
-    getAnonymousName(config) {
-      if (config && config.user &&
+    /**
+     * enableEmail when true enables to fallback to using email if
+     * the account name is not avilable.
+     */
+    getUserName(config, account, enableEmail) {
+      if (account && account.name) {
+        return account.name;
+      } else if (enableEmail && account && account.email) {
+        return account.email;
+      } else if (config && config.user &&
           config.user.anonymous_coward_name !== 'Anonymous Coward') {
         return config.user.anonymous_coward_name;
       }
diff --git a/polygerrit-ui/app/behaviors/gr-anonymous-name-behavior/gr-anonymous-name-behavior_test.html b/polygerrit-ui/app/behaviors/gr-anonymous-name-behavior/gr-anonymous-name-behavior_test.html
new file mode 100644
index 0000000..e4a409b
--- /dev/null
+++ b/polygerrit-ui/app/behaviors/gr-anonymous-name-behavior/gr-anonymous-name-behavior_test.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<!--
+Copyright (C) 2017 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-anonymous-name-behavior</title>
+
+<script src="../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
+<script src="../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../test/common-test-setup.html"/>
+<link rel="import" href="gr-anonymous-name-behavior.html">
+
+<test-fixture id="basic">
+  <template>
+    <test-element-anon></test-element-anon>
+  </template>
+</test-fixture>
+
+<script>
+  suite('gr-anonymous-name-behavior tests', () => {
+    let element;
+    // eslint-disable-next-line no-unused-vars
+    const config = {
+      user: {
+        anonymous_coward_name: 'Anonymous Coward',
+      },
+    };
+
+    suiteSetup(() => {
+      // Define a Polymer element that uses this behavior.
+      Polymer({
+        is: 'test-element-anon',
+        behaviors: [
+          Gerrit.AnonymousNameBehavior,
+        ],
+      });
+    });
+
+    setup(() => {
+      element = fixture('basic');
+    });
+
+    test('test for it to return name', () => {
+      const account = {
+        name: 'test-user',
+      };
+      assert.deepEqual(element.getUserName(config, account, true), 'test-user');
+    });
+
+    test('test for it to return email', () => {
+      const account = {
+        email: 'test-user@test-url.com',
+      };
+      assert.deepEqual(element.getUserName(config, account, true),
+          'test-user@test-url.com');
+    });
+
+    test('test for it not to Anonymous Coward as the anon name', () => {
+      assert.deepEqual(element.getUserName(config, null, true), 'Anonymous');
+    });
+
+    test('test for the config returning the anon name', () => {
+      const config = {
+        user: {
+          anonymous_coward_name: 'Test Anon',
+        },
+      };
+      assert.deepEqual(element.getUserName(config, null, true), 'Test Anon');
+    });
+  });
+</script>
diff --git a/polygerrit-ui/app/behaviors/gr-change-table-behavior/gr-change-table-behavior.html b/polygerrit-ui/app/behaviors/gr-change-table-behavior/gr-change-table-behavior.html
index af266b4..7037b53 100644
--- a/polygerrit-ui/app/behaviors/gr-change-table-behavior/gr-change-table-behavior.html
+++ b/polygerrit-ui/app/behaviors/gr-change-table-behavior/gr-change-table-behavior.html
@@ -40,6 +40,7 @@
     /**
      * Returns the complement to the given column array
      * @param {Array} columns
+     * @return {!Array}
      */
     getComplementColumns(columns) {
       return this.columnNames.filter(column => {
@@ -47,6 +48,11 @@
       });
     },
 
+    /**
+     * @param {string} columnToCheck
+     * @param {!Array} columnsToDisplay
+     * @return {boolean}
+     */
     isColumnHidden(columnToCheck, columnsToDisplay) {
       return !columnsToDisplay.includes(columnToCheck);
     },
diff --git a/polygerrit-ui/app/behaviors/gr-list-view-behavior/gr-list-view-behavior.html b/polygerrit-ui/app/behaviors/gr-list-view-behavior/gr-list-view-behavior.html
index 98749be..597300e 100644
--- a/polygerrit-ui/app/behaviors/gr-list-view-behavior/gr-list-view-behavior.html
+++ b/polygerrit-ui/app/behaviors/gr-list-view-behavior/gr-list-view-behavior.html
@@ -35,11 +35,19 @@
       return this.getBaseUrl() + path + this.encodeURL(item, true);
     },
 
+    /**
+     * @param {Object} params
+     * @return {string}
+     */
     getFilterValue(params) {
-      if (!params) { return null; }
-      return params.filter || null;
+      if (!params) { return ''; }
+      return params.filter || '';
     },
 
+    /**
+     * @param {Object} params
+     * @return {number}
+     */
     getOffsetValue(params) {
       if (params && params.offset) {
         return params.offset;
diff --git a/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html b/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html
index 74e2ee4..5110a28 100644
--- a/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html
+++ b/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html
@@ -39,7 +39,9 @@
      * this function checks for patchNum equality.
      *
      * @param {string|number} a
-     * @param {string|number} b
+     * @param {string|number|undefined} b Undefined sometimes because
+     *    computeLatestPatchNum can return undefined.
+     * @return {boolean}
      */
     patchNumEquals(a, b) {
       return a + '' === b + '';
@@ -180,6 +182,7 @@
       return patchNums;
     },
 
+    /** @return {number|undefined} */
     computeLatestPatchNum(allPatchSets) {
       if (!allPatchSets || !allPatchSets.length) { return undefined; }
       if (allPatchSets[allPatchSets.length - 1].num ===
diff --git a/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior.html b/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior.html
index 0d4e7f1..d63b961 100644
--- a/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior.html
+++ b/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior.html
@@ -20,6 +20,11 @@
   window.Gerrit = window.Gerrit || {};
   /** @polymerBehavior Gerrit.PathListBehavior */
   Gerrit.PathListBehavior = {
+    /**
+     * @param {string} a
+     * @param {string} b
+     * @return {number}
+     */
     specialFilePathCompare(a, b) {
       // The commit message always goes first.
       const COMMIT_MESSAGE_PATH = '/COMMIT_MSG';
diff --git a/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior.js b/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior.js
index 0cc3466..451fc22 100644
--- a/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior.js
+++ b/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior.js
@@ -33,7 +33,7 @@
           return 'ontouchstart' in document.documentElement;
         },
       },
-      _tooltip: Element,
+      _tooltip: Object,
       _titleText: String,
       _hasSetupTooltipListeners: {
         type: Boolean,
diff --git a/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html b/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html
index 9c414fc4..bd996760 100644
--- a/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html
+++ b/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html
@@ -22,6 +22,8 @@
 
   // Must be declared outside behavior implementation to be accessed inside
   // behavior functions.
+
+  /** @return {!Object} */
   const getKeyboardEvent = function(e) {
     e = Polymer.dom(e.detail ? e.detail.keyboardEvent : e);
     // When e is a keyboardEvent, e.event is not null.
@@ -55,6 +57,7 @@
     },
 
     // Alias for getKeyboardEvent.
+    /** @return {!Object} */
     getKeyboardEvent(e) {
       return getKeyboardEvent(e);
     },
diff --git a/polygerrit-ui/app/behaviors/rest-client-behavior/rest-client-behavior.html b/polygerrit-ui/app/behaviors/rest-client-behavior/rest-client-behavior.html
index f5e17c9..928de04 100644
--- a/polygerrit-ui/app/behaviors/rest-client-behavior/rest-client-behavior.html
+++ b/polygerrit-ui/app/behaviors/rest-client-behavior/rest-client-behavior.html
@@ -101,6 +101,9 @@
       return v.toString(16);
     },
 
+    /**
+     *  @return {string}
+     */
     changeBaseURL(changeNum, patchNum) {
       let v = this.getBaseUrl() + '/changes/' + changeNum;
       if (patchNum) {
diff --git a/polygerrit-ui/app/behaviors/rest-client-behavior/rest-client-behavior_test.html b/polygerrit-ui/app/behaviors/rest-client-behavior/rest-client-behavior_test.html
index 5c0d76a..968b855 100644
--- a/polygerrit-ui/app/behaviors/rest-client-behavior/rest-client-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/rest-client-behavior/rest-client-behavior_test.html
@@ -22,7 +22,7 @@
 <script src="../../bower_components/web-component-tester/browser.js"></script>
 <link rel="import" href="../../test/common-test-setup.html"/>
 <script>
-  /** @type {String} */
+  /** @type {string} */
   window.CANONICAL_PATH = '/r';
 </script>
 
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.html b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.html
index ff0bbfa..fb69208 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.html
+++ b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.html
@@ -29,6 +29,7 @@
 <link rel="import" href="../gr-admin-group-list/gr-admin-group-list.html">
 <link rel="import" href="../gr-admin-project-list/gr-admin-project-list.html">
 <link rel="import" href="../gr-group/gr-group.html">
+<link rel="import" href="../gr-group-members/gr-group-members.html">
 <link rel="import" href="../gr-plugin-list/gr-plugin-list.html">
 <link rel="import" href="../gr-project/gr-project.html">
 <link rel="import" href="../gr-group-audit-log/gr-group-audit-log.html">
@@ -88,6 +89,12 @@
             on-name-changed="_updateGroupName"></gr-group>
       </main>
     </template>
+    <template is="dom-if" if="[[_showGroupMembers]]" restamp="true">
+      <main>
+        <gr-group-members
+            group-id="[[params.groupId]]"></gr-group-members>
+      </main>
+    </template>
     <template is="dom-if" if="[[_showGroupList]]" restamp="true">
       <main class="table">
         <gr-admin-group-list class="table" params="[[params]]">
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.js b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.js
index 276a925..0616507 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.js
+++ b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.js
@@ -40,6 +40,7 @@
     is: 'gr-admin-view',
 
     properties: {
+      /** @type {?} */
       params: Object,
       path: String,
       adminView: String,
@@ -62,6 +63,7 @@
       _showGroup: Boolean,
       _showGroupAuditLog: Boolean,
       _showGroupList: Boolean,
+      _showGroupMembers: Boolean,
       _showProjectMain: Boolean,
       _showProjectList: Boolean,
       _showProjectDetailList: Boolean,
@@ -126,8 +128,16 @@
           linkCopy.subsection = {
             name: this._groupName,
             view: 'gr-group',
-            url: `/admin/groups/${this.encodeURL(this._groupId, true)}`,
-            children: [],
+            url: `/admin/groups/${this.encodeURL(this._groupId + '', true)}`,
+            children: [
+              {
+                name: 'Members',
+                detailType: 'members',
+                view: 'gr-group-members',
+                url: `/admin/groups/${this.encodeURL(this._groupId, true)}` +
+                    ',members',
+              },
+            ],
           };
           if (this._groupOwner) {
             linkCopy.subsection.children.push(
@@ -135,8 +145,8 @@
                   name: 'Audit Log',
                   detailType: 'audit-log',
                   view: 'gr-group-audit-log',
-                  url: `/admin/groups/${this.encodeURL(this._groupId, true)}` +
-                        ',audit-log',
+                  url: '/admin/groups/' +
+                      `${this.encodeURL(this._groupId + '', true)},audit-log`,
                 }
             );
           }
@@ -160,6 +170,7 @@
       this.set('_showGroup', params.adminView === 'gr-group');
       this.set('_showGroupAuditLog', params.adminView === 'gr-group-audit-log');
       this.set('_showGroupList', params.adminView === 'gr-admin-group-list');
+      this.set('_showGroupMembers', params.adminView === 'gr-group-members');
       this.set('_showProjectMain', params.adminView === 'gr-project');
       this.set('_showProjectList',
           params.adminView === 'gr-admin-project-list');
@@ -198,6 +209,11 @@
       return this._computeRelativeURL(link.url);
     },
 
+    /**
+     * @param {string} itemView
+     * @param {Object} params
+     * @param {string=} opt_detailType
+     */
     _computeSelectedClass(itemView, params, opt_detailType) {
       if (params.detailType && params.detailType !== opt_detailType) {
         return '';
@@ -211,8 +227,8 @@
         this._groupName = group.name;
         this.reload();
         this.$.restAPI.getIsGroupOwner(group.name).then(
-            configs => {
-              if (configs.hasOwnProperty(group.name)) {
+            isOwner => {
+              if (isOwner) {
                 this._groupOwner = true;
                 this.reload();
               }
diff --git a/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog.js b/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog.js
index 90cfd6b..de1d0f8 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog.js
+++ b/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog.js
@@ -68,7 +68,7 @@
                 page.show(this._computeItemUrl(this.itemDetail));
               }
             });
-      } else if (this.itemDetail === DETAIL_TYPES.tag) {
+      } else if (this.itemDetail === DETAIL_TYPES.tags) {
         return this.$.restAPI.createProjectTag(this.projectName,
             this._itemName, {revision: USE_HEAD})
             .then(itemRegistered => {
diff --git a/polygerrit-ui/app/elements/admin/gr-create-project-dialog/gr-create-project-dialog.js b/polygerrit-ui/app/elements/admin/gr-create-project-dialog/gr-create-project-dialog.js
index 837e2ce..94f782d 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-project-dialog/gr-create-project-dialog.js
+++ b/polygerrit-ui/app/elements/admin/gr-create-project-dialog/gr-create-project-dialog.js
@@ -25,12 +25,13 @@
         value: false,
       },
 
+      /** @type {?} */
       _projectConfig: {
         type: Object,
         value: () => { return {}; },
       },
       _projectCreated: {
-        type: Object,
+        type: Boolean,
         value: false,
       },
 
diff --git a/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.html b/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.html
index ee910b4..43c893c 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.html
+++ b/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.html
@@ -46,11 +46,11 @@
           </td>
           <td class="type">[[itemType(item.type)]]</td>
           <td class="member">
-            <a href$="[[_computeGroupUrl(item.member._account_id)]]">
-              [[_getName(item.member)]]
+            <a href$="[[_computeGroupUrl(item.member.group_id)]]">
+              [[_getNameForMember(item.member)]]
             </a>
           </td>
-          <td class="by-user">[[_getName(item.user)]]</td>
+          <td class="by-user">[[_getNameForUser(item.user)]]</td>
         </tr>
       </template>
     </table>
diff --git a/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.js b/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.js
index e73e765..84f73ff 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.js
+++ b/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log.js
@@ -19,7 +19,7 @@
 
     properties: {
       groupId: Object,
-      _auditLog: Array,
+      _auditLog: Object,
       _loading: {
         type: Boolean,
         value: true,
@@ -30,21 +30,24 @@
       Gerrit.ListViewBehavior,
     ],
 
+    attached() {
+      this.fire('title-change', {title: 'Audit Log'});
+    },
+
     ready() {
       this._getAuditLogs();
     },
 
     _getAuditLogs() {
+      if (!this.groupId) {
+        return '';
+      }
       return this.$.restAPI.getGroupAuditLog(this.groupId).then(auditLog => {
         if (!auditLog) {
           this._auditLog = [];
           return;
         }
-        this._auditLog = Object.keys(auditLog).map(key => {
-          const audit = auditLog[key];
-          audit.name = key;
-          return audit;
-        });
+        this._auditLog = auditLog;
         this._loading = false;
       });
     },
@@ -54,6 +57,9 @@
     },
 
     _computeGroupUrl(id) {
+      if (!id) {
+        return '';
+      }
       return this.getBaseUrl() + '/admin/groups/' + id;
     },
 
@@ -74,11 +80,21 @@
       return item;
     },
 
-    _getName(account) {
-      if (account.username) {
-        return account.username + ' (' + account._account_id + ')';
-      } else if (account.name) {
-        return account.name + ' (' + account._account_id + ')';
+    _getNameForUser(account) {
+      const accountId = account._account_id ? ' (' +
+        account._account_id + ')' : '';
+      if (account && account.username) {
+        return account.username + accountId;
+      } else if (account && account.name) {
+        return account.name + accountId;
+      }
+    },
+
+    _getNameForMember(account) {
+      if (account && account.name) {
+        return account.name;
+      } else if (account && account.username) {
+        return account.username;
       }
     },
   });
diff --git a/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log_test.html b/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log_test.html
index e0cdad2..e437f7d 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log_test.html
@@ -39,20 +39,64 @@
       element = fixture('basic');
     });
 
-    test('test _getName', () => {
-      let account;
-      account = {
-        username: 'test-user',
-        name: 'test-name',
-        _account_id: 12,
-      };
-      assert.deepEqual(element._getName(account), 'test-user (12)');
+    suite('members', () => {
+      test('test getNameForMember', () => {
+        let account = {
+          member: {
+            username: 'test-user',
+            _account_id: 12,
+          },
+        };
+        assert.deepEqual(
+            element._getNameForMember(account.member, false), 'test-user');
 
-      account = {
-        name: 'test-name',
-        _account_id: 12,
-      };
-      assert.deepEqual(element._getName(account), 'test-name (12)');
+        account = {
+          member: {
+            name: 'test-name',
+            _account_id: 12,
+          },
+        };
+        assert.deepEqual(
+            element._getNameForMember(account.member), 'test-name');
+      });
+    });
+
+    suite('users', () => {
+      test('test _getName', () => {
+        let account = {
+          user: {
+            username: 'test-user',
+            _account_id: 12,
+          },
+        };
+        assert.deepEqual(
+            element._getNameForUser(account.user), 'test-user (12)');
+
+        account = {
+          user: {
+            name: 'test-name',
+            _account_id: 12,
+          },
+        };
+        assert.deepEqual(
+            element._getNameForUser(account.user), 'test-name (12)');
+      });
+
+      test('test _account_id not present', () => {
+        let account = {
+          user: {
+            username: 'test-user',
+          },
+        };
+        assert.deepEqual(element._getNameForUser(account.user), 'test-user');
+
+        account = {
+          user: {
+            name: 'test-name',
+          },
+        };
+        assert.deepEqual(element._getNameForUser(account.user), 'test-name');
+      });
     });
   });
 </script>
diff --git a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.html b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.html
new file mode 100644
index 0000000..2877f14
--- /dev/null
+++ b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.html
@@ -0,0 +1,192 @@
+<!--
+Copyright (C) 2017 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
+<link rel="import" href="../../../behaviors/gr-url-encoding-behavior.html">
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../../bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
+<link rel="import" href="../../../bower_components/iron-input/iron-input.html">
+<link rel="import" href="../../../styles/gr-form-styles.html">
+<link rel="import" href="../../../styles/shared-styles.html">
+<link rel="import" href="../../shared/gr-autocomplete/gr-autocomplete.html">
+<link rel="import" href="../../shared/gr-button/gr-button.html">
+<link rel="import" href="../../shared/gr-overlay/gr-overlay.html">
+<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
+<link rel="import" href="../gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog.html">
+
+<dom-module id="gr-group-members">
+  <template>
+    <style include="gr-form-styles"></style>
+    <style include="shared-styles">
+      main {
+        margin: 2em 1em;
+      }
+      .loading {
+        display: none;
+      }
+      #loading.loading {
+        display: block;
+      }
+      #loading:not(.loading) {
+        display: none;
+      }
+      .input {
+        width: 15em;
+      }
+      gr-autocomplete {
+        width: 20em;
+        --gr-autocomplete: {
+          font-size: 1em;
+          height: 2em;
+          width: 20em;
+        }
+      }
+      a {
+        color: var(--default-text-color);
+        text-decoration: none;
+      }
+      a:hover {
+        text-decoration: underline;
+      }
+      th {
+        border-bottom: 1px solid #eee;
+        font-weight: bold;
+        text-align: left;
+      }
+    </style>
+    <style include="gr-form-styles"></style>
+    <main class="gr-form-styles">
+      <div id="loading" class$="[[_computeLoadingClass(_loading)]]">
+        Loading...
+      </div>
+      <div id="loadedContent" class$="[[_computeLoadingClass(_loading)]]">
+        <h1 id="Title">[[_groupName]]</h1>
+        <div id="form">
+          <fieldset>
+            <h3 id="members">Members</h3>
+            <fieldset>
+              <span class="value">
+                <gr-autocomplete
+                    id="groupMemberSearchInput"
+                    text="{{_groupMemberSearch}}"
+                    query="[[_queryMembers]]"
+                    placeholder="Name Or Email"
+                    hidden$="[[!_groupOwner]]">
+                </gr-autocomplete>
+              </span>
+              <gr-button
+                  id="saveGroupMember"
+                  on-tap="_handleSavingGroupMember"
+                  disabled="[[!_groupMemberSearch]]"
+                  hidden$="[[!_groupOwner]]">
+                Add
+              </gr-button>
+              <div class="gr-form-styles">
+                <table id="groupMembers" class="gr-form-styles">
+                  <tr class="headerRow">
+                    <th class="nameHeader">Name</th>
+                    <th class="emailAddressHeader">Email Address</th>
+                    <th class="deleteHeader" hidden$="[[!_groupOwner]]">
+                      Delete Member
+                    </th>
+                  </tr>
+                  <tbody>
+                    <template is="dom-repeat" items="[[_groupMembers]]">
+                      <tr>
+                        <td class="nameColumn">
+                          <a href$="[[_memberUrl(item)]]">[[item.name]]</a>
+                        </td>
+                        <td>[[item.email]]</td>
+                        <td hidden$="[[!_groupOwner]]">
+                          <gr-button
+                              class="deleteButton"
+                              on-tap="_handleDeleteMember">
+                            Delete
+                          </gr-button>
+                        </td>
+                      </tr>
+                    </template>
+                  </tbody>
+                </table>
+              </div>
+            </fieldset>
+          </fieldset>
+          <fieldset>
+            <h3 id="includedGroups">Included Groups</h3>
+            <fieldset>
+              <span class="value">
+                <gr-autocomplete
+                    id="includedGroupSearchInput"
+                    text="{{_includedGroupSearch}}"
+                    query="[[_queryIncludedGroup]]"
+                    placeholder="Group Name"
+                    hidden$="[[!_groupOwner]]">
+                </gr-autocomplete>
+              </span>
+              <gr-button
+                  id="saveIncludedGroups"
+                  on-tap="_handleSavingIncludedGroups"
+                  disabled="[[!_includedGroupSearch]]"
+                  hidden$="[[!_groupOwner]]">
+                Add
+              </gr-button>
+              <div class="gr-form-styles">
+                <table id="includedGroups" class="gr-form-styles">
+                  <tr class="headerRow">
+                    <th class="groupNameHeader">Group Name</th>
+                    <th class="descriptionHeader">Description</th>
+                    <th class="deleteIncludedHeader" hidden$="[[!_groupOwner]]">
+                      Delete Included Group
+                    </th>
+                  </tr>
+                  <tbody>
+                    <template is="dom-repeat" items="[[_includedGroups]]">
+                      <tr>
+                        <td class="nameColumn">
+                          <a href$="[[_groupUrl(item.group_id)]]">
+                            [[item.name]]
+                          </a>
+                        </td>
+                        <td>[[item.description]]</td>
+                        <td hidden$="[[!_groupOwner]]">
+                          <gr-button
+                              class="deleteIncludedGroupButton"
+                              on-tap="_handleDeleteIncludedGroup">
+                            Delete
+                          </gr-button>
+                        </td>
+                      </tr>
+                    </template>
+                  </tbody>
+                </table>
+              </div>
+            </fieldset>
+          </fieldset>
+        </div>
+      </div>
+    </main>
+    <gr-overlay id="overlay" with-backdrop>
+      <gr-confirm-delete-item-dialog
+          class="confirmDialog"
+          on-confirm="_handleDeleteConfirm"
+          on-cancel="_handleConfirmDialogCancel"
+          item="[[_itemName]]"
+          item-type="[[_itemType]]"></gr-confirm-delete-item-dialog>
+    </gr-overlay>
+    <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
+  </template>
+  <script src="gr-group-members.js"></script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.js b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.js
new file mode 100644
index 0000000..57b5c60
--- /dev/null
+++ b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.js
@@ -0,0 +1,239 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+(function() {
+  'use strict';
+
+  const SUGGESTIONS_LIMIT = 15;
+
+  Polymer({
+    is: 'gr-group-members',
+
+    properties: {
+      groupId: Number,
+      _groupMemberSearch: String,
+      _includedGroupSearch: String,
+      _loading: {
+        type: Boolean,
+        value: true,
+      },
+      _groupName: String,
+      _groupMembers: Object,
+      _includedGroups: Object,
+      _itemName: String,
+      _itemType: String,
+      _queryMembers: {
+        type: Function,
+        value() {
+          return this._getAccountSuggestions.bind(this);
+        },
+      },
+      _queryIncludedGroup: {
+        type: Function,
+        value() {
+          return this._getGroupSuggestions.bind(this);
+        },
+      },
+      _groupOwner: {
+        type: Boolean,
+        value: false,
+      },
+    },
+
+    behaviors: [
+      Gerrit.BaseUrlBehavior,
+      Gerrit.URLEncodingBehavior,
+    ],
+
+    attached() {
+      this._loadGroupDetails();
+
+      this.fire('title-change', {title: 'Members'});
+    },
+
+    _loadGroupDetails() {
+      if (!this.groupId) { return; }
+
+      const promises = [];
+
+      return this.$.restAPI.getGroupConfig(this.groupId).then(
+          config => {
+            this._groupName = config.name;
+            promises.push(this.$.restAPI.getIsGroupOwner(config.name)
+                .then(isOwner => { this._groupOwner = isOwner; }));
+            promises.push(this.$.restAPI.getGroupMembers(config.name).then(
+                members => {
+                  this._groupMembers = members;
+                }));
+            promises.push(this.$.restAPI.getIncludedGroup(config.name)
+                .then(includedGroup => {
+                  this._includedGroups = includedGroup;
+                }));
+            return Promise.all(promises).then(() => {
+              this._loading = false;
+            });
+          });
+    },
+
+    _computeLoadingClass(loading) {
+      return loading ? 'loading' : '';
+    },
+
+    _isLoading() {
+      return this._loading || this._loading === undefined;
+    },
+
+    _memberUrl(item) {
+      if (item.email) {
+        item = item.email;
+      } else if (item.username) {
+        item = item.username;
+      } else {
+        item = item.name;
+      }
+      return this.getBaseUrl() + '/q/owner:' + this.encodeURL(item, true) +
+          ' status:open';
+    },
+
+    _groupUrl(item) {
+      return this.getBaseUrl() + '/admin/groups/' + this.encodeURL(item, true);
+    },
+
+    _handleSavingGroupMember() {
+      return this.$.restAPI.saveGroupMembers(this._groupName,
+          this._groupMemberSearch).then(config => {
+            if (!config) {
+              return;
+            }
+            this.$.restAPI.getGroupMembers(this._groupName).then(members => {
+              this._groupMembers = members;
+            });
+            this._groupMemberSearch = '';
+          });
+    },
+
+    _handleDeleteConfirm() {
+      this.$.overlay.close();
+      if (this._itemType === 'member') {
+        return this.$.restAPI.deleteGroupMembers(this._groupName,
+            this._itemName)
+            .then(itemDeleted => {
+              if (itemDeleted.status === 204) {
+                this.$.restAPI.getGroupMembers(this._groupName)
+                    .then(members => {
+                      this._groupMembers = members;
+                    });
+              }
+            });
+      } else if (this._itemType === 'includedGroup') {
+        return this.$.restAPI.deleteIncludedGroup(this._groupName,
+            this._itemName)
+            .then(itemDeleted => {
+              if (itemDeleted.status === 204) {
+                this.$.restAPI.getIncludedGroup(this._groupName)
+                    .then(includedGroup => {
+                      this._includedGroups = includedGroup;
+                    });
+              }
+            });
+      }
+    },
+
+    _handleConfirmDialogCancel() {
+      this.$.overlay.close();
+    },
+
+    _handleDeleteMember(e) {
+      let item;
+      const name = e.model.get('item.name');
+      const username = e.model.get('item.username');
+      const email = e.model.get('item.email');
+      if (username) {
+        item = username;
+      } else if (name) {
+        item = name;
+      } else if (email) {
+        item = email;
+      }
+      if (!item) {
+        return '';
+      }
+      this._itemName = item;
+      this._itemType = 'member';
+      this.$.overlay.open();
+    },
+
+    _handleSavingIncludedGroups() {
+      return this.$.restAPI.saveIncludedGroup(this._groupName,
+          this._includedGroupSearch)
+          .then(config => {
+            if (!config) {
+              return;
+            }
+            this.$.restAPI.getIncludedGroup(this._groupName)
+                .then(includedGroup => {
+                  this._includedGroups = includedGroup;
+                });
+            this._includedGroupSearch = '';
+          });
+    },
+
+    _handleDeleteIncludedGroup(e) {
+      const name = e.model.get('item.name');
+      if (!name) {
+        return '';
+      }
+      this._itemName = name;
+      this._itemType = 'includedGroup';
+      this.$.overlay.open();
+    },
+
+    _getAccountSuggestions(input) {
+      if (input.length === 0) { return Promise.resolve([]); }
+      return this.$.restAPI.getSuggestedAccounts(
+          input, SUGGESTIONS_LIMIT).then(accounts => {
+            const accountSuggestions = [];
+            let nameAndEmail;
+            if (!accounts) { return []; }
+            for (const key in accounts) {
+              if (!accounts.hasOwnProperty(key)) { continue; }
+              if (accounts[key].email !== undefined) {
+                nameAndEmail = accounts[key].name +
+                  ' <' + accounts[key].email + '>';
+              } else {
+                nameAndEmail = accounts[key].name;
+              }
+              accountSuggestions.push({
+                name: nameAndEmail,
+              });
+            }
+            return accountSuggestions;
+          });
+    },
+
+    _getGroupSuggestions(input) {
+      return this.$.restAPI.getSuggestedGroups(input)
+          .then(response => {
+            const groups = [];
+            for (const key in response) {
+              if (!response.hasOwnProperty(key)) { continue; }
+              groups.push({
+                name: key,
+                value: response[key],
+              });
+            }
+            return groups;
+          });
+    },
+  });
+})();
diff --git a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_test.html b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_test.html
new file mode 100644
index 0000000..76a182b
--- /dev/null
+++ b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_test.html
@@ -0,0 +1,156 @@
+<!DOCTYPE html>
+<!--
+Copyright (C) 2017 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-group-members</title>
+
+<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
+<link rel="import" href="gr-group-members.html">
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+  <template>
+    <gr-group-members></gr-group-members>
+  </template>
+</test-fixture>
+
+<script>
+  suite('gr-group-members tests', () => {
+    let element;
+    let sandbox;
+    let groups;
+    let groupMembers;
+
+    setup(() => {
+      sandbox = sinon.sandbox.create();
+      groups = {
+        name: 'Administrators',
+        owner: 'Administrators',
+        group_id: 1,
+      };
+
+      groupMembers = [
+        {
+          _account_id: 1000097,
+          name: 'Jane Roe',
+          email: 'jane.roe@example.com',
+          username: 'jane',
+        },
+        {
+          _account_id: 1000096,
+          name: 'Test User',
+          email: 'john.doe@example.com',
+          username: 'john',
+        },
+        {
+          _account_id: 1000095,
+          name: 'Gerrit',
+          email: 'gerrit@example.com',
+          username: 'git',
+        },
+      ];
+
+      stub('gr-rest-api-interface', {
+        getSuggestedAccounts(input) {
+          if (input.startsWith('test')) {
+            return Promise.resolve([
+              {
+                _account_id: 1000096,
+                name: 'test-account',
+                email: 'test.account@example.com',
+                username: 'test123',
+              },
+              {
+                _account_id: 1001439,
+                name: 'test-admin',
+                email: 'test.admin@example.com',
+                username: 'test_admin',
+              },
+              {
+                _account_id: 1001439,
+                name: 'test-git',
+                username: 'test_git',
+              },
+            ]);
+          } else {
+            return Promise.resolve({});
+          }
+        },
+        getLoggedIn() { return Promise.resolve(true); },
+        getGroupConfig() {
+          return Promise.resolve(groups);
+        },
+        getGroupMembers() {
+          return Promise.resolve(groupMembers);
+        },
+        getIsGroupOwner() {
+          return Promise.resolve(true);
+        },
+      });
+
+      element = fixture('basic');
+    });
+
+    teardown(() => {
+      sandbox.restore();
+    });
+
+    test('save correctly', () => {
+      element._groupOwner = true;
+
+      const memberName = 'test-admin';
+
+      sandbox.stub(element.$.restAPI, 'saveGroupMembers', () => {
+        return Promise.resolve({});
+      });
+
+      const button = Polymer.dom(element.root).querySelector('gr-button');
+
+      assert.isTrue(button.hasAttribute('disabled'));
+
+      element.$.groupMemberSearchInput.text = memberName;
+
+      assert.isFalse(button.hasAttribute('disabled'));
+
+      element._handleSavingGroupMember().then(() => {
+        assert.isTrue(button.hasAttribute('disabled'));
+        assert.isFalse(element.$.Title.classList.contains('edited'));
+      });
+    });
+
+    test('_getAccountSuggestions empty', done => {
+      element._getAccountSuggestions('nonexistent').then(accounts => {
+        assert.equal(accounts.length, 0);
+        done();
+      });
+    });
+
+    test('_getAccountSuggestions non-empty', done => {
+      element._getAccountSuggestions('test-').then(accounts => {
+        assert.equal(accounts.length, 3);
+        assert.equal(accounts[0].name,
+            'test-account <test.account@example.com>');
+        assert.equal(accounts[1].name, 'test-admin <test.admin@example.com>');
+        assert.equal(accounts[2].name, 'test-git');
+        done();
+      });
+    });
+  });
+</script>
diff --git a/polygerrit-ui/app/elements/admin/gr-group/gr-group.html b/polygerrit-ui/app/elements/admin/gr-group/gr-group.html
index a80790a..2f3d59a 100644
--- a/polygerrit-ui/app/elements/admin/gr-group/gr-group.html
+++ b/polygerrit-ui/app/elements/admin/gr-group/gr-group.html
@@ -71,12 +71,12 @@
                 <gr-autocomplete
                     id="groupNameInput"
                     text="{{_groupConfig.name}}"
-                    disabled$="[[_groupOwner]]"></gr-autocomplete>
+                    disabled="[[!_groupOwner]]"></gr-autocomplete>
               </span>
               <gr-button
                   id="inputUpdateNameBtn"
                   on-tap="_handleSaveName"
-                  disabled$="[[_computeButtonDisabled(_groupOwner, _rename)]]">
+                  disabled="[[_computeButtonDisabled(_groupOwner, _rename)]]">
                 Rename Group</gr-button>
             </fieldset>
             <h3 class$="[[_computeHeaderClass(_owner)]]">
@@ -87,12 +87,12 @@
                 <gr-autocomplete
                     text="{{_groupConfig.owner}}"
                     query="[[_query]]"
-                    disabled$="[[_groupOwner]]">
+                    disabled$="[[!_groupOwner]]">
                 </gr-autocomplete>
               </span>
               <gr-button
                   on-tap="_handleSaveOwner"
-                  disabled$="[[_computeButtonDisabled(_groupOwner, _owner)]]">
+                  disabled="[[_computeButtonDisabled(_groupOwner, _owner)]]">
                 Change Owners</gr-button>
             </fieldset>
             <h3 class$="[[_computeHeaderClass(_description)]]">
@@ -104,11 +104,11 @@
                     class="description"
                     autocomplete="on"
                     bind-value="{{_groupConfig.description}}"
-                    disabled$="[[_groupOwner]]"></iron-autogrow-textarea>
+                    disabled="[[!_groupOwner]]"></iron-autogrow-textarea>
               </div>
               <gr-button
                   on-tap="_handleSaveDescription"
-                  disabled$=
+                  disabled=
                       "[[_computeButtonDisabled(_groupOwner, _description)]]">
                 Save Description
               </gr-button>
@@ -124,7 +124,7 @@
                 <span class="value">
                   <gr-select
                       bind-value="{{_groupConfig.options.visible_to_all}}">
-                    <select disabled$="[[_groupOwner]]">
+                    <select disabled$="[[!_groupOwner]]">
                       <template is="dom-repeat" items="[[_submitTypes]]">
                         <option value="[[item.value]]">[[item.label]]</option>
                       </template>
@@ -134,7 +134,7 @@
               </section>
               <gr-button
                   on-tap="_handleSaveOptions"
-                  disabled$=
+                  disabled=
                          "[[_computeButtonDisabled(_groupOwner, _options)]]">
                 Save Group Options
               </gr-button>
diff --git a/polygerrit-ui/app/elements/admin/gr-group/gr-group.js b/polygerrit-ui/app/elements/admin/gr-group/gr-group.js
index a91ad2e..55bf3b0 100644
--- a/polygerrit-ui/app/elements/admin/gr-group/gr-group.js
+++ b/polygerrit-ui/app/elements/admin/gr-group/gr-group.js
@@ -61,6 +61,7 @@
         value: false,
         observer: '_loggedInChanged',
       },
+      /** @type {?} */
       _groupConfig: Object,
       _groupName: Object,
       _groupOwner: {
@@ -99,14 +100,10 @@
           config => {
             this._groupConfig = config;
             this._groupName = config.name;
+            this.fire('title-change', {title: config.name});
             this._loading = false;
-            this.$.restAPI.getIsGroupOwner(config.name).then(
-                configs => {
-                  if (Object.keys(configs).length === 0 &&
-                      configs.constructor === Object) {
-                    this._groupOwner = true;
-                  }
-                });
+            this.$.restAPI.getIsGroupOwner(config.name)
+                .then(isOwner => { this._groupOwner = isOwner; });
           });
     },
 
@@ -186,7 +183,7 @@
     },
 
     _computeButtonDisabled(options, option) {
-      return options || !option;
+      return !options || !option;
     },
 
     _computeHeaderClass(configChanged) {
diff --git a/polygerrit-ui/app/elements/admin/gr-group/gr-group_test.html b/polygerrit-ui/app/elements/admin/gr-group/gr-group_test.html
index 5fc9527..75bd905 100644
--- a/polygerrit-ui/app/elements/admin/gr-group/gr-group_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-group/gr-group_test.html
@@ -76,9 +76,10 @@
         name: groupName,
       };
       element._groupName = groupName;
+      element._groupOwner = true;
 
       sandbox.stub(element.$.restAPI, 'getIsGroupOwner', () => {
-        return Promise.resolve({is_owner: true});
+        return Promise.resolve(true);
       });
 
       sandbox.stub(element.$.restAPI, 'saveGroupName', () => {
diff --git a/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.js b/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.js
index 624ff57..d441407 100644
--- a/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.js
+++ b/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.js
@@ -28,7 +28,10 @@
       /**
        * Offset of currently visible query results.
        */
-      _offset: Number,
+      _offset: {
+        type: Number,
+        value: 0,
+      },
       _path: {
         type: String,
         readOnly: true,
@@ -51,7 +54,10 @@
         type: Boolean,
         value: true,
       },
-      _filter: String,
+      _filter: {
+        type: String,
+        value: '',
+      },
     },
 
     behaviors: [
diff --git a/polygerrit-ui/app/elements/admin/gr-project-detail-list/gr-project-detail-list.js b/polygerrit-ui/app/elements/admin/gr-project-detail-list/gr-project-detail-list.js
index b155090..eec277e 100644
--- a/polygerrit-ui/app/elements/admin/gr-project-detail-list/gr-project-detail-list.js
+++ b/polygerrit-ui/app/elements/admin/gr-project-detail-list/gr-project-detail-list.js
@@ -73,6 +73,8 @@
       _filter: String,
       _refName: String,
       _hasNewItemName: Boolean,
+      _isEditing: Boolean,
+      _revisedRef: String,
     },
 
     behaviors: [
diff --git a/polygerrit-ui/app/elements/admin/gr-project/gr-project.js b/polygerrit-ui/app/elements/admin/gr-project/gr-project.js
index 1c0db91..b8a638a 100644
--- a/polygerrit-ui/app/elements/admin/gr-project/gr-project.js
+++ b/polygerrit-ui/app/elements/admin/gr-project/gr-project.js
@@ -67,6 +67,7 @@
         value: false,
         observer: '_loggedInChanged',
       },
+      /** @type {?} */
       _projectConfig: Object,
       _readOnly: {
         type: Boolean,
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 9181f5d..70275b3 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
@@ -19,6 +19,7 @@
 <link rel="import" href="../../../behaviors/rest-client-behavior/rest-client-behavior.html">
 <link rel="import" href="../../../bower_components/polymer/polymer.html">
 <link rel="import" href="../../../styles/gr-change-list-styles.html">
+<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
 <link rel="import" href="../../shared/gr-account-link/gr-account-link.html">
 <link rel="import" href="../../shared/gr-change-star/gr-change-star.html">
 <link rel="import" href="../../shared/gr-date-formatter/gr-date-formatter.html">
@@ -72,7 +73,8 @@
       }
       a {
         color: var(--default-text-color);
-        display: block;
+        cursor: pointer;
+        display: inline-block;
         text-decoration: none;
       }
       a:hover {
@@ -141,8 +143,11 @@
     <td class="cell branch"
         hidden$="[[isColumnHidden('Branch', visibleChangeTableColumns)]]">
       <a href$="[[_computeProjectBranchURL(change)]]">
-        [[_computeBranchText(change)]]
+        [[change.branch]]
       </a>
+      <template is="dom-if" if="[[change.topic]]">
+        (<a href$="[[_computeTopicURL(change)]]">[[change.topic]]</a>)
+      </template>
     </td>
     <td class="cell updated"
         hidden$="[[isColumnHidden('Updated', visibleChangeTableColumns)]]">
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 189a942..79f06fe 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
@@ -22,15 +22,18 @@
       labelNames: {
         type: Array,
       },
+
+      /** @type {?} */
       change: Object,
       changeURL: {
         type: String,
-        computed: '_computeChangeURL(change._number)',
+        computed: '_computeChangeURL(change)',
       },
       showStar: {
         type: Boolean,
         value: false,
       },
+      showNumber: Boolean,
     },
 
     behaviors: [
@@ -40,9 +43,8 @@
       Gerrit.URLEncodingBehavior,
     ],
 
-    _computeChangeURL(changeNum) {
-      if (!changeNum) { return ''; }
-      return this.getBaseUrl() + '/c/' + changeNum + '/';
+    _computeChangeURL(change) {
+      return Gerrit.Nav.getUrlForChange(change);
     },
 
     _computeLabelTitle(change, labelName) {
@@ -102,24 +104,16 @@
     },
 
     _computeProjectURL(project) {
-      return this.getBaseUrl() + '/q/status:open+project:' +
-          this.encodeURL(project, false);
+      return Gerrit.Nav.getUrlForProject(project, true);
     },
 
     _computeProjectBranchURL(change) {
-      // @see Issue 4255, Issue 6195.
-      let output = this._computeProjectURL(change.project);
-      output += '+branch:' + this.encodeURL(change.branch, false);
-      if (change.topic) {
-        output += '+topic:' + this.encodeURL(change.topic, false);
-      }
-      return output;
+      return Gerrit.Nav.getUrlForBranch(change.branch, change.project);
     },
 
-    _computeBranchText(change) {
-      let output = change.branch;
-      if (change.topic) { output += ` (${change.topic})`; }
-      return output;
+    _computeTopicURL(change) {
+      if (!change.topic) { return ''; }
+      return Gerrit.Nav.getUrlForTopic(change.topic);
     },
   });
 })();
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 502a720..8b13aa7 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
@@ -112,23 +112,6 @@
           {labels: {Verified: {approved: true}}}, 'Verified'), '✓');
       assert.equal(element._computeLabelValue(
           {labels: {Verified: {rejected: true}}}, 'Verified'), '✕');
-
-      assert.equal(element._computeProjectURL('combustible/stuff'),
-          '/q/status:open+project:combustible%252Fstuff');
-
-      const change = {project: 'combustible-stuff', branch: 'le/mons'};
-      assert.equal(element._computeProjectBranchURL(change),
-          '/q/status:open+project:combustible-stuff+branch:le%252Fmons');
-
-      change.topic = 'test/test';
-      assert.equal(element._computeProjectBranchURL(change),
-          '/q/status:open+project:combustible-stuff+branch:le%252Fmons' +
-              '+topic:test%252Ftest');
-
-      element.change = {_number: 42};
-      assert.equal(element.changeURL, '/c/42/');
-      element.change = {_number: 43};
-      assert.equal(element.changeURL, '/c/43/');
     });
 
     test('no hidden columns', () => {
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.js b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.js
index 91282f0..30fc679 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.js
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.js
@@ -54,6 +54,11 @@
 
       /**
        * State persisted across restamps of the element.
+       *
+       * Need sub-property declaration since it is used in template before
+       * assignment.
+       * @type {{ selectedChangeIndex: (number|undefined) }}
+       *
        */
       viewState: {
         type: Object,
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.html b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.html
index 7277fd2..d841fbc 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.html
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.html
@@ -21,6 +21,7 @@
 <link rel="import" href="../../../behaviors/rest-client-behavior/rest-client-behavior.html">
 <link rel="import" href="../../../bower_components/polymer/polymer.html">
 <link rel="import" href="../../../styles/gr-change-list-styles.html">
+<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
 <link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
 <link rel="import" href="../gr-change-list-item/gr-change-list-item.html">
 <link rel="import" href="../../../styles/shared-styles.html">
@@ -99,7 +100,6 @@
               needs-review$="[[_computeItemNeedsReview(account, change, showReviewedState)]]"
               change="[[change]]"
               visible-change-table-columns="[[visibleChangeTableColumns]]"
-              show-number="[[showNumber]]"
               show-star="[[showStar]]"
               label-names="[[labelNames]]"></gr-change-list-item>
         </template>
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.js b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.js
index af5a3b0..d302120 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.js
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.js
@@ -86,6 +86,7 @@
         value() { return document.body; },
       },
       changeTableColumns: Array,
+      visibleChangeTableColumns: Array,
     },
 
     behaviors: [
@@ -237,7 +238,7 @@
           this.modifierPressed(e)) { return; }
 
       e.preventDefault();
-      page.show(this._changeURLForIndex(this.selectedIndex));
+      Gerrit.Nav.navigateToChange(this._changeForIndex(this.selectedIndex));
     },
 
     _handleNKey(e) {
@@ -284,12 +285,12 @@
       this.$.restAPI.saveChangeStarred(change._number, newVal);
     },
 
-    _changeURLForIndex(index) {
+    _changeForIndex(index) {
       const changeEls = this._getListItems();
       if (index < changeEls.length && changeEls[index]) {
-        return changeEls[index].changeURL;
+        return changeEls[index].change;
       }
-      return '';
+      return null;
     },
 
     _getListItems() {
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html
index a570bd5..d758475 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html
@@ -42,11 +42,15 @@
 <script>
   suite('gr-change-list basic tests', () => {
     let element;
+    let sandbox;
 
     setup(() => {
+      sandbox = sinon.sandbox.create();
       element = fixture('basic');
     });
 
+    teardown(() => { sandbox.restore(); });
+
     function stubRestAPI(preferences) {
       const loggedInPromise = Promise.resolve(preferences !== null);
       const preferencesPromise = Promise.resolve(preferences);
@@ -149,16 +153,16 @@
         assert.equal(element.selectedIndex, 1);
         MockInteractions.pressAndReleaseKeyOn(element, 74, null, 'j');
 
-        const showStub = sinon.stub(page, 'show');
+        const navStub = sandbox.stub(Gerrit.Nav, 'navigateToChange');
         assert.equal(element.selectedIndex, 2);
         MockInteractions.pressAndReleaseKeyOn(element, 13, null, 'enter');
-        assert(showStub.lastCall.calledWithExactly('/c/2/'),
+        assert.deepEqual(navStub.lastCall.args[0], {_number: 2},
             'Should navigate to /c/2/');
 
         MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
         assert.equal(element.selectedIndex, 1);
         MockInteractions.pressAndReleaseKeyOn(element, 13, null, 'enter');
-        assert(showStub.lastCall.calledWithExactly('/c/1/'),
+        assert.deepEqual(navStub.lastCall.args[0], {_number: 1},
             'Should navigate to /c/1/');
 
         MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
@@ -166,7 +170,6 @@
         MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
         assert.equal(element.selectedIndex, 0);
 
-        showStub.restore();
         done();
       });
     });
@@ -367,11 +370,15 @@
 
   suite('gr-change-list sections', () => {
     let element;
+    let sandbox;
 
     setup(() => {
+      sandbox = sinon.sandbox.create();
       element = fixture('basic');
     });
 
+    teardown(() => { sandbox.restore(); });
+
     test('keyboard shortcuts', () => {
       element.selectedIndex = 0;
       element.sections = [
@@ -411,16 +418,17 @@
       assert.equal(element.selectedIndex, 1);
       MockInteractions.pressAndReleaseKeyOn(element, 74); // 'j'
 
-      const showStub = sinon.stub(page, 'show');
+      const navStub = sandbox.stub(Gerrit.Nav, 'navigateToChange');
       assert.equal(element.selectedIndex, 2);
+
       MockInteractions.pressAndReleaseKeyOn(element, 13); // 'enter'
-      assert(showStub.lastCall.calledWithExactly('/c/2/'),
+      assert.deepEqual(navStub.lastCall.args[0], {_number: 2},
           'Should navigate to /c/2/');
 
       MockInteractions.pressAndReleaseKeyOn(element, 75); // 'k'
       assert.equal(element.selectedIndex, 1);
       MockInteractions.pressAndReleaseKeyOn(element, 13); // 'enter'
-      assert(showStub.lastCall.calledWithExactly('/c/1/'),
+      assert.deepEqual(navStub.lastCall.args[0], {_number: 1},
           'Should navigate to /c/1/');
 
       MockInteractions.pressAndReleaseKeyOn(element, 74); // 'j'
@@ -428,9 +436,8 @@
       MockInteractions.pressAndReleaseKeyOn(element, 74); // 'j'
       assert.equal(element.selectedIndex, 4);
       MockInteractions.pressAndReleaseKeyOn(element, 13); // 'enter'
-      assert(showStub.lastCall.calledWithExactly('/c/4/'),
+      assert.deepEqual(navStub.lastCall.args[0], {_number: 4},
           'Should navigate to /c/4/');
-      showStub.restore();
     });
 
     test('assigned attribute set in each item', () => {
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js
index 4e39304..69550b9 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js
@@ -49,6 +49,7 @@
         type: Object,
         value() { return {}; },
       },
+      /** @type {{ selectedChangeIndex: number }} */
       viewState: Object,
       params: {
         type: Object,
diff --git a/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry.html b/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry.html
index 096794a..4931ff1 100644
--- a/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry.html
+++ b/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry.html
@@ -14,10 +14,11 @@
 limitations under the License.
 -->
 
+<link rel="import" href="../../../behaviors/gr-anonymous-name-behavior/gr-anonymous-name-behavior.html">
 <link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../../styles/shared-styles.html">
 <link rel="import" href="../../shared/gr-autocomplete/gr-autocomplete.html">
 <link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
-<link rel="import" href="../../../styles/shared-styles.html">
 
 <dom-module id="gr-account-entry">
   <template>
diff --git a/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry.js b/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry.js
index 7db943ca..3626b86 100644
--- a/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry.js
+++ b/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry.js
@@ -25,6 +25,7 @@
     properties: {
       borderless: Boolean,
       change: Object,
+      _config: Object,
       filter: Function,
       placeholder: String,
       /**
@@ -52,6 +53,16 @@
       },
     },
 
+    behaviors: [
+      Gerrit.AnonymousNameBehavior,
+    ],
+
+    attached() {
+      this.$.restAPI.getConfig().then(cfg => {
+        this._config = cfg;
+      });
+    },
+
     get focusStart() {
       return this.$.input.focusStart;
     },
@@ -77,6 +88,10 @@
       this.$.input.focus();
     },
 
+    _accountOrAnon(reviewer) {
+      return this.getUserName(this._config, reviewer, false);
+    },
+
     _makeSuggestion(reviewer) {
       let name;
       let value;
@@ -85,7 +100,8 @@
       };
       if (reviewer.account) {
         // Reviewer is an account suggestion from getChangeSuggestedReviewers.
-        name = reviewer.account.name + ' <' + reviewer.account.email + '>' +
+        const reviewerName = this._accountOrAnon(reviewer.account);
+        name = reviewerName + ' <' + reviewer.account.email + '>' +
             generateStatusStr(reviewer.account);
         value = reviewer;
       } else if (reviewer.group) {
@@ -94,7 +110,8 @@
         value = reviewer;
       } else if (reviewer._account_id) {
         // Reviewer is an account suggestion from getSuggestedAccounts.
-        name = reviewer.name + ' <' + reviewer.email + '>' +
+        const reviewerName = this._accountOrAnon(reviewer);
+        name = reviewerName + ' <' + reviewer.email + '>' +
             generateStatusStr(reviewer);
         value = {account: reviewer, count: 1};
       }
@@ -110,7 +127,9 @@
 
       return xhr.then(reviewers => {
         if (!reviewers) { return []; }
-        if (!this.filter) { return reviewers.map(this._makeSuggestion); }
+        if (!this.filter) {
+          return reviewers.map(this._makeSuggestion.bind(this));
+        }
         return reviewers
             .filter(this.filter)
             .map(this._makeSuggestion.bind(this));
diff --git a/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry_test.html b/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry_test.html
index 55213c1..a4dacc7 100644
--- a/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry_test.html
+++ b/polygerrit-ui/app/elements/change/gr-account-entry/gr-account-entry_test.html
@@ -46,6 +46,15 @@
         status: opt_status,
       };
     };
+    let _nextAccountId2 = 0;
+    const makeAccount2 = function(opt_status) {
+      const accountId2 = ++_nextAccountId2;
+      return {
+        _account_id: accountId2,
+        email: 'email ' + accountId2,
+        status: opt_status,
+      };
+    };
 
     let owner;
     let existingReviewer1;
@@ -98,6 +107,7 @@
 
       test('_makeSuggestion formats account or group accordingly', () => {
         let account = makeAccount();
+        const account2 = makeAccount2();
         let suggestion = element._makeSuggestion({account});
         assert.deepEqual(suggestion, {
           name: account.name + ' <' + account.email + '>',
@@ -117,6 +127,13 @@
           value: {account, count: 1},
         });
 
+        element._config = {
+          user: {
+            anonymous_coward_name: 'Anonymous Coward',
+          },
+        };
+        assert.deepEqual(element._accountOrAnon(account2), 'Anonymous');
+
         account = makeAccount('OOO');
 
         suggestion = element._makeSuggestion({account});
diff --git a/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list.js b/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list.js
index 4e403e6..c2dbf3f 100644
--- a/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list.js
+++ b/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list.js
@@ -34,6 +34,9 @@
       change: Object,
       filter: Function,
       placeholder: String,
+      /**
+       * Needed for template checking since value is initially set to null.
+       * @type {?Object} */
       pendingConfirmation: {
         type: Object,
         value: null,
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
index f8dad01..e9e60e0 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
@@ -18,7 +18,7 @@
   const ERR_COMMIT_EMPTY = 'The commit message can’t be empty.';
   const ERR_REVISION_ACTIONS = 'Couldn’t load revision actions.';
   /**
-   * @enum {number}
+   * @enum {string}
    */
   const LabelStatus = {
     /**
@@ -44,6 +44,7 @@
      * project owner or site administrator.
      */
     IMPOSSIBLE: 'IMPOSSIBLE',
+    OPTIONAL: 'OPTIONAL',
   };
 
   // TODO(davido): Add the rest of the change actions.
@@ -139,6 +140,7 @@
      */
 
     properties: {
+      /** @type {{ branch: string, project: string }} */
       change: Object,
       actions: {
         type: Object,
@@ -169,6 +171,7 @@
         type: String,
         value: '',
       },
+      /** @type {?} */
       revisionActions: {
         type: Object,
         value() { return {}; },
@@ -180,7 +183,7 @@
       },
       _actionLoadingMessage: {
         type: String,
-        value: null,
+        value: '',
       },
       _allActionValues: {
         type: Array,
@@ -417,7 +420,7 @@
       this.hidden = this._keyCount(actionsChangeRecord) === 0 &&
           this._keyCount(revisionActionsChangeRecord) === 0 &&
               additionalActions.length === 0;
-      this._actionLoadingMessage = null;
+      this._actionLoadingMessage = '';
       this._disabledMenuActions = [];
 
       const revisionActions = revisionActionsChangeRecord.base || {};
@@ -449,7 +452,7 @@
      * Get highest score for last missing permitted label for current change.
      * Returns null if no labels permitted or more than one label missing.
      *
-     * @return {{label: string, score: string}}
+     * @return {{label: string, score: string}|null}
      */
     _getTopMissingApproval() {
       if (!this.change ||
@@ -668,9 +671,8 @@
     },
 
     /**
-     * Returns true if hasParent is defined (can be either true or false).
-     * returns false otherwise.
-     * @return {boolean} hasParent
+     * _hasKnownChainState set to true true if hasParent is defined (can be
+     * either true or false). set to false otherwise.
      */
     _computeChainState(hasParent) {
       this._hasKnownChainState = true;
@@ -778,7 +780,7 @@
       if (this._getActionOverflowIndex(type, key) !== -1) {
         this.push('_disabledMenuActions', key === '/' ? 'delete' : key);
         return function() {
-          this._actionLoadingMessage = null;
+          this._actionLoadingMessage = '';
           this._disabledMenuActions = [];
         }.bind(this);
       }
@@ -788,12 +790,18 @@
       buttonEl.setAttribute('loading', true);
       buttonEl.disabled = true;
       return function() {
-        this._actionLoadingMessage = null;
+        this._actionLoadingMessage = '';
         buttonEl.removeAttribute('loading');
         buttonEl.disabled = false;
       }.bind(this);
     },
 
+    /**
+     * @param {string} endpoint
+     * @param {!Object|undefined} action
+     * @param {boolean} revAction
+     * @param {!Object|string=} opt_payload
+     */
     _fireAction(endpoint, action, revAction, opt_payload) {
       const cleanupFn =
           this._setLoadingOnButtonWithKey(action.__type, action.__key);
@@ -861,6 +869,14 @@
       });
     },
 
+    /**
+     * @param {string} method
+     * @param {string|!Object|undefined} payload
+     * @param {string} actionEndpoint
+     * @param {boolean} revisionAction
+     * @param {?Function} cleanupFn
+     * @param {?Function=} opt_errorFn
+     */
     _send(method, payload, actionEndpoint, revisionAction, cleanupFn,
         opt_errorFn) {
       return this.fetchIsLatestKnown(this.change, this.$.restAPI)
@@ -918,19 +934,19 @@
     /**
      * Merge sources of change actions into a single ordered array of action
      * values.
-     * @param {splices} changeActionsRecord
-     * @param {splices} revisionActionsRecord
-     * @param {splices} primariesRecord
-     * @param {splices} additionalActionsRecord
-     * @param {Object} change The change object.
-     * @return {Array}
+     * @param {!Array} changeActionsRecord
+     * @param {!Array} revisionActionsRecord
+     * @param {!Array} primariesRecord
+     * @param {!Array} additionalActionsRecord
+     * @param {!Object} change The change object.
+     * @return {!Array}
      */
     _computeAllActions(changeActionsRecord, revisionActionsRecord,
         primariesRecord, additionalActionsRecord, change) {
       const revisionActionValues = this._getActionValues(revisionActionsRecord,
           primariesRecord, additionalActionsRecord, ActionType.REVISION);
       const changeActionValues = this._getActionValues(changeActionsRecord,
-          primariesRecord, additionalActionsRecord, ActionType.CHANGE, change);
+          primariesRecord, additionalActionsRecord, ActionType.CHANGE);
       const quickApprove = this._getQuickApproveAction();
       if (quickApprove) {
         changeActionValues.unshift(quickApprove);
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
index 7e305bb..1658da8 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
@@ -228,7 +228,7 @@
     });
 
     test('submit change', done => {
-      sandbox.stub(element.$.restAPI, '_getFromProjectLookup')
+      sandbox.stub(element.$.restAPI, 'getFromProjectLookup')
           .returns(Promise.resolve('test'));
       sandbox.stub(element, 'fetchIsLatestKnown',
           () => { return Promise.resolve(true); });
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata-it_test.html b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata-it_test.html
index b347890..6129e9b 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata-it_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata-it_test.html
@@ -90,10 +90,12 @@
       setup(done => {
         const pluginHost = fixture('plugin-host');
         pluginHost.config = {
-          js_resource_paths: [],
-          html_resource_paths: [
-            new URL('test/plugin.html', window.location.href).toString(),
-          ],
+          plugin: {
+            js_resource_paths: [],
+            html_resource_paths: [
+              new URL('test/plugin.html', window.location.href).toString(),
+            ],
+          },
         };
         element = fixture('element');
         const importSpy = sandbox.spy(element.$.externalStyle, '_import');
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
index 11fcc4e..017f166 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
@@ -34,9 +34,13 @@
      */
 
     properties: {
+      /** @type {?} */
       change: Object,
       commitInfo: Object,
       mutable: Boolean,
+      /**
+       * @type {{ note_db_enabled: string }}
+       */
       serverConfig: Object,
       _topicReadOnly: {
         type: Boolean,
@@ -235,6 +239,13 @@
       return false;
     },
 
+    /**
+     * Closure annotation for Polymer.prototype.splice is off.
+     * For now, supressing annotations.
+     *
+     * TODO(beckysiegel) submit Polymer PR
+     *
+     * @suppress {checkTypes} */
     _onDeleteVote(e) {
       e.preventDefault();
       const target = Polymer.dom(e).rootTarget;
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
index 1d8025e..bd0ef38 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
@@ -68,6 +68,7 @@
         type: Object,
         observer: '_paramsChanged',
       },
+      /** @type {?} */
       viewState: {
         type: Object,
         notify: true,
@@ -80,6 +81,7 @@
         type: Object,
         value() { return document.body; },
       },
+      /** @type {?} */
       _serverConfig: {
         type: Object,
         observer: '_startUpdateCheckTimer',
@@ -99,10 +101,12 @@
         computed: '_computeCanStartReview(_loggedIn, _change, _account)',
       },
       _comments: Object,
+      /** @type {?} */
       _change: {
         type: Object,
         observer: '_changeChanged',
       },
+      /** @type {?} */
       _commitInfo: Object,
       _files: Object,
       _changeNum: String,
@@ -119,6 +123,7 @@
         computed: '_computeHideEditCommitMessage(_loggedIn, ' +
             '_editingCommitMessage, _change)',
       },
+      /** @type {?string} */
       _latestCommitMessage: {
         type: String,
         value: '',
@@ -129,6 +134,7 @@
         computed:
           '_computeChangeIdCommitMessageError(_latestCommitMessage, _change)',
       },
+        /** @type {?} */
       _patchRange: {
         type: Object,
         observer: '_updateSelected',
@@ -147,6 +153,7 @@
         value: false,
       },
       _loading: Boolean,
+      /** @type {?} */
       _projectConfig: Object,
       _rebaseOnCurrent: Boolean,
       _replyButtonLabel: {
@@ -180,6 +187,7 @@
         type: Boolean,
         value: true,
       },
+      /** @type {?number} */
       _updateCheckTimerHandle: Number,
       _sortedRevisions: Array,
       _editLoaded: {
@@ -715,7 +723,7 @@
       const t = labels[labelName];
       if (!t) { return result; }
       const approvals = t.all || [];
-      for (label of approvals) {
+      for (const label of approvals) {
         if (label.value && label.value != labels[labelName].default_value) {
           let labelClassName;
           let labelValPrefix = '';
@@ -853,6 +861,9 @@
       });
     },
 
+    /**
+     * @param {string=} opt_section
+     */
     _openReplyDialog(opt_section) {
       this.$.replyOverlay.open().then(() => {
         this.$.replyOverlay.setFocusStops(this.$.replyDialog.getFocusStops());
@@ -913,6 +924,30 @@
       return msg.replace(REVIEWERS_REGEX, '$1=\u200B');
     },
 
+    /**
+     * Utility function to make the necessary modifications to a change in the
+     * case an edit exists.
+     *
+     * @param {!Object} change
+     * @param {?Object} edit
+     */
+    _processEdit(change, edit) {
+      if (!edit) { return; }
+      change.revisions[edit.commit.commit] = {
+        _number: this.EDIT_NAME,
+        basePatchNum: edit.base_patch_set_number,
+        commit: edit.commit,
+        fetch: edit.fetch,
+      };
+      // If the edit is based on the most recent patchset, load it by
+      // default, unless another patch set to load was specified in the URL.
+      if (!this._patchRange.patchNum &&
+          change.current_revision === edit.base_revision) {
+        change.current_revision = edit.commit.commit;
+        this._patchRange.patchNum = this.EDIT_NAME;
+      }
+    },
+
     _getChangeDetail() {
       const detailCompletes = this.$.restAPI.getChangeDetail(
           this._changeNum, this._handleGetChangeDetailError.bind(this));
@@ -923,15 +958,7 @@
             if (!change) {
               return '';
             }
-            this._upgradeUrl(change, this.params);
-            if (edit) {
-              change.revisions[edit.commit.commit] = {
-                _number: this.EDIT_NAME,
-                basePatchNum: edit.base_patch_set_number,
-                commit: edit.commit,
-                fetch: edit.fetch,
-              };
-            }
+            this._processEdit(change, edit);
             // Issue 4190: Coalesce missing topics to null.
             if (!change.topic) { change.topic = null; }
             if (!change.reviewer_updates) {
@@ -946,7 +973,10 @@
               this._latestCommitMessage = null;
             }
             const lineHeight = getComputedStyle(this).lineHeight;
-            this._lineHeight = lineHeight.slice(0, lineHeight.length - 2);
+
+            // Slice returns a number as a string, convert to an int.
+            this._lineHeight =
+                parseInt(lineHeight.slice(0, lineHeight.length - 2), 10);
 
             this._change = change;
             if (!this._patchRange || !this._patchRange.patchNum ||
@@ -964,13 +994,6 @@
           });
     },
 
-    _upgradeUrl(change, params) {
-      const project = change.project;
-      if (!params.project || project !== params.project) {
-        Gerrit.Nav.upgradeUrl(Object.assign({}, params, {project}));
-      }
-    },
-
     _getComments() {
       return this.$.restAPI.getDiffComments(this._changeNum).then(comments => {
         this._comments = comments;
@@ -1122,9 +1145,9 @@
 
 
     /**
-     * @param {Object} revisions The revisions object keyed by revision hashes
-     * @param {Object} patchSet A revision already fetched from {revisions}
-     * @return {string} the SHA hash corresponding to the revision.
+     * @param {!Object} revisions The revisions object keyed by revision hashes
+     * @param {?Object} patchSet A revision already fetched from {revisions}
+     * @return {string|undefined} the SHA hash corresponding to the revision.
      */
     _getPatchsetHash(revisions, patchSet) {
       for (const rev in revisions) {
@@ -1304,7 +1327,9 @@
     },
 
     _cancelUpdateCheckTimer() {
-      this.cancelAsync(this._updateCheckTimerHandle);
+      if (this._updateCheckTimerHandle) {
+        this.cancelAsync(this._updateCheckTimerHandle);
+      }
       this._updateCheckTimerHandle = null;
     },
 
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
index ddbbd5f..2a8cf63 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
@@ -835,7 +835,6 @@
 
     test('topic is coalesced to null', done => {
       sandbox.stub(element, '_changeChanged');
-      sandbox.stub(element, '_upgradeUrl');
       sandbox.stub(element.$.restAPI, 'getChangeDetail', () => {
         return Promise.resolve({
           id: '123456789',
@@ -853,7 +852,6 @@
 
     test('commit sha is populated from getChangeDetail', done => {
       sandbox.stub(element, '_changeChanged');
-      sandbox.stub(element, '_upgradeUrl');
       sandbox.stub(element.$.restAPI, 'getChangeDetail', () => {
         return Promise.resolve({
           id: '123456789',
@@ -871,7 +869,6 @@
 
     test('edit is added to change', () => {
       sandbox.stub(element, '_changeChanged');
-      sandbox.stub(element, '_upgradeUrl');
       sandbox.stub(element.$.restAPI, 'getChangeDetail', () => {
         return Promise.resolve({
           id: '123456789',
@@ -886,6 +883,7 @@
           commit: {commit: 'bar'},
         });
       });
+      element._patchRange = {};
 
       return element._getChangeDetail().then(() => {
         const revs = element._change.revisions;
@@ -1296,30 +1294,41 @@
       assert.isTrue(callCompute({basePatchNum: 1, patchNum: 'edit'}));
     });
 
-    suite('_upgradeUrl calls', () => {
-      let upgradeStub;
-      const mockChange = {project: 'test'};
+    test('_processEdit', () => {
+      element._patchRange = {};
+      const change = {
+        current_revision: 'foo',
+        revisions: {foo: {commit: {}}},
+      };
+      let mockChange;
 
-      setup(() => {
-        upgradeStub = sandbox.stub(window.Gerrit.Nav, 'upgradeUrl');
-      });
+      // With no edit, mockChange should be unmodified.
+      element._processEdit(mockChange = _.cloneDeep(change), null);
+      assert.deepEqual(mockChange, change);
 
-      test('app.params.project undefined', () => {
-        element._upgradeUrl(mockChange, {});
-        assert.isTrue(upgradeStub.called);
-        assert.deepEqual(upgradeStub.lastCall.args[0], mockChange);
-      });
+      // When edit is not based on the latest PS, current_revision should be
+      // unmodified.
+      const edit = {
+        base_patch_set_number: 1,
+        commit: {commit: 'bar'},
+        fetch: true,
+      };
+      element._processEdit(mockChange = _.cloneDeep(change), edit);
+      assert.notDeepEqual(mockChange, change);
+      assert.equal(mockChange.revisions.bar._number, element.EDIT_NAME);
+      assert.equal(mockChange.current_revision, change.current_revision);
+      assert.deepEqual(mockChange.revisions.bar.commit, {commit: 'bar'});
 
-      test('app.params.project differs from change.project', () => {
-        element._upgradeUrl(mockChange, {project: 'demo'});
-        assert.isTrue(upgradeStub.called);
-        assert.deepEqual(upgradeStub.lastCall.args[0], mockChange);
-      });
+      edit.base_revision = 'foo';
+      element._processEdit(mockChange = _.cloneDeep(change), edit);
+      assert.notDeepEqual(mockChange, change);
+      assert.equal(mockChange.current_revision, 'bar');
 
-      test('app.params.project === change.project', () => {
-        element._upgradeUrl(mockChange, {project: 'test'});
-        assert.isFalse(upgradeStub.called);
-      });
+      // If _patchRange.patchNum is defined, do not load edit.
+      element._patchRange.patchNum = 'baz';
+      change.current_revision = 'baz';
+      element._processEdit(mockChange = _.cloneDeep(change), edit);
+      assert.equal(element._patchRange.patchNum, 'baz');
     });
   });
 </script>
diff --git a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.js b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.js
index 703f386..72f1bfe 100644
--- a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.js
+++ b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.js
@@ -32,6 +32,8 @@
       patchNum: Number,
       commentLinks: Object,
       projectName: String,
+      /** @type {?} */
+      projectConfig: Object,
     },
 
     _computeFilesFromComments(comments) {
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.js b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.js
index 6cb7a9b..eb0fa17 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.js
@@ -30,6 +30,10 @@
      */
 
     properties: {
+      /**
+       * Weird API usage requires this to be String or Null. Add this so
+       * the closure compiler doesn't complain.
+       * @type {?string} */
       base: String,
       branch: String,
       hasParent: Boolean,
@@ -62,7 +66,7 @@
       this.fire('cancel', null, {bubbles: false});
     },
 
-    _handleRebaseOnOther(e) {
+    _handleRebaseOnOther() {
       this.$.parentInput.focus();
     },
 
@@ -73,15 +77,15 @@
      * rebased on top of the target branch. Leaving out the base implies that it
      * should be rebased on top of its current parent.
      */
-    _handleRebaseOnTip(e) {
+    _handleRebaseOnTip() {
       this.base = '';
     },
 
-    _handleRebaseOnParent(e) {
+    _handleRebaseOnParent() {
       this.base = null;
     },
 
-    _handleEnterChangeNumberTap(e) {
+    _handleEnterChangeNumberTap() {
       this.$.rebaseOnOtherInput.checked = true;
     },
 
diff --git a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.js b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.js
index 66ba86f..41242f2 100644
--- a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.js
@@ -24,8 +24,10 @@
      */
 
     properties: {
+      /** @type {{ revisions: Array }} */
       change: Object,
       patchNum: String,
+      /** @type {?} */
       config: Object,
 
       _schemes: {
@@ -82,28 +84,55 @@
       return commands;
     },
 
+    /**
+     * @param {!Object} change
+     * @param {number|string} patchNum
+     *
+     * @return {string}
+     */
     _computeZipDownloadLink(change, patchNum) {
       return this._computeDownloadLink(change, patchNum, true);
     },
 
+    /**
+     * @param {!Object} change
+     * @param {number|string} patchNum
+     *
+     * @return {string}
+     */
     _computeZipDownloadFilename(change, patchNum) {
       return this._computeDownloadFilename(change, patchNum, true);
     },
 
-    _computeDownloadLink(change, patchNum, zip) {
+    /**
+     * @param {!Object} change
+     * @param {number|string} patchNum
+     * @param {boolean=} opt_zip
+     *
+     * @return {string} Not sure why there was a mismatch
+     */
+    _computeDownloadLink(change, patchNum, opt_zip) {
       return this.changeBaseURL(change._number, patchNum) + '/patch?' +
-          (zip ? 'zip' : 'download');
+          (opt_zip ? 'zip' : 'download');
     },
 
-    _computeDownloadFilename(change, patchNum, zip) {
-      let shortRev;
+
+    /**
+     * @param {!Object} change
+     * @param {number|string} patchNum
+     * @param {boolean=} opt_zip
+     *
+     * @return {string}
+     */
+    _computeDownloadFilename(change, patchNum, opt_zip) {
+      let shortRev = '';
       for (const rev in change.revisions) {
         if (this.patchNumEquals(change.revisions[rev]._number, patchNum)) {
           shortRev = rev.substr(0, 7);
           break;
         }
       }
-      return shortRev + '.diff.' + (zip ? 'zip' : 'base64');
+      return shortRev + '.diff.' + (opt_zip ? 'zip' : 'base64');
     },
 
     _computeArchiveDownloadLink(change, patchNum, format) {
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
index 49c14e4..7164a16 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
@@ -35,6 +35,7 @@
     is: 'gr-file-list',
 
     properties: {
+      /** @type {?} */
       patchRange: {
         type: Object,
         observer: '_updateSelected',
@@ -54,6 +55,7 @@
         type: Object,
         value() { return document.body; },
       },
+      /** @type {?} */
       change: Object,
       diffViewMode: {
         type: String,
@@ -80,6 +82,7 @@
         notify: true,
         observer: '_updateDiffPreferences',
       },
+      /** @type {?} */
       _userPrefs: Object,
       _localPrefs: Object,
       _showInlineDiffs: Boolean,
@@ -87,6 +90,7 @@
         type: Number,
         notify: true,
       },
+      /** @type {?} */
       _patchChange: {
         type: Object,
         computed: '_calculatePatchChange(_files)',
@@ -283,7 +287,7 @@
       }
     },
 
-    _expandAllDiffs(e) {
+    _expandAllDiffs() {
       this._showInlineDiffs = true;
 
       // Find the list of paths that are in the file list, but not in the
@@ -300,7 +304,7 @@
       this.splice(...['_expandedFilePaths', 0, 0].concat(newPaths));
     },
 
-    _collapseAllDiffs(e) {
+    _collapseAllDiffs() {
       this._showInlineDiffs = false;
       this._expandedFilePaths = [];
       this.$.diffCursor.handleDiffUpdate();
@@ -330,6 +334,12 @@
       });
     },
 
+    /**
+     * @param {!Array} comments
+     * @param {number} patchNum
+     * @param {string} path
+     * @param {string=} opt_noun
+     */
     _computeCountString(comments, patchNum, path, opt_noun) {
       if (!comments) { return ''; }
 
@@ -345,8 +355,8 @@
      * Computes a string counting the number of unresolved comment threads in a
      * given file and path.
      *
-     * @param {Object} comments
-     * @param {Object} drafts
+     * @param {!Object} comments
+     * @param {!Object} drafts
      * @param {number} patchNum
      * @param {string} path
      * @return {string}
@@ -642,6 +652,9 @@
           diff.patchRange.patchNum, this.patchRange.basePatchNum);
     },
 
+    /**
+     * @param {number=} opt_index
+     */
     _openSelectedFile(opt_index) {
       if (opt_index != null) {
         this.$.fileCursor.setCursorAtIndex(opt_index);
@@ -816,7 +829,7 @@
      *
      * Use side-by-side if there is no view mode or preferences.
      *
-     * @return {String}
+     * @return {string}
      */
     _getDiffViewMode(diffViewMode, userPrefs) {
       if (diffViewMode) {
@@ -858,7 +871,7 @@
      * entries in the expanded list, then render each diff corresponding in
      * order by waiting for the previous diff to finish before starting the next
      * one.
-     * @param  {splice} record The splice record in the expanded paths list.
+     * @param {!Array} record The splice record in the expanded paths list.
      */
     _expandedPathsChanged(record) {
       if (!record) { return; }
@@ -890,9 +903,9 @@
      * Given an array of paths and a NodeList of diff elements, render the diff
      * for each path in order, awaiting the previous render to complete before
      * continung.
-     * @param  {!Array<!String>} paths
-     * @param  {!NodeList<!GrDiffElement>} diffElements
-     * @param  {Number} initialCount The total number of paths in the pass. This
+     * @param  {!Array<string>} paths
+     * @param  {!NodeList<!Object>} diffElements (GrDiffElement)
+     * @param  {number} initialCount The total number of paths in the pass. This
      *   is used to generate log messages.
      * @return {!Promise}
      */
@@ -918,9 +931,9 @@
 
     /**
      * In the given NodeList of diff elements, find the diff for the given path.
-     * @param  {!String} path
-     * @param  {!NodeList<!GrDiffElement>} diffElements
-     * @return {!GrDiffElement}
+     * @param  {string} path
+     * @param  {!NodeList<!Object>} diffElements (GrDiffElement)
+     * @return {!Object|undefined} (GrDiffElement)
      */
     _findDiffByPath(path, diffElements) {
       for (let i = 0; i < diffElements.length; i++) {
@@ -941,13 +954,13 @@
      * Update the loading class for the file list rows. The update is inside a
      * debouncer so that the file list doesn't flash gray when the API requests
      * are reasonably fast.
-     * @param {string} loading
+     * @param {boolean} loading
      */
     _loadingChanged(loading) {
       this.debounce('loading-change', () => {
         // Only show set the loading if there have been files loaded to show. In
         // this way, the gray loading style is not shown on initial loads.
-        this.classList.toggle('loading', loading && this._files.length);
+        this.classList.toggle('loading', loading && !!this._files.length);
       }, LOADING_DEBOUNCE_INTERVAL);
     },
 
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 29019de..d5506ad 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
@@ -17,6 +17,9 @@
   Polymer({
     is: 'gr-label-score-row',
     properties: {
+      /**
+       * @type {{ name: string }}
+       */
       label: Object,
       labels: Object,
       name: {
@@ -54,12 +57,12 @@
     _computeBlankItems(permittedLabels, label, side) {
       if (!permittedLabels || !permittedLabels[label]) { return []; }
       const startPosition = this.labelValues[parseInt(
-          permittedLabels[label][0])];
+          permittedLabels[label][0], 10)];
       if (side === 'start') {
         return new Array(startPosition);
       }
       const endPosition = this.labelValues[parseInt(
-          permittedLabels[label][permittedLabels[label].length - 1])];
+          permittedLabels[label][permittedLabels[label].length - 1], 10)];
       return new Array(Object.keys(this.labelValues).length - endPosition - 1);
     },
 
diff --git a/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores.js b/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores.js
index 3b53a99..b3642a5 100644
--- a/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores.js
+++ b/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores.js
@@ -25,6 +25,7 @@
         type: Object,
         observer: '_computeColumns',
       },
+      /** @type {?} */
       change: Object,
       _labelValues: Object,
     },
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message.html b/polygerrit-ui/app/elements/change/gr-message/gr-message.html
index 78f59db..ae43edf 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message.html
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message.html
@@ -14,9 +14,8 @@
 limitations under the License.
 -->
 
-<link rel="import" href="../../../behaviors/gr-anonymous-name-behavior/gr-anonymous-name-behavior.html">
 <link rel="import" href="../../../bower_components/polymer/polymer.html">
-<link rel="import" href="../../shared/gr-account-link/gr-account-link.html">
+<link rel="import" href="../../shared/gr-account-label/gr-account-label.html">
 <link rel="import" href="../../shared/gr-button/gr-button.html">
 <link rel="import" href="../../shared/gr-date-formatter/gr-date-formatter.html">
 <link rel="import" href="../../shared/gr-formatted-text/gr-formatted-text.html">
@@ -135,6 +134,11 @@
       .negativeVote {
         box-shadow: inset 0 4.4em #ffd4d4;
       }
+      gr-account-label {
+        --gr-account-label-text-style: {
+          font-weight: bold;
+        };
+      }
     </style>
     <div class$="[[_computeClass(_expanded, showAvatar, message)]]">
       <gr-avatar account="[[author]]" image-size="100"></gr-avatar>
@@ -144,7 +148,9 @@
             <span class="name">[[message.real_author.name]]</span>
             on behalf of
           </span>
-          <span class="name">[[_authorOrAnon(author)]]</span>
+          <gr-account-label
+              account="[[author]]"
+              hide-avatar></gr-account-label>
         </div>
         <template is="dom-if" if="[[message.message]]">
           <div class="content">
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message.js b/polygerrit-ui/app/elements/change/gr-message/gr-message.js
index 2593869..5c49c1c 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message.js
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message.js
@@ -39,6 +39,7 @@
 
     properties: {
       changeNum: Number,
+      /** @type {?} */
       message: Object,
       author: {
         type: Object,
@@ -79,6 +80,10 @@
         observer: '_projectNameChanged',
       },
       _commentLinks: Object,
+      /**
+       * @type {{ commentlinks: Array }}
+       */
+      projectConfig: Object,
       // Computed property needed to trigger Polymer value observing.
       _expanded: {
         type: Object,
@@ -90,10 +95,6 @@
       },
     },
 
-    behaviors: [
-      Gerrit.AnonymousNameBehavior,
-    ],
-
     observers: [
       '_updateExpandedClass(message.expanded)',
     ],
@@ -235,16 +236,6 @@
       this.fire('reply', {message: this.message});
     },
 
-    _authorOrAnon(author) {
-      if (author && author.name) {
-        return author.name;
-      } else if (author && author.email) {
-        return author.email;
-      }
-
-      return this.getAnonymousName(this.config);
-    },
-
     _projectNameChanged(name) {
       this.$.restAPI.getProjectConfig(name).then(config => {
         this._commentLinks = config.commentlinks;
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message_test.html b/polygerrit-ui/app/elements/change/gr-message/gr-message_test.html
index cfeb9cd..aa18c4e 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message_test.html
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message_test.html
@@ -65,37 +65,6 @@
       MockInteractions.tap(element.$$('.replyContainer gr-button'));
     });
 
-    test('reviewer update', () => {
-      const author = {
-        _account_id: 1115495,
-        name: 'Andrew Bonventre',
-        email: 'andybons@chromium.org',
-      };
-      const reviewer = {
-        _account_id: 123456,
-        name: 'Foo Bar',
-        email: 'barbar@chromium.org',
-      };
-      element.message = {
-        id: 0xDEADBEEF,
-        author,
-        reviewer,
-        date: '2016-01-12 20:24:49.448000000',
-        type: 'REVIEWER_UPDATE',
-        updates: [
-          {
-            message: 'Added to CC:',
-            reviewers: [reviewer],
-          },
-        ],
-      };
-      flushAsynchronousOperations();
-      const content = element.$$('.contentContainer');
-      assert.isOk(content);
-      assert.strictEqual(element.$$('gr-account-chip').account, reviewer);
-      assert.equal(author.name, element.$$('.author > .name').textContent);
-    });
-
     test('autogenerated prefix hiding', () => {
       element.message = {
         tag: 'autogenerated:gerrit:test',
@@ -211,27 +180,5 @@
       };
       assert.isOk(Polymer.dom(element.root).querySelector('.positiveVote'));
     });
-
-    test('test for Anonymous Coward user and replace with Anonymous', () => {
-      element.config = {
-        user: {
-          anonymous_coward_name: 'Anonymous Coward',
-        },
-      };
-      element.account = {};
-      assert.deepEqual(
-          element._authorOrAnon(element.account), 'Anonymous');
-    });
-
-    test('test for anonymous_coward_name', () => {
-      element.config = {
-        user: {
-          anonymous_coward_name: 'TestAnon',
-        },
-      };
-      element.account = {};
-      assert.deepEqual(
-          element._authorOrAnon(element.account, element.config), 'TestAnon');
-    });
   });
 </script>
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js
index df2a5ed..4ef0428 100644
--- a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js
+++ b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js
@@ -22,6 +22,7 @@
       hasParent: {
         type: Boolean,
         notify: true,
+        value: false,
       },
       patchNum: String,
       parentChange: Object,
@@ -39,6 +40,7 @@
         computed: '_computeConnectedRevisions(change, patchNum, ' +
             '_relatedResponse.changes)',
       },
+      /** @type {?} */
       _relatedResponse: {
         type: Object,
         value() { return {changes: []}; },
@@ -124,9 +126,9 @@
      * Determines whether or not the given change has a parent change. If there
      * is a relation chain, and the change id is not the last item of the
      * relation chain, there is a parent.
-     * @param  {Number} currentChangeId
-     * @param  {Array} relatedChanges
-     * @return {Boolean}
+     * @param  {number} currentChangeId
+     * @param  {!Array} relatedChanges
+     * @return {boolean}
      */
     _calculateHasParent(currentChangeId, relatedChanges) {
       return relatedChanges.length > 0 &&
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.html b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.html
index e76a9e5..babd95c 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.html
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.html
@@ -132,10 +132,12 @@
     test('lgtm plugin', done => {
       const pluginHost = fixture('plugin-host');
       pluginHost.config = {
-        js_resource_paths: [],
-        html_resource_paths: [
-          new URL('test/plugin.html', window.location.href).toString(),
-        ],
+        plugin: {
+          js_resource_paths: [],
+          html_resource_paths: [
+            new URL('test/plugin.html', window.location.href).toString(),
+          ],
+        },
       };
       element = fixture('basic');
       setupElement(element);
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
index b743c7c..752f15a 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
@@ -74,6 +74,9 @@
      */
 
     properties: {
+      /**
+       * @type {{ _number: number, removable_reviewers: Array }}
+       */
       change: Object,
       patchNum: String,
       canBeStarted: {
@@ -94,13 +97,18 @@
         type: String,
         value: '',
       },
-      diffDrafts: Object,
+      diffDrafts: {
+        type: Object,
+        observer: '_handleHeightChanged',
+      },
+      /** @type {!Function} */
       filterReviewerSuggestion: {
         type: Function,
         value() {
           return this._filterReviewerSuggestionGenerator(false);
         },
       },
+      /** @type {!Function} */
       filterCCSuggestion: {
         type: Function,
         value() {
@@ -108,7 +116,13 @@
         },
       },
       permittedLabels: Object,
+      /**
+       * @type {{ note_db_enabled: boolean }}
+       */
       serverConfig: Object,
+      /**
+       * @type {{ commentlinks: Array }}
+       */
       projectConfig: Object,
       knownLatestState: String,
       underReview: {
@@ -118,6 +132,7 @@
 
       _account: Object,
       _ccs: Array,
+      /** @type {?Object} */
       _ccPendingConfirmation: {
         type: Object,
         observer: '_reviewerPendingConfirmationUpdated',
@@ -127,12 +142,14 @@
         computed: '_computeMessagePlaceholder(canBeStarted)',
       },
       _owner: Object,
+      /** @type {?} */
       _pendingConfirmationDetails: Object,
       _includeComments: {
         type: Boolean,
         value: true,
       },
       _reviewers: Array,
+      /** @type {?Object} */
       _reviewerPendingConfirmation: {
         type: Object,
         observer: '_reviewerPendingConfirmationUpdated',
@@ -296,8 +313,8 @@
      * Resets the state of the _reviewersPendingRemove object, and removes
      * accounts if necessary.
      *
-     * @param {Boolean} isCancel true if the action is a cancel.
-     * @param {Object} opt_accountIdsTransferred map of account IDs that must
+     * @param {boolean} isCancel true if the action is a cancel.
+     * @param {Object=} opt_accountIdsTransferred map of account IDs that must
      *     not be removed, because they have been readded in another state.
      */
     _purgeReviewersPendingRemove(isCancel, opt_accountIdsTransferred) {
@@ -322,8 +339,11 @@
      * Removes an account from the change, both on the backend and the client.
      * Does nothing if the account is a pending addition.
      *
-     * @param {Object} account
-     * @param {ReviewerTypes} type
+     * @param {!Object} account
+     * @param {string} type
+     *
+     * * TODO(beckysiegel) submit Polymer PR
+     * @suppress {checkTypes}
      */
     _removeAccount(account, type) {
       if (account._pendingAdd) { return; }
@@ -605,8 +625,8 @@
      * Generates a function to filter out reviewer/CC entries. When isCCs is
      * truthy, the function filters out entries that already exist in this._ccs.
      * When falsy, the function filters entries that exist in this._reviewers.
-     * @param {Boolean} isCCs
-     * @return {Function}
+     * @param {boolean} isCCs
+     * @return {!Function}
      */
     _filterReviewerSuggestionGenerator(isCCs) {
       return suggestion => {
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
index f4b1841..0f90019 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
@@ -802,6 +802,20 @@
       element._handle400Error(response);
     });
 
+    test('fires height change when the drafts load', done => {
+      // Flush DOM operations before binding to the autogrow event so we don't
+      // catch the events fired from the initial layout.
+      flush(() => {
+        const autoGrowHandler = sinon.stub();
+        element.addEventListener('autogrow', autoGrowHandler);
+        element.diffDrafts = {};
+        flush(() => {
+          assert.isTrue(autoGrowHandler.called);
+          done();
+        });
+      });
+    });
+
     suite('post review API', () => {
       let startReviewStub;
 
diff --git a/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.js b/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.js
index 2d740d5..c147456 100644
--- a/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.js
+++ b/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.js
@@ -47,7 +47,7 @@
         if (cfg && cfg.auth && cfg.auth.switch_account_url) {
           this._switchAccountUrl = cfg.auth.switch_account_url;
         } else {
-          this._switchAccountUrl = null;
+          this._switchAccountUrl = '';
         }
         this._hasAvatars = !!(cfg && cfg.plugin && cfg.plugin.has_avatars);
       });
@@ -93,13 +93,7 @@
     },
 
     _accountName(account) {
-      if (account && account.name) {
-        return account.name;
-      } else if (account && account.email) {
-        return account.email;
-      }
-
-      return this.getAnonymousName(this.config);
+      return this.getUserName(this.config, account, true);
     },
   });
 })();
diff --git a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.js b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.js
index 5fdbcfd..0a2b974 100644
--- a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.js
+++ b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.js
@@ -36,7 +36,9 @@
        */
       knownAccountId: Number,
 
-      _alertElement: Element,
+      /** @type {?Object} */
+      _alertElement: Object,
+      /** @type {?number} */
       _hideAlertHandle: Number,
       _refreshingCredentials: {
         type: Boolean,
@@ -116,12 +118,18 @@
       return this.$.restAPI.getLoggedIn();
     },
 
+    /**
+     * @param {string} text
+     * @param {?string=} opt_actionText
+     * @param {?Function=} opt_actionCallback
+     * @param {?boolean=} opt_dismissOnNavigation
+     */
     _showAlert(text, opt_actionText, opt_actionCallback,
-        dismissOnNavigation) {
+        opt_dismissOnNavigation) {
       if (this._alertElement) { return; }
 
       this._clearHideAlertHandle();
-      if (dismissOnNavigation) {
+      if (opt_dismissOnNavigation) {
         // Persist alert until navigation.
         this.listen(document, 'location-change', '_hideAlert');
       } else {
diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.html b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.html
index 2747dd0..a3c6bb7 100644
--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.html
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.html
@@ -86,7 +86,7 @@
       gr-dropdown {
         padding: 0.5em;
       }
-      .more {
+      .browse {
         padding: 1em;
         text-decoration: none;
       }
@@ -120,7 +120,7 @@
           font-weight: bold;
         }
         gr-search-bar,
-        .more,
+        .browse,
         li.hideOnMobile {
           display: none;
         }
@@ -153,8 +153,8 @@
           </li>
         </template>
         <li>
-          <a class="more linksTitle" href$="[[_computeRelativeURL('/admin/projects')]]">
-            More</a>
+          <a class="browse linksTitle" href$="[[_computeRelativeURL('/admin/projects')]]">
+            Browse</a>
         </li>
       </ul>
       <div class="rightItems">
diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js
index b065000..c7a3815 100644
--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js
@@ -72,6 +72,7 @@
         notify: true,
       },
 
+      /** @type {?Object} */
       _account: Object,
       _adminLinks: {
         type: Array,
@@ -125,11 +126,12 @@
     },
 
     _handleLocationChange(e) {
-      if (this.getBaseUrl()) {
+      const baseUrl = this.getBaseUrl();
+      if (baseUrl) {
         // Strip the canonical path from the path since needing canonical in
         // the path is uneeded and breaks the url.
-        this._loginURL = this.getBaseUrl() + '/login/' + encodeURIComponent(
-            '/' + window.location.pathname.substring(this.getBaseUrl().length) +
+        this._loginURL = baseUrl + '/login/' + encodeURIComponent(
+            '/' + window.location.pathname.substring(baseUrl.length) +
             window.location.search +
             window.location.hash);
       } else {
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 4f8e842..3d57469 100644
--- a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html
+++ b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html
@@ -85,6 +85,10 @@
        */
       _upgradeUrl: uninitialized,
 
+      /**
+       * @param {number=} patchNum
+       * @param {number|string=} basePatchNum
+       */
       _checkPatchRange(patchNum, basePatchNum) {
         if (basePatchNum && !patchNum) {
           throw new Error('Cannot use base patch number without patch number.');
@@ -119,28 +123,31 @@
       },
 
       /**
-       * @param {string} project The name of the project.
+       * @param {!string} project The name of the project.
+       * @param {boolean=} opt_openOnly When true, only search open changes in
+       *     the project.
        * @return {string}
        */
-      getUrlForProject(project) {
+      getUrlForProject(project, opt_openOnly) {
         return this._getUrlFor({
           view: Gerrit.Nav.View.SEARCH,
           project,
+          statuses: opt_openOnly ? ['open'] : [],
         });
       },
 
       /**
        * @param {string} branch The name of the branch.
        * @param {string} project The name of the project.
-       * @param {string} status The status to search.
+       * @param {string=} opt_status The status to search.
        * @return {string}
        */
-      getUrlForBranch(branch, project, status) {
+      getUrlForBranch(branch, project, opt_status) {
         return this._getUrlFor({
           view: Gerrit.Nav.View.SEARCH,
           branch,
           project,
-          statuses: [status],
+          statuses: opt_status ? [opt_status] : undefined,
         });
       },
 
@@ -195,7 +202,6 @@
        * @param {number=} opt_patchNum
        * @param {number|string=} opt_basePatchNum The string 'PARENT' can be
        *     used for none.
-       * @return {string}
        */
       navigateToChange(change, opt_patchNum, opt_basePatchNum) {
         this._navigate(this.getUrlForChange(change, opt_patchNum,
@@ -203,8 +209,8 @@
       },
 
       /**
-       * @param {!Object} change The change object.
-       * @param {!string} path The file path.
+       * @param {{ _number: number, project: string }} change The change object.
+       * @param {string} path The file path.
        * @param {number=} opt_patchNum
        * @param {number|string=} opt_basePatchNum The string 'PARENT' can be
        *     used for none.
@@ -216,9 +222,9 @@
       },
 
       /**
-       * @param {!number} change The change object.
-       * @param {!string} project The name of the project.
-       * @param {!string} path The file path.
+       * @param {number} changeNum
+       * @param {string} project The name of the project.
+       * @param {string} path The file path.
        * @param {number=} opt_patchNum
        * @param {number|string=} opt_basePatchNum The string 'PARENT' can be
        *     used for none.
@@ -247,7 +253,7 @@
 
       /**
        * @param {!Object} change The change object.
-       * @param {!string} path The file path.
+       * @param {string} path The file path.
        * @param {number=} opt_patchNum
        * @param {number|string=} opt_basePatchNum The string 'PARENT' can be
        *     used for none.
@@ -270,7 +276,7 @@
 
       /**
        * Navigate to an arbitrary relative URL.
-       * @param {!string} relativeUrl
+       * @param {string} relativeUrl
        */
       navigateToRelativeUrl(relativeUrl) {
         if (!relativeUrl.startsWith('/')) {
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router.js b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
index 370da91..6a0cf8d 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.js
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
@@ -67,6 +67,21 @@
 
     Gerrit.Nav.setup(url => { page.show(url); }, generateUrl, upgradeUrl);
 
+    /**
+     * Given a set of params without a project, gets the project from the rest
+     * API project lookup and then sets the app params.
+     *
+     * @param {?Object} params
+     */
+    const normalizeLegacyRouteParams = params => {
+      if (!params.changeNum) { return; }
+
+      restAPI.getFromProjectLookup(params.changeNum).then(project => {
+        params.project = project;
+        normalizePatchRangeParams(params);
+      });
+    };
+
     // Middleware
     page((ctx, next) => {
       document.body.scrollTop = 0;
@@ -152,7 +167,7 @@
       });
     });
 
-    // Matches /admin/groups/<group>,audit-log[/]
+    // Matches /admin/groups/<group>,audit-log
     page(/^\/admin\/groups\/(.+),audit-log$/, loadUser, data => {
       restAPI.getLoggedIn().then(loggedIn => {
         if (loggedIn) {
@@ -168,6 +183,16 @@
       });
     });
 
+    // Matches /admin/groups/<group>,members
+    page(/^\/admin\/groups\/(.+),members$/, loadUser, data => {
+      app.params = {
+        view: Gerrit.Nav.View.ADMIN,
+        adminView: 'gr-group-members',
+        detailType: 'members',
+        groupId: data.params[0],
+      };
+    });
+
     // Matches /admin/groups[,<offset>][/].
     page(/^\/admin\/groups(,(\d+))?(\/)?$/, loadUser, data => {
       restAPI.getLoggedIn().then(loggedIn => {
@@ -214,7 +239,7 @@
     });
 
     // Matches /admin/groups/<group>
-    page(/^\/admin\/groups\/(.+)$/, loadUser, data => {
+    page(/^\/admin\/groups\/([^,]+)$/, loadUser, data => {
       restAPI.getLoggedIn().then(loggedIn => {
         if (loggedIn) {
           app.params = {
@@ -326,7 +351,7 @@
     });
 
     // Matches /admin/projects/<project>
-    page(/^\/admin\/projects\/(.+)$/, loadUser, data => {
+    page(/^\/admin\/projects\/([^,]+)$/, loadUser, data => {
       app.params = {
         view: Gerrit.Nav.View.ADMIN,
         project: data.params[0],
@@ -450,10 +475,10 @@
             patchNum: ctx.params[6],
             path: ctx.params[8],
             view: ctx.params[8] ? Gerrit.Nav.View.DIFF : Gerrit.Nav.View.CHANGE,
+            hash: ctx.hash,
           };
           normalizePatchRangeParams(params);
           app.params = params;
-          upgradeUrl(params);
           restAPI.setInProjectLookup(params.changeNum, params.project);
         });
 
@@ -467,8 +492,7 @@
         view: Gerrit.Nav.View.CHANGE,
       };
 
-      normalizePatchRangeParams(params);
-      app.params = params;
+      normalizeLegacyRouteParams(params);
     });
 
     // Matches /c/<changeNum>/[<basePatchNum>..]<patchNum>/<path>.
@@ -491,8 +515,7 @@
         view: Gerrit.Nav.View.DIFF,
       };
 
-      normalizePatchRangeParams(params);
-      app.params = params;
+      normalizeLegacyRouteParams(params);
     });
 
     page(/^\/settings\/(agreements|new-agreement)/, loadUser, data => {
diff --git a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.js b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.js
index d33ed23..ef39e1f 100644
--- a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.js
+++ b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.js
@@ -20,9 +20,13 @@
     is: 'gr-comment-api',
 
     properties: {
+      /** @type {number} */
       _changeNum: Number,
+      /** @type {!Object|undefined} */
       _comments: Object,
+      /** @type {!Object|undefined} */
       _drafts: Object,
+      /** @type {!Object|undefined} */
       _robotComments: Object,
     },
 
@@ -35,7 +39,7 @@
      * number. The returned promise resolves when the comments have loaded, but
      * does not yield the comment data.
      *
-     * @param {!number} changeNum
+     * @param {number} changeNum
      * @return {!Promise}
      */
     loadAll(changeNum) {
@@ -91,7 +95,7 @@
      * @param {!string} path
      * @param {!Object} patchRange The patch-range object containing patchNum
      *     and basePatchNum properties to represent the range.
-     * @param {Object} opt_projectConfig Optional project config object to
+     * @param {Object=} opt_projectConfig Optional project config object to
      *     include in the meta sub-object.
      * @return {Object}
      */
@@ -160,7 +164,7 @@
      * Whether the given comment should be included in the given patch range.
      * @param {!Object} comment
      * @param {!Object} range
-     * @return {boolean}
+     * @return {boolean|undefined}
      */
     _isInPatchRange(comment, range) {
       return this._isInBaseOfPatchRange(comment, range) ||
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html
index 22e5080..f11b573 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html
@@ -393,7 +393,7 @@
         },
 
         /**
-         * @return {Boolean} whether any of the lines in _groups are longer
+         * @return {boolean} whether any of the lines in _groups are longer
          * than SYNTAX_MAX_LINE_LENGTH.
          */
         _anyLineTooLong() {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js
index a9438bb7..40fcf0d 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js
@@ -153,12 +153,12 @@
   /**
    * Find line elements or line objects by a range of line numbers and a side.
    *
-   * @param {Number} start The first line number
-   * @param {Number} end The last line number
-   * @param {String} opt_side The side of the range. Either 'left' or 'right'.
-   * @param {Array<GrDiffLine>} out_lines The output list of line objects. Use
+   * @param {number} start The first line number
+   * @param {number} end The last line number
+   * @param {string} opt_side The side of the range. Either 'left' or 'right'.
+   * @param {!Array<GrDiffLine>} out_lines The output list of line objects. Use
    *     null if not desired.
-   * @param  {Array<HTMLElement>} out_elements The output list of line elements.
+   * @param  {!Array<HTMLElement>} out_elements The output list of line elements.
    *     Use null if not desired.
    */
   GrDiffBuilder.prototype.findLinesByRange = function(start, end, opt_side,
@@ -443,7 +443,7 @@
 
   /**
    * Returns the text length after normalizing unicode and tabs.
-   * @return {Number} The normalized length of the text.
+   * @return {number} The normalized length of the text.
    */
   GrDiffBuilder.prototype._textLength = function(text, tabSize) {
     text = text.replace(REGEX_ASTRAL_SYMBOL, '_');
@@ -536,9 +536,9 @@
    * elements in place of tab characters. In each case tab elements are given
    * the width needed to reach the next tab-stop.
    *
-   * @param {String} A line of text potentially containing tab characters.
-   * @param {Number} The width for tabs.
-   * @return {String} An HTML string potentially containing tab elements.
+   * @param {string} A line of text potentially containing tab characters.
+   * @param {number} The width for tabs.
+   * @return {string} An HTML string potentially containing tab elements.
    */
   GrDiffBuilder.prototype._addTabWrappers = function(line, tabSize) {
     if (!line.length) { return ''; }
@@ -600,7 +600,7 @@
    * Finds the next DIV.contentText element following the given element, and on
    * the same side. Will only search within a group.
    * @param {HTMLElement} content
-   * @param {String} side Either 'left' or 'right'
+   * @param {string} side Either 'left' or 'right'
    * @return {HTMLElement}
    */
   GrDiffBuilder.prototype._getNextContentOnSide = function(content, side) {
@@ -610,8 +610,8 @@
   /**
    * Determines whether the given group is either totally an addition or totally
    * a removal.
-   * @param {GrDiffGroup} group
-   * @return {Boolean}
+   * @param {!Object} group (GrDiffGroup)
+   * @return {boolean}
    */
   GrDiffBuilder.prototype._isTotal = function(group) {
     return group.type === GrDiffGroup.Type.DELTA &&
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.js b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.js
index d8dccad..e9ab3d6 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread.js
@@ -213,6 +213,9 @@
       return !!comment.__draft;
     },
 
+    /**
+     * @param {boolean=} opt_quote
+     */
     _processCommentReply(opt_quote) {
       const comment = this._lastComment;
       let quoteStr;
@@ -273,6 +276,10 @@
       return d;
     },
 
+    /**
+     * @param {number=} opt_lineNum
+     * @param {!Object=} opt_range
+     */
     _newDraft(opt_lineNum, opt_range) {
       const d = {
         __draft: true,
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 dcba80d..4f0ebcc 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
@@ -53,6 +53,7 @@
 
     properties: {
       changeNum: String,
+      /** @type {?} */
       comment: {
         type: Object,
         notify: true,
@@ -88,6 +89,7 @@
         value: true,
         observer: '_toggleCollapseClass',
       },
+      /** @type {?} */
       projectConfig: Object,
       robotButtonDisabled: Boolean,
       _isAdmin: {
@@ -215,6 +217,11 @@
       }
     },
 
+    /**
+     * @param {!Object=} opt_mixin
+     *
+     * @return {!Object}
+     */
     _getEventPayload(opt_mixin) {
       return Object.assign({}, opt_mixin, {
         comment: this.comment,
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.js b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.js
index e47db8f..b14ea7f 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.js
@@ -43,6 +43,7 @@
         type: String,
         value: DiffSides.RIGHT,
       },
+      /** @type {!HTMLElement|undefined} */
       diffRow: {
         type: Object,
         notify: true,
@@ -61,6 +62,8 @@
        * If set, the cursor will attempt to move to the line number (instead of
        * the first chunk) the next time the diff renders. It is set back to null
        * when used.
+       *
+       * @type (?number)
        */
       initialLineNumber: {
         type: Number,
@@ -147,6 +150,11 @@
       this._fixSide();
     },
 
+    /**
+     * @param {number} number
+     * @param {string} side
+     * @param {string=} opt_path
+     */
     moveToLineNumber(number, side, opt_path) {
       const row = this._findRowByNumberAndFile(number, side, opt_path);
       if (row) {
@@ -157,7 +165,7 @@
 
     /**
      * Get the line number element targeted by the cursor row and side.
-     * @return {DOMElement}
+     * @return {?Element|undefined}
      */
     getTargetLineElement() {
       let lineElSelector = '.lineNum';
@@ -221,7 +229,7 @@
      * Get a short address for the location of the cursor. Such as '123' for
      * line 123 of the revision, or 'b321' for line 321 of the base patch.
      * Returns an empty string if an address is not available.
-     * @return {String}
+     * @return {string}
      */
     getAddress() {
       if (!this.diffRow) { return ''; }
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-annotation.js b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-annotation.js
index 9afbf2b..e18f6ca 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-annotation.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-annotation.js
@@ -28,8 +28,8 @@
     /**
      * The DOM API textContent.length calculation is broken when the text
      * contains Unicode. See https://mathiasbynens.be/notes/javascript-unicode .
-     * @param  {Text} A text node.
-     * @return {Number} The length of the text.
+     * @param  {!Text} node text node.
+     * @return {number} The length of the text.
      */
     getLength(node) {
       return this.getStringLength(node.textContent);
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.js b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.js
index 3288608..2490509 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.js
@@ -20,6 +20,11 @@
     properties: {
       comments: Object,
       loggedIn: Boolean,
+      /**
+       * querySelector can return null, so needs to be nullable.
+       *
+       * @type {?HTMLElement}
+       * */
       _cachedDiffBuilder: Object,
       isAttached: Boolean,
     },
@@ -99,7 +104,7 @@
      * Merges multiple ranges, accounts for triple click, accounts for
      * syntax highligh, convert native DOM Range objects to Gerrit concepts
      * (line, side, etc).
-     * @return {{
+     * @return {({
      *   start: {
      *     node: Node,
      *     side: string,
@@ -112,7 +117,7 @@
      *     line: Number,
      *     column: Number
      *   }
-     * }}
+     * })|null|!Object}
      */
     _getNormalizedRange() {
       const selection = window.getSelection();
@@ -134,6 +139,7 @@
 
     /**
      * Normalize a specific DOM Range.
+     * @return {!Object} fixed normalized range
      */
     _normalizeRange(domRange) {
       const range = GrRangeNormalizer.normalize(domRange);
@@ -195,12 +201,12 @@
      *
      * @param {Node} node td.content child
      * @param {number} offset offset within node
-     * @return {{
+     * @return {({
      *   node: Node,
      *   side: string,
      *   line: Number,
      *   column: Number
-     * }}
+     * }|undefined)}
      */
     _normalizeSelectionSide(node, offset) {
       let column;
@@ -251,6 +257,7 @@
         return;
       }
       const domRange = window.getSelection().getRangeAt(0);
+      /** @type {?} */
       const start = normalizedRange.start;
       if (!start) {
         return;
@@ -280,7 +287,9 @@
       if (start.line === end.line) {
         actionBox.placeAbove(domRange);
       } else if (start.node instanceof Text) {
-        actionBox.placeAbove(start.node.splitText(start.column));
+        if (start.column) {
+          actionBox.placeAbove(start.node.splitText(start.column));
+        }
         start.node.parentElement.normalize(); // Undo splitText from above.
       } else if (start.node.classList.contains('content') &&
                  start.node.firstChild) {
@@ -325,7 +334,7 @@
      * Traverse Element from right to left, call callback for each node.
      * Stops if callback returns true.
      *
-     * @param {!Node} startNode
+     * @param {!Element} startNode
      * @param {function(Node):boolean} callback
      * @param {Object=} opt_flags If flags.left is true, traverse left.
      */
@@ -350,7 +359,7 @@
      * Get length of a node. If the node is a content node, then only give the
      * length of its .contentText child.
      *
-     * @param {!Node} node
+     * @param {?Element} node this is sometimes passed as null.
      * @return {number}
      */
     _getLength(node) {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-range-normalizer.js b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-range-normalizer.js
index 0b0131e..1ea5037 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-range-normalizer.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-range-normalizer.js
@@ -27,8 +27,8 @@
      * highlighting, the containers are remapped to the .contentText divs that
      * contain the entire line of code.
      *
-     * @param {Object} range - the standard DOM selector range.
-     * @return {Object} A modified version of the range that correctly accounts
+     * @param {!Object} range - the standard DOM selector range.
+     * @return {!Object} A modified version of the range that correctly accounts
      *     for syntax highlighting.
      */
     normalize(range) {
@@ -94,8 +94,8 @@
     /**
      * The DOM API textContent.length calculation is broken when the text
      * contains Unicode. See https://mathiasbynens.be/notes/javascript-unicode .
-     * @param {Text} A text node.
-     * @return {Number} The length of the text.
+     * @param {text} node A text node.
+     * @return {number} The length of the text.
      */
     _getLength(node) {
       return node.textContent.replace(REGEX_ASTRAL_SYMBOL, '_').length;
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences.js b/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences.js
index 20833d0..f5944c3 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences.js
@@ -32,6 +32,7 @@
         reflectToAttribute: true,
       },
 
+      /** @type {?} */
       _newPrefs: Object,
       _newLocalPrefs: Object,
     },
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.js b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.js
index 07b8429..bca6bea 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.js
@@ -77,6 +77,7 @@
         value: 64,
       },
 
+      /** @type {number|undefined} */
       _nextStepHandle: Number,
       _isScrolling: Boolean,
     },
@@ -225,14 +226,14 @@
     /**
      * Take rows of a shared diff section and produce an array of corresponding
      * (potentially collapsed) groups.
-     * @param {Array<String>} rows
-     * @param {Number} context
-     * @param {Number} startLineNumLeft
-     * @param {Number} startLineNumRight
-     * @param {String} opt_sectionEnd String representing whether this is the
+     * @param {!Array<string>} rows
+     * @param {number} context
+     * @param {number} startLineNumLeft
+     * @param {number} startLineNumRight
+     * @param {?string=} opt_sectionEnd String representing whether this is the
      *     first section or the last section or neither. Use the values 'first',
      *     'last' and null respectively.
-     * @return {Array<GrDiffGroup>}
+     * @return {!Array<!Object>} Array of GrDiffGroup
      */
     _sharedGroupsFromRows(rows, context, startLineNumLeft,
         startLineNumRight, opt_sectionEnd) {
@@ -288,11 +289,11 @@
     /**
      * Take the rows of a delta diff section and produce the corresponding
      * group.
-     * @param {Array<String>} rowsAdded
-     * @param {Array<String>} rowsRemoved
-     * @param {Number} startLineNumLeft
-     * @param {Number} startLineNumRight
-     * @return {GrDiffGroup}
+     * @param {!Array<string>} rowsAdded
+     * @param {!Array<string>} rowsRemoved
+     * @param {number} startLineNumLeft
+     * @param {number} startLineNumRight
+     * @return {!Object} (Gr-Diff-Group)
      */
     _deltaGroupFromRows(rowsAdded, rowsRemoved, startLineNumLeft,
         startLineNumRight, highlights) {
@@ -309,7 +310,7 @@
     },
 
     /**
-     * @return {Array<GrDiffLine>}
+     * @return {!Array<!Object>} Array of GrDiffLines
      */
     _deltaLinesFromRows(lineType, rows, startLineNum,
         opt_highlights) {
@@ -348,8 +349,8 @@
      * In order to show comments out of the bounds of the selected context,
      * treat them as separate chunks within the model so that the content (and
      * context surrounding it) renders correctly.
-     * @param {Object} content The diff content object.
-     * @return {Object} A new diff content object with regions split up.
+     * @param {?} content The diff content object. (has to be iterable)
+     * @return {!Object} A new diff content object with regions split up.
      */
     _splitCommonGroupsWithComments(content) {
       const result = [];
@@ -492,7 +493,7 @@
      * If a group is an addition or a removal, break it down into smaller groups
      * of that type using the MAX_GROUP_SIZE. If the group is a shared section
      * or a delta it is returned as the single element of the result array.
-     * @param {!Object} A raw chunk from a diff response.
+     * @param {!Object} group A raw chunk from a diff response.
      * @return {!Array<!Array<!Object>>}
      */
     _breakdownGroup(group) {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js
index ecddba2..3280b68 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js
@@ -31,6 +31,7 @@
 
     properties: {
       diff: Object,
+      /** @type {?Object} */
       _cachedDiffBuilder: Object,
       _linesCache: {
         type: Object,
@@ -144,14 +145,14 @@
      * true, it returns only the text of comments within the selection.
      * Otherwise it returns the text of the selected diff region.
      *
-     * @param {!string} The side that is selected.
-     * @param {boolean} Whether or not a comment is selected.
+     * @param {!string} side The side that is selected.
+     * @param {boolean} commentSelected Whether or not a comment is selected.
      * @return {string} The selected text.
      */
     _getSelectedText(side, commentSelected) {
       const sel = window.getSelection();
       if (sel.rangeCount != 1) {
-        return; // No multi-select support yet.
+        return ''; // No multi-select support yet.
       }
       if (commentSelected) {
         return this._getCommentLines(sel, side);
@@ -170,10 +171,10 @@
     /**
      * Query the diff object for the selected lines.
      *
-     * @param {int} startLineNum
-     * @param {int} startOffset
-     * @param {int} endLineNum
-     * @param {int} endOffset
+     * @param {number} startLineNum
+     * @param {number} startOffset
+     * @param {number} endLineNum
+     * @param {number} endOffset
      * @param {!string} side The side that is currently selected.
      * @return {string} The selected diff text.
      */
@@ -192,7 +193,7 @@
      * Query the diff object for the lines from a particular side.
      *
      * @param {!string} side The side that is currently selected.
-     * @return {Array.string} An array of strings indexed by line number.
+     * @return {!Array<string>} An array of strings indexed by line number.
      */
     _getDiffLines(side) {
       if (this._linesCache[side]) {
@@ -254,9 +255,9 @@
      * of the text content within that selection.
      * Using a domNode that isn't in the selection returns an empty string.
      *
-     * @param {Element} domNode The root DOM node.
-     * @param {Selection} sel The selection.
-     * @param {Range} range The normalized selection range.
+     * @param {!Node} domNode The root DOM node.
+     * @param {!Selection} sel The selection.
+     * @param {!Range} range The normalized selection range.
      * @return {string} The text within the selection.
      */
     _getTextContentForRange(domNode, sel, range) {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
index c12f700..fbd5b1b 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
@@ -55,6 +55,9 @@
         type: Object,
         value() { return document.body; },
       },
+      /**
+       * @type {{ diffMode: (string|undefined) }}
+       */
       changeViewState: {
         type: Object,
         notify: true,
@@ -63,6 +66,13 @@
       },
 
       _patchRange: Object,
+      /**
+       * @type {{
+       *  subject: string,
+       *  project: string,
+       *  revisions: string,
+       * }}
+       */
       _change: Object,
       _changeNum: String,
       _diff: Object,
@@ -169,17 +179,9 @@
     _getChangeDetail(changeNum) {
       return this.$.restAPI.getDiffChangeDetail(changeNum).then(change => {
         this._change = change;
-        this._upgradeUrl(change, this.params);
       });
     },
 
-    _upgradeUrl(change, params) {
-      const project = change.project;
-      if (!params.project || project !== params.project) {
-        Gerrit.Nav.upgradeUrl(Object.assign({}, params, {project}));
-      }
-    },
-
     _getChangeEdit(changeNum) {
       return this.$.restAPI.getChangeEdit(this._changeNum);
     },
@@ -400,7 +402,7 @@
 
     /**
      * @param {?string} path The path of the current file being shown.
-     * @param {Array.<string>} fileList The list of files in this change and
+     * @param {!Array<string>} fileList The list of files in this change and
      *     patch range.
      * @param {number} direction Either 1 (next file) or -1 (prev file).
      * @param {(number|boolean)} opt_noUp Whether to return to the change view
@@ -433,12 +435,12 @@
      *   * null - When no navigation is possible for the given direction.
      *
      * @param {?string} path The path of the current file being shown.
-     * @param {Array.<string>} fileList The list of files in this change and
+     * @param {!Array<string>} fileList The list of files in this change and
      *     patch range.
      * @param {number} direction Either 1 (next file) or -1 (prev file).
-     * @param {(number|boolean)} opt_noUp Whether to return to the change view
+     * @param {?number|boolean=} opt_noUp Whether to return to the change view
      *     when advancing the file goes outside the bounds of fileList.
-     * @return {Object}
+     * @return {?Object}
      */
     _getNavLinkPath(path, fileList, direction, opt_noUp) {
       if (!path || fileList.length === 0) { return null; }
@@ -589,6 +591,10 @@
      * When the latest patch of the change is selected (and there is no base
      * patch) then the patch range need not appear in the URL. Return a patch
      * range object with undefined values when a range is not needed.
+     *
+     * @param {!Object} patchRange
+     * @param {!Object} revisions
+     * @return {!Object}
      */
     _getChangeUrlRange(patchRange, revisions) {
       let patchNum = undefined;
@@ -702,7 +708,7 @@
      *
      * Use side-by-side if the user is not logged in.
      *
-     * @return {String}
+     * @return {string}
      */
     _getDiffViewMode() {
       if (this.changeViewState.diffMode) {
@@ -721,7 +727,7 @@
 
     _onLineSelected(e, detail) {
       this.$.cursor.moveToLineNumber(detail.number, detail.side);
-      history.replaceState(null, null, '#' + this.$.cursor.getAddress());
+      history.replaceState(null, '', '#' + this.$.cursor.getAddress());
     },
 
     _computeDownloadLink(changeNum, patchRange, path) {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
index 49e82d4..e9c5de8 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
@@ -463,7 +463,6 @@
       stub('gr-rest-api-interface', {
         getDiffComments() { return Promise.resolve({}); },
       });
-      sandbox.stub(element, '_upgradeUrl');
       const saveReviewedStub = sandbox.stub(element, '_saveReviewedState',
           () => Promise.resolve());
       sandbox.stub(element.$.diff, 'reload');
@@ -509,7 +508,6 @@
       stub('gr-rest-api-interface', {
         getDiffComments() { return Promise.resolve({}); },
       });
-      sandbox.stub(element, '_upgradeUrl');
       sandbox.stub(element.$.diff, 'reload');
       sandbox.stub(element, '_loadHash');
 
@@ -732,32 +730,6 @@
       });
     });
 
-    suite('_upgradeUrl calls', () => {
-      let upgradeStub;
-      const mockChange = {project: 'test'};
-
-      setup(() => {
-        upgradeStub = sandbox.stub(window.Gerrit.Nav, 'upgradeUrl');
-      });
-
-      test('app.params.project undefined', () => {
-        element._upgradeUrl(mockChange, {});
-        assert.isTrue(upgradeStub.called);
-        assert.deepEqual(upgradeStub.lastCall.args[0], mockChange);
-      });
-
-      test('app.params.project differs from change.project', () => {
-        element._upgradeUrl(mockChange, {project: 'demo'});
-        assert.isTrue(upgradeStub.called);
-        assert.deepEqual(upgradeStub.lastCall.args[0], mockChange);
-      });
-
-      test('app.params.project === change.project', () => {
-        element._upgradeUrl(mockChange, {project: 'test'});
-        assert.isFalse(upgradeStub.called);
-      });
-    });
-
     test('_computeEditLoaded', () => {
       const callCompute = range => element._computeEditLoaded({base: range});
       assert.isFalse(callCompute({}));
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
index 728a766..0f30171 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
@@ -106,7 +106,9 @@
         type: String,
         value: '',
       },
+      /** @type {?Object} */
       _baseImage: Object,
+      /** @type {?Object} */
       _revisionImage: Object,
 
       /**
@@ -114,6 +116,8 @@
        * been bypassed. If the value is null, then the safety has not been
        * bypassed. If the value is a number, then that number represents the
        * context preference to use when rendering the bypassed diff.
+       *
+       * @type (number|null)
        */
       _safetyBypass: {
         type: Number,
@@ -147,6 +151,7 @@
       }
     },
 
+    /** @return {!Promise} */
     reload() {
       this.$.diffBuilder.cancel();
       this._safetyBypass = null;
@@ -168,6 +173,7 @@
       });
     },
 
+    /** @return {!Array<!HTMLElement>} */
     getCursorStops() {
       if (this.hidden && this.noAutoRender) {
         return [];
@@ -176,6 +182,7 @@
       return Polymer.dom(this.root).querySelectorAll('.diff-row');
     },
 
+    /** @return {boolean} */
     isRangeSelected() {
       return this.$.highlights.isRangeSelected();
     },
@@ -184,15 +191,18 @@
       this.toggleClass('no-left');
     },
 
+    /** @return {boolean}} */
     _canRender() {
-      return this.changeNum && this.patchRange && this.path &&
+      return !!this.changeNum && !!this.patchRange && !!this.path &&
           !this.noAutoRender;
     },
 
+    /** @return {!Array<!HTMLElement>} */
     _getCommentThreads() {
       return Polymer.dom(this.root).querySelectorAll('gr-diff-comment-thread');
     },
 
+    /** @return {string} */
     _computeContainerClass(loggedIn, viewMode, displayLine) {
       const classes = ['diffContainer'];
       switch (viewMode) {
@@ -263,7 +273,7 @@
       const side = e.detail.side;
       const lineNum = range.endLine;
       const lineEl = this.$.diffBuilder.getLineElByNumber(lineNum, side);
-      this._isValidElForComment(el).then(valid => {
+      this._isValidElForComment(lineEl).then(valid => {
         if (!valid) { return; }
 
         this._createComment(lineEl, lineNum, side, range);
@@ -288,6 +298,12 @@
       });
     },
 
+    /**
+     * @param {!Object} lineEl
+     * @param {number=} opt_lineNum
+     * @param {string=} opt_side
+     * @param {!Object=} opt_range
+     */
     _createComment(lineEl, opt_lineNum, opt_side, opt_range) {
       const contentText = this.$.diffBuilder.getContentByLineEl(lineEl);
       const contentEl = contentText.parentElement;
@@ -309,14 +325,21 @@
       return contentEl.querySelector('gr-diff-comment-thread-group');
     },
 
+    /**
+     * @param {!Object} contentEl
+     * @param {number} patchNum
+     * @param {string} commentSide
+     * @param {boolean} isOnParent
+     * @param {!Object=} opt_range
+     */
     _getOrCreateThreadAtLineRange(contentEl, patchNum, commentSide,
-        isOnParent, range) {
-      const rangeToCheck = range ?
+        isOnParent, opt_range) {
+      const rangeToCheck = opt_range ?
           'range-' +
-          range.startLine + '-' +
-          range.startChar + '-' +
-          range.endLine + '-' +
-          range.endChar + '-' +
+          opt_range.startLine + '-' +
+          opt_range.startChar + '-' +
+          opt_range.endLine + '-' +
+          opt_range.endChar + '-' +
           commentSide : 'line-' + commentSide;
 
       // Check if thread group exists.
@@ -338,6 +361,7 @@
       return threadEl;
     },
 
+    /** @return {number} */
     _getPatchNumByLineAndContent(lineEl, contentEl) {
       let patchNum = this.patchRange.patchNum;
       if ((lineEl.classList.contains(DiffSide.LEFT) ||
@@ -348,6 +372,7 @@
       return patchNum;
     },
 
+    /** @return {boolean} */
     _getIsParentCommentByLineAndContent(lineEl, contentEl) {
       let isOnParent = false;
       if ((lineEl.classList.contains(DiffSide.LEFT) ||
@@ -358,6 +383,7 @@
       return isOnParent;
     },
 
+    /** @return {string} */
     _getCommentSideByLineAndContent(lineEl, contentEl) {
       let side = 'right';
       if (lineEl.classList.contains(DiffSide.LEFT) ||
@@ -374,7 +400,7 @@
 
     _handleCommentDiscard(e) {
       const comment = e.detail.comment;
-      this._removeComment(comment, e.detail.patchNum);
+      this._removeComment(comment);
     },
 
     _removeComment(comment) {
@@ -389,6 +415,12 @@
       this.set(['comments', side, idx], comment);
     },
 
+    /**
+     * Closure annotation for Polymer.prototype.push is off. Submitted PR:
+     * https://github.com/Polymer/polymer/pull/4776
+     * but for not supressing annotations.
+     *
+     * @suppress {checkTypes} */
     _handleCommentUpdate(e) {
       const comment = e.detail.comment;
       const side = e.detail.comment.__commentSide;
@@ -413,6 +445,7 @@
       }
     },
 
+    /** @return {number} */
     _findCommentIndex(comment, side) {
       if (!comment.id || !this.comments[side]) {
         return -1;
@@ -422,6 +455,7 @@
       });
     },
 
+    /** @return {number} */
     _findDraftIndex(comment, side) {
       if (!comment.__draftID || !this.comments[side]) {
         return -1;
@@ -517,6 +551,7 @@
       this.fire('page-error', {response});
     },
 
+    /** @return {!Promise<!Object>} */
     _getDiff() {
       return this.$.restAPI.getDiff(
           this.changeNum,
@@ -532,10 +567,12 @@
           });
     },
 
+    /** @return {!Promise} */
     _getLoggedIn() {
       return this.$.restAPI.getLoggedIn();
     },
 
+    /** @return {boolean} */
     _computeIsImageDiff() {
       if (!this._diff) { return false; }
 
@@ -547,6 +584,7 @@
       return this._diff.binary && (isA || isB);
     },
 
+    /** @return {!Promise} */
     _loadDiffAssets() {
       if (this.isImageDiff) {
         return this._getImages().then(images => {
@@ -560,6 +598,7 @@
       }
     },
 
+    /** @return {!Promise} */
     _getImages() {
       return this.$.restAPI.getImagesForDiff(this.changeNum, this._diff,
           this.patchRange);
@@ -572,6 +611,7 @@
       }
     },
 
+    /** @return {!Array} */
     _computeDiffHeaderItems(diffInfoRecord) {
       const diffInfo = diffInfoRecord.base;
       if (!diffInfo || !diffInfo.diff_header || diffInfo.binary) { return []; }
@@ -583,6 +623,7 @@
       });
     },
 
+    /** @return {boolean} */
     _computeDiffHeaderHidden(items) {
       return items.length === 0;
     },
@@ -591,7 +632,7 @@
      * The number of lines in the diff. For delta chunks that are different
      * sizes on the left and the right, the longer side is used.
      * @param {!Object} diff
-     * @return {Number}
+     * @return {number}
      */
     _diffLength(diff) {
       return diff.content.reduce((sum, sec) => {
@@ -616,6 +657,7 @@
       this._renderDiffTable();
     },
 
+    /** @return {string} */
     _computeWarningClass(showWarning) {
       return showWarning ? 'warn' : '';
     },
diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.js b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.js
index 9a4000f..d6052de 100644
--- a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.js
+++ b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.js
@@ -23,6 +23,7 @@
     properties: {
       availablePatches: Array,
       changeNum: String,
+      /** @type {{ meta_a: !Array, meta_b: !Array}} */
       filesWeblinks: Object,
       path: String,
       patchRange: {
diff --git a/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer.js b/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer.js
index aa55414..3de18be 100644
--- a/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer.js
+++ b/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer.js
@@ -43,9 +43,9 @@
 
     /**
      * Layer method to add annotations to a line.
-     * @param {HTMLElement} el The DIV.contentText element to apply the
+     * @param {!HTMLElement} el The DIV.contentText element to apply the
      *     annotation to.
-     * @param {GrDiffLine} line The line object.
+     * @param {!Object} line The line object. (GrDiffLine)
      */
     annotate(el, line) {
       let ranges = [];
@@ -79,9 +79,9 @@
 
     /**
      * Notify Layer listeners of changes to annotations.
-     * @param {Number} start The line where the update starts.
-     * @param {Number} end The line where the update ends.
-     * @param {String} side The side of the update. ('left' or 'right')
+     * @param {number} start The line where the update starts.
+     * @param {number} end The line where the update ends.
+     * @param {string} side The side of the update. ('left' or 'right')
      */
     _notifyUpdateRange(start, end, side) {
       for (const listener of this._listeners) {
@@ -133,8 +133,9 @@
      * Take a list of comments and return a sparse list mapping line numbers to
      * partial ranges. Uses an end-character-index of -1 to indicate the end of
      * the line.
-     * @param {Array<Object>} commentList The list of comments.
-     * @return {Object} The sparse list.
+     * @param {?} commentList The list of comments.
+     *    Getting this param to match closure requirements caused problems.
+     * @return {!Object} The sparse list.
      */
     _computeCommentMap(commentList) {
       const result = {};
diff --git a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.js b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.js
index 2c1167c..91e6287 100644
--- a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.js
+++ b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.js
@@ -114,6 +114,7 @@
         type: Array,
         value() { return []; },
       },
+      /** @type {?number} */
       _processHandle: Number,
       _hljs: Object,
     },
@@ -126,7 +127,7 @@
      * Annotation layer method to add syntax annotations to the given element
      * for the given line.
      * @param {!HTMLElement} el
-     * @param {!GrDiffLine} line
+     * @param {!Object} line (GrDiffLine)
      */
     annotate(el, line) {
       if (!this.enabled) { return; }
diff --git a/polygerrit-ui/app/elements/gr-app.html b/polygerrit-ui/app/elements/gr-app.html
index 07ecb8d..defbe8a 100644
--- a/polygerrit-ui/app/elements/gr-app.html
+++ b/polygerrit-ui/app/elements/gr-app.html
@@ -206,7 +206,7 @@
     <gr-reporting id="reporting"></gr-reporting>
     <gr-router id="router"></gr-router>
     <gr-plugin-host id="plugins"
-        config="[[_serverConfig.plugin]]">
+        config="[[_serverConfig]]">
     </gr-plugin-host>
     <gr-external-style id="externalStyle" name="app-theme"></gr-external-style>
   </template>
diff --git a/polygerrit-ui/app/elements/gr-app.js b/polygerrit-ui/app/elements/gr-app.js
index ba168e4..84aa438 100644
--- a/polygerrit-ui/app/elements/gr-app.js
+++ b/polygerrit-ui/app/elements/gr-app.js
@@ -31,6 +31,9 @@
      */
 
     properties: {
+      /**
+       * @type {{ query: string, view: string }}
+       */
       params: Object,
       keyEventTarget: {
         type: Object,
@@ -41,6 +44,9 @@
         type: Object,
         observer: '_accountChanged',
       },
+      /**
+       * @type {{ plugin: Object }}
+       */
       _serverConfig: Object,
       _version: String,
       _showChangeListView: Boolean,
@@ -50,7 +56,9 @@
       _showSettingsView: Boolean,
       _showAdminView: Boolean,
       _showCLAView: Boolean,
+      /** @type {?} */
       _viewState: Object,
+      /** @type {?} */
       _lastError: Object,
       _lastSearchPage: String,
       _path: String,
diff --git a/polygerrit-ui/app/elements/gr-app_test.html b/polygerrit-ui/app/elements/gr-app_test.html
index 905c5c4..3712ffa 100644
--- a/polygerrit-ui/app/elements/gr-app_test.html
+++ b/polygerrit-ui/app/elements/gr-app_test.html
@@ -50,7 +50,7 @@
         getConfig() {
           return Promise.resolve({
             gerrit: {web_uis: ['GWT', 'POLYGERRIT']},
-            plugin: {js_resource_paths: []},
+            plugin: {},
           });
         },
         getPreferences() { return Promise.resolve({my: []}); },
@@ -100,11 +100,9 @@
       });
     });
 
-    test('passes config to gr-plugin-host', done => {
-      element.$.restAPI.getConfig.lastCall.returnValue.then(config => {
-        const pluginConfig = config.plugin;
-        assert.deepEqual(element.$.plugins.config, pluginConfig);
-        done();
+    test('passes config to gr-plugin-host', () => {
+      return element.$.restAPI.getConfig.lastCall.returnValue.then(config => {
+        assert.deepEqual(element.$.plugins.config, config);
       });
     });
   });
diff --git a/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host.js b/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host.js
index 03b6d56..3a0c898 100644
--- a/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host.js
+++ b/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host.js
@@ -29,8 +29,9 @@
     ],
 
     _configChanged(config) {
-      const jsPlugins = config.js_resource_paths || [];
-      const htmlPlugins = config.html_resource_paths || [];
+      const plugins = config.plugin;
+      const jsPlugins = plugins.js_resource_paths || [];
+      const htmlPlugins = plugins.html_resource_paths || [];
       const defaultTheme = config.default_theme;
       if (defaultTheme) {
         // Make theme first to be first to load.
@@ -41,6 +42,12 @@
       this._importHtmlPlugins(htmlPlugins);
     },
 
+    /**
+     * @suppress {checkTypes}
+     * States that it expects no more than 3 parameters, but that's not true.
+     * @todo (beckysiegel) check Polymer annotations and submit change.
+     */
+
     _importHtmlPlugins(plugins) {
       for (const url of plugins) {
         this.importHref(
diff --git a/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host_test.html b/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host_test.html
index 2cacede..27adbe1 100644
--- a/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host_test.html
@@ -48,15 +48,17 @@
     test('counts plugins', () => {
       sandbox.stub(Gerrit, '_setPluginsCount');
       element.config = {
-        html_resource_paths: ['foo/bar', 'baz'],
-        js_resource_paths: ['42'],
+        plugin: {
+          html_resource_paths: ['foo/bar', 'baz'],
+          js_resource_paths: ['42'],
+        },
       };
       assert.isTrue(Gerrit._setPluginsCount.calledWith(3));
     });
 
     test('imports relative html plugins from config', () => {
       element.config = {
-        html_resource_paths: ['foo/bar', 'baz'],
+        plugin: {html_resource_paths: ['foo/bar', 'baz']},
       };
       assert.isTrue(element.importHref.calledWith(
           '/foo/bar', Gerrit._pluginInstalled, Gerrit._pluginInstalled, true));
@@ -67,8 +69,7 @@
     test('imports relative html plugins from config with a base url', () => {
       sandbox.stub(element, 'getBaseUrl').returns('/the-base');
       element.config = {
-        html_resource_paths: ['foo/bar', 'baz'],
-      };
+        plugin: {html_resource_paths: ['foo/bar', 'baz']}};
       assert.isTrue(element.importHref.calledWith(
           '/the-base/foo/bar', Gerrit._pluginInstalled, Gerrit._pluginInstalled,
           true));
@@ -79,10 +80,12 @@
 
     test('imports absolute html plugins from config', () => {
       element.config = {
-        html_resource_paths: [
-          'http://example.com/foo/bar',
-          'https://example.com/baz',
-        ],
+        plugin: {
+          html_resource_paths: [
+            'http://example.com/foo/bar',
+            'https://example.com/baz',
+          ],
+        },
       };
       assert.isTrue(element.importHref.calledWith(
           'http://example.com/foo/bar', Gerrit._pluginInstalled,
@@ -93,17 +96,13 @@
     });
 
     test('adds js plugins from config to the body', () => {
-      element.config = {
-        js_resource_paths: ['foo/bar', 'baz'],
-      };
+      element.config = {plugin: {js_resource_paths: ['foo/bar', 'baz']}};
       assert.isTrue(document.body.appendChild.calledTwice);
     });
 
     test('imports relative js plugins from config', () => {
       sandbox.stub(element, '_createScriptTag');
-      element.config = {
-        js_resource_paths: ['foo/bar', 'baz'],
-      };
+      element.config = {plugin: {js_resource_paths: ['foo/bar', 'baz']}};
       assert.isTrue(element._createScriptTag.calledWith('/foo/bar'));
       assert.isTrue(element._createScriptTag.calledWith('/baz'));
     });
@@ -111,9 +110,7 @@
     test('imports relative html plugins from config with a base url', () => {
       sandbox.stub(element, '_createScriptTag');
       sandbox.stub(element, 'getBaseUrl').returns('/the-base');
-      element.config = {
-        js_resource_paths: ['foo/bar', 'baz'],
-      };
+      element.config = {plugin: {js_resource_paths: ['foo/bar', 'baz']}};
       assert.isTrue(element._createScriptTag.calledWith('/the-base/foo/bar'));
       assert.isTrue(element._createScriptTag.calledWith('/the-base/baz'));
     });
@@ -121,10 +118,12 @@
     test('imports absolute html plugins from config', () => {
       sandbox.stub(element, '_createScriptTag');
       element.config = {
-        js_resource_paths: [
-          'http://example.com/foo/bar',
-          'https://example.com/baz',
-        ],
+        plugin: {
+          js_resource_paths: [
+            'http://example.com/foo/bar',
+            'https://example.com/baz',
+          ],
+        },
       };
       assert.isTrue(element._createScriptTag.calledWith(
           'http://example.com/foo/bar'));
@@ -135,7 +134,9 @@
     test('default theme is loaded with html plugins', () => {
       element.config = {
         default_theme: '/oof',
-        html_resource_paths: ['some'],
+        plugin: {
+          html_resource_paths: ['some'],
+        },
       };
       assert.isTrue(element.importHref.calledWith(
           '/oof', Gerrit._pluginInstalled, Gerrit._pluginInstalled, true));
diff --git a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.js b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.js
index 010b136..5d7f8a6 100644
--- a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.js
+++ b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.js
@@ -51,6 +51,7 @@
         type: Boolean,
         value: false,
       },
+      /** @type {?} */
       _account: Object,
       _serverConfig: Object,
     },
diff --git a/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.html b/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.html
index e4324de..dfaa6eb 100644
--- a/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.html
+++ b/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.html
@@ -24,7 +24,7 @@
   <template>
     <style include="shared-styles">
       #agreements .nameColumn {
-        min-width: 11em;
+        min-width: 15em;
         width: auto;
       }
       #agreements .descriptionColumn {
diff --git a/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor.js b/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor.js
index 8490c1e..c8d3319 100644
--- a/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor.js
+++ b/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor.js
@@ -29,6 +29,7 @@
         type: Array,
         value() { return []; },
       },
+      /** @type {?string} */
       _newPreferred: {
         type: String,
         value: null,
@@ -61,7 +62,7 @@
     },
 
     _handleDeleteButton(e) {
-      const index = parseInt(e.target.getAttribute('data-index'));
+      const index = parseInt(e.target.getAttribute('data-index'), 10);
       const email = this._emails[index];
       this.push('_emailsToRemove', email);
       this.splice('_emails', index, 1);
diff --git a/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.js b/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.js
index d9c19e2..3dc92d1 100644
--- a/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.js
+++ b/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.js
@@ -50,7 +50,7 @@
     },
 
     _generatedPasswordOverlayClosed() {
-      this._generatedPassword = null;
+      this._generatedPassword = '';
     },
   });
 })();
diff --git a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.js b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.js
index 319143a..71d80eb 100644
--- a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.js
+++ b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.js
@@ -30,6 +30,7 @@
      */
 
     properties: {
+      /** @type {?} */
       _account: Object,
       _saving: Boolean,
     },
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.js b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.js
index c20cf82..5c56826 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.js
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.js
@@ -56,8 +56,10 @@
       },
       _accountNameMutable: Boolean,
       _accountInfoChanged: Boolean,
+      /** @type {?} */
       _diffPrefs: Object,
       _changeTableColumnsNotDisplayed: Array,
+      /** @type {?} */
       _localPrefs: {
         type: Object,
         value() { return {}; },
@@ -107,8 +109,11 @@
         type: String,
         value: null,
       },
+      /** @type {?} */
       _serverConfig: Object,
+      /** @type {?string} */
       _docsBaseUrl: String,
+      _emailsChanged: Boolean,
 
       /**
        * For testing purposes.
diff --git a/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor.js b/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor.js
index d9f02d5..d5067aa 100644
--- a/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor.js
+++ b/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor.js
@@ -24,6 +24,7 @@
         notify: true,
       },
       _keys: Array,
+      /** @type {?} */
       _keyToView: Object,
       _newKey: {
         type: String,
diff --git a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.html b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.html
index a93198c..21f4c3e 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.html
+++ b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.html
@@ -14,9 +14,12 @@
 limitations under the License.
 -->
 
+<link rel="import" href="../../../behaviors/gr-anonymous-name-behavior/gr-anonymous-name-behavior.html">
+<link rel="import" href="../../../behaviors/gr-tooltip-behavior/gr-tooltip-behavior.html">
 <link rel="import" href="../../../bower_components/polymer/polymer.html">
-<link rel="import" href="../gr-avatar/gr-avatar.html">
 <link rel="import" href="../../../styles/shared-styles.html">
+<link rel="import" href="../gr-avatar/gr-avatar.html">
+<link rel="import" href="../gr-rest-api-interface/gr-rest-api-interface.html">
 
 <dom-module id="gr-account-label">
   <template>
@@ -33,15 +36,20 @@
         margin-right: .15em;
         vertical-align: -.25em;
       }
+      .text {
+        @apply(--gr-account-label-text-style);
+      }
       .text:hover {
         @apply(--gr-account-label-text-hover-style);
       }
     </style>
-    <span title$="[[_computeAccountTitle(account)]]">
-      <gr-avatar account="[[account]]"
-          image-size="[[avatarImageSize]]"></gr-avatar>
+    <span>
+      <template is="dom-if" if="[[!hideAvatar]]">
+        <gr-avatar account="[[account]]"
+            image-size="[[avatarImageSize]]"></gr-avatar>
+      </template>
       <span class="text">
-        <span>[[account.name]]</span>
+        <span>[[_computeName(account, _serverConfig)]]</span>
         <span hidden$="[[!_computeShowEmail(showEmail, account)]]">
           [[_computeEmailStr(account)]]
         </span>
@@ -50,6 +58,7 @@
         </template>
       </span>
     </span>
+    <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
   </template>
   <script src="../../../scripts/util.js"></script>
   <script src="gr-account-label.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.js b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.js
index 9ebef82..2dee2f6 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.js
+++ b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label.js
@@ -18,6 +18,9 @@
     is: 'gr-account-label',
 
     properties: {
+      /**
+       * @type {{ name: string, status: string }}
+       */
       account: Object,
       avatarImageSize: {
         type: Number,
@@ -27,13 +30,45 @@
         type: Boolean,
         value: false,
       },
+      title: {
+        type: String,
+        reflectToAttribute: true,
+        computed: '_computeAccountTitle(account)',
+      },
+      hasTooltip: {
+        type: Boolean,
+        reflectToAttribute: true,
+        computed: '_computeHasTooltip(account)',
+      },
+      hideAvatar: {
+        type: Boolean,
+        value: false,
+      },
+      _serverConfig: {
+        type: Object,
+        value: null,
+      },
+    },
+
+    behaviors: [
+      Gerrit.AnonymousNameBehavior,
+      Gerrit.TooltipBehavior,
+    ],
+
+    ready() {
+      this.$.restAPI.getConfig()
+          .then(config => { this._serverConfig = config; });
+    },
+
+    _computeName(account, config) {
+      return this.getUserName(config, account, false);
     },
 
     _computeAccountTitle(account) {
-      if (!account || (!account.name && !account.email)) { return; }
+      if (!account) { return; }
       let result = '';
-      if (account.name) {
-        result += account.name;
+      if (this._computeName(account, this._serverConfig)) {
+        result += this._computeName(account, this._serverConfig);
       }
       if (account.email) {
         result += ' <' + account.email + '>';
@@ -54,5 +89,10 @@
       }
       return account.email;
     },
+
+    _computeHasTooltip(account) {
+      // If an account has loaded to fire this method, then set to true.
+      return !!account;
+    },
   });
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.html b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.html
index 3b1e1de..731c9b7 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.html
@@ -43,6 +43,11 @@
         getLoggedIn() { return Promise.resolve(false); },
       });
       element = fixture('basic');
+      element._config = {
+        user: {
+          anonymous_coward_name: 'Anonymous Coward',
+        },
+      };
     });
 
     test('null guard', () => {
@@ -67,6 +72,12 @@
           {name: 'Andrew Bonventre'}),
           'Andrew Bonventre');
 
+      assert.equal(element._computeAccountTitle(
+          {
+            email: 'andybons+gerrit@gmail.com',
+          }),
+          'Anonymous <andybons+gerrit@gmail.com>');
+
       assert.equal(element._computeShowEmail(true,
           {
             name: 'Andrew Bonventre',
@@ -89,5 +100,40 @@
           element._computeEmailStr({name: 'test', email: 'test'}), '(test)');
       assert.equal(element._computeEmailStr({email: 'test'}, ''), 'test');
     });
+
+    suite('_computeName', () => {
+      test('not showing anonymous', () => {
+        const account = {name: 'Wyatt'};
+        assert.deepEqual(element._computeName(account, null), 'Wyatt');
+      });
+
+      test('showing anonymous but no config', () => {
+        const account = {};
+        assert.deepEqual(element._computeName(account, null),
+            'Anonymous');
+      });
+
+      test('test for Anonymous Coward user and replace with Anonymous', () => {
+        const config = {
+          user: {
+            anonymous_coward_name: 'Anonymous Coward',
+          },
+        };
+        const account = {};
+        assert.deepEqual(element._computeName(account, config),
+            'Anonymous');
+      });
+
+      test('test for anonymous_coward_name', () => {
+        const config = {
+          user: {
+            anonymous_coward_name: 'TestAnon',
+          },
+        };
+        const account = {};
+        assert.deepEqual(element._computeName(account, config),
+            'TestAnon');
+      });
+    });
   });
 </script>
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.js b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.js
index 68800a1..12fb074 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.js
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.js
@@ -102,13 +102,13 @@
       }
     },
 
-    cursorDown(e) {
+    cursorDown() {
       if (!this.hidden) {
         this.$.cursor.next();
       }
     },
 
-    cursorUp(e) {
+    cursorUp() {
       if (!this.hidden) {
         this.$.cursor.previous();
       }
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js
index 65ab22a..c3031e2 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js
@@ -48,7 +48,7 @@
        * suggestion entry. The "value" property will be emitted if that
        * suggestion is selected.
        *
-       * @type {function(String): Promise<Array<Object>>}
+       * @type {function(string): Promise<?>}
        */
       query: {
         type: Function,
@@ -95,7 +95,7 @@
         value: false,
       },
 
-      value: Object,
+      value: String,
 
       /**
        * Multi mode appends autocompleted entries to the value.
@@ -115,6 +115,7 @@
         value: false,
       },
 
+      /** @type {?} */
       _suggestions: {
         type: Array,
         value() { return []; },
@@ -170,7 +171,7 @@
 
     /**
      * Set the text of the input without triggering the suggestion dropdown.
-     * @param {String} text The new text for the input.
+     * @param {string} text The new text for the input.
      */
     setText(text) {
       this._disableSuggestions = true;
@@ -270,6 +271,9 @@
       }
     },
 
+    /**
+     * @param {boolean=} opt_tabComplete
+     */
     _handleInputCommit(opt_tabComplete) {
       this._selected = this.$.suggestions.getCursorTarget();
       this._commit(opt_tabComplete);
@@ -308,11 +312,11 @@
     /**
      * Commits the suggestion, optionally firing the commit event.
      *
-     * @param {Boolean} silent Allows for silent committing of an autocomplete
-     *     suggestion in order to handle cases like tab-to-complete without
-     *     firing the commit event.
+     * @param {boolean=} opt_silent Allows for silent committing of an
+     *     autocomplete suggestion in order to handle cases like tab-to-complete
+     *     without firing the commit event.
      */
-    _commit(silent) {
+    _commit(opt_silent) {
       // Allow values that are not in suggestion list iff suggestions are empty.
       if (this._suggestions.length > 0) {
         this._updateValue(this._selected, this._suggestions);
@@ -334,7 +338,7 @@
       }
 
       this._suggestions = [];
-      if (!silent) {
+      if (!opt_silent) {
         this.fire('commit', {value});
       }
     },
diff --git a/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star.js b/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star.js
index f3c3767..5009a6f 100644
--- a/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star.js
+++ b/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star.js
@@ -18,6 +18,7 @@
     is: 'gr-change-star',
 
     properties: {
+      /** @type {?} */
       change: {
         type: Object,
         notify: true,
diff --git a/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js b/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js
index c259a3c..c0ea5a8 100644
--- a/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js
+++ b/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js
@@ -30,6 +30,9 @@
         },
         observer: '_updateIndex',
       },
+      /**
+       * @type (?Object)
+       */
       target: {
         type: Object,
         notify: true,
@@ -37,6 +40,7 @@
       },
       /**
        * The height of content intended to be included with the target.
+       * @type (?number)
        */
       _targetHeight: Number,
 
@@ -61,6 +65,8 @@
        * The scroll behavior for the cursor. Values are 'never' and
        * 'keep-visible'. 'keep-visible' will only scroll if the cursor is beyond
        * the viewport.
+       * TODO (beckysiegel) figure out why it can be undefined
+       * @type (string|undefined)
        */
       scrollBehavior: {
         type: String,
@@ -90,8 +96,8 @@
 
     /**
      * Set the cursor to an arbitrary element.
-     * @param {DOMElement} element
-     * @param {boolean} opt_noScroll prevent any potential scrolling in response
+     * @param {!HTMLElement} element
+     * @param {boolean=} opt_noScroll prevent any potential scrolling in response
      *   setting the cursor.
      */
     setCursor(element, opt_noScroll) {
@@ -137,11 +143,11 @@
     /**
      * Move the cursor forward or backward by delta. Noop if moving past either
      * end of the stop list.
-     * @param {Number} delta either -1 or 1.
-     * @param {Function} opt_condition Optional stop condition. If a condition
+     * @param {number} delta either -1 or 1.
+     * @param {!Function=} opt_condition Optional stop condition. If a condition
      *    is passed the cursor will continue to move in the specified direction
      *    until the condition is met.
-     * @param {Function} opt_getTargetHeight Optional function to calculate the
+     * @param {!Function=} opt_getTargetHeight Optional function to calculate the
      *    height of the target's 'section'. The height of the target itself is
      *    sometimes different, used by the diff cursor.
      * @private
@@ -191,9 +197,9 @@
 
     /**
      * Get the next stop index indicated by the delta direction.
-     * @param {Number} delta either -1 or 1.
-     * @param {Function} opt_condition Optional stop condition.
-     * @return {Number} the new index.
+     * @param {number} delta either -1 or 1.
+     * @param {!Function=} opt_condition Optional stop condition.
+     * @return {number} the new index.
      * @private
      */
     _getNextindex(delta, opt_condition) {
@@ -239,7 +245,7 @@
 
     /**
      * Calculate where the element is relative to the window.
-     * @param {object} target Target to scroll to.
+     * @param {!Object} target Target to scroll to.
      * @return {number} Distance to top of the target.
      */
     _getTop(target) {
diff --git a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.js b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.js
index 7c114c6..d5eaa0f 100644
--- a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.js
+++ b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands.js
@@ -23,6 +23,7 @@
         value: false,
         observer: '_loggedInChanged',
       },
+      schemes: Array,
       selectedScheme: {
         type: String,
         notify: true,
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.js b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.js
index 7ccc3ab..a78e94c 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.js
+++ b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.js
@@ -96,6 +96,11 @@
       this._inputText = this.value;
     },
 
+    /**
+     * @suppress {checkTypes}
+     * Closure doesn't think 'e' is an Event.
+     * TODO(beckysiegel) figure out why.
+     */
     _handleEnter(e) {
       e = this.getKeyboardEvent(e);
       const target = Polymer.dom(e).rootTarget;
@@ -105,6 +110,11 @@
       }
     },
 
+    /**
+     * @suppress {checkTypes}
+     * Closure doesn't think 'e' is an Event.
+     * TODO(beckysiegel) figure out why.
+     */
     _handleEsc(e) {
       e = this.getKeyboardEvent(e);
       const target = Polymer.dom(e).rootTarget;
diff --git a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.js b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.js
index d2436e7..d2392e1 100644
--- a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.js
+++ b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text.js
@@ -171,7 +171,9 @@
             if (inParagraph) {
               // Add the finished paragraph block to the result.
               inParagraph = false;
-              out.push(block);
+              if (block !== null) {
+                out.push(block);
+              }
             }
             inList = true;
             block = {type: 'list', items: []};
@@ -193,7 +195,7 @@
         }
         block.items.push(line);
       }
-      if (block != null) {
+      if (block !== null) {
         out.push(block);
       }
     },
@@ -223,12 +225,16 @@
           p.startsWith('- ') || p.startsWith('* ');
     },
 
-    _makeLinkedText(content, isPre) {
+    /**
+     * @param {string} content
+     * @param {boolean=} opt_isPre
+     */
+    _makeLinkedText(content, opt_isPre) {
       const text = document.createElement('gr-linked-text');
       text.config = this.config;
       text.content = content;
       text.pre = true;
-      if (isPre) {
+      if (opt_isPre) {
         text.classList.add('pre');
       }
       return text;
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
index a631c2f..d1b9417 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
@@ -67,12 +67,16 @@
     const base = Gerrit.BaseUrlBehavior.getBaseUrl();
 
     this._url = new URL(opt_url);
-    if (!this._url.pathname.startsWith(base + '/plugins')) {
+    const pathname = this._url.pathname.replace(base, '');
+    // Site theme is server from predefined path.
+    if (pathname === '/static/gerrit-theme.html') {
+      this._name = 'gerrit-theme';
+    } else if (!pathname.startsWith('/plugins')) {
       console.warn('Plugin not being loaded from /plugins base path:',
           this._url.href, '— Unable to determine name.');
       return;
     }
-    this._name = this._url.pathname.replace(base, '').split('/')[2];
+    this._name = pathname.split('/')[2];
   }
 
   Plugin._sharedAPIElement = document.createElement('gr-js-api-interface');
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
index e983d1d..fe3646b 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
@@ -94,7 +94,7 @@
      * Doesn't do error checking. Supports cancel condition. Performs auth.
      * Validates auth expiry errors.
      * @param {string} url
-     * @param {?function(?Response, string)=} opt_errFn
+     * @param {?function(?Response, string=)=} opt_errFn
      *    passed as null sometimes.
      * @param {?function()=} opt_cancelCondition
      *    passed as null sometimes.
@@ -181,6 +181,10 @@
       return this.getBaseUrl() + url + '?' + params.join('&');
     },
 
+    /**
+     * @param {!Object} response
+     * @return {?}
+     */
     getResponseObject(response) {
       return response.text().then(text => {
         let result;
@@ -307,9 +311,26 @@
           revision, opt_errFn, opt_ctx);
     },
 
-    getIsGroupOwner(groupId) {
-      const encodeId = encodeURIComponent(groupId);
-      return this._fetchSharedCacheURL('/groups/?owned&q=' + encodeId);
+    /**
+     * @param {!string} groupName
+     * @returns {!Promise<boolean>}
+     */
+    getIsGroupOwner(groupName) {
+      const encodeName = encodeURIComponent(groupName);
+      return this._fetchSharedCacheURL('/groups/?owned&q=' + encodeName)
+          .then(configs => configs.hasOwnProperty(encodeName));
+    },
+
+    getGroupMembers(groupName) {
+      const encodeName = encodeURIComponent(groupName);
+      return this.send('GET', `/groups/${encodeName}/members/`)
+          .then(response => this.getResponseObject(response));
+    },
+
+    getIncludedGroup(groupName) {
+      const encodeName = encodeURIComponent(groupName);
+      return this.send('GET', `/groups/${encodeName}/groups/`)
+          .then(response => this.getResponseObject(response));
     },
 
     saveGroupName(groupId, name) {
@@ -337,6 +358,35 @@
       return this._fetchSharedCacheURL('/groups/' + group + '/log.audit');
     },
 
+    saveGroupMembers(groupName, groupMembers) {
+      const encodeName = encodeURIComponent(groupName);
+      const encodeMember = encodeURIComponent(groupMembers);
+      return this.send('PUT', `/groups/${encodeName}/members/${encodeMember}`)
+          .then(response => this.getResponseObject(response));
+    },
+
+    saveIncludedGroup(groupName, includedGroup) {
+      const encodeName = encodeURIComponent(groupName);
+      const encodeIncludedGroup = encodeURIComponent(includedGroup);
+      return this.send('PUT',
+          `/groups/${encodeName}/groups/${encodeIncludedGroup}`)
+          .then(response => this.getResponseObject(response));
+    },
+
+    deleteGroupMembers(groupName, groupMembers) {
+      const encodeName = encodeURIComponent(groupName);
+      const encodeMember = encodeURIComponent(groupMembers);
+      return this.send('DELETE',
+          `/groups/${encodeName}/members/${encodeMember}`);
+    },
+
+    deleteIncludedGroup(groupName, includedGroup) {
+      const encodeName = encodeURIComponent(groupName);
+      const encodeIncludedGroup = encodeURIComponent(includedGroup);
+      return this.send('DELETE',
+          `/groups/${encodeName}/groups/${encodeIncludedGroup}`);
+    },
+
     getVersion() {
       return this._fetchSharedCacheURL('/config/server/version');
     },
@@ -699,7 +749,7 @@
      * @param {number|string} changeNum
      * @param {?number|string=} opt_patchNum passed as null sometimes.
      * @param {?=} endpoint
-     * @return {string}
+     * @return {!Promise<string>}
      */
     getChangeActionURL(changeNum, opt_patchNum, endpoint) {
       return this._changeBaseURL(changeNum, opt_patchNum)
@@ -713,10 +763,10 @@
      */
     getChangeDetail(changeNum, opt_errFn, opt_cancelCondition) {
       const options = this.listChangesOptionsToHex(
+          this.ListChangesOption.ALL_COMMITS,
           this.ListChangesOption.ALL_REVISIONS,
           this.ListChangesOption.CHANGE_ACTIONS,
           this.ListChangesOption.CURRENT_ACTIONS,
-          this.ListChangesOption.CURRENT_COMMIT,
           this.ListChangesOption.DOWNLOAD_COMMANDS,
           this.ListChangesOption.SUBMITTABLE,
           this.ListChangesOption.WEB_LINKS
@@ -845,7 +895,6 @@
      * @param {number|string} changeNum
      * @param {string} inputVal
      * @param {function(?Response, string=)=} opt_errFn
-     * @param {?=} opt_ctx
      */
     getChangeSuggestedReviewers(changeNum, inputVal, opt_errFn) {
       const params = {n: 10};
@@ -1310,11 +1359,11 @@
        * Fetches the comments for a given patchNum.
        * Helper function to make promises more legible.
        *
-       * @param {string|number} patchNum
+       * @param {string|number=} opt_patchNum
        * @return {!Object} Diff comments response.
        */
-      const fetchComments = patchNum => {
-        return this._getChangeURLAndFetch(changeNum, endpoint, patchNum);
+      const fetchComments = opt_patchNum => {
+        return this._getChangeURLAndFetch(changeNum, endpoint, opt_patchNum);
       };
 
       if (!opt_basePatchNum && !opt_patchNum && !opt_path) {
@@ -1507,7 +1556,7 @@
       // stack every time _changeBaseURL is called without a project.
       const projectPromise = opt_project ?
           Promise.resolve(opt_project) :
-          this._getFromProjectLookup(changeNum);
+          this.getFromProjectLookup(changeNum);
       return projectPromise.then(project => {
         let url = `/changes/${encodeURIComponent(project)}~${changeNum}`;
         if (opt_patchNum) {
@@ -1517,12 +1566,22 @@
       });
     },
 
+    /**
+     * @suppress {checkTypes}
+     * Resulted in error: Promise.prototype.then does not match formal
+     * parameter.
+     */
     setChangeTopic(changeNum, topic) {
       const p = {topic};
       return this.getChangeURLAndSend(changeNum, 'PUT', null, '/topic', p)
           .then(this.getResponseObject);
     },
 
+    /**
+     * @suppress {checkTypes}
+     * Resulted in error: Promise.prototype.then does not match formal
+     * parameter.
+     */
     setChangeHashtag(changeNum, hashtag) {
       return this.getChangeURLAndSend(changeNum, 'POST', null, '/hashtags',
           hashtag).then(this.getResponseObject);
@@ -1532,6 +1591,11 @@
       return this.send('DELETE', '/accounts/self/password.http');
     },
 
+    /**
+     * @suppress {checkTypes}
+     * Resulted in error: Promise.prototype.then does not match formal
+     * parameter.
+     */
     generateAccountHttpPassword() {
       return this.send('PUT', '/accounts/self/password.http', {generate: true})
           .then(this.getResponseObject);
@@ -1624,6 +1688,11 @@
           opt_body, opt_errFn);
     },
 
+    /**
+     * @suppress {checkTypes}
+     * Resulted in error: Promise.prototype.then does not match formal
+     * parameter.
+     */
     deleteComment(changeNum, patchNum, commentID, reason) {
       const endpoint = `/comments/${commentID}/delete`;
       const payload = {reason};
@@ -1663,7 +1732,7 @@
      * @param {string|number} changeNum
      * @return {!Promise<string|undefined>}
      */
-    _getFromProjectLookup(changeNum) {
+    getFromProjectLookup(changeNum) {
       const project = this._projectLookup[changeNum];
       if (project) { return Promise.resolve(project); }
       return this.getChange(changeNum).then(change => {
@@ -1675,18 +1744,21 @@
 
     /**
      * Alias for _changeBaseURL.then(send).
-     *
+     * @todo(beckysiegel) clean up comments
      * @param {string|number} changeNum
      * @param {string} method
-     * @param {string} endpoint
-     * @param {string|number=} opt_patchNum
-     * @param {!Object=} opt_payload
-     * @param {function(?Response, string)=} opt_errFn
+     * @param {?string|number} patchNum gets passed as null.
+     * @param {?string} endpoint gets passed as null.
+     * @param {?Object|number|string=} opt_payload gets passed as null, string,
+     *    Object, or number.
+     * @param {function(?Response, string=)=} opt_errFn
+     * @param {?=} opt_ctx
+     * @param {?=} opt_contentType
      * @return {!Promise<!Object>}
      */
-    getChangeURLAndSend(changeNum, method, opt_patchNum, endpoint, opt_payload,
+    getChangeURLAndSend(changeNum, method, patchNum, endpoint, opt_payload,
         opt_errFn, opt_ctx, opt_contentType) {
-      return this._changeBaseURL(changeNum, opt_patchNum).then(url => {
+      return this._changeBaseURL(changeNum, patchNum).then(url => {
         return this.send(method, url + endpoint, opt_payload, opt_errFn,
             opt_ctx, opt_contentType);
       });
@@ -1694,13 +1766,13 @@
 
    /**
     * Alias for _changeBaseURL.then(fetchJSON).
-    *
+     * @todo(beckysiegel) clean up comments
     * @param {string|number} changeNum
     * @param {string} endpoint
-    * @param {string|number=} opt_patchNum
-    * @param {function(?Response, string)=} opt_errFn
-    * @param {!function()=} opt_cancelCondition
-    * @param {!Object=} opt_params
+    * @param {?string|number=} opt_patchNum gets passed as null.
+    * @param {?function(?Response, string=)=} opt_errFn gets passed as null.
+    * @param {?function()=} opt_cancelCondition gets passed as null.
+    * @param {?Object=} opt_params gets passed as null.
     * @param {!Object=} opt_options
     * @return {!Promise<!Object>}
     */
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
index eac758f..54ec90e 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
@@ -269,7 +269,7 @@
     });
 
     test('differing patch diff comments are properly grouped', done => {
-      sandbox.stub(element, '_getFromProjectLookup')
+      sandbox.stub(element, 'getFromProjectLookup')
           .returns(Promise.resolve('test'));
       sandbox.stub(element, 'fetchJSON', url => {
         if (url === '/changes/test~42/revisions/1') {
@@ -780,11 +780,11 @@
       assert.deepEqual(element._projectLookup, {test: 'project'});
     });
 
-    suite('_getFromProjectLookup', () => {
+    suite('getFromProjectLookup', () => {
       test('getChange fails', () => {
         sandbox.stub(element, 'getChange')
             .returns(Promise.resolve());
-        return element._getFromProjectLookup().then(val => {
+        return element.getFromProjectLookup().then(val => {
           assert.strictEqual(val, undefined);
           assert.deepEqual(element._projectLookup, {});
         });
@@ -793,7 +793,7 @@
       test('getChange succeeds, no project', () => {
         sandbox.stub(element, 'getChange')
             .returns(Promise.resolve({}));
-        return element._getFromProjectLookup().then(val => {
+        return element.getFromProjectLookup().then(val => {
           assert.strictEqual(val, undefined);
           assert.deepEqual(element._projectLookup, {});
         });
@@ -802,7 +802,7 @@
       test('getChange succeeds with project', () => {
         sandbox.stub(element, 'getChange')
             .returns(Promise.resolve({project: 'project'}));
-        return element._getFromProjectLookup('test').then(val => {
+        return element.getFromProjectLookup('test').then(val => {
           assert.equal(val, 'project');
           assert.deepEqual(element._projectLookup, {test: 'project'});
         });
diff --git a/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.js b/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.js
index 2e7f17c..7f61edd 100644
--- a/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.js
+++ b/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.js
@@ -25,6 +25,7 @@
 
     properties: {
       _lastCleanup: Number,
+      /** @type {?Storage} */
       _storage: {
         type: Object,
         value() {
diff --git a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.js b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.js
index 1bc565d..a172a09 100644
--- a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.js
+++ b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea.js
@@ -82,6 +82,7 @@
         type: Boolean,
         value: false,
       },
+      /** @type(?number) */
       _colonIndex: Number,
       _currentSearchString: {
         type: String,
@@ -193,7 +194,7 @@
     },
 
     _getText(value) {
-      return this.text.substr(0, this._colonIndex) +
+      return this.text.substr(0, this._colonIndex || 0) +
           value + this.text.substr(this.$.textarea.selectionStart) + ' ';
     },
 
@@ -272,7 +273,7 @@
             this._currentSearchString.length + this._colonIndex + 1 ||
             this._currentSearchString === ' ' ||
             this._currentSearchString === '\n' ||
-            !e.detail.value[this._colonIndex] === ':' ||
+            !(e.detail.value[this._colonIndex] === ':') ||
             !this._suggestions.length) {
           this._resetEmojiDropdown();
         // Otherwise open the dropdown and set the position to be just below the
diff --git a/polygerrit-ui/app/scripts/util.js b/polygerrit-ui/app/scripts/util.js
index 07b543f..22b483d 100644
--- a/polygerrit-ui/app/scripts/util.js
+++ b/polygerrit-ui/app/scripts/util.js
@@ -47,7 +47,7 @@
    * Example
    * // returns 'text.html'
    * util.truncatePath.('text.html');
-   * @return {String} Returns the truncated value of a URL.
+   * @return {string} Returns the truncated value of a URL.
    */
   util.truncatePath = function(path) {
     const pathPieces = path.split('/');
diff --git a/polygerrit-ui/app/styles/gr-form-styles.html b/polygerrit-ui/app/styles/gr-form-styles.html
index 04346fe..7e0bf5a 100644
--- a/polygerrit-ui/app/styles/gr-form-styles.html
+++ b/polygerrit-ui/app/styles/gr-form-styles.html
@@ -41,7 +41,7 @@
         color: #666;
         font-weight: bold;
         padding-right: .5em;
-        width: 11em;
+        width: 15em;
       }
       .gr-form-styles iron-autogrow-textarea {
         font-size: 1em;
@@ -68,11 +68,11 @@
       }
       .gr-form-styles th:first-child,
       .gr-form-styles td:first-child {
-        width: 11em;
+        width: 15em;
       }
       .gr-form-styles th:first-child input,
       .gr-form-styles td:first-child input {
-        width: 10em;
+        width: 14em;
       }
       .gr-form-styles input:not([type="checkbox"]),
       .gr-form-styles select,
@@ -110,7 +110,7 @@
           font-size: 1em;
           height: 2em;
           padding: 0 .15em;
-          width: 10em;
+          width: 14em;
         }
       }
       @media only screen and (max-width: 40em) {
diff --git a/polygerrit-ui/app/styles/gr-menu-page-styles.html b/polygerrit-ui/app/styles/gr-menu-page-styles.html
index 19ff2e5..81d4179 100644
--- a/polygerrit-ui/app/styles/gr-menu-page-styles.html
+++ b/polygerrit-ui/app/styles/gr-menu-page-styles.html
@@ -22,7 +22,7 @@
       }
       main {
         margin: 2em auto;
-        max-width: 46em;
+        max-width: 50em;
       }
       main.table {
         margin-top: 0;
diff --git a/polygerrit-ui/app/template_test.sh b/polygerrit-ui/app/template_test.sh
new file mode 100755
index 0000000..0ed6d94
--- /dev/null
+++ b/polygerrit-ui/app/template_test.sh
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+set -ex
+
+npm_bin=$(which npm)
+if [ -z "$npm_bin" ]; then
+    echo "NPM must be on the path."
+    exit 1
+fi
+
+node_bin=$(which node)
+if [ -z "$node_bin" ]; then
+    echo "node must be on the path."
+    exit 1
+fi
+
+fried_twinkie_config=$(npm list -g | grep -c fried-twinkie)
+typescript_config=$(npm list -g | grep -c typescript)
+if [ -z "$npm_bin" ] || [ "$fried_twinkie_config" -eq "0" ]; then
+    echo "You must install fried twinkie and its dependencies from NPM."
+    echo "> npm install -g fried-twinkie"
+    exit 1
+fi
+
+# Have to find where node_modules are installed and set the NODE_PATH
+
+get_node_path() {
+    cd $(dirname $node_bin)
+    cd ../lib/node_modules
+    pwd
+}
+
+export NODE_PATH=$(get_node_path)
+
+unzip -o polygerrit-ui/polygerrit_components.bower_components.zip -d polygerrit-ui/app
+python $TEST_SRCDIR/gerrit/polygerrit-ui/app/template_test_srcs/convert_for_template_tests.py
+# Pass a file name argument from the --test_args (example: --test_arg=gr-list-view)
+${node_bin} $TEST_SRCDIR/gerrit/polygerrit-ui/app/template_test_srcs/template_test.js $1
diff --git a/polygerrit-ui/app/template_test_srcs/convert_for_template_tests.py b/polygerrit-ui/app/template_test_srcs/convert_for_template_tests.py
new file mode 100644
index 0000000..d293b69
--- /dev/null
+++ b/polygerrit-ui/app/template_test_srcs/convert_for_template_tests.py
@@ -0,0 +1,102 @@
+import os, re, json
+from shutil import copyfile, rmtree
+
+polymerRegex = r"Polymer\({"
+polymerCompiledRegex = re.compile(polymerRegex)
+
+removeSelfInvokeRegex = r"\(function\(\) {\n(.+)}\)\(\);"
+fnCompiledRegex = re.compile(removeSelfInvokeRegex, re.DOTALL)
+
+regexBehavior = r"<script>(.+)<\/script>"
+behaviorCompiledRegex = re.compile(regexBehavior, re.DOTALL)
+
+def replaceBehaviorLikeHTML (fileIn, fileOut):
+  with open(fileIn) as f:
+    file_str = f.read()
+    match = behaviorCompiledRegex.search(file_str)
+    if (match):
+      with open("polygerrit-ui/temp/behaviors/" + fileOut.replace("html", "js") , "w+") as f:
+        f.write(match.group(1))
+
+def replaceBehaviorLikeJS (fileIn, fileOut):
+  with open(fileIn) as f:
+    file_str = f.read()
+    with open("polygerrit-ui/temp/behaviors/" + fileOut , "w+") as f:
+      f.write(file_str)
+
+def generateStubBehavior(behaviorName):
+  with open("polygerrit-ui/temp/behaviors/" + behaviorName + ".js", "w+") as f:
+    f.write("/** @polymerBehavior **/\n" + behaviorName + "= {};")
+
+def replacePolymerElement (fileIn, fileOut, root):
+  with open(fileIn) as f:
+    # Removed self invoked function
+    file_str = f.read()
+    file_str_no_fn = fnCompiledRegex.search(file_str)
+
+    if file_str_no_fn:
+      package = root.replace("/", ".") + "." + fileOut
+
+      with open("polygerrit-ui/temp/" + fileOut, "w+") as f:
+        mainFileContents = re.sub(polymerCompiledRegex, "exports = Polymer({", file_str_no_fn.group(1)).replace("'use strict';", "")
+        f.write("/** \n" \
+          "* @fileoverview \n" \
+          "* @suppress {missingProperties} \n" \
+          "*/ \n\n" \
+          "goog.module('polygerrit." + package + "')\n\n" + mainFileContents)
+
+      # Add package and javascript to files object.
+      elements[root]["js"] = "polygerrit-ui/temp/" + fileOut
+      elements[root]["package"] = package
+
+def writeTempFile(file, root):
+  # This is included in an extern because it is directly on the window object.
+  # (for now at least).
+  if "gr-reporting" in file:
+    return
+  if not root in elements:
+    elements[root] = {}
+  if file.endswith(".html") and not file.endswith("_test.html"):
+    # gr-navigation is treated like a behavior rather than a standard element
+    # because of the way it added to the Gerrit object.
+    if file.endswith("gr-navigation.html"):
+      replaceBehaviorLikeHTML(os.path.join(root, file), file)
+    else:
+      elements[root]["html"] = os.path.join(root, file)
+  if file.endswith(".js"):
+    replacePolymerElement(os.path.join(root, file), file, root)
+
+
+if __name__ == "__main__":
+  # Create temp directory.
+  if not os.path.exists("polygerrit-ui/temp"):
+    os.makedirs("polygerrit-ui/temp")
+
+  # Within temp directory create behavior directory.
+  if not os.path.exists("polygerrit-ui/temp/behaviors"):
+    os.makedirs("polygerrit-ui/temp/behaviors")
+
+  elements = {}
+
+  # Go through every file in app/elements, and re-write accordingly to temp
+  # directory, and also added to elements object, which is used to generate a
+  # map of html files, package names, and javascript files.
+  for root, dirs, files in os.walk("polygerrit-ui/app/elements"):
+    for file in files:
+      writeTempFile(file, root)
+
+  # Special case for polymer behaviors we are using.
+  replaceBehaviorLikeHTML("polygerrit-ui/app/bower_components/iron-a11y-keys-behavior/iron-a11y-keys-behavior.html", "iron-a11y-keys-behavior.html")
+  generateStubBehavior("Polymer.IronOverlayBehavior")
+
+  #TODO figure out something to do with iron-overlay-behavior. it is hard-coded reformatted.
+
+  with open("polygerrit-ui/temp/map.json", "w+") as f:
+    f.write(json.dumps(elements))
+
+  for root, dirs, files in os.walk("polygerrit-ui/app/behaviors"):
+    for file in files:
+      if file.endswith("behavior.html"):
+        replaceBehaviorLikeHTML(os.path.join(root, file), file)
+      elif file.endswith("behavior.js"):
+        replaceBehaviorLikeJS(os.path.join(root, file), file)
\ No newline at end of file
diff --git a/polygerrit-ui/app/template_test_srcs/template_test.js b/polygerrit-ui/app/template_test_srcs/template_test.js
new file mode 100644
index 0000000..e5dae8b
--- /dev/null
+++ b/polygerrit-ui/app/template_test_srcs/template_test.js
@@ -0,0 +1,84 @@
+const fs = require('fs');
+const twinkie = require('fried-twinkie');
+
+const EXTERN_NAMES = [
+  'Gerrit',
+  'GrAnnotation',
+  'GrAttributeHelper',
+  'GrChangeActionsInterface',
+  'GrChangeReplyInterface',
+  'GrDiffBuilder',
+  'GrDiffBuilderImage',
+  'GrDiffBuilderSideBySide',
+  'GrDiffBuilderUnified',
+  'GrDiffGroup',
+  'GrDiffLine',
+  'GrDomHooks',
+  'GrEtagDecorator',
+  'GrGapiAuth',
+  'GrGerritAuth',
+  'GrLinkTextParser',
+  'GrPluginEndpoints',
+  'GrRangeNormalizer',
+  'GrReporting',
+  'GrReviewerUpdatesParser',
+  'GrThemeApi',
+  'moment',
+  'page',
+  'util'];
+
+fs.readdir('./polygerrit-ui/temp/behaviors/', (err, data) => {
+  if (err) {
+    console.log('error /polygerrit-ui/temp/behaviors/ directory');
+  }
+  const behaviors = data;
+  const externs = [];
+
+  for (const behavior of behaviors) {
+    externs.push({
+      path: `./polygerrit-ui/temp/behaviors/${behavior}`,
+      src: fs.readFileSync(
+          `./polygerrit-ui/temp/behaviors/${behavior}`, 'utf-8'),
+    });
+  }
+
+  let mappings = JSON.parse(fs.readFileSync(
+      `./polygerrit-ui/temp/map.json`, 'utf-8'));
+
+  // If a particular file was passed by the user, don't test everything.
+  const file = process.argv[2];
+  if (file) {
+    const mappingSpecificFile = {};
+    for (key of Object.keys(mappings)) {
+      if (key.includes(file)) {
+        mappingSpecificFile[key] = mappings[key];
+      }
+    }
+    mappings = mappingSpecificFile;
+  }
+
+  externs.push({
+    path: 'custom-externs.js',
+    src: '/** @externs */' +
+        EXTERN_NAMES.map( name => { return `var ${name};`; }).join(' '),
+  });
+
+  const promises = [];
+
+  for (key of Object.keys(mappings)) {
+    if (mappings[key].html && mappings[key].js) {
+      promises.push(twinkie.checkTemplate(
+          mappings[key].html,
+          mappings[key].js,
+          'polygerrit.' + mappings[key].package,
+          externs
+      ));
+    }
+  }
+
+  Promise.all(promises).then(() => {}, joinedErrors => {
+    if (joinedErrors) {
+      process.exit(1);
+    }
+  });
+});
\ No newline at end of file
diff --git a/polygerrit-ui/app/test/index.html b/polygerrit-ui/app/test/index.html
index c9035be..634dfd6 100644
--- a/polygerrit-ui/app/test/index.html
+++ b/polygerrit-ui/app/test/index.html
@@ -39,6 +39,7 @@
     'admin/gr-create-project-dialog/gr-create-project-dialog_test.html',
     'admin/gr-group/gr-group_test.html',
     'admin/gr-group-audit-log/gr-group-audit-log_test.html',
+    'admin/gr-group-members/gr-group-members_test.html',
     'admin/gr-plugin-list/gr-plugin-list_test.html',
     'admin/gr-project/gr-project_test.html',
     'admin/gr-project-detail-list/gr-project-detail-list_test.html',
@@ -150,6 +151,7 @@
     'docs-url-behavior/docs-url-behavior_test.html',
     'keyboard-shortcut-behavior/keyboard-shortcut-behavior_test.html',
     'rest-client-behavior/rest-client-behavior_test.html',
+    'gr-anonymous-name-behavior/gr-anonymous-name-behavior_test.html',
     'gr-change-table-behavior/gr-change-table-behavior_test.html',
     'gr-patch-set-behavior/gr-patch-set-behavior_test.html',
     'gr-path-list-behavior/gr-path-list-behavior_test.html',
diff --git a/tools/eclipse/BUILD b/tools/eclipse/BUILD
index 7d79bc2..303c6f3 100644
--- a/tools/eclipse/BUILD
+++ b/tools/eclipse/BUILD
@@ -11,6 +11,7 @@
     "//gerrit-gpg:gpg_tests",
     "//gerrit-gwtui:ui_tests",
     "//gerrit-httpd:httpd_tests",
+    "//gerrit-index:index_tests",
     "//gerrit-patch-jgit:jgit_patch_tests",
     "//gerrit-reviewdb:client_tests",
     "//gerrit-server:server_tests",