Merge "Merge top menu items contributed by plugins" into stable-2.16
diff --git a/BUILD b/BUILD
index d924417..0b528d8 100644
--- a/BUILD
+++ b/BUILD
@@ -57,7 +57,6 @@
     cmd = ("cat bazel-out/volatile-status.txt bazel-out/stable-status.txt | " +
            "grep STABLE_BUILD_GERRIT_LABEL | cut -d ' ' -f 2 > $@"),
     stamp = 1,
-    visibility = ["//visibility:public"],
 )
 
 genrule(
@@ -65,7 +64,6 @@
     srcs = ["//Documentation:licenses.txt"],
     outs = ["LICENSES.txt"],
     cmd = "cp $< $@",
-    visibility = ["//visibility:public"],
 )
 
 pkg_war(
@@ -112,7 +110,7 @@
 
 genrule2(
     name = "api",
-    testonly = 1,
+    testonly = True,
     srcs = API_DEPS,
     outs = ["api.zip"],
     cmd = " && ".join([
diff --git a/Documentation/BUILD b/Documentation/BUILD
index 4177f51..6fa5983 100644
--- a/Documentation/BUILD
+++ b/Documentation/BUILD
@@ -40,7 +40,6 @@
         ":prettify_files",
         "//:LICENSES.txt",
     ],
-    visibility = ["//visibility:public"],
 )
 
 license_map(
@@ -51,7 +50,6 @@
         "//polygerrit-ui/app:polygerrit_ui",
         "//java/com/google/gerrit/pgm",
     ],
-    visibility = ["//visibility:public"],
 )
 
 license_map(
@@ -60,7 +58,6 @@
         "//gerrit-gwtui:ui_module",
         "//polygerrit-ui/app:polygerrit_ui",
     ],
-    visibility = ["//visibility:public"],
 )
 
 DOC_DIR = "Documentation"
@@ -88,7 +85,6 @@
     srcs = SRCS,
     attributes = documentation_attributes(),
     backend = "html5",
-    visibility = ["//visibility:public"],
 )
 
 genasciidoc_zip(
@@ -97,7 +93,6 @@
     attributes = documentation_attributes(),
     backend = "html5",
     directory = DOC_DIR,
-    visibility = ["//visibility:public"],
 )
 
 genasciidoc_zip(
@@ -107,5 +102,4 @@
     backend = "html5",
     directory = DOC_DIR,
     searchbox = False,
-    visibility = ["//visibility:public"],
 )
diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt
index d3f5d77..05c2731 100644
--- a/Documentation/access-control.txt
+++ b/Documentation/access-control.txt
@@ -1356,6 +1356,12 @@
 command, but also to the web UI results pagination size.
 
 
+[[capability_readAs]]
+=== Read As
+
+Allow users to impersonate any user to see which refs they can read.
+
+
 [[capability_runAs]]
 === Run As
 
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 0cb2b00..1b7637d 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -2987,7 +2987,7 @@
 for production use. For compatibility information, please refer to the
 link:https://www.gerritcodereview.com/elasticsearch.html[project homepage].
 
-When using Elasticsearch versions 2.4 and 5.6, the open and closed changes are
+When using Elasticsearch version 5.6, the open and closed changes are
 indexed in a single index, separated into types `open_changes` and `closed_changes`
 respectively. When using version 6.2 or later, the open and closed changes are
 merged into the default `_doc` type. The latter is also used for the accounts and
@@ -3024,6 +3024,22 @@
 +
 Defaults to `30000 ms`.
 
+[[elasticsearch.numberOfShards]]elasticsearch.numberOfShards::
++
+Sets the number of shards to use per index. Refer to the
+link:https://www.elastic.co/guide/en/elasticsearch/reference/current/_basic_concepts.html#getting-started-shards-and-replicas[
+Elasticsearch documentation] for details.
++
+Defaults to 5.
+
+[[elasticsearch.numberOfReplicas]]elasticsearch.numberOfReplicas::
++
+Sets the number of replicas to use per index. Refer to the
+link:https://www.elastic.co/guide/en/elasticsearch/reference/current/_basic_concepts.html#getting-started-shards-and-replicas[
+Elasticsearch documentation] for details.
++
+Defaults to 1.
+
 ==== Elasticsearch Security
 
 When security is enabled in Elasticsearch, the username and password must be provided.
@@ -3031,11 +3047,11 @@
 
 For further information about Elasticsearch security, please refer to the documentation:
 
-* link:https://www.elastic.co/guide/en/elasticsearch/plugins/2.4/security.html[Elasticsearch 2.4]
 * link:https://www.elastic.co/guide/en/x-pack/5.6/security-getting-started.html[Elasticsearch 5.6]
 * link:https://www.elastic.co/guide/en/x-pack/6.2/security-getting-started.html[Elasticsearch 6.2]
 * link:https://www.elastic.co/guide/en/elastic-stack-overview/6.3/security-getting-started.html[Elasticsearch 6.3]
 * link:https://www.elastic.co/guide/en/elastic-stack-overview/6.4/security-getting-started.html[Elasticsearch 6.4]
+* link:https://www.elastic.co/guide/en/elastic-stack-overview/6.5/security-getting-started.html[Elasticsearch 6.5]
 
 [[elasticsearch.username]]elasticsearch.username::
 +
@@ -3077,6 +3093,19 @@
   groupMemberPattern = (&(objectClass=group)(member=${dn}))
 ----
 
+[[ldap.guessRelevantGroups]]ldap.guessRelevantGroups::
++
+Filter the groups found in LDAP by guessing the ones relevant to
+Gerrit and removing the others from list completions and ACL evaluations.
+The guess is based on two elements: the projects most recently
+accessed in the cache and the list of LDAP groups included in their ACLs.
++
+Please note that projects rarely used and thus not cached may be
+temporarily inaccessible by users even with LDAP membership and grants
+referenced in the ACLs.
++
+By default, true.
+
 [[ldap.server]]ldap.server::
 +
 URL of the organization's LDAP server to query for user information
@@ -3806,21 +3835,6 @@
 If no keys are specified, web-of-trust checks are disabled. This is the
 default behavior.
 
-[[receive.enableProtocolV2]]receive.enableProtocolV2::
-+
-Enable support for git protocol version 2.
-+
-When this option is enabled, clients may send upload pack using git
-protocol version 2.
-+
-The repository must also be configured on the server side to use protocol
-version 2 by setting `protocol.version = 2` either in the gerrit user's
-`~/.gitconfig` file (which will enable it for all repositories) or on
-a per repository basis by setting the option in the `.git/config` file
-of the repository.
-+
-Defaults to false, git protocol version 2 is not enabled.
-
 [[repository]]
 === Section repository
 
diff --git a/Documentation/config-plugins.txt b/Documentation/config-plugins.txt
index b9c5172..6d4fe3e 100644
--- a/Documentation/config-plugins.txt
+++ b/Documentation/config-plugins.txt
@@ -112,16 +112,6 @@
 link:https://gerrit.googlesource.com/plugins/reviewnotes/+doc/master/src/main/resources/Documentation/about.md[
 Documentation]
 
-[[review-strategy]]
-=== review-strategy
-
-This plugin allows users to configure different review strategies.
-
-link:https://gerrit-review.googlesource.com/admin/repos/plugins/review-strategy[
-Project] |
-link:https://gerrit.googlesource.com/plugins/review-strategy/+doc/master/src/main/resources/Documentation/about.md[
-Documentation]
-
 [[singleusergroup]]
 === singleusergroup
 
@@ -565,6 +555,16 @@
 link:https://gerrit.googlesource.com/plugins/reparent/+doc/master/src/main/resources/Documentation/config.md[
 Configuration]
 
+[[review-strategy]]
+=== review-strategy
+
+This plugin allows users to configure different review strategies.
+
+link:https://gerrit-review.googlesource.com/admin/repos/plugins/review-strategy[
+Project] |
+link:https://gerrit.googlesource.com/plugins/review-strategy/+doc/master/src/main/resources/Documentation/about.md[
+Documentation]
+
 [[reviewers]]
 === reviewers
 
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index fd29bf7..53b03b1 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -2355,6 +2355,16 @@
 `com.google.gerrit.server.git.ChangeReportFormatter` interface, a plugin
 may change the formatting of the report.
 
+[[url-formatting]]
+== URL Formatting
+
+URLs to various parts of Gerrit are usually formed by adding suffixes to
+the canonical web URL.
+
+By implementing the
+`com.google.gerrit.server.config.UrlFormatter` interface, a plugin may
+change the format of the URL.
+
 [[links-to-external-tools]]
 == Links To External Tools
 
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 1829c6b..4aae2dd 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -2454,7 +2454,7 @@
 Retrieves a change message including link:#detailed-accounts[detailed account information].
 
 --
-'GET /changes/link:#change-id[\{change-id\}]/message/link:#change-message-id[\{change-message-id\}'
+'GET /changes/link:#change-id[\{change-id\}]/message/link:#change-message-id[\{change-message-id\}]'
 --
 
 As response a link:#change-message-info[ChangeMessageInfo] entity is returned.
@@ -2483,8 +2483,8 @@
 [[delete-change-message]]
 === Delete Change Message
 --
-'DELETE /changes/link:#change-id[\{change-id\}]/message/link:#change-message-id[\{change-message-id\}' +
-'POST /changes/link:#change-id[\{change-id\}]//message/link:#change-message-id[\{change-message-id\}/delete'
+'DELETE /changes/link:#change-id[\{change-id\}]/message/link:#change-message-id[\{change-message-id\}]' +
+'POST /changes/link:#change-id[\{change-id\}]//message/link:#change-message-id[\{change-message-id\}]/delete'
 --
 
 Deletes a change message by replacing the change message with a new message,
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index 3214761..fbcca51 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -1192,21 +1192,19 @@
   Content-Type: application/json; charset=UTF-8
 
   {
-    "remove": [
-      {
-        "refs/*": {
-          "permissions": {
-            "read": {
-              "rules": {
-                "c2ce4749a32ceb82cd6adcce65b8216e12afb41c": {
-                  "action": "ALLOW"
-                }
+    "remove": {
+      "refs/*": {
+        "permissions": {
+          "read": {
+            "rules": {
+              "c2ce4749a32ceb82cd6adcce65b8216e12afb41c": {
+                "action": "ALLOW"
               }
             }
           }
         }
       }
-    ]
+    }
   }
 ----
 
diff --git a/WORKSPACE b/WORKSPACE
index 8ea46e3..81b9ba5 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -15,9 +15,9 @@
 
 http_archive(
     name = "io_bazel_rules_closure",
-    sha256 = "4dd84dd2bdd6c9f56cb5a475d504ea31d199c34309e202e9379501d01c3067e5",
-    strip_prefix = "rules_closure-3103a773820b59b76345f94c231cb213e0d404e2",
-    urls = ["https://github.com/bazelbuild/rules_closure/archive/3103a773820b59b76345f94c231cb213e0d404e2.tar.gz"],
+    sha256 = "4f2c173ebf95e94d98a0d5cb799e734536eaf3eca280eb15e124f5e5ef8b6e39",
+    strip_prefix = "rules_closure-6fd76e645b5c622221c9920f41a4d0bc578a3046",
+    urls = ["https://github.com/bazelbuild/rules_closure/archive/6fd76e645b5c622221c9920f41a4d0bc578a3046.tar.gz"],
 )
 
 # File is specific to Polymer and copied from the Closure Github -- should be
@@ -48,8 +48,8 @@
 # Golang support for PolyGerrit local dev server.
 http_archive(
     name = "io_bazel_rules_go",
-    sha256 = "97cf62bdef33519412167fd1e4b0810a318a7c234f5f8dc4f53e2da86241c492",
-    urls = ["https://github.com/bazelbuild/rules_go/releases/download/0.15.3/rules_go-0.15.3.tar.gz"],
+    sha256 = "ee5fe78fe417c685ecb77a0a725dc9f6040ae5beb44a0ba4ddb55453aad23a8a",
+    url = "https://github.com/bazelbuild/rules_go/releases/download/0.16.0/rules_go-0.16.0.tar.gz",
 )
 
 load("@io_bazel_rules_go//go:def.bzl", "go_register_toolchains", "go_rules_dependencies")
@@ -1091,22 +1091,30 @@
 # and httpasyncclient as necessary.
 maven_jar(
     name = "elasticsearch-rest-client",
-    artifact = "org.elasticsearch.client:elasticsearch-rest-client:6.5.0",
-    sha1 = "241436d27cf65b84d17126dc7b6b947e8e2c173c",
+    artifact = "org.elasticsearch.client:elasticsearch-rest-client:6.5.4",
+    sha1 = "552175b06e34df96f114d1c8aaa908e535c8f1be",
 )
 
-JACKSON_VERSION = "2.9.7"
+JACKSON_VERSION = "2.9.8"
 
 maven_jar(
     name = "jackson-core",
     artifact = "com.fasterxml.jackson.core:jackson-core:" + JACKSON_VERSION,
-    sha1 = "4b7f0e0dc527fab032e9800ed231080fdc3ac015",
+    sha1 = "0f5a654e4675769c716e5b387830d19b501ca191",
 )
 
+TESTCONTAINERS_VERSION = "1.10.3"
+
 maven_jar(
     name = "testcontainers",
-    artifact = "org.testcontainers:testcontainers:1.8.0",
-    sha1 = "bc413912f7044f9f12aa0782853aef0a067ee52a",
+    artifact = "org.testcontainers:testcontainers:" + TESTCONTAINERS_VERSION,
+    sha1 = "e561ce99fc616b383d85f35ce881e58e8de59ae7",
+)
+
+maven_jar(
+    name = "testcontainers-elasticsearch",
+    artifact = "org.testcontainers:elasticsearch:" + TESTCONTAINERS_VERSION,
+    sha1 = "0cb114ecba0ed54a116e2be2f031bc45ca4cbfc8",
 )
 
 maven_jar(
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChangesTab.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChangesTab.java
index aa049ca..c53427b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChangesTab.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChangesTab.java
@@ -248,8 +248,9 @@
     }
   }
 
-  @SuppressWarnings("serial")
   private class RowSafeHtml implements SafeHtml {
+    private static final long serialVersionUID = 1L;
+
     private String html;
     private ChangeAndCommit info;
     private final boolean notConnected;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/RevisionInfoCache.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/RevisionInfoCache.java
index 0b83119..fde2b05 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/RevisionInfoCache.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/RevisionInfoCache.java
@@ -35,10 +35,11 @@
 
   private final LinkedHashMap<PatchSet.Id, String> psToCommit;
 
-  @SuppressWarnings("serial")
   private RevisionInfoCache() {
     psToCommit =
         new LinkedHashMap<PatchSet.Id, String>(LIMIT) {
+          private static final long serialVersionUID = 1L;
+
           @Override
           protected boolean removeEldestEntry(Map.Entry<PatchSet.Id, String> e) {
             return size() > LIMIT;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/NavigationTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/NavigationTable.java
index 7e34730..b2306a6 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/NavigationTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/NavigationTable.java
@@ -73,9 +73,10 @@
     }
   }
 
-  @SuppressWarnings("serial")
   private static final LinkedHashMap<String, Object> savedPositions =
       new LinkedHashMap<String, Object>(10, 0.75f, true) {
+        private static final long serialVersionUID = 1L;
+
         @Override
         protected boolean removeEldestEntry(Entry<String, Object> eldest) {
           return size() >= 20;
diff --git a/java/com/google/gerrit/acceptance/BUILD b/java/com/google/gerrit/acceptance/BUILD
index 861372d..a9a077f 100644
--- a/java/com/google/gerrit/acceptance/BUILD
+++ b/java/com/google/gerrit/acceptance/BUILD
@@ -2,7 +2,7 @@
 
 java_library(
     name = "lib",
-    testonly = 1,
+    testonly = True,
     resource_strip_prefix = "resources",
     resources = ["//resources/com/google/gerrit/acceptance"],
     visibility = ["//visibility:public"],
@@ -55,7 +55,7 @@
 
 java_binary(
     name = "framework",
-    testonly = 1,
+    testonly = True,
     main_class = "Dummy",
     visibility = ["//visibility:public"],
     runtime_deps = [":framework-lib"],
@@ -63,7 +63,7 @@
 
 java_library2(
     name = "framework-lib",
-    testonly = 1,
+    testonly = True,
     srcs = glob(["**/*.java"]),
     exported_deps = [
         "//java/com/google/gerrit/gpg",
@@ -129,7 +129,7 @@
 
 java_doc(
     name = "framework-javadoc",
-    testonly = 1,
+    testonly = True,
     libs = [":framework-lib"],
     pkgs = ["com.google.gerrit.acceptance"],
     title = "Gerrit Acceptance Test Framework Documentation",
diff --git a/java/com/google/gerrit/acceptance/GerritServer.java b/java/com/google/gerrit/acceptance/GerritServer.java
index be8932e..799feae 100644
--- a/java/com/google/gerrit/acceptance/GerritServer.java
+++ b/java/com/google/gerrit/acceptance/GerritServer.java
@@ -226,6 +226,13 @@
 
           // Silence non-critical messages from apache.http.
           .put("org.apache.http", Level.WARN)
+
+          // Silence non-critical messages from Jetty.
+          .put("org.eclipse.jetty", Level.WARN)
+
+          // Silence non-critical messages from JGit.
+          .put("org.eclipse.jgit.transport.PacketLineIn", Level.WARN)
+          .put("org.eclipse.jgit.transport.PacketLineOut", Level.WARN)
           .build();
 
   private static boolean forceLocalDisk() {
@@ -385,6 +392,7 @@
     cfg.setBoolean("httpd", null, "requestLog", false);
     cfg.setBoolean("sshd", null, "requestLog", false);
     cfg.setBoolean("index", "lucene", "testInmemory", true);
+    cfg.setBoolean("index", null, "onlineUpgrade", false);
     cfg.setString("gitweb", null, "cgi", "");
     daemon.setEnableHttpd(desc.httpd());
     daemon.setLuceneModule(LuceneIndexModule.singleVersionAllLatest(0, isSlave(baseConfig)));
diff --git a/java/com/google/gerrit/common/data/testing/BUILD b/java/com/google/gerrit/common/data/testing/BUILD
index 3899e39..32815d5 100644
--- a/java/com/google/gerrit/common/data/testing/BUILD
+++ b/java/com/google/gerrit/common/data/testing/BUILD
@@ -1,6 +1,6 @@
 java_library(
     name = "common-data-test-util",
-    testonly = 1,
+    testonly = True,
     srcs = glob(["**/*.java"]),
     visibility = ["//visibility:public"],
     deps = [
diff --git a/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java b/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
index 6da19cd..3b589b2 100644
--- a/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
+++ b/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
@@ -107,6 +107,7 @@
     return content;
   }
 
+  private final ElasticConfiguration config;
   private final Schema<V> schema;
   private final SitePaths sitePaths;
   private final String indexNameRaw;
@@ -118,17 +119,18 @@
   protected final ElasticQueryBuilder queryBuilder;
 
   AbstractElasticIndex(
-      ElasticConfiguration cfg,
+      ElasticConfiguration config,
       SitePaths sitePaths,
       Schema<V> schema,
       ElasticRestClientProvider client,
       String indexName,
       String indexType) {
+    this.config = config;
     this.sitePaths = sitePaths;
     this.schema = schema;
     this.gson = new GsonBuilder().setFieldNamingPolicy(LOWER_CASE_WITH_UNDERSCORES).create();
     this.queryBuilder = new ElasticQueryBuilder();
-    this.indexName = cfg.getIndexName(indexName, schema.getVersion());
+    this.indexName = config.getIndexName(indexName, schema.getVersion());
     this.indexNameRaw = indexName;
     this.client = client;
     this.type = client.adapter().getType(indexType);
@@ -199,7 +201,7 @@
   protected abstract String getMappings();
 
   private String getSettings() {
-    return gson.toJson(ImmutableMap.of(SETTINGS, ElasticSetting.createSetting()));
+    return gson.toJson(ImmutableMap.of(SETTINGS, ElasticSetting.createSetting(config)));
   }
 
   protected abstract String getId(V v);
@@ -293,8 +295,11 @@
   }
 
   protected String getURI(String type, String request) throws UnsupportedEncodingException {
-    String encodedType = URLEncoder.encode(type, UTF_8.toString());
     String encodedIndexName = URLEncoder.encode(indexName, UTF_8.toString());
+    if (SEARCH.equals(request) && client.adapter().omitTypeFromSearch()) {
+      return encodedIndexName + "/" + request;
+    }
+    String encodedType = URLEncoder.encode(type, UTF_8.toString());
     return encodedIndexName + "/" + encodedType + "/" + request;
   }
 
diff --git a/java/com/google/gerrit/elasticsearch/ElasticConfiguration.java b/java/com/google/gerrit/elasticsearch/ElasticConfiguration.java
index 8d29d21..6863238 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticConfiguration.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticConfiguration.java
@@ -40,9 +40,13 @@
   static final String KEY_MAX_RETRY_TIMEOUT = "maxRetryTimeout";
   static final String KEY_PREFIX = "prefix";
   static final String KEY_SERVER = "server";
+  static final String KEY_NUMBER_OF_SHARDS = "numberOfShards";
+  static final String KEY_NUMBER_OF_REPLICAS = "numberOfReplicas";
   static final String DEFAULT_PORT = "9200";
   static final String DEFAULT_USERNAME = "elastic";
   static final int DEFAULT_MAX_RETRY_TIMEOUT_MS = 30000;
+  static final int DEFAULT_NUMBER_OF_SHARDS = 5;
+  static final int DEFAULT_NUMBER_OF_REPLICAS = 1;
   static final TimeUnit MAX_RETRY_TIMEOUT_UNIT = TimeUnit.MILLISECONDS;
 
   private final Config cfg;
@@ -51,6 +55,8 @@
   final String username;
   final String password;
   final int maxRetryTimeout;
+  final int numberOfShards;
+  final int numberOfReplicas;
   final String prefix;
 
   @Inject
@@ -71,6 +77,10 @@
                 DEFAULT_MAX_RETRY_TIMEOUT_MS,
                 MAX_RETRY_TIMEOUT_UNIT);
     this.prefix = Strings.nullToEmpty(cfg.getString(SECTION_ELASTICSEARCH, null, KEY_PREFIX));
+    this.numberOfShards =
+        cfg.getInt(SECTION_ELASTICSEARCH, null, KEY_NUMBER_OF_SHARDS, DEFAULT_NUMBER_OF_SHARDS);
+    this.numberOfReplicas =
+        cfg.getInt(SECTION_ELASTICSEARCH, null, KEY_NUMBER_OF_REPLICAS, DEFAULT_NUMBER_OF_REPLICAS);
     this.hosts = new ArrayList<>();
     for (String server : cfg.getStringList(SECTION_ELASTICSEARCH, null, KEY_SERVER)) {
       try {
diff --git a/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java b/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java
index 1e41985..15d6126 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java
@@ -25,20 +25,15 @@
 public class ElasticIndexModule extends AbstractIndexModule {
   public static ElasticIndexModule singleVersionWithExplicitVersions(
       Map<String, Integer> versions, int threads, boolean slave) {
-    return new ElasticIndexModule(versions, threads, false, slave);
+    return new ElasticIndexModule(versions, threads, slave);
   }
 
-  public static ElasticIndexModule latestVersionWithOnlineUpgrade(boolean slave) {
-    return new ElasticIndexModule(null, 0, true, slave);
+  public static ElasticIndexModule latestVersion(boolean slave) {
+    return new ElasticIndexModule(null, 0, slave);
   }
 
-  public static ElasticIndexModule latestVersionWithoutOnlineUpgrade(boolean slave) {
-    return new ElasticIndexModule(null, 0, false, slave);
-  }
-
-  private ElasticIndexModule(
-      Map<String, Integer> singleVersions, int threads, boolean onlineUpgrade, boolean slave) {
-    super(singleVersions, threads, onlineUpgrade, slave);
+  private ElasticIndexModule(Map<String, Integer> singleVersions, int threads, boolean slave) {
+    super(singleVersions, threads, slave);
   }
 
   @Override
diff --git a/java/com/google/gerrit/elasticsearch/ElasticQueryAdapter.java b/java/com/google/gerrit/elasticsearch/ElasticQueryAdapter.java
index 65d2916..40c1bbb 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticQueryAdapter.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticQueryAdapter.java
@@ -21,6 +21,7 @@
 
   private final boolean ignoreUnmapped;
   private final boolean usePostV5Type;
+  private final boolean omitTypeFromSearch;
 
   private final String searchFilteringName;
   private final String indicesExistParam;
@@ -31,33 +32,16 @@
   private final String versionDiscoveryUrl;
 
   ElasticQueryAdapter(ElasticVersion version) {
-    this.ignoreUnmapped = version == ElasticVersion.V2_4;
-    this.usePostV5Type = version.isV6();
-    this.versionDiscoveryUrl = version.isV6() ? "/%s*" : "/%s*/_aliases";
-
-    switch (version) {
-      case V5_6:
-      case V6_2:
-      case V6_3:
-      case V6_4:
-      case V6_5:
-        this.searchFilteringName = "_source";
-        this.indicesExistParam = "?allow_no_indices=false";
-        this.exactFieldType = "keyword";
-        this.stringFieldType = "text";
-        this.indexProperty = "true";
-        this.rawFieldsKey = "_source";
-        break;
-      case V2_4:
-      default:
-        this.searchFilteringName = "fields";
-        this.indicesExistParam = "";
-        this.exactFieldType = "string";
-        this.stringFieldType = "string";
-        this.indexProperty = "not_analyzed";
-        this.rawFieldsKey = "fields";
-        break;
-    }
+    this.ignoreUnmapped = false;
+    this.usePostV5Type = version.isV6OrLater();
+    this.omitTypeFromSearch = version.isV7OrLater();
+    this.versionDiscoveryUrl = version.isV6OrLater() ? "/%s*" : "/%s*/_aliases";
+    this.searchFilteringName = "_source";
+    this.indicesExistParam = "?allow_no_indices=false";
+    this.exactFieldType = "keyword";
+    this.stringFieldType = "text";
+    this.indexProperty = "true";
+    this.rawFieldsKey = "_source";
   }
 
   void setIgnoreUnmapped(JsonObject properties) {
@@ -100,8 +84,12 @@
     return usePostV5Type;
   }
 
-  String getType(String preV6Type) {
-    return usePostV5Type() ? POST_V5_TYPE : preV6Type;
+  boolean omitTypeFromSearch() {
+    return omitTypeFromSearch;
+  }
+
+  String getType(String type) {
+    return usePostV5Type() ? POST_V5_TYPE : type;
   }
 
   String getVersionDiscoveryUrl(String name) {
diff --git a/java/com/google/gerrit/elasticsearch/ElasticSetting.java b/java/com/google/gerrit/elasticsearch/ElasticSetting.java
index 6fd234d..98c313c 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticSetting.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticSetting.java
@@ -22,33 +22,33 @@
   private static final ImmutableMap<String, String> CUSTOM_CHAR_MAPPING =
       ImmutableMap.of("\\u002E", "\\u0020", "\\u005F", "\\u0020");
 
-  static SettingProperties createSetting() {
-    ElasticSetting.Builder settings = new ElasticSetting.Builder();
-    settings.addCharFilter();
-    settings.addAnalyzer();
-    return settings.build();
+  static SettingProperties createSetting(ElasticConfiguration config) {
+    return new ElasticSetting.Builder().addCharFilter().addAnalyzer().build(config);
   }
 
   static class Builder {
     private final ImmutableMap.Builder<String, FieldProperties> fields =
         new ImmutableMap.Builder<>();
 
-    SettingProperties build() {
+    SettingProperties build(ElasticConfiguration config) {
       SettingProperties properties = new SettingProperties();
       properties.analysis = fields.build();
+      properties.numberOfShards = config.numberOfShards;
+      properties.numberOfReplicas = config.numberOfReplicas;
       return properties;
     }
 
-    void addCharFilter() {
+    Builder addCharFilter() {
       FieldProperties charMapping = new FieldProperties("mapping");
       charMapping.mappings = getCustomCharMappings(CUSTOM_CHAR_MAPPING);
 
       FieldProperties charFilter = new FieldProperties();
       charFilter.customMapping = charMapping;
       fields.put("char_filter", charFilter);
+      return this;
     }
 
-    void addAnalyzer() {
+    Builder addAnalyzer() {
       FieldProperties customAnalyzer = new FieldProperties("custom");
       customAnalyzer.tokenizer = "standard";
       customAnalyzer.charFilter = new String[] {"custom_mapping"};
@@ -57,6 +57,7 @@
       FieldProperties analyzer = new FieldProperties();
       analyzer.customWithCharFilter = customAnalyzer;
       fields.put("analyzer", analyzer);
+      return this;
     }
 
     private static String[] getCustomCharMappings(ImmutableMap<String, String> map) {
@@ -72,6 +73,8 @@
 
   static class SettingProperties {
     Map<String, FieldProperties> analysis;
+    Integer numberOfShards;
+    Integer numberOfReplicas;
   }
 
   static class FieldProperties {
diff --git a/java/com/google/gerrit/elasticsearch/ElasticVersion.java b/java/com/google/gerrit/elasticsearch/ElasticVersion.java
index 4c98df1..b69f8f9 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticVersion.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticVersion.java
@@ -18,12 +18,12 @@
 import java.util.regex.Pattern;
 
 public enum ElasticVersion {
-  V2_4("2.4.*"),
   V5_6("5.6.*"),
   V6_2("6.2.*"),
   V6_3("6.3.*"),
   V6_4("6.4.*"),
-  V6_5("6.5.*");
+  V6_5("6.5.*"),
+  V7_0("7.0.*");
 
   private final String version;
   private final Pattern pattern;
@@ -56,8 +56,16 @@
     return Joiner.on(", ").join(ElasticVersion.values());
   }
 
-  public boolean isV6() {
-    return version.startsWith("6.");
+  public boolean isV6OrLater() {
+    return isAtLeastVersion(6);
+  }
+
+  public boolean isV7OrLater() {
+    return isAtLeastVersion(7);
+  }
+
+  private boolean isAtLeastVersion(int v) {
+    return Integer.valueOf(version.split("\\.")[0]) >= v;
   }
 
   @Override
diff --git a/java/com/google/gerrit/extensions/api/accounts/AccountApi.java b/java/com/google/gerrit/extensions/api/accounts/AccountApi.java
index b7fce5f..056565e 100644
--- a/java/com/google/gerrit/extensions/api/accounts/AccountApi.java
+++ b/java/com/google/gerrit/extensions/api/accounts/AccountApi.java
@@ -112,6 +112,8 @@
   List<DeletedDraftCommentInfo> deleteDraftComments(DeleteDraftCommentsInput input)
       throws RestApiException;
 
+  void setName(String name) throws RestApiException;
+
   /**
    * A default implementation which allows source compatibility when adding new methods to the
    * interface.
@@ -310,5 +312,10 @@
         throws RestApiException {
       throw new NotImplementedException();
     }
+
+    @Override
+    public void setName(String name) throws RestApiException {
+      throw new NotImplementedException();
+    }
   }
 }
diff --git a/java/com/google/gerrit/extensions/api/changes/ChangeApi.java b/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
index 0150e1e..ff7e068 100644
--- a/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
+++ b/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
@@ -93,9 +93,13 @@
 
   void setPrivate(boolean value, @Nullable String message) throws RestApiException;
 
-  void setWorkInProgress(String message) throws RestApiException;
+  default void setPrivate(boolean value) throws RestApiException {
+    setPrivate(value, null);
+  }
 
-  void setReadyForReview(String message) throws RestApiException;
+  void setWorkInProgress(@Nullable String message) throws RestApiException;
+
+  void setReadyForReview(@Nullable String message) throws RestApiException;
 
   default void setWorkInProgress() throws RestApiException {
     setWorkInProgress(null);
diff --git a/java/com/google/gerrit/extensions/api/projects/CommitApi.java b/java/com/google/gerrit/extensions/api/projects/CommitApi.java
index 6084962..6b3c1fb 100644
--- a/java/com/google/gerrit/extensions/api/projects/CommitApi.java
+++ b/java/com/google/gerrit/extensions/api/projects/CommitApi.java
@@ -16,6 +16,7 @@
 
 import com.google.gerrit.extensions.api.changes.ChangeApi;
 import com.google.gerrit.extensions.api.changes.CherryPickInput;
+import com.google.gerrit.extensions.api.changes.IncludedInInfo;
 import com.google.gerrit.extensions.restapi.NotImplementedException;
 import com.google.gerrit.extensions.restapi.RestApiException;
 
@@ -23,11 +24,18 @@
 
   ChangeApi cherryPick(CherryPickInput input) throws RestApiException;
 
+  IncludedInInfo includedIn() throws RestApiException;
+
   /** A default implementation for source compatibility when adding new methods to the interface. */
   class NotImplemented implements CommitApi {
     @Override
     public ChangeApi cherryPick(CherryPickInput input) throws RestApiException {
       throw new NotImplementedException();
     }
+
+    @Override
+    public IncludedInInfo includedIn() throws RestApiException {
+      throw new NotImplementedException();
+    }
   }
 }
diff --git a/java/com/google/gerrit/extensions/api/projects/RefInfo.java b/java/com/google/gerrit/extensions/api/projects/RefInfo.java
index c573600..dde6e4b 100644
--- a/java/com/google/gerrit/extensions/api/projects/RefInfo.java
+++ b/java/com/google/gerrit/extensions/api/projects/RefInfo.java
@@ -14,8 +14,19 @@
 
 package com.google.gerrit.extensions.api.projects;
 
+import com.google.common.base.MoreObjects;
+
 public class RefInfo {
   public String ref;
   public String revision;
   public Boolean canDelete;
+
+  @Override
+  public String toString() {
+    return MoreObjects.toStringHelper(this)
+        .add("ref", ref)
+        .add("revision", revision)
+        .add("canDelete", canDelete)
+        .toString();
+  }
 }
diff --git a/java/com/google/gerrit/extensions/common/testing/BUILD b/java/com/google/gerrit/extensions/common/testing/BUILD
index 94fecbf..6679104 100644
--- a/java/com/google/gerrit/extensions/common/testing/BUILD
+++ b/java/com/google/gerrit/extensions/common/testing/BUILD
@@ -1,6 +1,6 @@
 java_library(
     name = "common-test-util",
-    testonly = 1,
+    testonly = True,
     srcs = glob(["**/*.java"]),
     visibility = ["//visibility:public"],
     deps = [
diff --git a/java/com/google/gerrit/extensions/restapi/testing/BUILD b/java/com/google/gerrit/extensions/restapi/testing/BUILD
index 434591e..3417cae2 100644
--- a/java/com/google/gerrit/extensions/restapi/testing/BUILD
+++ b/java/com/google/gerrit/extensions/restapi/testing/BUILD
@@ -1,6 +1,6 @@
 java_library(
     name = "restapi-test-util",
-    testonly = 1,
+    testonly = True,
     srcs = glob(["**/*.java"]),
     visibility = ["//visibility:public"],
     deps = [
diff --git a/java/com/google/gerrit/git/testing/BUILD b/java/com/google/gerrit/git/testing/BUILD
index 4900339..497510d 100644
--- a/java/com/google/gerrit/git/testing/BUILD
+++ b/java/com/google/gerrit/git/testing/BUILD
@@ -1,4 +1,4 @@
-package(default_testonly = 1)
+package(default_testonly = True)
 
 java_library(
     name = "testing",
diff --git a/java/com/google/gerrit/gpg/testing/BUILD b/java/com/google/gerrit/gpg/testing/BUILD
index ff8fecf..0282d3a 100644
--- a/java/com/google/gerrit/gpg/testing/BUILD
+++ b/java/com/google/gerrit/gpg/testing/BUILD
@@ -1,6 +1,6 @@
 java_library(
     name = "gpg-test-util",
-    testonly = 1,
+    testonly = True,
     srcs = glob(["**/*.java"]),
     visibility = ["//visibility:public"],
     deps = [
diff --git a/java/com/google/gerrit/httpd/GitOverHttpServlet.java b/java/com/google/gerrit/httpd/GitOverHttpServlet.java
index e74c4b2..08ff8a7 100644
--- a/java/com/google/gerrit/httpd/GitOverHttpServlet.java
+++ b/java/com/google/gerrit/httpd/GitOverHttpServlet.java
@@ -49,7 +49,6 @@
 import com.google.inject.name.Named;
 import java.io.IOException;
 import java.time.Duration;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Set;
@@ -259,13 +258,6 @@
       up.setTimeout(config.getTimeout());
       up.setPreUploadHook(PreUploadHookChain.newChain(Lists.newArrayList(preUploadHooks)));
       up.setPostUploadHook(PostUploadHookChain.newChain(Lists.newArrayList(postUploadHooks)));
-      if (config.enableProtocolV2()) {
-        String header = req.getHeader("Git-Protocol");
-        if (header != null) {
-          String[] params = header.split(":");
-          up.setExtraParameters(Arrays.asList(params));
-        }
-      }
       ProjectState state = (ProjectState) req.getAttribute(ATT_STATE);
       for (UploadPackInitializer initializer : uploadPackInitializers) {
         initializer.init(state.getNameKey(), up);
diff --git a/java/com/google/gerrit/httpd/UrlModule.java b/java/com/google/gerrit/httpd/UrlModule.java
index 1dd176a..94855b9 100644
--- a/java/com/google/gerrit/httpd/UrlModule.java
+++ b/java/com/google/gerrit/httpd/UrlModule.java
@@ -134,7 +134,8 @@
 
           @Override
           protected void doGet(HttpServletRequest req, HttpServletResponse rsp) throws IOException {
-            toGerrit(req.getRequestURI().substring(req.getContextPath().length()), req, rsp);
+            String path = req.getRequestURI().substring(req.getContextPath().length());
+            toGerrit(path, req, rsp, true);
           }
         });
   }
@@ -276,8 +277,16 @@
 
   static void toGerrit(String target, HttpServletRequest req, HttpServletResponse rsp)
       throws IOException {
+    toGerrit(target, req, rsp, false);
+  }
+
+  static void toGerrit(String target, HttpServletRequest req, HttpServletResponse rsp, boolean gwt)
+      throws IOException {
     final StringBuilder url = new StringBuilder();
     url.append(req.getContextPath());
+    if (gwt) {
+      url.append("/#");
+    }
     url.append(target);
     rsp.sendRedirect(url.toString());
   }
diff --git a/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java b/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
index ea01809..9cf8b58 100644
--- a/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
+++ b/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
@@ -55,9 +55,10 @@
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 
-@SuppressWarnings("serial")
 @Singleton
 class BecomeAnyAccountLoginServlet extends HttpServlet {
+  private static final long serialVersionUID = 1L;
+
   private final DynamicItem<WebSession> webSession;
   private final SchemaFactory<ReviewDb> schema;
   private final Accounts accounts;
diff --git a/java/com/google/gerrit/httpd/auth/ldap/LdapLoginServlet.java b/java/com/google/gerrit/httpd/auth/ldap/LdapLoginServlet.java
index 116ad6d..a09866e 100644
--- a/java/com/google/gerrit/httpd/auth/ldap/LdapLoginServlet.java
+++ b/java/com/google/gerrit/httpd/auth/ldap/LdapLoginServlet.java
@@ -46,9 +46,10 @@
 import org.w3c.dom.Element;
 
 /** Handles username/password based authentication against the directory. */
-@SuppressWarnings("serial")
 @Singleton
 class LdapLoginServlet extends HttpServlet {
+  private static final long serialVersionUID = 1L;
+
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
   private final AccountManager accountManager;
diff --git a/java/com/google/gerrit/httpd/auth/openid/LoginForm.java b/java/com/google/gerrit/httpd/auth/openid/LoginForm.java
index adf6458..283cd50 100644
--- a/java/com/google/gerrit/httpd/auth/openid/LoginForm.java
+++ b/java/com/google/gerrit/httpd/auth/openid/LoginForm.java
@@ -52,9 +52,10 @@
 import org.w3c.dom.Element;
 
 /** Handles OpenID based login flow. */
-@SuppressWarnings("serial")
 @Singleton
 class LoginForm extends HttpServlet {
+  private static final long serialVersionUID = 1L;
+
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
   private static final ImmutableMap<String, String> ALL_PROVIDERS =
diff --git a/java/com/google/gerrit/httpd/auth/openid/OpenIdLoginServlet.java b/java/com/google/gerrit/httpd/auth/openid/OpenIdLoginServlet.java
index 23cf468..1536741 100644
--- a/java/com/google/gerrit/httpd/auth/openid/OpenIdLoginServlet.java
+++ b/java/com/google/gerrit/httpd/auth/openid/OpenIdLoginServlet.java
@@ -23,9 +23,10 @@
 import javax.servlet.http.HttpServletResponse;
 
 /** Handles the {@code /OpenID} URL for web based single-sign-on. */
-@SuppressWarnings("serial")
 @Singleton
 class OpenIdLoginServlet extends HttpServlet {
+  private static final long serialVersionUID = 1L;
+
   private final OpenIdServiceImpl impl;
 
   @Inject
diff --git a/java/com/google/gerrit/httpd/gitweb/GitLogoServlet.java b/java/com/google/gerrit/httpd/gitweb/GitLogoServlet.java
index d57e629..0344bc7 100644
--- a/java/com/google/gerrit/httpd/gitweb/GitLogoServlet.java
+++ b/java/com/google/gerrit/httpd/gitweb/GitLogoServlet.java
@@ -32,9 +32,10 @@
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-@SuppressWarnings("serial")
 @Singleton
 class GitLogoServlet extends HttpServlet {
+  private static final long serialVersionUID = 1L;
+
   private final long modified;
   private final byte[] raw;
 
diff --git a/java/com/google/gerrit/httpd/gitweb/GitwebCssServlet.java b/java/com/google/gerrit/httpd/gitweb/GitwebCssServlet.java
index feee3ba..0074035 100644
--- a/java/com/google/gerrit/httpd/gitweb/GitwebCssServlet.java
+++ b/java/com/google/gerrit/httpd/gitweb/GitwebCssServlet.java
@@ -32,10 +32,13 @@
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-@SuppressWarnings("serial")
 abstract class GitwebCssServlet extends HttpServlet {
+  private static final long serialVersionUID = 1L;
+
   @Singleton
   static class Site extends GitwebCssServlet {
+    private static final long serialVersionUID = 1L;
+
     @Inject
     Site(SitePaths paths) throws IOException {
       super(paths.site_css);
@@ -44,6 +47,8 @@
 
   @Singleton
   static class Default extends GitwebCssServlet {
+    private static final long serialVersionUID = 1L;
+
     @Inject
     Default(GitwebCgiConfig gwcc) throws IOException {
       super(gwcc.getGitwebCss());
diff --git a/java/com/google/gerrit/httpd/gitweb/GitwebJavaScriptServlet.java b/java/com/google/gerrit/httpd/gitweb/GitwebJavaScriptServlet.java
index 82dd901..d035036 100644
--- a/java/com/google/gerrit/httpd/gitweb/GitwebJavaScriptServlet.java
+++ b/java/com/google/gerrit/httpd/gitweb/GitwebJavaScriptServlet.java
@@ -32,9 +32,10 @@
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-@SuppressWarnings("serial")
 @Singleton
 class GitwebJavaScriptServlet extends HttpServlet {
+  private static final long serialVersionUID = 1L;
+
   private final long modified;
   private final byte[] raw;
 
diff --git a/java/com/google/gerrit/httpd/gitweb/GitwebServlet.java b/java/com/google/gerrit/httpd/gitweb/GitwebServlet.java
index 917ea91..6cb094f 100644
--- a/java/com/google/gerrit/httpd/gitweb/GitwebServlet.java
+++ b/java/com/google/gerrit/httpd/gitweb/GitwebServlet.java
@@ -89,9 +89,10 @@
 import org.eclipse.jgit.lib.Repository;
 
 /** Invokes {@code gitweb.cgi} for the project given in {@code p}. */
-@SuppressWarnings("serial")
 @Singleton
 class GitwebServlet extends HttpServlet {
+  private static final long serialVersionUID = 1L;
+
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
   private static final String PROJECT_LIST_ACTION = "project_list";
diff --git a/java/com/google/gerrit/httpd/init/WebAppInitializer.java b/java/com/google/gerrit/httpd/init/WebAppInitializer.java
index 624b307..aadfeba 100644
--- a/java/com/google/gerrit/httpd/init/WebAppInitializer.java
+++ b/java/com/google/gerrit/httpd/init/WebAppInitializer.java
@@ -74,6 +74,8 @@
 import com.google.gerrit.server.git.WorkQueue;
 import com.google.gerrit.server.index.IndexModule;
 import com.google.gerrit.server.index.IndexModule.IndexType;
+import com.google.gerrit.server.index.OnlineUpgrader;
+import com.google.gerrit.server.index.VersionManager;
 import com.google.gerrit.server.mail.SignedTokenEmailTokenVerifier;
 import com.google.gerrit.server.mail.receive.MailReceiver;
 import com.google.gerrit.server.mail.send.SmtpEmailSender;
@@ -339,21 +341,6 @@
     modules.add(new SignedTokenEmailTokenVerifier.Module());
     modules.add(new LocalMergeSuperSetComputation.Module());
     modules.add(new AuditModule());
-
-    // Plugin module needs to be inserted *before* the index module.
-    // There is the concept of LifecycleModule, in Gerrit's own extension
-    // to Guice, which has these:
-    //  listener().to(SomeClassImplementingLifecycleListener.class);
-    // and the start() methods of each such listener are executed in the
-    // order they are declared.
-    // Makes sure that PluginLoader.start() is executed before the
-    // LuceneIndexModule.start() so that plugins get loaded and the respective
-    // Guice modules installed so that the on-line reindexing will happen
-    // with the proper classes (e.g. group backends, custom Prolog
-    // predicates) and the associated rules ready to be evaluated.
-    modules.add(new PluginModule());
-
-    modules.add(new RestApiModule());
     modules.add(new GpgModule(config));
     modules.add(new StartupChecks.Module());
 
@@ -361,6 +348,12 @@
     // work queue can get stuck waiting on index futures that will never return.
     modules.add(createIndexModule());
 
+    modules.add(new PluginModule());
+    if (VersionManager.getOnlineUpgrade(config)) {
+      modules.add(new OnlineUpgrader.Module());
+    }
+
+    modules.add(new RestApiModule());
     modules.add(new WorkQueue.Module());
     modules.add(new GerritInstanceNameModule());
     modules.add(
@@ -392,9 +385,9 @@
   private Module createIndexModule() {
     switch (indexType) {
       case LUCENE:
-        return LuceneIndexModule.latestVersionWithOnlineUpgrade(false);
+        return LuceneIndexModule.latestVersion(false);
       case ELASTICSEARCH:
-        return ElasticIndexModule.latestVersionWithOnlineUpgrade(false);
+        return ElasticIndexModule.latestVersion(false);
       default:
         throw new IllegalStateException("unsupported index.type = " + indexType);
     }
@@ -443,7 +436,10 @@
     modules.add(sysInjector.getInstance(GetUserFilter.Module.class));
 
     // StaticModule contains a "/*" wildcard, place it last.
-    modules.add(sysInjector.getInstance(StaticModule.class));
+    GerritOptions opts = sysInjector.getInstance(GerritOptions.class);
+    if (opts.enableMasterFeatures()) {
+      modules.add(sysInjector.getInstance(StaticModule.class));
+    }
 
     return sysInjector.createChildInjector(modules);
   }
diff --git a/java/com/google/gerrit/httpd/raw/CatServlet.java b/java/com/google/gerrit/httpd/raw/CatServlet.java
index 4b5c227..70ae3ae 100644
--- a/java/com/google/gerrit/httpd/raw/CatServlet.java
+++ b/java/com/google/gerrit/httpd/raw/CatServlet.java
@@ -49,9 +49,10 @@
  * this site, and will execute it with the site's own protection domain. This opens a massive
  * security hole so we package the content into a zip file.
  */
-@SuppressWarnings("serial")
 @Singleton
 public class CatServlet extends HttpServlet {
+  private static final long serialVersionUID = 1L;
+
   private final Provider<ReviewDb> requestDb;
   private final ChangeEditUtil changeEditUtil;
   private final PatchSetUtil psUtil;
diff --git a/java/com/google/gerrit/httpd/raw/HostPageServlet.java b/java/com/google/gerrit/httpd/raw/HostPageServlet.java
index 160732e..b593723 100644
--- a/java/com/google/gerrit/httpd/raw/HostPageServlet.java
+++ b/java/com/google/gerrit/httpd/raw/HostPageServlet.java
@@ -66,9 +66,10 @@
 import org.w3c.dom.Node;
 
 /** Sends the Gerrit host page to clients. */
-@SuppressWarnings("serial")
 @Singleton
 public class HostPageServlet extends HttpServlet {
+  private static final long serialVersionUID = 1L;
+
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
   private static final String HPD_ID = "gerrit_hostpagedata";
diff --git a/java/com/google/gerrit/httpd/raw/LegacyGerritServlet.java b/java/com/google/gerrit/httpd/raw/LegacyGerritServlet.java
index e12f0a5..7bc56a6 100644
--- a/java/com/google/gerrit/httpd/raw/LegacyGerritServlet.java
+++ b/java/com/google/gerrit/httpd/raw/LegacyGerritServlet.java
@@ -34,9 +34,10 @@
  * as it would lose any history token that appears in the URL. Instead we send an HTML page which
  * instructs the browser to replace the URL, but preserve the history token.
  */
-@SuppressWarnings("serial")
 @Singleton
 public class LegacyGerritServlet extends HttpServlet {
+  private static final long serialVersionUID = 1L;
+
   private final byte[] raw;
   private final byte[] compressed;
 
diff --git a/java/com/google/gerrit/httpd/raw/SshInfoServlet.java b/java/com/google/gerrit/httpd/raw/SshInfoServlet.java
index 1d1fe6cc..1605360 100644
--- a/java/com/google/gerrit/httpd/raw/SshInfoServlet.java
+++ b/java/com/google/gerrit/httpd/raw/SshInfoServlet.java
@@ -46,9 +46,10 @@
  *  Port 8010
  * }</pre>
  */
-@SuppressWarnings("serial")
 @Singleton
 public class SshInfoServlet extends HttpServlet {
+  private static final long serialVersionUID = 1L;
+
   private final SshInfo sshd;
 
   @Inject
diff --git a/java/com/google/gerrit/httpd/raw/StaticModule.java b/java/com/google/gerrit/httpd/raw/StaticModule.java
index 062d776..fc6762b 100644
--- a/java/com/google/gerrit/httpd/raw/StaticModule.java
+++ b/java/com/google/gerrit/httpd/raw/StaticModule.java
@@ -142,10 +142,8 @@
         });
     if (!options.headless()) {
       install(new CoreStaticModule());
+      install(new PolyGerritModule());
     }
-
-    install(new PolyGerritModule());
-
     if (options.enableGwtUi()) {
       install(new GwtUiModule());
     }
diff --git a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
index 519a218..bd6b836 100644
--- a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
+++ b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
@@ -1544,8 +1544,9 @@
     return new TemporaryBuffer.Heap(est, max);
   }
 
-  @SuppressWarnings("serial")
   private static class AmbiguousViewException extends Exception {
+    private static final long serialVersionUID = 1L;
+
     AmbiguousViewException(String message) {
       super(message);
     }
diff --git a/java/com/google/gerrit/httpd/rpc/GerritJsonServlet.java b/java/com/google/gerrit/httpd/rpc/GerritJsonServlet.java
index 16e82e9..8dbebd0 100644
--- a/java/com/google/gerrit/httpd/rpc/GerritJsonServlet.java
+++ b/java/com/google/gerrit/httpd/rpc/GerritJsonServlet.java
@@ -41,8 +41,9 @@
 import javax.servlet.http.HttpServletResponse;
 
 /** Base JSON servlet to ensure the current user is not forged. */
-@SuppressWarnings("serial")
 final class GerritJsonServlet extends JsonServlet<GerritJsonServlet.GerritCall> {
+  private static final long serialVersionUID = 1L;
+
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
   private static final ThreadLocal<GerritCall> currentCall = new ThreadLocal<>();
diff --git a/java/com/google/gerrit/lucene/LuceneIndexModule.java b/java/com/google/gerrit/lucene/LuceneIndexModule.java
index 121b96b..302a2da 100644
--- a/java/com/google/gerrit/lucene/LuceneIndexModule.java
+++ b/java/com/google/gerrit/lucene/LuceneIndexModule.java
@@ -29,29 +29,24 @@
 
 public class LuceneIndexModule extends AbstractIndexModule {
   public static LuceneIndexModule singleVersionAllLatest(int threads, boolean slave) {
-    return new LuceneIndexModule(ImmutableMap.of(), threads, false, slave);
+    return new LuceneIndexModule(ImmutableMap.of(), threads, slave);
   }
 
   public static LuceneIndexModule singleVersionWithExplicitVersions(
       Map<String, Integer> versions, int threads, boolean slave) {
-    return new LuceneIndexModule(versions, threads, false, slave);
+    return new LuceneIndexModule(versions, threads, slave);
   }
 
-  public static LuceneIndexModule latestVersionWithOnlineUpgrade(boolean slave) {
-    return new LuceneIndexModule(null, 0, true, slave);
-  }
-
-  public static LuceneIndexModule latestVersionWithoutOnlineUpgrade(boolean slave) {
-    return new LuceneIndexModule(null, 0, false, slave);
+  public static LuceneIndexModule latestVersion(boolean slave) {
+    return new LuceneIndexModule(null, 0, slave);
   }
 
   static boolean isInMemoryTest(Config cfg) {
     return cfg.getBoolean("index", "lucene", "testInmemory", false);
   }
 
-  private LuceneIndexModule(
-      Map<String, Integer> singleVersions, int threads, boolean onlineUpgrade, boolean slave) {
-    super(singleVersions, threads, onlineUpgrade, slave);
+  private LuceneIndexModule(Map<String, Integer> singleVersions, int threads, boolean slave) {
+    super(singleVersions, threads, slave);
   }
 
   @Override
diff --git a/java/com/google/gerrit/pgm/Daemon.java b/java/com/google/gerrit/pgm/Daemon.java
index a777a1c..95b2af0 100644
--- a/java/com/google/gerrit/pgm/Daemon.java
+++ b/java/com/google/gerrit/pgm/Daemon.java
@@ -81,6 +81,7 @@
 import com.google.gerrit.server.group.PeriodicGroupIndexer;
 import com.google.gerrit.server.index.IndexModule;
 import com.google.gerrit.server.index.IndexModule.IndexType;
+import com.google.gerrit.server.index.OnlineUpgrader;
 import com.google.gerrit.server.index.VersionManager;
 import com.google.gerrit.server.mail.SignedTokenEmailTokenVerifier;
 import com.google.gerrit.server.mail.receive.MailReceiver;
@@ -401,19 +402,6 @@
     modules.add(new DropWizardMetricMaker.RestModule());
     modules.add(new LogFileCompressor.Module());
 
-    // Plugin module needs to be inserted *before* the index module.
-    // There is the concept of LifecycleModule, in Gerrit's own extension
-    // to Guice, which has these:
-    //  listener().to(SomeClassImplementingLifecycleListener.class);
-    // and the start() methods of each such listener are executed in the
-    // order they are declared.
-    // Makes sure that PluginLoader.start() is executed before the
-    // LuceneIndexModule.start() so that plugins get loaded and the respective
-    // Guice modules installed so that the on-line reindexing will happen
-    // with the proper classes (e.g. group backends, custom Prolog
-    // predicates) and the associated rules ready to be evaluated.
-    modules.add(new PluginModule());
-
     // Index module shutdown must happen before work queue shutdown, otherwise
     // work queue can get stuck waiting on index futures that will never return.
     modules.add(createIndexModule());
@@ -449,6 +437,12 @@
       modules.add(new AuditModule());
     }
     modules.add(new SignedTokenEmailTokenVerifier.Module());
+    modules.add(new PluginModule());
+    if (VersionManager.getOnlineUpgrade(config)
+        // Schema upgrade is handled by OnlineNoteDbMigrator in this case.
+        && !migrateToNoteDb()) {
+      modules.add(new OnlineUpgrader.Module());
+    }
     modules.add(new RestApiModule());
     modules.add(new GpgModule(config));
     modules.add(new StartupChecks.Module());
@@ -517,19 +511,11 @@
     if (luceneModule != null) {
       return luceneModule;
     }
-    boolean onlineUpgrade =
-        VersionManager.getOnlineUpgrade(config)
-            // Schema upgrade is handled by OnlineNoteDbMigrator in this case.
-            && !migrateToNoteDb();
     switch (indexType) {
       case LUCENE:
-        return onlineUpgrade
-            ? LuceneIndexModule.latestVersionWithOnlineUpgrade(slave)
-            : LuceneIndexModule.latestVersionWithoutOnlineUpgrade(slave);
+        return LuceneIndexModule.latestVersion(slave);
       case ELASTICSEARCH:
-        return onlineUpgrade
-            ? ElasticIndexModule.latestVersionWithOnlineUpgrade(slave)
-            : ElasticIndexModule.latestVersionWithoutOnlineUpgrade(slave);
+        return ElasticIndexModule.latestVersion(slave);
       default:
         throw new IllegalStateException("unsupported index.type = " + indexType);
     }
@@ -612,7 +598,10 @@
     modules.add(sysInjector.getInstance(GetUserFilter.Module.class));
 
     // StaticModule contains a "/*" wildcard, place it last.
-    modules.add(sysInjector.getInstance(StaticModule.class));
+    GerritOptions opts = sysInjector.getInstance(GerritOptions.class);
+    if (opts.enableMasterFeatures()) {
+      modules.add(sysInjector.getInstance(StaticModule.class));
+    }
 
     return sysInjector.createChildInjector(modules);
   }
diff --git a/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java b/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java
index 15e21fe..6f89058 100644
--- a/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java
+++ b/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java
@@ -41,6 +41,7 @@
 import com.google.gerrit.extensions.common.GpgKeyInfo;
 import com.google.gerrit.extensions.common.GroupInfo;
 import com.google.gerrit.extensions.common.Input;
+import com.google.gerrit.extensions.common.NameInput;
 import com.google.gerrit.extensions.common.SshKeyInfo;
 import com.google.gerrit.extensions.restapi.IdString;
 import com.google.gerrit.extensions.restapi.Response;
@@ -74,6 +75,7 @@
 import com.google.gerrit.server.restapi.account.PostWatchedProjects;
 import com.google.gerrit.server.restapi.account.PutActive;
 import com.google.gerrit.server.restapi.account.PutAgreement;
+import com.google.gerrit.server.restapi.account.PutName;
 import com.google.gerrit.server.restapi.account.PutStatus;
 import com.google.gerrit.server.restapi.account.SetDiffPreferences;
 import com.google.gerrit.server.restapi.account.SetEditPreferences;
@@ -132,6 +134,7 @@
   private final PutStatus putStatus;
   private final GetGroups getGroups;
   private final EmailApiImpl.Factory emailApi;
+  private final PutName putName;
 
   @Inject
   AccountApiImpl(
@@ -173,6 +176,7 @@
       PutStatus putStatus,
       GetGroups getGroups,
       EmailApiImpl.Factory emailApi,
+      PutName putName,
       @Assisted AccountResource account) {
     this.account = account;
     this.accountLoaderFactory = ailf;
@@ -213,6 +217,7 @@
     this.putStatus = putStatus;
     this.getGroups = getGroups;
     this.emailApi = emailApi;
+    this.putName = putName;
   }
 
   @Override
@@ -577,4 +582,15 @@
       throw asRestApiException("Cannot delete draft comments", e);
     }
   }
+
+  @Override
+  public void setName(String name) throws RestApiException {
+    NameInput input = new NameInput();
+    input.name = name;
+    try {
+      putName.apply(account, input);
+    } catch (Exception e) {
+      throw asRestApiException("Cannot set account name", e);
+    }
+  }
 }
diff --git a/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java b/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
index 358a3a8..44e2e12 100644
--- a/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
+++ b/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
@@ -342,7 +342,7 @@
   }
 
   @Override
-  public void setWorkInProgress(String message) throws RestApiException {
+  public void setWorkInProgress(@Nullable String message) throws RestApiException {
     try {
       setWip.apply(change, new WorkInProgressOp.Input(message));
     } catch (Exception e) {
@@ -351,7 +351,7 @@
   }
 
   @Override
-  public void setReadyForReview(String message) throws RestApiException {
+  public void setReadyForReview(@Nullable String message) throws RestApiException {
     try {
       setReady.apply(change, new WorkInProgressOp.Input(message));
     } catch (Exception e) {
diff --git a/java/com/google/gerrit/server/api/projects/CommitApiImpl.java b/java/com/google/gerrit/server/api/projects/CommitApiImpl.java
index a81e0de..49eec6e 100644
--- a/java/com/google/gerrit/server/api/projects/CommitApiImpl.java
+++ b/java/com/google/gerrit/server/api/projects/CommitApiImpl.java
@@ -19,10 +19,12 @@
 import com.google.gerrit.extensions.api.changes.ChangeApi;
 import com.google.gerrit.extensions.api.changes.Changes;
 import com.google.gerrit.extensions.api.changes.CherryPickInput;
+import com.google.gerrit.extensions.api.changes.IncludedInInfo;
 import com.google.gerrit.extensions.api.projects.CommitApi;
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.server.project.CommitResource;
 import com.google.gerrit.server.restapi.change.CherryPickCommit;
+import com.google.gerrit.server.restapi.project.CommitIncludedIn;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 
@@ -33,13 +35,18 @@
 
   private final Changes changes;
   private final CherryPickCommit cherryPickCommit;
+  private final CommitIncludedIn includedIn;
   private final CommitResource commitResource;
 
   @Inject
   CommitApiImpl(
-      Changes changes, CherryPickCommit cherryPickCommit, @Assisted CommitResource commitResource) {
+      Changes changes,
+      CherryPickCommit cherryPickCommit,
+      CommitIncludedIn includedIn,
+      @Assisted CommitResource commitResource) {
     this.changes = changes;
     this.cherryPickCommit = cherryPickCommit;
+    this.includedIn = includedIn;
     this.commitResource = commitResource;
   }
 
@@ -51,4 +58,13 @@
       throw asRestApiException("Cannot cherry pick", e);
     }
   }
+
+  @Override
+  public IncludedInInfo includedIn() throws RestApiException {
+    try {
+      return includedIn.apply(commitResource);
+    } catch (Exception e) {
+      throw asRestApiException("Could not extract IncludedIn data", e);
+    }
+  }
 }
diff --git a/java/com/google/gerrit/server/auth/ldap/LdapGroupBackend.java b/java/com/google/gerrit/server/auth/ldap/LdapGroupBackend.java
index c338cd3..87a4abf 100644
--- a/java/com/google/gerrit/server/auth/ldap/LdapGroupBackend.java
+++ b/java/com/google/gerrit/server/auth/ldap/LdapGroupBackend.java
@@ -34,6 +34,7 @@
 import com.google.gerrit.server.account.GroupMembership;
 import com.google.gerrit.server.account.externalids.ExternalId;
 import com.google.gerrit.server.auth.ldap.Helper.LdapSchema;
+import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectState;
 import com.google.inject.Inject;
@@ -52,6 +53,7 @@
 import javax.naming.ldap.LdapName;
 import javax.naming.ldap.Rdn;
 import javax.security.auth.login.LoginException;
+import org.eclipse.jgit.lib.Config;
 
 /** Implementation of GroupBackend for the LDAP group system. */
 public class LdapGroupBackend implements GroupBackend {
@@ -65,6 +67,7 @@
   private final LoadingCache<String, Boolean> existsCache;
   private final ProjectCache projectCache;
   private final Provider<CurrentUser> userProvider;
+  private final Config gerritConfig;
 
   @Inject
   LdapGroupBackend(
@@ -72,12 +75,14 @@
       @Named(GROUP_CACHE) LoadingCache<String, Set<AccountGroup.UUID>> membershipCache,
       @Named(GROUP_EXIST_CACHE) LoadingCache<String, Boolean> existsCache,
       ProjectCache projectCache,
-      Provider<CurrentUser> userProvider) {
+      Provider<CurrentUser> userProvider,
+      @GerritServerConfig Config gerritConfig) {
     this.helper = helper;
     this.membershipCache = membershipCache;
     this.projectCache = projectCache;
     this.existsCache = existsCache;
     this.userProvider = userProvider;
+    this.gerritConfig = gerritConfig;
   }
 
   private boolean isLdapUUID(AccountGroup.UUID uuid) {
@@ -178,7 +183,7 @@
     if (id == null) {
       return GroupMembership.EMPTY;
     }
-    return new LdapGroupMembership(membershipCache, projectCache, id);
+    return new LdapGroupMembership(membershipCache, projectCache, id, gerritConfig);
   }
 
   private static String findId(Collection<ExternalId> extIds) {
diff --git a/java/com/google/gerrit/server/auth/ldap/LdapGroupMembership.java b/java/com/google/gerrit/server/auth/ldap/LdapGroupMembership.java
index 7f0bd7b..f5406c2 100644
--- a/java/com/google/gerrit/server/auth/ldap/LdapGroupMembership.java
+++ b/java/com/google/gerrit/server/auth/ldap/LdapGroupMembership.java
@@ -22,20 +22,24 @@
 import java.util.HashSet;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
+import org.eclipse.jgit.lib.Config;
 
 class LdapGroupMembership implements GroupMembership {
   private final LoadingCache<String, Set<AccountGroup.UUID>> membershipCache;
   private final ProjectCache projectCache;
   private final String id;
+  private final boolean guessRelevantGroups;
   private GroupMembership membership;
 
   LdapGroupMembership(
       LoadingCache<String, Set<AccountGroup.UUID>> membershipCache,
       ProjectCache projectCache,
-      String id) {
+      String id,
+      Config gerritConfig) {
     this.membershipCache = membershipCache;
     this.projectCache = projectCache;
     this.id = id;
+    this.guessRelevantGroups = gerritConfig.getBoolean("ldap", "guessRelevantGroups", true);
   }
 
   @Override
@@ -56,7 +60,9 @@
   @Override
   public Set<AccountGroup.UUID> getKnownGroups() {
     Set<AccountGroup.UUID> g = new HashSet<>(get().getKnownGroups());
-    g.retainAll(projectCache.guessRelevantGroupUUIDs());
+    if (guessRelevantGroups) {
+      g.retainAll(projectCache.guessRelevantGroupUUIDs());
+    }
     return g;
   }
 
diff --git a/java/com/google/gerrit/server/cache/testing/BUILD b/java/com/google/gerrit/server/cache/testing/BUILD
index 9a9f1ef..f87c006 100644
--- a/java/com/google/gerrit/server/cache/testing/BUILD
+++ b/java/com/google/gerrit/server/cache/testing/BUILD
@@ -1,4 +1,4 @@
-package(default_testonly = 1)
+package(default_testonly = True)
 
 java_library(
     name = "testing",
diff --git a/java/com/google/gerrit/server/change/ConsistencyChecker.java b/java/com/google/gerrit/server/change/ConsistencyChecker.java
index a379f2c..128297f 100644
--- a/java/com/google/gerrit/server/change/ConsistencyChecker.java
+++ b/java/com/google/gerrit/server/change/ConsistencyChecker.java
@@ -503,7 +503,7 @@
     List<ProblemInfo> currProblems = new ArrayList<>(3);
     currProblems.add(notFound);
     if (deleteOldPatchSetProblem != null) {
-      currProblems.add(insertPatchSetProblem);
+      currProblems.add(deleteOldPatchSetProblem);
     }
     currProblems.add(insertPatchSetProblem);
 
diff --git a/java/com/google/gerrit/server/change/ReviewerJson.java b/java/com/google/gerrit/server/change/ReviewerJson.java
index 6502569..ef2c926 100644
--- a/java/com/google/gerrit/server/change/ReviewerJson.java
+++ b/java/com/google/gerrit/server/change/ReviewerJson.java
@@ -23,6 +23,7 @@
 import com.google.gerrit.common.data.SubmitRecord;
 import com.google.gerrit.extensions.api.changes.ReviewerInfo;
 import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.mail.Address;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
@@ -77,12 +78,15 @@
       if (cd == null || !cd.getId().equals(rsrc.getChangeId())) {
         cd = changeDataFactory.create(db.get(), rsrc.getChangeResource().getNotes());
       }
-      ReviewerInfo info =
-          format(
-              new ReviewerInfo(rsrc.getReviewerUser().getAccountId().get()),
-              rsrc.getReviewerUser().getAccountId(),
-              cd);
-      loader.put(info);
+      ReviewerInfo info;
+      if (rsrc.isByEmail()) {
+        Address address = rsrc.getReviewerByEmail();
+        info = ReviewerInfo.byEmail(address.getName(), address.getEmail());
+      } else {
+        Account.Id reviewerAccountId = rsrc.getReviewerUser().getAccountId();
+        info = format(new ReviewerInfo(reviewerAccountId.get()), reviewerAccountId, cd);
+        loader.put(info);
+      }
       infos.add(info);
     }
     loader.fill();
@@ -94,19 +98,21 @@
     return format(ImmutableList.<ReviewerResource>of(rsrc));
   }
 
-  public ReviewerInfo format(ReviewerInfo out, Account.Id reviewer, ChangeData cd)
+  public ReviewerInfo format(ReviewerInfo out, Account.Id reviewerAccountId, ChangeData cd)
       throws OrmException, PermissionBackendException {
     PatchSet.Id psId = cd.change().currentPatchSetId();
     return format(
         out,
-        reviewer,
+        reviewerAccountId,
         cd,
-        approvalsUtil.byPatchSetUser(
-            db.get(), cd.notes(), psId, new Account.Id(out._accountId), null, null));
+        approvalsUtil.byPatchSetUser(db.get(), cd.notes(), psId, reviewerAccountId, null, null));
   }
 
   public ReviewerInfo format(
-      ReviewerInfo out, Account.Id reviewer, ChangeData cd, Iterable<PatchSetApproval> approvals)
+      ReviewerInfo out,
+      Account.Id reviewerAccountId,
+      ChangeData cd,
+      Iterable<PatchSetApproval> approvals)
       throws OrmException, PermissionBackendException {
     LabelTypes labelTypes = cd.getLabelTypes();
 
@@ -123,7 +129,7 @@
     PatchSet ps = cd.currentPatchSet();
     if (ps != null) {
       PermissionBackend.ForChange perm =
-          permissionBackend.absentUser(reviewer).database(db).change(cd);
+          permissionBackend.absentUser(reviewerAccountId).database(db).change(cd);
 
       for (SubmitRecord rec : submitRuleEvaluator.evaluate(cd)) {
         if (rec.labels == null) {
diff --git a/java/com/google/gerrit/server/config/AllProjectsName.java b/java/com/google/gerrit/server/config/AllProjectsName.java
index 198d5c5..7719e38 100644
--- a/java/com/google/gerrit/server/config/AllProjectsName.java
+++ b/java/com/google/gerrit/server/config/AllProjectsName.java
@@ -17,8 +17,9 @@
 import com.google.gerrit.reviewdb.client.Project;
 
 /** Special name of the project that all projects derive from. */
-@SuppressWarnings("serial")
 public class AllProjectsName extends Project.NameKey {
+  private static final long serialVersionUID = 1L;
+
   public AllProjectsName(String name) {
     super(name);
   }
diff --git a/java/com/google/gerrit/server/config/AllUsersName.java b/java/com/google/gerrit/server/config/AllUsersName.java
index ff28be4..22d29a4 100644
--- a/java/com/google/gerrit/server/config/AllUsersName.java
+++ b/java/com/google/gerrit/server/config/AllUsersName.java
@@ -17,8 +17,9 @@
 import com.google.gerrit.reviewdb.client.Project;
 
 /** Special name of the project in which meta data for all users is stored. */
-@SuppressWarnings("serial")
 public class AllUsersName extends Project.NameKey {
+  private static final long serialVersionUID = 1L;
+
   public AllUsersName(String name) {
     super(name);
   }
diff --git a/java/com/google/gerrit/server/config/DefaultUrlFormatter.java b/java/com/google/gerrit/server/config/DefaultUrlFormatter.java
index 70fb465..060ee3f 100644
--- a/java/com/google/gerrit/server/config/DefaultUrlFormatter.java
+++ b/java/com/google/gerrit/server/config/DefaultUrlFormatter.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.config;
 
+import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.inject.AbstractModule;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -27,7 +28,8 @@
   public static class Module extends AbstractModule {
     @Override
     protected void configure() {
-      bind(UrlFormatter.class).to(DefaultUrlFormatter.class);
+      DynamicItem.itemOf(binder(), UrlFormatter.class);
+      DynamicItem.bind(binder(), UrlFormatter.class).to(DefaultUrlFormatter.class);
     }
   }
 
diff --git a/java/com/google/gerrit/server/documentation/QueryDocumentationExecutor.java b/java/com/google/gerrit/server/documentation/QueryDocumentationExecutor.java
index 66cb380..569399b 100644
--- a/java/com/google/gerrit/server/documentation/QueryDocumentationExecutor.java
+++ b/java/com/google/gerrit/server/documentation/QueryDocumentationExecutor.java
@@ -129,8 +129,9 @@
     return parser != null && searcher != null;
   }
 
-  @SuppressWarnings("serial")
   public static class DocQueryException extends Exception {
+    private static final long serialVersionUID = 1L;
+
     DocQueryException() {}
 
     DocQueryException(String msg) {
diff --git a/java/com/google/gerrit/server/git/TransferConfig.java b/java/com/google/gerrit/server/git/TransferConfig.java
index dde524f..55b9448 100644
--- a/java/com/google/gerrit/server/git/TransferConfig.java
+++ b/java/com/google/gerrit/server/git/TransferConfig.java
@@ -29,7 +29,6 @@
   private final long maxObjectSizeLimit;
   private final String maxObjectSizeLimitFormatted;
   private final boolean inheritProjectMaxObjectSizeLimit;
-  private final boolean enableProtocolV2;
 
   @Inject
   TransferConfig(@GerritServerConfig Config cfg) {
@@ -46,7 +45,6 @@
     maxObjectSizeLimitFormatted = cfg.getString("receive", null, "maxObjectSizeLimit");
     inheritProjectMaxObjectSizeLimit =
         cfg.getBoolean("receive", "inheritProjectMaxObjectSizeLimit", false);
-    enableProtocolV2 = cfg.getBoolean("receive", "enableProtocolV2", false);
 
     packConfig = new PackConfig();
     packConfig.setDeltaCompress(false);
@@ -74,8 +72,4 @@
   public boolean inheritProjectMaxObjectSizeLimit() {
     return inheritProjectMaxObjectSizeLimit;
   }
-
-  public boolean enableProtocolV2() {
-    return enableProtocolV2;
-  }
 }
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
index 6267590..6cbce07 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
@@ -728,44 +728,39 @@
       addMessage("");
       addMessage("Updated Changes:");
       boolean edit = magicBranch != null && (magicBranch.edit || magicBranch.draft);
-      Boolean isPrivate = null;
-      Boolean wip = null;
-      if (magicBranch != null) {
-        if (magicBranch.isPrivate) {
-          isPrivate = true;
-        } else if (magicBranch.removePrivate) {
-          isPrivate = false;
-        }
-        if (magicBranch.workInProgress) {
-          wip = true;
-        } else if (magicBranch.ready) {
-          wip = false;
-        }
-      }
       for (ReplaceRequest u : updated) {
         String subject;
+        Change change = u.notes.getChange();
         if (edit) {
           try {
             subject = receivePack.getRevWalk().parseCommit(u.newCommitId).getShortMessage();
           } catch (IOException e) {
             // Log and fall back to original change subject
             logger.atWarning().withCause(e).log("failed to get subject for edit patch set");
-            subject = u.notes.getChange().getSubject();
+            subject = change.getSubject();
           }
         } else {
           subject = u.info.getSubject();
         }
 
-        if (isPrivate == null) {
-          isPrivate = u.notes.getChange().isPrivate();
-        }
-        if (wip == null) {
-          wip = u.notes.getChange().isWorkInProgress();
+        boolean isPrivate = change.isPrivate();
+        boolean wip = change.isWorkInProgress();
+        if (magicBranch != null) {
+          if (magicBranch.isPrivate) {
+            isPrivate = true;
+          } else if (magicBranch.removePrivate) {
+            isPrivate = false;
+          }
+          if (magicBranch.workInProgress) {
+            wip = true;
+          } else if (magicBranch.ready) {
+            wip = false;
+          }
         }
 
         ChangeReportFormatter.Input input =
             ChangeReportFormatter.Input.builder()
-                .setChange(u.notes.getChange())
+                .setChange(change)
                 .setSubject(subject)
                 .setIsEdit(edit)
                 .setIsPrivate(isPrivate)
diff --git a/java/com/google/gerrit/server/group/db/GroupsUpdate.java b/java/com/google/gerrit/server/group/db/GroupsUpdate.java
index 6477f31..82a1bf4 100644
--- a/java/com/google/gerrit/server/group/db/GroupsUpdate.java
+++ b/java/com/google/gerrit/server/group/db/GroupsUpdate.java
@@ -204,6 +204,7 @@
     }
 
     UpdateResult result = updateGroupInNoteDbWithRetry(groupUuid, groupUpdate);
+    updateNameInProjectConfigsIfNecessary(result);
     updateCachesOnGroupUpdate(result);
     dispatchAuditEventsOnGroupUpdate(result, updatedOn.get());
   }
@@ -345,21 +346,37 @@
   }
 
   private void updateCachesOnGroupCreation(InternalGroup createdGroup) throws IOException {
+    // By UUID is used for the index and hence should be evicted before refreshing the index.
+    groupCache.evict(createdGroup.getGroupUUID());
     indexer.get().index(createdGroup.getGroupUUID());
-    for (Account.Id modifiedMember : createdGroup.getMembers()) {
-      groupIncludeCache.evictGroupsWithMember(modifiedMember);
-    }
-    for (AccountGroup.UUID modifiedSubgroup : createdGroup.getSubgroups()) {
-      groupIncludeCache.evictParentGroupsOf(modifiedSubgroup);
-    }
+    // These caches use the result from the index and hence must be evicted after refreshing the
+    // index.
+    groupCache.evict(createdGroup.getId());
+    groupCache.evict(createdGroup.getNameKey());
+    createdGroup.getMembers().forEach(groupIncludeCache::evictGroupsWithMember);
+    createdGroup.getSubgroups().forEach(groupIncludeCache::evictParentGroupsOf);
   }
 
   private void updateCachesOnGroupUpdate(UpdateResult result) throws IOException {
+    // By UUID is used for the index and hence should be evicted before refreshing the index.
+    groupCache.evict(result.getGroupUuid());
+    indexer.get().index(result.getGroupUuid());
+    // These caches use the result from the index and hence must be evicted after refreshing the
+    // index.
+    groupCache.evict(result.getGroupId());
+    groupCache.evict(result.getGroupName());
+    result.getPreviousGroupName().ifPresent(groupCache::evict);
+
+    result.getAddedMembers().forEach(groupIncludeCache::evictGroupsWithMember);
+    result.getDeletedMembers().forEach(groupIncludeCache::evictGroupsWithMember);
+    result.getAddedSubgroups().forEach(groupIncludeCache::evictParentGroupsOf);
+    result.getDeletedSubgroups().forEach(groupIncludeCache::evictParentGroupsOf);
+  }
+
+  private void updateNameInProjectConfigsIfNecessary(UpdateResult result) {
     if (result.getPreviousGroupName().isPresent()) {
       AccountGroup.NameKey previousName = result.getPreviousGroupName().get();
-      groupCache.evict(previousName);
 
-      // TODO(aliceks): After switching to NoteDb, consider to use a BatchRefUpdate.
       @SuppressWarnings("unused")
       Future<?> possiblyIgnoredError =
           renameGroupOpFactory
@@ -370,15 +387,6 @@
                   result.getGroupName().get())
               .start(0, TimeUnit.MILLISECONDS);
     }
-    groupCache.evict(result.getGroupUuid());
-    groupCache.evict(result.getGroupId());
-    groupCache.evict(result.getGroupName());
-    indexer.get().index(result.getGroupUuid());
-
-    result.getAddedMembers().forEach(groupIncludeCache::evictGroupsWithMember);
-    result.getDeletedMembers().forEach(groupIncludeCache::evictGroupsWithMember);
-    result.getAddedSubgroups().forEach(groupIncludeCache::evictParentGroupsOf);
-    result.getDeletedSubgroups().forEach(groupIncludeCache::evictParentGroupsOf);
   }
 
   private void dispatchAuditEventsOnGroupCreation(InternalGroup createdGroup) {
diff --git a/java/com/google/gerrit/server/group/db/testing/BUILD b/java/com/google/gerrit/server/group/db/testing/BUILD
index 6961b65..c13abba 100644
--- a/java/com/google/gerrit/server/group/db/testing/BUILD
+++ b/java/com/google/gerrit/server/group/db/testing/BUILD
@@ -2,7 +2,7 @@
 
 java_library(
     name = "testing",
-    testonly = 1,
+    testonly = True,
     srcs = glob(["*.java"]),
     deps = [
         "//java/com/google/gerrit/common:server",
diff --git a/java/com/google/gerrit/server/group/testing/BUILD b/java/com/google/gerrit/server/group/testing/BUILD
index 8b8cd00..3ef712c 100644
--- a/java/com/google/gerrit/server/group/testing/BUILD
+++ b/java/com/google/gerrit/server/group/testing/BUILD
@@ -2,7 +2,7 @@
 
 java_library(
     name = "testing",
-    testonly = 1,
+    testonly = True,
     srcs = glob(["*.java"]),
     deps = [
         "//java/com/google/gerrit/common:server",
diff --git a/java/com/google/gerrit/server/index/AbstractIndexModule.java b/java/com/google/gerrit/server/index/AbstractIndexModule.java
index 12aedfd..352ea4b 100644
--- a/java/com/google/gerrit/server/index/AbstractIndexModule.java
+++ b/java/com/google/gerrit/server/index/AbstractIndexModule.java
@@ -14,8 +14,6 @@
 
 package com.google.gerrit.server.index;
 
-import static com.google.common.base.Preconditions.checkArgument;
-
 import com.google.gerrit.index.IndexConfig;
 import com.google.gerrit.index.Schema;
 import com.google.gerrit.index.project.ProjectIndex;
@@ -35,17 +33,11 @@
 
   private final int threads;
   private final Map<String, Integer> singleVersions;
-  private final boolean onlineUpgrade;
   private final boolean slave;
 
-  protected AbstractIndexModule(
-      Map<String, Integer> singleVersions, int threads, boolean onlineUpgrade, boolean slave) {
-    if (singleVersions != null) {
-      checkArgument(!onlineUpgrade, "online upgrade is incompatible with single version map");
-    }
+  protected AbstractIndexModule(Map<String, Integer> singleVersions, int threads, boolean slave) {
     this.singleVersions = singleVersions;
     this.threads = threads;
-    this.onlineUpgrade = onlineUpgrade;
     this.slave = slave;
   }
 
@@ -113,9 +105,6 @@
       Class<? extends VersionManager> versionManagerClass = getVersionManager();
       bind(VersionManager.class).to(versionManagerClass);
       listener().to(versionManagerClass);
-      if (onlineUpgrade) {
-        listener().to(OnlineUpgrader.class);
-      }
     }
   }
 }
diff --git a/java/com/google/gerrit/server/index/OnlineUpgrader.java b/java/com/google/gerrit/server/index/OnlineUpgrader.java
index 9fc3aa9..bfcf55f 100644
--- a/java/com/google/gerrit/server/index/OnlineUpgrader.java
+++ b/java/com/google/gerrit/server/index/OnlineUpgrader.java
@@ -15,10 +15,18 @@
 package com.google.gerrit.server.index;
 
 import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.inject.Inject;
 
 /** Listener to handle upgrading index schema versions at startup. */
 public class OnlineUpgrader implements LifecycleListener {
+  public static class Module extends LifecycleModule {
+    @Override
+    protected void configure() {
+      listener().to(OnlineUpgrader.class);
+    }
+  }
+
   private final VersionManager versionManager;
 
   @Inject
diff --git a/java/com/google/gerrit/server/notedb/CommentJsonMigrator.java b/java/com/google/gerrit/server/notedb/CommentJsonMigrator.java
index b250a34..361c435 100644
--- a/java/com/google/gerrit/server/notedb/CommentJsonMigrator.java
+++ b/java/com/google/gerrit/server/notedb/CommentJsonMigrator.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.server.notedb;
 
 import static com.google.common.collect.ImmutableList.toImmutableList;
-import static com.google.gerrit.server.notedb.RevisionNote.MAX_NOTE_SZ;
 import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
 
 import com.google.common.collect.ImmutableList;
@@ -39,6 +38,7 @@
 import org.eclipse.jgit.lib.CommitBuilder;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.ObjectLoader;
 import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
@@ -220,7 +220,11 @@
       NoteMap noteMap = NoteMap.read(reader, c);
       for (Note note : noteMap) {
         // Match pre-parsing logic in RevisionNote#parse().
-        byte[] raw = reader.open(note.getData(), OBJ_BLOB).getCachedBytes(MAX_NOTE_SZ);
+        ObjectLoader objectLoader = reader.open(note.getData(), OBJ_BLOB);
+        if (objectLoader.isLarge()) {
+          throw new IOException(String.format("Comment note %s is too large", note.name()));
+        }
+        byte[] raw = objectLoader.getCachedBytes();
         MutableInteger p = new MutableInteger();
         RevisionNote.trimLeadingEmptyLines(raw, p);
         if (!ChangeRevisionNote.isJson(raw, p.value)) {
diff --git a/java/com/google/gerrit/server/plugincontext/PluginSetEntryContext.java b/java/com/google/gerrit/server/plugincontext/PluginSetEntryContext.java
index afffbef..2268c07 100644
--- a/java/com/google/gerrit/server/plugincontext/PluginSetEntryContext.java
+++ b/java/com/google/gerrit/server/plugincontext/PluginSetEntryContext.java
@@ -103,8 +103,9 @@
    *
    * <p>Should only be used in exceptional cases to get direct access to the extension
    * implementation. If possible the extension should be invoked through {@link
-   * #run(ExtensionImplConsumer)}, {@link #run(ExtensionImplConsumer, Class)}, {@link
-   * #call(ExtensionImplFunction)} and {@link #call(CheckedExtensionImplFunction, Class)}.
+   * #run(PluginContext.ExtensionImplConsumer)}, {@link #run(PluginContext.ExtensionImplConsumer,
+   * java.lang.Class)}, {@link #call(PluginContext.ExtensionImplFunction)} and {@link
+   * #call(PluginContext.CheckedExtensionImplFunction, java.lang.Class)}.
    *
    * @return the implementation of this extension
    */
diff --git a/java/com/google/gerrit/server/project/testing/BUILD b/java/com/google/gerrit/server/project/testing/BUILD
index ca1ffae..f221e00 100644
--- a/java/com/google/gerrit/server/project/testing/BUILD
+++ b/java/com/google/gerrit/server/project/testing/BUILD
@@ -1,6 +1,6 @@
 java_library(
     name = "project-test-util",
-    testonly = 1,
+    testonly = True,
     srcs = glob(["*.java"]),
     visibility = ["//visibility:public"],
     deps = [
diff --git a/java/com/google/gerrit/server/query/change/HashtagPredicate.java b/java/com/google/gerrit/server/query/change/HashtagPredicate.java
index bea5688..95ecf89 100644
--- a/java/com/google/gerrit/server/query/change/HashtagPredicate.java
+++ b/java/com/google/gerrit/server/query/change/HashtagPredicate.java
@@ -20,7 +20,9 @@
 
 public class HashtagPredicate extends ChangeIndexPredicate {
   public HashtagPredicate(String hashtag) {
-    super(ChangeField.HASHTAG, HashtagsUtil.cleanupHashtag(hashtag));
+    // Use toLowerCase without locale to match behavior in ChangeField.
+    // TODO(dborowitz): Change both.
+    super(ChangeField.HASHTAG, HashtagsUtil.cleanupHashtag(hashtag).toLowerCase());
   }
 
   @Override
diff --git a/java/com/google/gerrit/sshd/AbstractGitCommand.java b/java/com/google/gerrit/sshd/AbstractGitCommand.java
index 921b416..c49ae82 100644
--- a/java/com/google/gerrit/sshd/AbstractGitCommand.java
+++ b/java/com/google/gerrit/sshd/AbstractGitCommand.java
@@ -29,8 +29,6 @@
 import org.kohsuke.args4j.Argument;
 
 public abstract class AbstractGitCommand extends BaseCommand {
-  private static final String GIT_PROTOCOL = "GIT_PROTOCOL";
-
   @Argument(index = 0, metaVar = "PROJECT.git", required = true, usage = "project name")
   protected ProjectState projectState;
 
@@ -47,15 +45,9 @@
   protected Repository repo;
   protected Project.NameKey projectName;
   protected Project project;
-  protected String[] extraParameters;
 
   @Override
   public void start(Environment env) {
-    String gitProtocol = env.getEnv().get(GIT_PROTOCOL);
-    if (gitProtocol != null) {
-      extraParameters = gitProtocol.split(":");
-    }
-
     Context ctx = context.subContext(newSession(), context.getCommandLine());
     final Context old = sshScope.set(ctx);
     try {
diff --git a/java/com/google/gerrit/sshd/commands/KillCommand.java b/java/com/google/gerrit/sshd/commands/KillCommand.java
index ef12f5f..df74f86 100644
--- a/java/com/google/gerrit/sshd/commands/KillCommand.java
+++ b/java/com/google/gerrit/sshd/commands/KillCommand.java
@@ -43,7 +43,7 @@
   @Inject private DeleteTask deleteTask;
 
   @Argument(index = 0, multiValued = true, required = true, metaVar = "ID")
-  private final List<String> taskIds = new ArrayList<>();
+  private List<String> taskIds = new ArrayList<>();
 
   @Override
   protected void run() {
diff --git a/java/com/google/gerrit/sshd/commands/Upload.java b/java/com/google/gerrit/sshd/commands/Upload.java
index cb5d449..24a6975 100644
--- a/java/com/google/gerrit/sshd/commands/Upload.java
+++ b/java/com/google/gerrit/sshd/commands/Upload.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.sshd.commands;
 
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.extensions.restapi.AuthException;
@@ -53,6 +52,7 @@
     PermissionBackend.ForProject perm =
         permissionBackend.user(user).project(projectState.getNameKey());
     try {
+
       perm.check(ProjectPermission.RUN_UPLOAD_PACK);
     } catch (AuthException e) {
       throw new Failure(1, "fatal: upload-pack not permitted on this server");
@@ -65,9 +65,6 @@
     up.setPackConfig(config.getPackConfig());
     up.setTimeout(config.getTimeout());
     up.setPostUploadHook(PostUploadHookChain.newChain(Lists.newArrayList(postUploadHooks)));
-    if (config.enableProtocolV2() && extraParameters != null) {
-      up.setExtraParameters(ImmutableList.copyOf(extraParameters));
-    }
 
     List<PreUploadHook> allPreUploadHooks = Lists.newArrayList(preUploadHooks);
     allPreUploadHooks.add(
diff --git a/java/com/google/gerrit/testing/BUILD b/java/com/google/gerrit/testing/BUILD
index 412a071..71efda6 100644
--- a/java/com/google/gerrit/testing/BUILD
+++ b/java/com/google/gerrit/testing/BUILD
@@ -1,6 +1,6 @@
 java_library(
     name = "gerrit-test-util",
-    testonly = 1,
+    testonly = True,
     srcs = glob(["**/*.java"]),
     visibility = ["//visibility:public"],
     exports = [
diff --git a/java/com/google/gerrit/testing/FakeGroupAuditService.java b/java/com/google/gerrit/testing/FakeGroupAuditService.java
index 7c6674b..f2e85cd 100644
--- a/java/com/google/gerrit/testing/FakeGroupAuditService.java
+++ b/java/com/google/gerrit/testing/FakeGroupAuditService.java
@@ -35,8 +35,8 @@
 @Singleton
 public class FakeGroupAuditService implements GroupAuditService {
 
-  private final PluginSetContext<GroupAuditListener> groupAuditListeners;
-  private final PluginSetContext<AuditListener> auditListeners;
+  protected final PluginSetContext<GroupAuditListener> groupAuditListeners;
+  protected final PluginSetContext<AuditListener> auditListeners;
 
   public static class Module extends AbstractModule {
     @Override
diff --git a/java/com/google/gerrit/truth/BUILD b/java/com/google/gerrit/truth/BUILD
index 719ddce..786ae0d 100644
--- a/java/com/google/gerrit/truth/BUILD
+++ b/java/com/google/gerrit/truth/BUILD
@@ -1,6 +1,6 @@
 java_library(
     name = "truth",
-    testonly = 1,
+    testonly = True,
     srcs = glob(["**/*.java"]),
     visibility = ["//visibility:public"],
     deps = [
diff --git a/java/com/google/gwtexpui/safehtml/client/SafeHtml.java b/java/com/google/gwtexpui/safehtml/client/SafeHtml.java
index 2a1ddc0..5b3b9b6 100644
--- a/java/com/google/gwtexpui/safehtml/client/SafeHtml.java
+++ b/java/com/google/gwtexpui/safehtml/client/SafeHtml.java
@@ -28,8 +28,9 @@
 import java.util.List;
 
 /** Immutable string safely placed as HTML without further escaping. */
-@SuppressWarnings("serial")
 public abstract class SafeHtml implements com.google.gwt.safehtml.shared.SafeHtml {
+  private static final long serialVersionUID = 1L;
+
   public static final SafeHtmlResources RESOURCES;
 
   static {
diff --git a/java/com/google/gwtexpui/safehtml/client/SafeHtmlBuilder.java b/java/com/google/gwtexpui/safehtml/client/SafeHtmlBuilder.java
index a926906..cde0f2a 100644
--- a/java/com/google/gwtexpui/safehtml/client/SafeHtmlBuilder.java
+++ b/java/com/google/gwtexpui/safehtml/client/SafeHtmlBuilder.java
@@ -17,8 +17,9 @@
 import com.google.gwt.core.client.GWT;
 
 /** Safely constructs a {@link SafeHtml}, escaping user provided content. */
-@SuppressWarnings("serial")
 public class SafeHtmlBuilder extends SafeHtml {
+  private static final long serialVersionUID = 1L;
+
   private static final Impl impl;
 
   static {
diff --git a/java/com/google/gwtexpui/safehtml/client/SafeHtmlString.java b/java/com/google/gwtexpui/safehtml/client/SafeHtmlString.java
index 889509a..5335170 100644
--- a/java/com/google/gwtexpui/safehtml/client/SafeHtmlString.java
+++ b/java/com/google/gwtexpui/safehtml/client/SafeHtmlString.java
@@ -14,8 +14,9 @@
 
 package com.google.gwtexpui.safehtml.client;
 
-@SuppressWarnings("serial")
 class SafeHtmlString extends SafeHtml {
+  private static final long serialVersionUID = 1L;
+
   private final String html;
 
   SafeHtmlString(String h) {
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
index 5e46a03..6f4549f 100644
--- a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
@@ -52,6 +52,7 @@
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.GerritConfig;
 import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.Sandboxed;
 import com.google.gerrit.acceptance.TestAccount;
 import com.google.gerrit.acceptance.UseSsh;
 import com.google.gerrit.acceptance.testsuite.account.AccountOperations;
@@ -1184,6 +1185,33 @@
   }
 
   @Test
+  public void setName() throws Exception {
+    gApi.accounts().self().setName("Admin McAdminface");
+    assertThat(gApi.accounts().self().get().name).isEqualTo("Admin McAdminface");
+  }
+
+  @Test
+  public void adminCanSetNameOfOtherUser() throws Exception {
+    gApi.accounts().id(user.username).setName("User McUserface");
+    assertThat(gApi.accounts().id(user.username).get().name).isEqualTo("User McUserface");
+  }
+
+  @Test
+  public void userCannotSetNameOfOtherUser() throws Exception {
+    setApiUser(user);
+    exception.expect(AuthException.class);
+    gApi.accounts().id(admin.username).setName("Admin McAdminface");
+  }
+
+  @Test
+  @Sandboxed
+  public void userCanSetNameOfOtherUserWithModifyAccountPermission() throws Exception {
+    allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.MODIFY_ACCOUNT);
+    gApi.accounts().id(admin.username).setName("Admin McAdminface");
+    assertThat(gApi.accounts().id(admin.username).get().name).isEqualTo("Admin McAdminface");
+  }
+
+  @Test
   public void fetchUserBranch() throws Exception {
     setApiUser(user);
 
diff --git a/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java b/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java
index ade0f3c..e2d390e 100644
--- a/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java
@@ -16,6 +16,7 @@
 
 import static com.google.common.collect.ImmutableList.toImmutableList;
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth8.assertThat;
 import static com.google.gerrit.acceptance.GitUtil.deleteRef;
 import static com.google.gerrit.acceptance.GitUtil.fetch;
 import static com.google.gerrit.acceptance.api.group.GroupAssert.assertGroupInfo;
@@ -204,6 +205,15 @@
   }
 
   @Test
+  public void cachedGroupByNameIsUpdatedOnCreation() throws Exception {
+    String newGroupName = name("newGroup");
+    AccountGroup.NameKey nameKey = new AccountGroup.NameKey(newGroupName);
+    assertThat(groupCache.get(nameKey)).isEmpty();
+    gApi.groups().create(newGroupName);
+    assertThat(groupCache.get(nameKey)).isPresent();
+  }
+
+  @Test
   public void addExistingMember_OK() throws Exception {
     String g = "Administrators";
     assertMembers(g, admin);
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/CommitIncludedInIT.java b/javatests/com/google/gerrit/acceptance/api/project/CommitIncludedInIT.java
similarity index 88%
rename from javatests/com/google/gerrit/acceptance/rest/project/CommitIncludedInIT.java
rename to javatests/com/google/gerrit/acceptance/api/project/CommitIncludedInIT.java
index c0a413b..4b5fe1e 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/CommitIncludedInIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/project/CommitIncludedInIT.java
@@ -12,14 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.acceptance.rest.project;
+package com.google.gerrit.acceptance.api.project;
 
 import static com.google.common.truth.Truth.assertThat;
 import static org.eclipse.jgit.lib.Constants.R_TAGS;
 
 import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.NoHttpd;
 import com.google.gerrit.acceptance.PushOneCommit.Result;
-import com.google.gerrit.acceptance.RestResponse;
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.extensions.api.changes.IncludedInInfo;
 import com.google.gerrit.extensions.api.changes.ReviewInput;
@@ -28,6 +28,7 @@
 import org.eclipse.jgit.lib.ObjectId;
 import org.junit.Test;
 
+@NoHttpd
 public class CommitIncludedInIT extends AbstractDaemonTest {
   @Test
   public void includedInOpenChange() throws Exception {
@@ -60,10 +61,6 @@
   }
 
   private IncludedInInfo getIncludedIn(ObjectId id) throws Exception {
-    RestResponse r =
-        userRestSession.get("/projects/" + project.get() + "/commits/" + id.name() + "/in");
-    IncludedInInfo result = newGson().fromJson(r.getReader(), IncludedInInfo.class);
-    r.consume();
-    return result;
+    return gApi.projects().name(project.get()).commit(id.name()).includedIn();
   }
 }
diff --git a/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java b/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
index a5ff746..015dd31 100644
--- a/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
+++ b/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
@@ -1413,6 +1413,7 @@
 
     PushResult pr =
         GitUtil.pushHead(testRepo, "refs/for/foo%base=" + rBase.getCommit().name(), false, false);
+    assertThat(pr.getMessages()).containsMatch("changes: .*new: 1.*done");
 
     // BatchUpdate implementations differ in how they hook into progress monitors. We mostly just
     // care that there is a new change.
diff --git a/javatests/com/google/gerrit/acceptance/git/BUILD b/javatests/com/google/gerrit/acceptance/git/BUILD
index 3541fec..e222a4f 100644
--- a/javatests/com/google/gerrit/acceptance/git/BUILD
+++ b/javatests/com/google/gerrit/acceptance/git/BUILD
@@ -12,7 +12,7 @@
 
 java_library(
     name = "push_for_review",
-    testonly = 1,
+    testonly = True,
     srcs = ["AbstractPushForReview.java"],
     deps = [
         "//java/com/google/gerrit/acceptance:lib",
@@ -22,7 +22,7 @@
 
 java_library(
     name = "submodule_util",
-    testonly = 1,
+    testonly = True,
     srcs = ["AbstractSubmoduleSubscription.java"],
     deps = ["//java/com/google/gerrit/acceptance:lib"],
 )
diff --git a/javatests/com/google/gerrit/acceptance/pgm/BUILD b/javatests/com/google/gerrit/acceptance/pgm/BUILD
index e0d2f86..e8cf516 100644
--- a/javatests/com/google/gerrit/acceptance/pgm/BUILD
+++ b/javatests/com/google/gerrit/acceptance/pgm/BUILD
@@ -41,7 +41,7 @@
 
 java_library(
     name = "util",
-    testonly = 1,
+    testonly = True,
     srcs = [
         "AbstractReindexTests.java",
         "IndexUpgradeController.java",
diff --git a/javatests/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java b/javatests/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java
index 29a5bd0..bb7cff7 100644
--- a/javatests/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java
+++ b/javatests/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java
@@ -26,11 +26,6 @@
 public class ElasticReindexIT extends AbstractReindexTests {
 
   @ConfigSuite.Default
-  public static Config elasticsearchV2() {
-    return getConfig(ElasticVersion.V2_4);
-  }
-
-  @ConfigSuite.Config
   public static Config elasticsearchV5() {
     return getConfig(ElasticVersion.V5_6);
   }
@@ -40,6 +35,11 @@
     return getConfig(ElasticVersion.V6_5);
   }
 
+  @ConfigSuite.Config
+  public static Config elasticsearchV7() {
+    return getConfig(ElasticVersion.V7_0);
+  }
+
   @Override
   public void configureIndex(Injector injector) throws Exception {
     createAllIndexes(injector);
diff --git a/javatests/com/google/gerrit/acceptance/rest/BUILD b/javatests/com/google/gerrit/acceptance/rest/BUILD
index b94a98d..40abd6b 100644
--- a/javatests/com/google/gerrit/acceptance/rest/BUILD
+++ b/javatests/com/google/gerrit/acceptance/rest/BUILD
@@ -12,7 +12,7 @@
 
 java_library(
     name = "util",
-    testonly = 1,
+    testonly = True,
     srcs = [
         "AbstractRestApiBindingsTest.java",
     ],
diff --git a/javatests/com/google/gerrit/acceptance/rest/account/BUILD b/javatests/com/google/gerrit/acceptance/rest/account/BUILD
index 217d716..433b854 100644
--- a/javatests/com/google/gerrit/acceptance/rest/account/BUILD
+++ b/javatests/com/google/gerrit/acceptance/rest/account/BUILD
@@ -9,7 +9,7 @@
 
 java_library(
     name = "util",
-    testonly = 1,
+    testonly = True,
     srcs = [
         "AccountAssert.java",
         "CapabilityInfo.java",
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/BUILD b/javatests/com/google/gerrit/acceptance/rest/change/BUILD
index d955f1b..9a65378 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/BUILD
+++ b/javatests/com/google/gerrit/acceptance/rest/change/BUILD
@@ -30,7 +30,7 @@
 
 java_library(
     name = "submit_util",
-    testonly = 1,
+    testonly = True,
     srcs = SUBMIT_UTIL_SRCS,
     deps = [
         "//java/com/google/gerrit/acceptance:lib",
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/ChangeReviewersByEmailIT.java b/javatests/com/google/gerrit/acceptance/rest/change/ChangeReviewersByEmailIT.java
index 257c88b..dc71c1f 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/ChangeReviewersByEmailIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/ChangeReviewersByEmailIT.java
@@ -22,8 +22,8 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.NoHttpd;
 import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.RestResponse;
 import com.google.gerrit.extensions.api.changes.AddReviewerInput;
 import com.google.gerrit.extensions.api.changes.AddReviewerResult;
 import com.google.gerrit.extensions.api.changes.ReviewInput;
@@ -35,11 +35,12 @@
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.mail.Address;
 import com.google.gerrit.testing.FakeEmailSender.Message;
+import com.google.gson.reflect.TypeToken;
+import java.lang.reflect.Type;
 import java.util.List;
 import org.junit.Before;
 import org.junit.Test;
 
-@NoHttpd
 public class ChangeReviewersByEmailIT extends AbstractDaemonTest {
 
   @Before
@@ -96,6 +97,34 @@
   }
 
   @Test
+  public void listReviewersByEmail() throws Exception {
+    assume().that(notesMigration.readChanges()).isTrue();
+    AccountInfo acc = new AccountInfo("Foo Bar", "foo.bar@gerritcodereview.com");
+
+    for (ReviewerState state : ImmutableList.of(ReviewerState.CC, ReviewerState.REVIEWER)) {
+      PushOneCommit.Result r = createChange();
+
+      AddReviewerInput input = new AddReviewerInput();
+      input.reviewer = toRfcAddressString(acc);
+      input.state = state;
+      gApi.changes().id(r.getChangeId()).addReviewer(input);
+
+      RestResponse restResponse =
+          adminRestSession.get("/changes/" + r.getChangeId() + "/reviewers/");
+      restResponse.assertOK();
+      Type type = new TypeToken<List<ReviewerInfo>>() {}.getType();
+      List<ReviewerInfo> reviewers = newGson().fromJson(restResponse.getReader(), type);
+      restResponse.consume();
+
+      assertThat(reviewers).hasSize(1);
+      ReviewerInfo reviewerInfo = Iterables.getOnlyElement(reviewers);
+      assertThat(reviewerInfo._accountId).isNull();
+      assertThat(reviewerInfo.name).isEqualTo(acc.name);
+      assertThat(reviewerInfo.email).isEqualTo(acc.email);
+    }
+  }
+
+  @Test
   public void removeByEmail() throws Exception {
     assume().that(notesMigration.readChanges()).isTrue();
     AccountInfo acc = new AccountInfo("Foo Bar", "foo.bar@gerritcodereview.com");
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/BUILD b/javatests/com/google/gerrit/acceptance/rest/project/BUILD
index dad3ca9..64bd5c3 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/BUILD
+++ b/javatests/com/google/gerrit/acceptance/rest/project/BUILD
@@ -40,7 +40,7 @@
 
 java_library(
     name = "push_tag_util",
-    testonly = 1,
+    testonly = True,
     srcs = [
         "AbstractPushTag.java",
     ],
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/ListBranchesIT.java b/javatests/com/google/gerrit/acceptance/rest/project/ListBranchesIT.java
index bc029ae..b46b087 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/ListBranchesIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/ListBranchesIT.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.acceptance.rest.project;
 
-import static com.google.gerrit.acceptance.rest.project.RefAssert.assertRefNames;
 import static com.google.gerrit.acceptance.rest.project.RefAssert.assertRefs;
 
 import com.google.common.collect.ImmutableList;
@@ -56,10 +55,11 @@
   public void listBranches() throws Exception {
     String master = pushTo("refs/heads/master").getCommit().name();
     String dev = pushTo("refs/heads/dev").getCommit().name();
+    String refsConfig = getRemoteHead(project, RefNames.REFS_CONFIG).name();
     assertRefs(
         ImmutableList.of(
             branch("HEAD", "master", false),
-            branch(RefNames.REFS_CONFIG, null, false),
+            branch(RefNames.REFS_CONFIG, refsConfig, false),
             branch("refs/heads/dev", dev, true),
             branch("refs/heads/master", master, false)),
         list().get());
@@ -90,72 +90,64 @@
 
   @Test
   public void listBranchesUsingPagination() throws Exception {
-    pushTo("refs/heads/master");
-    pushTo("refs/heads/someBranch1");
-    pushTo("refs/heads/someBranch2");
-    pushTo("refs/heads/someBranch3");
+    BranchInfo head = branch("HEAD", "master", false);
+    BranchInfo refsConfig =
+        branch(RefNames.REFS_CONFIG, getRemoteHead(project, RefNames.REFS_CONFIG).name(), false);
+    BranchInfo master =
+        branch("refs/heads/master", pushTo("refs/heads/master").getCommit().getName(), false);
+    BranchInfo branch1 =
+        branch(
+            "refs/heads/someBranch1", pushTo("refs/heads/someBranch1").getCommit().getName(), true);
+    BranchInfo branch2 =
+        branch(
+            "refs/heads/someBranch2", pushTo("refs/heads/someBranch2").getCommit().getName(), true);
+    BranchInfo branch3 =
+        branch(
+            "refs/heads/someBranch3", pushTo("refs/heads/someBranch3").getCommit().getName(), true);
 
     // Using only limit.
-    assertRefNames(
-        ImmutableList.of(
-            "HEAD", RefNames.REFS_CONFIG, "refs/heads/master", "refs/heads/someBranch1"),
-        list().withLimit(4).get());
+    assertRefs(ImmutableList.of(head, refsConfig, master, branch1), list().withLimit(4).get());
 
     // Limit higher than total number of branches.
-    assertRefNames(
-        ImmutableList.of(
-            "HEAD",
-            RefNames.REFS_CONFIG,
-            "refs/heads/master",
-            "refs/heads/someBranch1",
-            "refs/heads/someBranch2",
-            "refs/heads/someBranch3"),
+    assertRefs(
+        ImmutableList.of(head, refsConfig, master, branch1, branch2, branch3),
         list().withLimit(25).get());
 
     // Using start only.
-    assertRefNames(
-        ImmutableList.of(
-            "refs/heads/master",
-            "refs/heads/someBranch1",
-            "refs/heads/someBranch2",
-            "refs/heads/someBranch3"),
-        list().withStart(2).get());
+    assertRefs(ImmutableList.of(master, branch1, branch2, branch3), list().withStart(2).get());
 
     // Skip more branches than the number of available branches.
-    assertRefNames(ImmutableList.<String>of(), list().withStart(7).get());
+    assertRefs(ImmutableList.of(), list().withStart(7).get());
 
     // Ssing start and limit.
-    assertRefNames(
-        ImmutableList.of("refs/heads/master", "refs/heads/someBranch1"),
-        list().withStart(2).withLimit(2).get());
+    assertRefs(ImmutableList.of(master, branch1), list().withStart(2).withLimit(2).get());
   }
 
   @Test
   public void listBranchesUsingFilter() throws Exception {
-    pushTo("refs/heads/master");
-    pushTo("refs/heads/someBranch1");
-    pushTo("refs/heads/someBranch2");
-    pushTo("refs/heads/someBranch3");
+    BranchInfo master =
+        branch("refs/heads/master", pushTo("refs/heads/master").getCommit().getName(), false);
+    BranchInfo branch1 =
+        branch(
+            "refs/heads/someBranch1", pushTo("refs/heads/someBranch1").getCommit().getName(), true);
+    BranchInfo branch2 =
+        branch(
+            "refs/heads/someBranch2", pushTo("refs/heads/someBranch2").getCommit().getName(), true);
+    BranchInfo branch3 =
+        branch(
+            "refs/heads/someBranch3", pushTo("refs/heads/someBranch3").getCommit().getName(), true);
 
     // Using substring.
-    assertRefNames(
-        ImmutableList.of(
-            "refs/heads/someBranch1", "refs/heads/someBranch2", "refs/heads/someBranch3"),
-        list().withSubstring("some").get());
+    assertRefs(ImmutableList.of(branch1, branch2, branch3), list().withSubstring("some").get());
 
-    assertRefNames(
-        ImmutableList.of(
-            "refs/heads/someBranch1", "refs/heads/someBranch2", "refs/heads/someBranch3"),
-        list().withSubstring("Branch").get());
+    assertRefs(ImmutableList.of(branch1, branch2, branch3), list().withSubstring("Branch").get());
 
-    assertRefNames(
-        ImmutableList.of(
-            "refs/heads/someBranch1", "refs/heads/someBranch2", "refs/heads/someBranch3"),
-        list().withSubstring("somebranch").get());
+    assertRefs(
+        ImmutableList.of(branch1, branch2, branch3), list().withSubstring("somebranch").get());
 
     // Using regex.
-    assertRefNames(ImmutableList.of("refs/heads/master"), list().withRegex(".*ast.*r").get());
-    assertRefNames(ImmutableList.of(), list().withRegex(".*AST.*R").get());
+    assertRefs(ImmutableList.of(master), list().withRegex(".*ast.*r").get());
+    assertRefs(ImmutableList.of(), list().withRegex(".*AST.*R").get());
 
     // Conflicting options
     assertBadRequest(list().withSubstring("somebranch").withRegex(".*ast.*r"));
diff --git a/javatests/com/google/gerrit/acceptance/server/mail/BUILD b/javatests/com/google/gerrit/acceptance/server/mail/BUILD
index b5ad425..e21789b 100644
--- a/javatests/com/google/gerrit/acceptance/server/mail/BUILD
+++ b/javatests/com/google/gerrit/acceptance/server/mail/BUILD
@@ -21,7 +21,7 @@
 
 java_library(
     name = "util",
-    testonly = 1,
+    testonly = True,
     srcs = ["AbstractMailIT.java"],
     deps = DEPS + ["//java/com/google/gerrit/acceptance:lib"],
 )
diff --git a/javatests/com/google/gerrit/acceptance/ssh/BUILD b/javatests/com/google/gerrit/acceptance/ssh/BUILD
index a01cd3e..00a0914 100644
--- a/javatests/com/google/gerrit/acceptance/ssh/BUILD
+++ b/javatests/com/google/gerrit/acceptance/ssh/BUILD
@@ -2,7 +2,7 @@
 
 java_library(
     name = "util",
-    testonly = 1,
+    testonly = True,
     srcs = ["AbstractIndexTests.java"],
     deps = ["//java/com/google/gerrit/acceptance:lib"],
 )
diff --git a/javatests/com/google/gerrit/acceptance/ssh/ElasticIndexIT.java b/javatests/com/google/gerrit/acceptance/ssh/ElasticIndexIT.java
index 1e60071..4e88955 100644
--- a/javatests/com/google/gerrit/acceptance/ssh/ElasticIndexIT.java
+++ b/javatests/com/google/gerrit/acceptance/ssh/ElasticIndexIT.java
@@ -25,11 +25,6 @@
 public class ElasticIndexIT extends AbstractIndexTests {
 
   @ConfigSuite.Default
-  public static Config elasticsearchV2() {
-    return getConfig(ElasticVersion.V2_4);
-  }
-
-  @ConfigSuite.Config
   public static Config elasticsearchV5() {
     return getConfig(ElasticVersion.V5_6);
   }
@@ -39,6 +34,11 @@
     return getConfig(ElasticVersion.V6_5);
   }
 
+  @ConfigSuite.Config
+  public static Config elasticsearchV7() {
+    return getConfig(ElasticVersion.V7_0);
+  }
+
   @Override
   public void configureIndex(Injector injector) throws Exception {
     createAllIndexes(injector);
diff --git a/javatests/com/google/gerrit/acceptance/ssh/SshCommandsIT.java b/javatests/com/google/gerrit/acceptance/ssh/SshCommandsIT.java
index e603413..eb79c18 100644
--- a/javatests/com/google/gerrit/acceptance/ssh/SshCommandsIT.java
+++ b/javatests/com/google/gerrit/acceptance/ssh/SshCommandsIT.java
@@ -28,7 +28,6 @@
 import com.google.gerrit.acceptance.Sandboxed;
 import com.google.gerrit.acceptance.UseSsh;
 import com.google.gerrit.common.data.GlobalCapability;
-import com.google.gerrit.sshd.Commands;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
@@ -80,27 +79,35 @@
           "stream-events",
           "test-submit");
 
+  private static final ImmutableList<String> EMPTY = ImmutableList.of();
   private static final ImmutableMap<String, List<String>> MASTER_COMMANDS =
-      ImmutableMap.of(
-          Commands.ROOT,
-          Streams.concat(COMMON_ROOT_COMMANDS.stream(), MASTER_ONLY_ROOT_COMMANDS.stream())
-              .sorted()
-              .collect(toImmutableList()),
-          "index",
-          ImmutableList.of(
-              "changes", "changes-in-project"), // "activate" and "start" are not included
-          "logging",
-          ImmutableList.of("ls", "set"),
-          "plugin",
-          ImmutableList.of("add", "enable", "install", "ls", "reload", "remove", "rm"),
-          "test-submit",
-          ImmutableList.of("rule", "type"));
+      ImmutableMap.<String, List<String>>builder()
+          .put("kill", EMPTY)
+          .put("ps", EMPTY)
+          // TODO(dpursehouse): Add "scp" and "suexec"
+          .put(
+              "gerrit",
+              Streams.concat(COMMON_ROOT_COMMANDS.stream(), MASTER_ONLY_ROOT_COMMANDS.stream())
+                  .sorted()
+                  .collect(toImmutableList()))
+          .put(
+              "gerrit index",
+              ImmutableList.of(
+                  "changes", "changes-in-project")) // "activate" and "start" are not included
+          .put("gerrit logging", ImmutableList.of("ls", "set"))
+          .put(
+              "gerrit plugin",
+              ImmutableList.of("add", "enable", "install", "ls", "reload", "remove", "rm"))
+          .put("gerrit test-submit", ImmutableList.of("rule", "type"))
+          .build();
 
   private static final ImmutableMap<String, List<String>> SLAVE_COMMANDS =
       ImmutableMap.of(
-          Commands.ROOT,
+          "kill",
+          EMPTY,
+          "gerrit",
           COMMON_ROOT_COMMANDS,
-          "plugin",
+          "gerrit plugin",
           ImmutableList.of("add", "enable", "install", "ls", "reload", "remove", "rm"));
 
   @Test
@@ -117,22 +124,30 @@
 
   private void testCommandExecution(Map<String, List<String>> commands) throws Exception {
     for (String root : commands.keySet()) {
-      for (String command : commands.get(root)) {
-        // We can't assert that adminSshSession.hasError() is false, because using the --help
-        // option causes the usage info to be written to stderr. Instead, we assert on the
-        // content of the stderr, which will always start with "gerrit command" when the --help
-        // option is used.
-        String cmd = String.format("gerrit%s%s %s", root.isEmpty() ? "" : " ", root, command);
-        logger.atFine().log(cmd);
-        adminSshSession.exec(String.format("%s --help", cmd));
-        String response = adminSshSession.getError();
-        assertWithMessage(String.format("command %s failed: %s", command, response))
-            .that(response)
-            .startsWith(cmd);
+      List<String> cmds = commands.get(root);
+      if (cmds.isEmpty()) {
+        testCommandExecution(root);
+      } else {
+        for (String cmd : cmds) {
+          testCommandExecution(String.format("%s %s", root, cmd));
+        }
       }
     }
   }
 
+  private void testCommandExecution(String cmd) throws Exception {
+    // We can't assert that adminSshSession.hasError() is false, because using the --help
+    // option causes the usage info to be written to stderr. Instead, we assert on the
+    // content of the stderr, which will always start with "gerrit command" when the --help
+    // option is used.
+    logger.atFine().log(cmd);
+    adminSshSession.exec(String.format("%s --help", cmd));
+    String response = adminSshSession.getError();
+    assertWithMessage(String.format("command %s failed: %s", cmd, response))
+        .that(response)
+        .startsWith(cmd);
+  }
+
   @Test
   public void nonExistingCommandFails() throws Exception {
     adminSshSession.exec("gerrit non-existing-command --help");
@@ -145,12 +160,12 @@
   public void listCommands() throws Exception {
     adminSshSession.exec("gerrit --help");
     List<String> commands = parseCommandsFromGerritHelpText(adminSshSession.getError());
-    assertThat(commands).containsExactlyElementsIn(MASTER_COMMANDS.get(Commands.ROOT)).inOrder();
+    assertThat(commands).containsExactlyElementsIn(MASTER_COMMANDS.get("gerrit")).inOrder();
 
     restartAsSlave();
     adminSshSession.exec("gerrit --help");
     commands = parseCommandsFromGerritHelpText(adminSshSession.getError());
-    assertThat(commands).containsExactlyElementsIn(SLAVE_COMMANDS.get(Commands.ROOT)).inOrder();
+    assertThat(commands).containsExactlyElementsIn(SLAVE_COMMANDS.get("gerrit")).inOrder();
   }
 
   private List<String> parseCommandsFromGerritHelpText(String helpText) {
diff --git a/javatests/com/google/gerrit/elasticsearch/BUILD b/javatests/com/google/gerrit/elasticsearch/BUILD
index cc849eb..4806acc 100644
--- a/javatests/com/google/gerrit/elasticsearch/BUILD
+++ b/javatests/com/google/gerrit/elasticsearch/BUILD
@@ -2,7 +2,7 @@
 
 java_library(
     name = "elasticsearch_test_utils",
-    testonly = 1,
+    testonly = True,
     srcs = [
         "ElasticContainer.java",
         "ElasticTestUtils.java",
@@ -18,6 +18,7 @@
         "//lib/httpcomponents:httpcore",
         "//lib/jgit/org.eclipse.jgit:jgit",
         "//lib/testcontainers",
+        "//lib/testcontainers:testcontainers-elasticsearch",
     ],
 )
 
@@ -40,12 +41,12 @@
 
 SUFFIX = "sTest.java"
 
-ELASTICSEARCH_TESTS = {i: "ElasticQuery" + i.capitalize() + SUFFIX for i in TYPES}
-
 ELASTICSEARCH_TESTS_V5 = {i: "ElasticV5Query" + i.capitalize() + SUFFIX for i in TYPES}
 
 ELASTICSEARCH_TESTS_V6 = {i: "ElasticV6Query" + i.capitalize() + SUFFIX for i in TYPES}
 
+ELASTICSEARCH_TESTS_V7 = {i: "ElasticV7Query" + i.capitalize() + SUFFIX for i in TYPES}
+
 ELASTICSEARCH_TAGS = [
     "docker",
     "elastic",
@@ -53,14 +54,6 @@
 ]
 
 [junit_tests(
-    name = "elasticsearch_query_%ss_test" % name,
-    size = "large",
-    srcs = [src],
-    tags = ELASTICSEARCH_TAGS,
-    deps = ELASTICSEARCH_DEPS + [QUERY_TESTS_DEP % name],
-) for name, src in ELASTICSEARCH_TESTS.items()]
-
-[junit_tests(
     name = "elasticsearch_query_%ss_test_V5" % name,
     size = "large",
     srcs = [src],
@@ -76,6 +69,17 @@
     deps = ELASTICSEARCH_DEPS + [QUERY_TESTS_DEP % name],
 ) for name, src in ELASTICSEARCH_TESTS_V6.items()]
 
+[junit_tests(
+    name = "elasticsearch_query_%ss_test_V7" % name,
+    size = "large",
+    srcs = [src],
+    tags = ELASTICSEARCH_TAGS + ["flaky"],
+    deps = ELASTICSEARCH_DEPS + [QUERY_TESTS_DEP % name] + [
+        "//lib/httpcomponents:httpasyncclient",
+        "//lib/httpcomponents:httpclient",
+    ],
+) for name, src in ELASTICSEARCH_TESTS_V7.items()]
+
 junit_tests(
     name = "elasticsearch_tests",
     size = "small",
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java b/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java
index c3150f1..f617488 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java
@@ -14,21 +14,19 @@
 
 package com.google.gerrit.elasticsearch;
 
-import com.google.common.collect.ImmutableSet;
-import java.util.Set;
 import org.apache.http.HttpHost;
 import org.junit.AssumptionViolatedException;
-import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.elasticsearch.ElasticsearchContainer;
 
 /* Helper class for running ES integration tests in docker container */
-public class ElasticContainer<SELF extends ElasticContainer<SELF>> extends GenericContainer<SELF> {
+public class ElasticContainer extends ElasticsearchContainer {
   private static final int ELASTICSEARCH_DEFAULT_PORT = 9200;
 
-  public static ElasticContainer<?> createAndStart(ElasticVersion version) {
+  public static ElasticContainer createAndStart(ElasticVersion version) {
     // Assumption violation is not natively supported by Testcontainers.
     // See https://github.com/testcontainers/testcontainers-java/issues/343
     try {
-      ElasticContainer<?> container = new ElasticContainer<>(version);
+      ElasticContainer container = new ElasticContainer(version);
       container.start();
       return container;
     } catch (Throwable t) {
@@ -36,16 +34,10 @@
     }
   }
 
-  public static ElasticContainer<?> createAndStart() {
-    return createAndStart(ElasticVersion.V2_4);
-  }
-
   private static String getImageName(ElasticVersion version) {
     switch (version) {
-      case V2_4:
-        return "elasticsearch:2.4.6-alpine";
       case V5_6:
-        return "docker.elastic.co/elasticsearch/elasticsearch:5.6.13";
+        return "docker.elastic.co/elasticsearch/elasticsearch:5.6.14";
       case V6_2:
         return "docker.elastic.co/elasticsearch/elasticsearch-oss:6.2.4";
       case V6_3:
@@ -53,7 +45,9 @@
       case V6_4:
         return "docker.elastic.co/elasticsearch/elasticsearch-oss:6.4.3";
       case V6_5:
-        return "docker.elastic.co/elasticsearch/elasticsearch-oss:6.5.0";
+        return "docker.elastic.co/elasticsearch/elasticsearch-oss:6.5.4";
+      case V7_0:
+        return "docker.elastic.co/elasticsearch/elasticsearch-oss:7.0.0-alpha1";
     }
     throw new IllegalStateException("No tests for version: " + version.name());
   }
@@ -62,19 +56,6 @@
     super(getImageName(version));
   }
 
-  @Override
-  protected void configure() {
-    addExposedPort(ELASTICSEARCH_DEFAULT_PORT);
-
-    // https://github.com/docker-library/elasticsearch/issues/58
-    addEnv("-Ees.network.host", "0.0.0.0");
-  }
-
-  @Override
-  public Set<Integer> getLivenessCheckPortNumbers() {
-    return ImmutableSet.of(getMappedPort(ELASTICSEARCH_DEFAULT_PORT));
-  }
-
   public HttpHost getHttpHost() {
     return new HttpHost(getContainerIpAddress(), getMappedPort(ELASTICSEARCH_DEFAULT_PORT));
   }
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticTestUtils.java b/javatests/com/google/gerrit/elasticsearch/ElasticTestUtils.java
index 9f7b60c..020a158 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticTestUtils.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticTestUtils.java
@@ -58,7 +58,7 @@
 
   public static Config getConfig(ElasticVersion version) {
     ElasticNodeInfo elasticNodeInfo;
-    ElasticContainer<?> container = ElasticContainer.createAndStart(version);
+    ElasticContainer container = ElasticContainer.createAndStart(version);
     elasticNodeInfo = new ElasticNodeInfo(container.getHttpHost().getPort());
     String indicesPrefix = UUID.randomUUID().toString();
     Config cfg = new Config();
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticV5QueryAccountsTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV5QueryAccountsTest.java
index 5d2f944..c8ce54a 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticV5QueryAccountsTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticV5QueryAccountsTest.java
@@ -32,7 +32,7 @@
   }
 
   private static ElasticNodeInfo nodeInfo;
-  private static ElasticContainer<?> container;
+  private static ElasticContainer container;
 
   @BeforeClass
   public static void startIndexService() {
@@ -52,10 +52,6 @@
     }
   }
 
-  private String testName() {
-    return testName.getMethodName().toLowerCase() + "_";
-  }
-
   @Override
   protected void initAfterLifecycleStart() throws Exception {
     super.initAfterLifecycleStart();
@@ -66,7 +62,7 @@
   protected Injector createInjector() {
     Config elasticsearchConfig = new Config(config);
     InMemoryModule.setDefaults(elasticsearchConfig);
-    String indicesPrefix = testName();
+    String indicesPrefix = getSanitizedMethodName();
     ElasticTestUtils.configure(
         elasticsearchConfig, nodeInfo.port, indicesPrefix, ElasticVersion.V5_6);
     return Guice.createInjector(new InMemoryModule(elasticsearchConfig, notesMigration));
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticV5QueryChangesTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV5QueryChangesTest.java
index 5d76162..cfdfa98 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticV5QueryChangesTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticV5QueryChangesTest.java
@@ -32,7 +32,7 @@
   }
 
   private static ElasticNodeInfo nodeInfo;
-  private static ElasticContainer<?> container;
+  private static ElasticContainer container;
 
   @BeforeClass
   public static void startIndexService() {
@@ -52,10 +52,6 @@
     }
   }
 
-  private String testName() {
-    return testName.getMethodName().toLowerCase() + "_";
-  }
-
   @Override
   protected void initAfterLifecycleStart() throws Exception {
     super.initAfterLifecycleStart();
@@ -66,7 +62,7 @@
   protected Injector createInjector() {
     Config elasticsearchConfig = new Config(config);
     InMemoryModule.setDefaults(elasticsearchConfig);
-    String indicesPrefix = testName();
+    String indicesPrefix = getSanitizedMethodName();
     ElasticTestUtils.configure(
         elasticsearchConfig, nodeInfo.port, indicesPrefix, ElasticVersion.V5_6);
     return Guice.createInjector(new InMemoryModule(elasticsearchConfig, notesMigration));
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticV5QueryGroupsTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV5QueryGroupsTest.java
index 9ce2e93..832a7bd 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticV5QueryGroupsTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticV5QueryGroupsTest.java
@@ -32,7 +32,7 @@
   }
 
   private static ElasticNodeInfo nodeInfo;
-  private static ElasticContainer<?> container;
+  private static ElasticContainer container;
 
   @BeforeClass
   public static void startIndexService() {
@@ -52,10 +52,6 @@
     }
   }
 
-  private String testName() {
-    return testName.getMethodName().toLowerCase() + "_";
-  }
-
   @Override
   protected void initAfterLifecycleStart() throws Exception {
     super.initAfterLifecycleStart();
@@ -66,7 +62,7 @@
   protected Injector createInjector() {
     Config elasticsearchConfig = new Config(config);
     InMemoryModule.setDefaults(elasticsearchConfig);
-    String indicesPrefix = testName();
+    String indicesPrefix = getSanitizedMethodName();
     ElasticTestUtils.configure(
         elasticsearchConfig, nodeInfo.port, indicesPrefix, ElasticVersion.V5_6);
     return Guice.createInjector(new InMemoryModule(elasticsearchConfig, notesMigration));
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticV5QueryProjectsTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV5QueryProjectsTest.java
index 4184935..29d3fa4 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticV5QueryProjectsTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticV5QueryProjectsTest.java
@@ -32,7 +32,7 @@
   }
 
   private static ElasticNodeInfo nodeInfo;
-  private static ElasticContainer<?> container;
+  private static ElasticContainer container;
 
   @BeforeClass
   public static void startIndexService() {
@@ -52,10 +52,6 @@
     }
   }
 
-  private String testName() {
-    return testName.getMethodName().toLowerCase() + "_";
-  }
-
   @Override
   protected void initAfterLifecycleStart() throws Exception {
     super.initAfterLifecycleStart();
@@ -66,7 +62,7 @@
   protected Injector createInjector() {
     Config elasticsearchConfig = new Config(config);
     InMemoryModule.setDefaults(elasticsearchConfig);
-    String indicesPrefix = testName();
+    String indicesPrefix = getSanitizedMethodName();
     ElasticTestUtils.configure(
         elasticsearchConfig, nodeInfo.port, indicesPrefix, ElasticVersion.V5_6);
     return Guice.createInjector(new InMemoryModule(elasticsearchConfig, notesMigration));
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryAccountsTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryAccountsTest.java
index eeb4c09..8833907 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryAccountsTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryAccountsTest.java
@@ -32,7 +32,7 @@
   }
 
   private static ElasticNodeInfo nodeInfo;
-  private static ElasticContainer<?> container;
+  private static ElasticContainer container;
 
   @BeforeClass
   public static void startIndexService() {
@@ -52,10 +52,6 @@
     }
   }
 
-  private String testName() {
-    return testName.getMethodName().toLowerCase() + "_";
-  }
-
   @Override
   protected void initAfterLifecycleStart() throws Exception {
     super.initAfterLifecycleStart();
@@ -66,7 +62,7 @@
   protected Injector createInjector() {
     Config elasticsearchConfig = new Config(config);
     InMemoryModule.setDefaults(elasticsearchConfig);
-    String indicesPrefix = testName();
+    String indicesPrefix = getSanitizedMethodName();
     ElasticTestUtils.configure(elasticsearchConfig, nodeInfo.port, indicesPrefix);
     return Guice.createInjector(new InMemoryModule(elasticsearchConfig, notesMigration));
   }
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryChangesTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryChangesTest.java
index 7525b65..8ba753c 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryChangesTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryChangesTest.java
@@ -32,7 +32,7 @@
   }
 
   private static ElasticNodeInfo nodeInfo;
-  private static ElasticContainer<?> container;
+  private static ElasticContainer container;
 
   @BeforeClass
   public static void startIndexService() {
@@ -52,10 +52,6 @@
     }
   }
 
-  private String testName() {
-    return testName.getMethodName().toLowerCase() + "_";
-  }
-
   @Override
   protected void initAfterLifecycleStart() throws Exception {
     super.initAfterLifecycleStart();
@@ -66,7 +62,7 @@
   protected Injector createInjector() {
     Config elasticsearchConfig = new Config(config);
     InMemoryModule.setDefaults(elasticsearchConfig);
-    String indicesPrefix = testName();
+    String indicesPrefix = getSanitizedMethodName();
     ElasticTestUtils.configure(elasticsearchConfig, nodeInfo.port, indicesPrefix);
     return Guice.createInjector(new InMemoryModule(elasticsearchConfig, notesMigration));
   }
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryGroupsTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryGroupsTest.java
index e8d5683..cecb085 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryGroupsTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryGroupsTest.java
@@ -32,7 +32,7 @@
   }
 
   private static ElasticNodeInfo nodeInfo;
-  private static ElasticContainer<?> container;
+  private static ElasticContainer container;
 
   @BeforeClass
   public static void startIndexService() {
@@ -52,10 +52,6 @@
     }
   }
 
-  private String testName() {
-    return testName.getMethodName().toLowerCase() + "_";
-  }
-
   @Override
   protected void initAfterLifecycleStart() throws Exception {
     super.initAfterLifecycleStart();
@@ -66,7 +62,7 @@
   protected Injector createInjector() {
     Config elasticsearchConfig = new Config(config);
     InMemoryModule.setDefaults(elasticsearchConfig);
-    String indicesPrefix = testName();
+    String indicesPrefix = getSanitizedMethodName();
     ElasticTestUtils.configure(elasticsearchConfig, nodeInfo.port, indicesPrefix);
     return Guice.createInjector(new InMemoryModule(elasticsearchConfig, notesMigration));
   }
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryProjectsTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryProjectsTest.java
index eaaf0c8..47e9b10 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryProjectsTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryProjectsTest.java
@@ -32,7 +32,7 @@
   }
 
   private static ElasticNodeInfo nodeInfo;
-  private static ElasticContainer<?> container;
+  private static ElasticContainer container;
 
   @BeforeClass
   public static void startIndexService() {
@@ -52,10 +52,6 @@
     }
   }
 
-  private String testName() {
-    return testName.getMethodName().toLowerCase() + "_";
-  }
-
   @Override
   protected void initAfterLifecycleStart() throws Exception {
     super.initAfterLifecycleStart();
@@ -66,7 +62,7 @@
   protected Injector createInjector() {
     Config elasticsearchConfig = new Config(config);
     InMemoryModule.setDefaults(elasticsearchConfig);
-    String indicesPrefix = testName();
+    String indicesPrefix = getSanitizedMethodName();
     ElasticTestUtils.configure(elasticsearchConfig, nodeInfo.port, indicesPrefix);
     return Guice.createInjector(new InMemoryModule(elasticsearchConfig, notesMigration));
   }
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticQueryAccountsTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryAccountsTest.java
similarity index 85%
rename from javatests/com/google/gerrit/elasticsearch/ElasticQueryAccountsTest.java
rename to javatests/com/google/gerrit/elasticsearch/ElasticV7QueryAccountsTest.java
index 4f0f8b0..bddbbc9 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticQueryAccountsTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryAccountsTest.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2016 The Android Open Source Project
+// Copyright (C) 2018 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -25,14 +25,14 @@
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 
-public class ElasticQueryAccountsTest extends AbstractQueryAccountsTest {
+public class ElasticV7QueryAccountsTest extends AbstractQueryAccountsTest {
   @ConfigSuite.Default
   public static Config defaultConfig() {
     return IndexConfig.createForElasticsearch();
   }
 
   private static ElasticNodeInfo nodeInfo;
-  private static ElasticContainer<?> container;
+  private static ElasticContainer container;
 
   @BeforeClass
   public static void startIndexService() {
@@ -41,7 +41,7 @@
       return;
     }
 
-    container = ElasticContainer.createAndStart();
+    container = ElasticContainer.createAndStart(ElasticVersion.V7_0);
     nodeInfo = new ElasticNodeInfo(container.getHttpHost().getPort());
   }
 
@@ -52,10 +52,6 @@
     }
   }
 
-  private String testName() {
-    return testName.getMethodName().toLowerCase() + "_";
-  }
-
   @Override
   protected void initAfterLifecycleStart() throws Exception {
     super.initAfterLifecycleStart();
@@ -66,7 +62,7 @@
   protected Injector createInjector() {
     Config elasticsearchConfig = new Config(config);
     InMemoryModule.setDefaults(elasticsearchConfig);
-    String indicesPrefix = testName();
+    String indicesPrefix = getSanitizedMethodName();
     ElasticTestUtils.configure(elasticsearchConfig, nodeInfo.port, indicesPrefix);
     return Guice.createInjector(new InMemoryModule(elasticsearchConfig, notesMigration));
   }
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticQueryChangesTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryChangesTest.java
similarity index 69%
rename from javatests/com/google/gerrit/elasticsearch/ElasticQueryChangesTest.java
rename to javatests/com/google/gerrit/elasticsearch/ElasticV7QueryChangesTest.java
index a02d691..5dcf159 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticQueryChangesTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryChangesTest.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2014 The Android Open Source Project
+// Copyright (C) 2018 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -21,18 +21,24 @@
 import com.google.gerrit.testing.IndexConfig;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
+import org.apache.http.impl.nio.client.HttpAsyncClients;
 import org.eclipse.jgit.lib.Config;
+import org.junit.After;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 
-public class ElasticQueryChangesTest extends AbstractQueryChangesTest {
+public class ElasticV7QueryChangesTest extends AbstractQueryChangesTest {
   @ConfigSuite.Default
   public static Config defaultConfig() {
     return IndexConfig.createForElasticsearch();
   }
 
   private static ElasticNodeInfo nodeInfo;
-  private static ElasticContainer<?> container;
+  private static ElasticContainer container;
+  private static CloseableHttpAsyncClient client;
 
   @BeforeClass
   public static void startIndexService() {
@@ -41,8 +47,10 @@
       return;
     }
 
-    container = ElasticContainer.createAndStart();
+    container = ElasticContainer.createAndStart(ElasticVersion.V7_0);
     nodeInfo = new ElasticNodeInfo(container.getHttpHost().getPort());
+    client = HttpAsyncClients.createDefault();
+    client.start();
   }
 
   @AfterClass
@@ -52,8 +60,14 @@
     }
   }
 
-  private String testName() {
-    return testName.getMethodName().toLowerCase() + "_";
+  @After
+  public void closeIndex() {
+    client.execute(
+        new HttpPost(
+            String.format(
+                "http://localhost:%d/%s*/_close", nodeInfo.port, getSanitizedMethodName())),
+        HttpClientContext.create(),
+        null);
   }
 
   @Override
@@ -66,7 +80,7 @@
   protected Injector createInjector() {
     Config elasticsearchConfig = new Config(config);
     InMemoryModule.setDefaults(elasticsearchConfig);
-    String indicesPrefix = testName();
+    String indicesPrefix = getSanitizedMethodName();
     ElasticTestUtils.configure(elasticsearchConfig, nodeInfo.port, indicesPrefix);
     return Guice.createInjector(new InMemoryModule(elasticsearchConfig, notesMigration));
   }
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticQueryGroupsTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryGroupsTest.java
similarity index 85%
rename from javatests/com/google/gerrit/elasticsearch/ElasticQueryGroupsTest.java
rename to javatests/com/google/gerrit/elasticsearch/ElasticV7QueryGroupsTest.java
index f13c491..54be7b9 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticQueryGroupsTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryGroupsTest.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2017 The Android Open Source Project
+// Copyright (C) 2018 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -25,14 +25,14 @@
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 
-public class ElasticQueryGroupsTest extends AbstractQueryGroupsTest {
+public class ElasticV7QueryGroupsTest extends AbstractQueryGroupsTest {
   @ConfigSuite.Default
   public static Config defaultConfig() {
     return IndexConfig.createForElasticsearch();
   }
 
   private static ElasticNodeInfo nodeInfo;
-  private static ElasticContainer<?> container;
+  private static ElasticContainer container;
 
   @BeforeClass
   public static void startIndexService() {
@@ -41,7 +41,7 @@
       return;
     }
 
-    container = ElasticContainer.createAndStart();
+    container = ElasticContainer.createAndStart(ElasticVersion.V7_0);
     nodeInfo = new ElasticNodeInfo(container.getHttpHost().getPort());
   }
 
@@ -52,10 +52,6 @@
     }
   }
 
-  private String testName() {
-    return testName.getMethodName().toLowerCase() + "_";
-  }
-
   @Override
   protected void initAfterLifecycleStart() throws Exception {
     super.initAfterLifecycleStart();
@@ -66,7 +62,7 @@
   protected Injector createInjector() {
     Config elasticsearchConfig = new Config(config);
     InMemoryModule.setDefaults(elasticsearchConfig);
-    String indicesPrefix = testName();
+    String indicesPrefix = getSanitizedMethodName();
     ElasticTestUtils.configure(elasticsearchConfig, nodeInfo.port, indicesPrefix);
     return Guice.createInjector(new InMemoryModule(elasticsearchConfig, notesMigration));
   }
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticQueryProjectsTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryProjectsTest.java
similarity index 85%
rename from javatests/com/google/gerrit/elasticsearch/ElasticQueryProjectsTest.java
rename to javatests/com/google/gerrit/elasticsearch/ElasticV7QueryProjectsTest.java
index dd04010..e8b4a2c 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticQueryProjectsTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryProjectsTest.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2017 The Android Open Source Project
+// Copyright (C) 2018 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -25,14 +25,14 @@
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 
-public class ElasticQueryProjectsTest extends AbstractQueryProjectsTest {
+public class ElasticV7QueryProjectsTest extends AbstractQueryProjectsTest {
   @ConfigSuite.Default
   public static Config defaultConfig() {
     return IndexConfig.createForElasticsearch();
   }
 
   private static ElasticNodeInfo nodeInfo;
-  private static ElasticContainer<?> container;
+  private static ElasticContainer container;
 
   @BeforeClass
   public static void startIndexService() {
@@ -41,7 +41,7 @@
       return;
     }
 
-    container = ElasticContainer.createAndStart();
+    container = ElasticContainer.createAndStart(ElasticVersion.V7_0);
     nodeInfo = new ElasticNodeInfo(container.getHttpHost().getPort());
   }
 
@@ -52,10 +52,6 @@
     }
   }
 
-  private String testName() {
-    return testName.getMethodName().toLowerCase() + "_";
-  }
-
   @Override
   protected void initAfterLifecycleStart() throws Exception {
     super.initAfterLifecycleStart();
@@ -66,7 +62,7 @@
   protected Injector createInjector() {
     Config elasticsearchConfig = new Config(config);
     InMemoryModule.setDefaults(elasticsearchConfig);
-    String indicesPrefix = testName();
+    String indicesPrefix = getSanitizedMethodName();
     ElasticTestUtils.configure(elasticsearchConfig, nodeInfo.port, indicesPrefix);
     return Guice.createInjector(new InMemoryModule(elasticsearchConfig, notesMigration));
   }
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticVersionTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticVersionTest.java
index b598a0a..2456535 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticVersionTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticVersionTest.java
@@ -25,9 +25,6 @@
 
   @Test
   public void supportedVersion() throws Exception {
-    assertThat(ElasticVersion.forVersion("2.4.0")).isEqualTo(ElasticVersion.V2_4);
-    assertThat(ElasticVersion.forVersion("2.4.6")).isEqualTo(ElasticVersion.V2_4);
-
     assertThat(ElasticVersion.forVersion("5.6.0")).isEqualTo(ElasticVersion.V5_6);
     assertThat(ElasticVersion.forVersion("5.6.11")).isEqualTo(ElasticVersion.V5_6);
 
@@ -39,6 +36,12 @@
 
     assertThat(ElasticVersion.forVersion("6.4.0")).isEqualTo(ElasticVersion.V6_4);
     assertThat(ElasticVersion.forVersion("6.4.1")).isEqualTo(ElasticVersion.V6_4);
+
+    assertThat(ElasticVersion.forVersion("6.5.0")).isEqualTo(ElasticVersion.V6_5);
+    assertThat(ElasticVersion.forVersion("6.5.1")).isEqualTo(ElasticVersion.V6_5);
+
+    assertThat(ElasticVersion.forVersion("7.0.0")).isEqualTo(ElasticVersion.V7_0);
+    assertThat(ElasticVersion.forVersion("7.0.1")).isEqualTo(ElasticVersion.V7_0);
   }
 
   @Test
@@ -51,9 +54,19 @@
 
   @Test
   public void version6() throws Exception {
-    assertThat(ElasticVersion.V6_2.isV6()).isTrue();
-    assertThat(ElasticVersion.V6_3.isV6()).isTrue();
-    assertThat(ElasticVersion.V6_4.isV6()).isTrue();
-    assertThat(ElasticVersion.V5_6.isV6()).isFalse();
+    assertThat(ElasticVersion.V5_6.isV6OrLater()).isFalse();
+    assertThat(ElasticVersion.V6_2.isV6OrLater()).isTrue();
+    assertThat(ElasticVersion.V6_3.isV6OrLater()).isTrue();
+    assertThat(ElasticVersion.V6_4.isV6OrLater()).isTrue();
+    assertThat(ElasticVersion.V7_0.isV6OrLater()).isTrue();
+  }
+
+  @Test
+  public void version7() throws Exception {
+    assertThat(ElasticVersion.V5_6.isV7OrLater()).isFalse();
+    assertThat(ElasticVersion.V6_2.isV7OrLater()).isFalse();
+    assertThat(ElasticVersion.V6_3.isV7OrLater()).isFalse();
+    assertThat(ElasticVersion.V6_4.isV7OrLater()).isFalse();
+    assertThat(ElasticVersion.V7_0.isV7OrLater()).isTrue();
   }
 }
diff --git a/javatests/com/google/gerrit/server/BUILD b/javatests/com/google/gerrit/server/BUILD
index 8749b7a..8b35b6a 100644
--- a/javatests/com/google/gerrit/server/BUILD
+++ b/javatests/com/google/gerrit/server/BUILD
@@ -6,7 +6,7 @@
 
 java_library(
     name = "custom-truth-subjects",
-    testonly = 1,
+    testonly = True,
     srcs = CUSTOM_TRUTH_SUBJECTS,
     deps = [
         "//java/com/google/gerrit/extensions:api",
diff --git a/javatests/com/google/gerrit/server/query/account/BUILD b/javatests/com/google/gerrit/server/query/account/BUILD
index e6c631b..e41d390 100644
--- a/javatests/com/google/gerrit/server/query/account/BUILD
+++ b/javatests/com/google/gerrit/server/query/account/BUILD
@@ -4,7 +4,7 @@
 
 java_library(
     name = "abstract_query_tests",
-    testonly = 1,
+    testonly = True,
     srcs = ABSTRACT_QUERY_TEST,
     visibility = ["//visibility:public"],
     deps = [
diff --git a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index b9973e9..7cf148d 100644
--- a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -1597,7 +1597,7 @@
     in.add = ImmutableSet.of("foo");
     gApi.changes().id(change1.getId().get()).setHashtags(in);
 
-    in.add = ImmutableSet.of("foo", "bar", "a tag");
+    in.add = ImmutableSet.of("foo", "bar", "a tag", "ACamelCaseTag");
     gApi.changes().id(change2.getId().get()).setHashtags(in);
 
     return ImmutableList.of(change1, change2);
@@ -1614,6 +1614,8 @@
     assertQuery("hashtag:\" a tag \"", changes.get(1));
     assertQuery("hashtag:\"#a tag\"", changes.get(1));
     assertQuery("hashtag:\"# #a tag\"", changes.get(1));
+    assertQuery("hashtag:acamelcasetag", changes.get(1));
+    assertQuery("hashtag:ACamelCaseTAg", changes.get(1));
   }
 
   @Test
@@ -1709,6 +1711,10 @@
         accountManager.authenticate(AuthRequest.forUser("anotheruser")).getAccountId();
     assertQuery(q + " visibleto:" + user2.get(), change1);
 
+    String g1 = createGroup("group1", "Administrators");
+    gApi.groups().id(g1).addMembers("anotheruser");
+    assertQuery(q + " visibleto:" + g1, change1);
+
     requestContext.setContext(
         newRequestContext(
             accountManager.authenticate(AuthRequest.forUser("anotheruser")).getAccountId()));
diff --git a/javatests/com/google/gerrit/server/query/change/BUILD b/javatests/com/google/gerrit/server/query/change/BUILD
index c27be68..fc1483c 100644
--- a/javatests/com/google/gerrit/server/query/change/BUILD
+++ b/javatests/com/google/gerrit/server/query/change/BUILD
@@ -4,7 +4,7 @@
 
 java_library(
     name = "abstract_query_tests",
-    testonly = 1,
+    testonly = True,
     srcs = ABSTRACT_QUERY_TEST,
     visibility = ["//visibility:public"],
     runtime_deps = ["//prolog:gerrit-prolog-common"],
diff --git a/javatests/com/google/gerrit/server/query/group/BUILD b/javatests/com/google/gerrit/server/query/group/BUILD
index 0dd16cd..3f147c9 100644
--- a/javatests/com/google/gerrit/server/query/group/BUILD
+++ b/javatests/com/google/gerrit/server/query/group/BUILD
@@ -4,7 +4,7 @@
 
 java_library(
     name = "abstract_query_tests",
-    testonly = 1,
+    testonly = True,
     srcs = ABSTRACT_QUERY_TEST,
     visibility = ["//visibility:public"],
     deps = [
diff --git a/javatests/com/google/gerrit/server/query/project/BUILD b/javatests/com/google/gerrit/server/query/project/BUILD
index f0c455e..4ce1c00 100644
--- a/javatests/com/google/gerrit/server/query/project/BUILD
+++ b/javatests/com/google/gerrit/server/query/project/BUILD
@@ -4,7 +4,7 @@
 
 java_library(
     name = "abstract_query_tests",
-    testonly = 1,
+    testonly = True,
     srcs = ABSTRACT_QUERY_TEST,
     visibility = ["//visibility:public"],
     deps = [
diff --git a/javatests/com/google/gerrit/util/http/testutil/BUILD b/javatests/com/google/gerrit/util/http/testutil/BUILD
index b925188..adae68e 100644
--- a/javatests/com/google/gerrit/util/http/testutil/BUILD
+++ b/javatests/com/google/gerrit/util/http/testutil/BUILD
@@ -1,6 +1,6 @@
 java_library(
     name = "testutil",
-    testonly = 1,
+    testonly = True,
     srcs = glob(["**/*.java"]),
     visibility = ["//visibility:public"],
     deps = [
diff --git a/lib/commons/BUILD b/lib/commons/BUILD
index bb36389..93d3c2f 100644
--- a/lib/commons/BUILD
+++ b/lib/commons/BUILD
@@ -3,21 +3,18 @@
 java_library(
     name = "codec",
     data = ["//lib:LICENSE-Apache2.0"],
-    visibility = ["//visibility:public"],
     exports = ["@commons-codec//jar"],
 )
 
 java_library(
     name = "compress",
     data = ["//lib:LICENSE-Apache2.0"],
-    visibility = ["//visibility:public"],
     exports = ["@commons-compress//jar"],
 )
 
 java_library(
     name = "lang",
     data = ["//lib:LICENSE-Apache2.0"],
-    visibility = ["//visibility:public"],
     exports = ["@commons-lang//jar"],
 )
 
@@ -30,14 +27,12 @@
 java_library(
     name = "net",
     data = ["//lib:LICENSE-Apache2.0"],
-    visibility = ["//visibility:public"],
     exports = ["@commons-net//jar"],
 )
 
 java_library(
     name = "dbcp",
     data = ["//lib:LICENSE-Apache2.0"],
-    visibility = ["//visibility:public"],
     exports = ["@commons-dbcp//jar"],
     runtime_deps = [":pool"],
 )
@@ -45,20 +40,17 @@
 java_library(
     name = "pool",
     data = ["//lib:LICENSE-Apache2.0"],
-    visibility = ["//visibility:public"],
     exports = ["@commons-pool//jar"],
 )
 
 java_library(
     name = "validator",
     data = ["//lib:LICENSE-Apache2.0"],
-    visibility = ["//visibility:public"],
     exports = ["@commons-validator//jar"],
 )
 
 java_library(
     name = "io",
     data = ["//lib:LICENSE-Apache2.0"],
-    visibility = ["//visibility:public"],
     exports = ["@commons-io//jar"],
 )
diff --git a/lib/elasticsearch-rest-client/BUILD b/lib/elasticsearch-rest-client/BUILD
index c6357d0..8df3c70 100644
--- a/lib/elasticsearch-rest-client/BUILD
+++ b/lib/elasticsearch-rest-client/BUILD
@@ -3,6 +3,5 @@
 java_library(
     name = "elasticsearch-rest-client",
     data = ["//lib:LICENSE-elasticsearch"],
-    visibility = ["//visibility:public"],
     exports = ["@elasticsearch-rest-client//jar"],
 )
diff --git a/lib/greenmail/BUILD b/lib/greenmail/BUILD
index 55eb9f3..062f866 100644
--- a/lib/greenmail/BUILD
+++ b/lib/greenmail/BUILD
@@ -2,7 +2,7 @@
 
 java_library(
     name = "greenmail",
+    testonly = True,
     data = ["//lib:LICENSE-Apache2.0"],
-    visibility = ["//visibility:public"],
     exports = ["@greenmail//jar"],
 )
diff --git a/lib/httpcomponents/BUILD b/lib/httpcomponents/BUILD
index a875eaf..03d9b68 100644
--- a/lib/httpcomponents/BUILD
+++ b/lib/httpcomponents/BUILD
@@ -3,7 +3,6 @@
 java_library(
     name = "fluent-hc",
     data = ["//lib:LICENSE-Apache2.0"],
-    visibility = ["//visibility:public"],
     exports = ["@fluent-hc//jar"],
     runtime_deps = [":httpclient"],
 )
@@ -11,7 +10,6 @@
 java_library(
     name = "httpclient",
     data = ["//lib:LICENSE-Apache2.0"],
-    visibility = ["//visibility:public"],
     exports = ["@httpclient//jar"],
     runtime_deps = [
         ":httpcore",
@@ -23,14 +21,16 @@
 java_library(
     name = "httpcore",
     data = ["//lib:LICENSE-Apache2.0"],
-    visibility = ["//visibility:public"],
     exports = ["@httpcore//jar"],
 )
 
 java_library(
     name = "httpasyncclient",
     data = ["//lib:LICENSE-Apache2.0"],
-    visibility = ["//java/com/google/gerrit/elasticsearch:__pkg__"],
+    visibility = [
+        "//java/com/google/gerrit/elasticsearch:__pkg__",
+        "//javatests/com/google/gerrit/elasticsearch:__pkg__",
+    ],
     exports = ["@httpasyncclient//jar"],
 )
 
diff --git a/lib/jackson/BUILD b/lib/jackson/BUILD
index 3d751ab..0034748 100644
--- a/lib/jackson/BUILD
+++ b/lib/jackson/BUILD
@@ -1,5 +1,3 @@
-package(default_visibility = ["//visibility:public"])
-
 java_library(
     name = "jackson-core",
     data = ["//lib:LICENSE-Apache2.0"],
diff --git a/lib/jgit/org.eclipse.jgit.junit/BUILD b/lib/jgit/org.eclipse.jgit.junit/BUILD
index 85e9167..29d80d3 100644
--- a/lib/jgit/org.eclipse.jgit.junit/BUILD
+++ b/lib/jgit/org.eclipse.jgit.junit/BUILD
@@ -2,7 +2,7 @@
 
 java_library(
     name = "junit",
-    testonly = 1,
+    testonly = True,
     data = ["//lib:LICENSE-DO_NOT_DISTRIBUTE"],
     visibility = ["//visibility:public"],
     exports = [jgit_dep("@jgit-junit//jar")],
diff --git a/lib/lucene/BUILD b/lib/lucene/BUILD
index 421caed..eab2ac8 100644
--- a/lib/lucene/BUILD
+++ b/lib/lucene/BUILD
@@ -11,13 +11,11 @@
         "@lucene-core//jar",
     ],
     data = ["//lib:LICENSE-Apache2.0"],
-    visibility = ["//visibility:public"],
 )
 
 java_library(
     name = "lucene-analyzers-common",
     data = ["//lib:LICENSE-Apache2.0"],
-    visibility = ["//visibility:public"],
     exports = ["@lucene-analyzers-common//jar"],
     runtime_deps = [":lucene-core-and-backward-codecs"],
 )
@@ -25,14 +23,12 @@
 java_library(
     name = "lucene-core",
     data = ["//lib:LICENSE-Apache2.0"],
-    visibility = ["//visibility:public"],
     exports = ["@lucene-core//jar"],
 )
 
 java_library(
     name = "lucene-misc",
     data = ["//lib:LICENSE-Apache2.0"],
-    visibility = ["//visibility:public"],
     exports = ["@lucene-misc//jar"],
     runtime_deps = [":lucene-core-and-backward-codecs"],
 )
@@ -40,7 +36,6 @@
 java_library(
     name = "lucene-queryparser",
     data = ["//lib:LICENSE-Apache2.0"],
-    visibility = ["//visibility:public"],
     exports = ["@lucene-queryparser//jar"],
     runtime_deps = [":lucene-core-and-backward-codecs"],
 )
diff --git a/lib/polymer_externs/BUILD b/lib/polymer_externs/BUILD
index 2f1bdbd..cd71d64 100644
--- a/lib/polymer_externs/BUILD
+++ b/lib/polymer_externs/BUILD
@@ -12,9 +12,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-package(
-    default_visibility = ["//visibility:public"],
-)
+package(default_visibility = ["//visibility:public"])
 
 load("@io_bazel_rules_closure//closure:defs.bzl", "closure_js_library")
 
diff --git a/lib/testcontainers/BUILD b/lib/testcontainers/BUILD
index f99365d..25ca327 100644
--- a/lib/testcontainers/BUILD
+++ b/lib/testcontainers/BUILD
@@ -35,3 +35,12 @@
         "//lib/log:ext",
     ],
 )
+
+java_library(
+    name = "testcontainers-elasticsearch",
+    testonly = True,
+    data = ["//lib:LICENSE-testcontainers"],
+    visibility = ["//visibility:public"],
+    exports = ["@testcontainers-elasticsearch//jar"],
+    runtime_deps = [":testcontainers"],
+)
diff --git a/plugins/BUILD b/plugins/BUILD
index eed8383..b06c6e4 100644
--- a/plugins/BUILD
+++ b/plugins/BUILD
@@ -32,6 +32,7 @@
     "//java/com/google/gerrit/common:server",
     "//java/com/google/gerrit/extensions:api",
     "//java/com/google/gerrit/index",
+    "//java/com/google/gerrit/index/project",
     "//java/com/google/gerrit/index:query_exception",
     "//java/com/google/gerrit/lifecycle",
     "//java/com/google/gerrit/metrics",
diff --git a/plugins/hooks b/plugins/hooks
index de469e8..25ac76f 160000
--- a/plugins/hooks
+++ b/plugins/hooks
@@ -1 +1 @@
-Subproject commit de469e8e2598779773652abb43a0356650e257b3
+Subproject commit 25ac76fe18537c33f9f27c5463a081449c13ba67
diff --git a/plugins/replication b/plugins/replication
index bc5efb5..3e97cd9 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit bc5efb5b60a5a93c25c075f3667841e02532a99c
+Subproject commit 3e97cd967fc9a6413ae4265a4d7cb45a3730463a
diff --git a/plugins/reviewnotes b/plugins/reviewnotes
index 131f578..5ff3620 160000
--- a/plugins/reviewnotes
+++ b/plugins/reviewnotes
@@ -1 +1 @@
-Subproject commit 131f5782a6723d1e017f53cf4e56ff363263b076
+Subproject commit 5ff36200b29567118b3aede8e49ba0b6c6b1adb1
diff --git a/polygerrit-ui/BUILD b/polygerrit-ui/BUILD
index 384f835..5889ffd 100644
--- a/polygerrit-ui/BUILD
+++ b/polygerrit-ui/BUILD
@@ -1,6 +1,4 @@
-package(
-    default_visibility = ["//visibility:public"],
-)
+package(default_visibility = ["//visibility:public"])
 
 load("@io_bazel_rules_go//go:def.bzl", "go_binary")
 load("//tools/bzl:js.bzl", "bower_component_bundle")
@@ -51,7 +49,6 @@
         "zip -qr $$ROOT/$@ fonts",
     ]),
     output_to_bindir = 1,
-    visibility = ["//visibility:public"],
 )
 
 go_binary(
diff --git a/polygerrit-ui/app/BUILD b/polygerrit-ui/app/BUILD
index c735746..d48641b 100644
--- a/polygerrit-ui/app/BUILD
+++ b/polygerrit-ui/app/BUILD
@@ -1,6 +1,4 @@
-package(
-    default_visibility = ["//visibility:public"],
-)
+package(default_visibility = ["//visibility:public"])
 
 load(":rules.bzl", "polygerrit_bundle")
 load("//tools/bzl:genrule2.bzl", "genrule2")
@@ -25,7 +23,7 @@
 
 bower_component_bundle(
     name = "test_components",
-    testonly = 1,
+    testonly = True,
     deps = [
         "//lib/js:iron-test-helpers",
         "//lib/js:test-fixture",
diff --git a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.js b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.js
index 6443095..77a1e2a 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.js
@@ -307,9 +307,9 @@
         commands.push({
           title,
           command: commandObj[title]
-              .replace('${project}', repo)
+              .replace('${project}', encodeURI(repo))
               .replace('${project-base-name}',
-              repo.substring(repo.lastIndexOf('/') + 1)),
+              encodeURI(repo.substring(repo.lastIndexOf('/') + 1))),
         });
       }
       return commands;
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 9a3fc03..8c152b6 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
@@ -165,8 +165,9 @@
         hidden$="[[isColumnHidden('Assignee', visibleChangeTableColumns)]]">
       <template is="dom-if" if="[[change.assignee]]">
         <gr-account-link
+            id="assigneeAccountLink"
             account="[[change.assignee]]"
-            additional-text="[[_computeAccountStatusString(change.owner)]]"></gr-account-link>
+            additional-text="[[_computeAccountStatusString(change.assignee)]]"></gr-account-link>
       </template>
       <template is="dom-if" if="[[!change.assignee]]">
         <span class="placeholder">--</span>
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 aaad362..3637653 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
@@ -182,10 +182,13 @@
       element.change = {
         assignee: {
           name: 'test',
+          status: 'test',
         },
       };
       flushAsynchronousOperations();
       assert.isOk(element.$$('.assignee gr-account-link'));
+      assert.equal(Polymer.dom(element.root)
+          .querySelector('#assigneeAccountLink').additionalText, '(test)');
     });
 
     test('_computeAccountStatusString', () => {
diff --git a/polygerrit-ui/app/polylint_test.sh b/polygerrit-ui/app/polylint_test.sh
index ca9f9a9..ee69ce2 100755
--- a/polygerrit-ui/app/polylint_test.sh
+++ b/polygerrit-ui/app/polylint_test.sh
@@ -1,4 +1,4 @@
-#!/bin/sh
+#!/bin/bash
 
 set -ex
 
diff --git a/polygerrit-ui/app/template_test.sh b/polygerrit-ui/app/template_test.sh
index fcadc1b..7177e8a 100755
--- a/polygerrit-ui/app/template_test.sh
+++ b/polygerrit-ui/app/template_test.sh
@@ -1,4 +1,4 @@
-#!/bin/sh
+#!/bin/bash
 
 set -ex
 
diff --git a/resources/com/google/gerrit/reviewdb/server/index_postgres.sql b/resources/com/google/gerrit/reviewdb/server/index_postgres.sql
index f2f24e1..439fed7 100644
--- a/resources/com/google/gerrit/reviewdb/server/index_postgres.sql
+++ b/resources/com/google/gerrit/reviewdb/server/index_postgres.sql
@@ -8,7 +8,6 @@
 ALTER TABLE patch_comments CLUSTER ON patch_comments_pkey;
 ALTER TABLE patch_set_approvals CLUSTER ON patch_set_approvals_pkey;
 
-ALTER TABLE account_group_members CLUSTER ON account_group_members_pkey;
 CLUSTER;
 
 
diff --git a/tools/coverage.sh b/tools/coverage.sh
index 22b40d8..72e1d5b 100755
--- a/tools/coverage.sh
+++ b/tools/coverage.sh
@@ -1,4 +1,4 @@
-#!/bin/sh
+#!/bin/bash
 #
 # Usage
 #
diff --git a/tools/eclipse/BUILD b/tools/eclipse/BUILD
index 0c9d023..5ac3a36 100644
--- a/tools/eclipse/BUILD
+++ b/tools/eclipse/BUILD
@@ -37,13 +37,13 @@
 
 java_library(
     name = "classpath",
-    testonly = 1,
+    testonly = True,
     runtime_deps = LIBS + PGMLIBS + DEPS,
 )
 
 classpath_collector(
     name = "main_classpath_collect",
-    testonly = 1,
+    testonly = True,
     deps = LIBS + PGMLIBS + DEPS + TEST_DEPS +
            ["//plugins/%s:%s__plugin" % (n, n) for n in CORE_PLUGINS + CUSTOM_PLUGINS] +
            ["//plugins/%s:%s__plugin_test_deps" % (n, n) for n in CUSTOM_PLUGINS_TEST_DEPS],
diff --git a/tools/maven/gerrit-acceptance-framework_pom.xml b/tools/maven/gerrit-acceptance-framework_pom.xml
index 57f0d52..978fa7f 100644
--- a/tools/maven/gerrit-acceptance-framework_pom.xml
+++ b/tools/maven/gerrit-acceptance-framework_pom.xml
@@ -2,7 +2,7 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.google.gerrit</groupId>
   <artifactId>gerrit-acceptance-framework</artifactId>
-  <version>2.16.1-SNAPSHOT</version>
+  <version>2.16.2</version>
   <packaging>jar</packaging>
   <name>Gerrit Code Review - Acceptance Test Framework</name>
   <description>Framework for Gerrit's acceptance tests</description>
@@ -44,6 +44,9 @@
       <name>Edwin Kempin</name>
     </developer>
     <developer>
+      <name>Han-Wen Nienhuys</name>
+    </developer>
+    <developer>
       <name>Hugo Arès</name>
     </developer>
     <developer>
diff --git a/tools/maven/gerrit-extension-api_pom.xml b/tools/maven/gerrit-extension-api_pom.xml
index 872b09b..4178ae8 100644
--- a/tools/maven/gerrit-extension-api_pom.xml
+++ b/tools/maven/gerrit-extension-api_pom.xml
@@ -2,7 +2,7 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.google.gerrit</groupId>
   <artifactId>gerrit-extension-api</artifactId>
-  <version>2.16.1-SNAPSHOT</version>
+  <version>2.16.2</version>
   <packaging>jar</packaging>
   <name>Gerrit Code Review - Extension API</name>
   <description>API for Gerrit Extensions</description>
@@ -44,6 +44,9 @@
       <name>Edwin Kempin</name>
     </developer>
     <developer>
+      <name>Han-Wen Nienhuys</name>
+    </developer>
+    <developer>
       <name>Hugo Arès</name>
     </developer>
     <developer>
diff --git a/tools/maven/gerrit-plugin-api_pom.xml b/tools/maven/gerrit-plugin-api_pom.xml
index 47c2587..9dc4a38 100644
--- a/tools/maven/gerrit-plugin-api_pom.xml
+++ b/tools/maven/gerrit-plugin-api_pom.xml
@@ -2,7 +2,7 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.google.gerrit</groupId>
   <artifactId>gerrit-plugin-api</artifactId>
-  <version>2.16.1-SNAPSHOT</version>
+  <version>2.16.2</version>
   <packaging>jar</packaging>
   <name>Gerrit Code Review - Plugin API</name>
   <description>API for Gerrit Plugins</description>
@@ -44,6 +44,9 @@
       <name>Edwin Kempin</name>
     </developer>
     <developer>
+      <name>Han-Wen Nienhuys</name>
+    </developer>
+    <developer>
       <name>Hugo Arès</name>
     </developer>
     <developer>
diff --git a/tools/maven/gerrit-plugin-gwtui_pom.xml b/tools/maven/gerrit-plugin-gwtui_pom.xml
index 375f5ea..8089c44 100644
--- a/tools/maven/gerrit-plugin-gwtui_pom.xml
+++ b/tools/maven/gerrit-plugin-gwtui_pom.xml
@@ -2,7 +2,7 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.google.gerrit</groupId>
   <artifactId>gerrit-plugin-gwtui</artifactId>
-  <version>2.16.1-SNAPSHOT</version>
+  <version>2.16.2</version>
   <packaging>jar</packaging>
   <name>Gerrit Code Review - Plugin GWT UI</name>
   <description>Common Classes for Gerrit GWT UI Plugins</description>
@@ -44,6 +44,9 @@
       <name>Edwin Kempin</name>
     </developer>
     <developer>
+      <name>Han-Wen Nienhuys</name>
+    </developer>
+    <developer>
       <name>Hugo Arès</name>
     </developer>
     <developer>
diff --git a/tools/maven/gerrit-war_pom.xml b/tools/maven/gerrit-war_pom.xml
index 53ba70d..531ec08 100644
--- a/tools/maven/gerrit-war_pom.xml
+++ b/tools/maven/gerrit-war_pom.xml
@@ -2,7 +2,7 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.google.gerrit</groupId>
   <artifactId>gerrit-war</artifactId>
-  <version>2.16.1-SNAPSHOT</version>
+  <version>2.16.2</version>
   <packaging>war</packaging>
   <name>Gerrit Code Review - WAR</name>
   <description>Gerrit WAR</description>
@@ -44,6 +44,9 @@
       <name>Edwin Kempin</name>
     </developer>
     <developer>
+      <name>Han-Wen Nienhuys</name>
+    </developer>
+    <developer>
       <name>Hugo Arès</name>
     </developer>
     <developer>
diff --git a/tools/maven/package.bzl b/tools/maven/package.bzl
index 5b497f8..11e569d 100644
--- a/tools/maven/package.bzl
+++ b/tools/maven/package.bzl
@@ -50,7 +50,7 @@
         srcs = api_targets,
         outs = ["api_install.sh"],
         executable = True,
-        testonly = 1,
+        testonly = True,
     )
 
     if repository and url:
@@ -70,7 +70,7 @@
             srcs = api_targets,
             outs = ["api_deploy.sh"],
             executable = True,
-            testonly = 1,
+            testonly = True,
         )
 
     war_cmd = mvn_cmd[:]
diff --git a/version.bzl b/version.bzl
index e0182e7..d9f9c1b 100644
--- a/version.bzl
+++ b/version.bzl
@@ -2,4 +2,4 @@
 # Used by :api_install and :api_deploy targets
 # when talking to the destination repository.
 #
-GERRIT_VERSION = "2.16.1-SNAPSHOT"
+GERRIT_VERSION = "2.16.2"