Merge "Revert "GetCapabilities#CheckOne: Return json content type"" into stable-2.15
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 53f5fbd..8bd9c59 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -3635,6 +3635,14 @@
 +
 Common unit suffixes of 'k', 'm', or 'g' are supported.
 
+[[receive.inheritProjectMaxObjectSizeLimit]]receive.inheritProjectMaxObjectSizeLimit::
++
+Controls whether the project-level link:config-project-config.html[`receive.maxObjectSizeLimit`]
+value is inherited from the parent project. When `true`, the value is
+inherited, otherwise it is not inherited.
++
+Default is false, the value is not inherited.
+
 [[receive.maxTrustDepth]]receive.maxTrustDepth::
 +
 If signed push validation is link:#receive.enableSignedPush[enabled],
diff --git a/Documentation/config-project-config.txt b/Documentation/config-project-config.txt
index 7790ffc..b473b43 100644
--- a/Documentation/config-project-config.txt
+++ b/Documentation/config-project-config.txt
@@ -144,8 +144,10 @@
 than the global limit (if configured). In other words, it is only honored when
 it further reduces the global limit.
 +
-The setting is not inherited from the parent project; it must be explicitly
-set per project.
+When link:config-gerrit.html#receive.inheritProjectMaxObjectSizeLimit[
+`receive.inheritProjectmaxObjectSizeLimit`] is enabled in the global config,
+the value is inherited from the parent project. Otherwise, it is not inherited
+and must be explicitly set per project.
 +
 Default is zero.
 +
diff --git a/Documentation/dev-bazel.txt b/Documentation/dev-bazel.txt
index dc04a8d..2e7cb23 100644
--- a/Documentation/dev-bazel.txt
+++ b/Documentation/dev-bazel.txt
@@ -121,6 +121,11 @@
 Note that when building an individual plugin, the `core.zip` package
 is not regenerated.
 
+To build with all Error Prone warnings activated, run:
+
+----
+  bazel build --java_toolchain //tools:error_prone_warnings_toolchain //...
+----
 
 
 [[IDEs]]
diff --git a/Documentation/rest-api-access.txt b/Documentation/rest-api-access.txt
index 38f04ed..6f49a7d 100644
--- a/Documentation/rest-api-access.txt
+++ b/Documentation/rest-api-access.txt
@@ -263,6 +263,7 @@
       ],
       "can_upload": true,
       "can_add": true,
+      "can_add_tags": true,
       "config_visible": true,
       "groups": {
          "53a4f647a89ea57992571187d8025f830625192a": {
@@ -313,6 +314,7 @@
       ],
       "can_upload": true,
       "can_add": true,
+      "can_add_tags": true,
       "config_visible": true
     }
   }
@@ -399,6 +401,8 @@
 Whether the calling user can upload to any ref.
 |`can_add`            |not set if `false`|
 Whether the calling user can add any ref.
+|`can_add_tags`       |not set if `false`|
+Whether the calling user can add any tag ref.
 |`config_visible`     |not set if `false`|
 Whether the calling user can see the `refs/meta/config` branch of the
 project.
diff --git a/Documentation/rest-api-accounts.txt b/Documentation/rest-api-accounts.txt
index 95dd4f2..dc45ff5 100644
--- a/Documentation/rest-api-accounts.txt
+++ b/Documentation/rest-api-accounts.txt
@@ -412,7 +412,10 @@
 .Response
 ----
   HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json; charset=UTF-8
 
+  )]}'
   ok
 ----
 
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index 54d356e..1c54a7f 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -1034,6 +1034,7 @@
     ],
     "can_upload": true,
     "can_add": true,
+    "can_add_tags": true,
     "config_visible": true,
     "groups": {
       "c2ce4749a32ceb82cd6adcce65b8216e12afb41c": {
@@ -1135,6 +1136,7 @@
     ],
     "can_upload": true,
     "can_add": true,
+    "can_add_tags": true,
     "config_visible": true,
     "groups": {
       "global:Anonymous-Users": {
@@ -3061,10 +3063,10 @@
 formatted string. +
 Not set if there is no limit for the object size configured on project
 level.
-|`inherited_value` |optional|
-The max object size limit that is inherited from the global config as a
-formatted string. +
-Not set if there is no global limit for the object size.
+|`summary`         |optional|
+A string describing whether the value was inherited or overridden from
+the parent project or global config. +
+Not set if not inherited or overridden.
 |===============================
 
 [[project-access-input]]
diff --git a/WORKSPACE b/WORKSPACE
index 32de77b..0678878 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -879,8 +879,8 @@
 
 maven_jar(
     name = "elasticsearch-rest-client",
-    artifact = "org.elasticsearch.client:elasticsearch-rest-client:6.3.2",
-    sha1 = "2077ea5f00fdd2d6af85223b730ba8047303297f",
+    artifact = "org.elasticsearch.client:elasticsearch-rest-client:6.4.0",
+    sha1 = "0eaa13decb9796eb671c5841d0770ae68b348da5",
 )
 
 JACKSON_VERSION = "2.6.6"
diff --git a/gerrit-acceptance-framework/pom.xml b/gerrit-acceptance-framework/pom.xml
index aec8e7a4..b220ad5 100644
--- a/gerrit-acceptance-framework/pom.xml
+++ b/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.15.3</version>
+  <version>2.15.4-SNAPSHOT</version>
   <packaging>jar</packaging>
   <name>Gerrit Code Review - Acceptance Test Framework</name>
   <description>Framework for Gerrit's acceptance tests</description>
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
index 081c297..1fb25c7 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/AccountIT.java
@@ -1775,6 +1775,38 @@
   }
 
   @Test
+  public void createUserWithValidUsername() throws Exception {
+    ImmutableList<String> names =
+        ImmutableList.of(
+            "user@domain",
+            "user-name",
+            "user_name",
+            "1234",
+            "user1234",
+            "1234@domain",
+            "user!+alias{*}#$%&’^=~|@domain");
+    for (String name : names) {
+      gApi.accounts().create(name);
+    }
+  }
+
+  @Test
+  public void createUserWithInvalidUsername() throws Exception {
+    ImmutableList<String> invalidNames =
+        ImmutableList.of(
+            "@", "@foo", "-", "-foo", "_", "_foo", "!", "+", "{", "}", "*", "%", "#", "$", "&", "’",
+            "^", "=", "~");
+    for (String name : invalidNames) {
+      try {
+        gApi.accounts().create(name);
+        fail(String.format("Expected BadRequestException for username [%s]", name));
+      } catch (BadRequestException e) {
+        assertThat(e).hasMessageThat().isEqualTo(String.format("Invalid username '%s'", name));
+      }
+    }
+  }
+
+  @Test
   public void groups() throws Exception {
     assertGroups(
         admin.username, ImmutableList.of("Anonymous Users", "Registered Users", "Administrators"));
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/ProjectIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/ProjectIT.java
index 206adfd..7d6f589 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/ProjectIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/ProjectIT.java
@@ -16,6 +16,10 @@
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
+import static com.google.gerrit.server.project.ProjectState.INHERITED_FROM_GLOBAL;
+import static com.google.gerrit.server.project.ProjectState.INHERITED_FROM_PARENT;
+import static com.google.gerrit.server.project.ProjectState.OVERRIDDEN_BY_GLOBAL;
+import static com.google.gerrit.server.project.ProjectState.OVERRIDDEN_BY_PARENT;
 
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.GerritConfig;
@@ -215,7 +219,7 @@
     ConfigInfo info = getConfig();
     assertThat(info.maxObjectSizeLimit.value).isNull();
     assertThat(info.maxObjectSizeLimit.configuredValue).isNull();
-    assertThat(info.maxObjectSizeLimit.inheritedValue).isNull();
+    assertThat(info.maxObjectSizeLimit.summary).isNull();
   }
 
   @Test
@@ -224,13 +228,30 @@
     ConfigInfo info = setMaxObjectSize("100k");
     assertThat(info.maxObjectSizeLimit.value).isEqualTo("102400");
     assertThat(info.maxObjectSizeLimit.configuredValue).isEqualTo("100k");
-    assertThat(info.maxObjectSizeLimit.inheritedValue).isNull();
+    assertThat(info.maxObjectSizeLimit.summary).isNull();
 
     // Clear the value
     info = setMaxObjectSize("0");
     assertThat(info.maxObjectSizeLimit.value).isNull();
     assertThat(info.maxObjectSizeLimit.configuredValue).isNull();
-    assertThat(info.maxObjectSizeLimit.inheritedValue).isNull();
+    assertThat(info.maxObjectSizeLimit.summary).isNull();
+  }
+
+  @Test
+  @GerritConfig(name = "receive.inheritProjectMaxObjectSizeLimit", value = "true")
+  public void maxObjectSizeIsInheritedFromParentProject() throws Exception {
+    Project.NameKey child = createProject(name("child"), project);
+
+    ConfigInfo info = setMaxObjectSize("100k");
+    assertThat(info.maxObjectSizeLimit.value).isEqualTo("102400");
+    assertThat(info.maxObjectSizeLimit.configuredValue).isEqualTo("100k");
+    assertThat(info.maxObjectSizeLimit.summary).isNull();
+
+    info = getConfig(child);
+    assertThat(info.maxObjectSizeLimit.value).isEqualTo("102400");
+    assertThat(info.maxObjectSizeLimit.configuredValue).isNull();
+    assertThat(info.maxObjectSizeLimit.summary)
+        .isEqualTo(String.format(INHERITED_FROM_PARENT, project));
   }
 
   @Test
@@ -240,21 +261,75 @@
     ConfigInfo info = setMaxObjectSize("100k");
     assertThat(info.maxObjectSizeLimit.value).isEqualTo("102400");
     assertThat(info.maxObjectSizeLimit.configuredValue).isEqualTo("100k");
-    assertThat(info.maxObjectSizeLimit.inheritedValue).isNull();
+    assertThat(info.maxObjectSizeLimit.summary).isNull();
 
     info = getConfig(child);
     assertThat(info.maxObjectSizeLimit.value).isNull();
     assertThat(info.maxObjectSizeLimit.configuredValue).isNull();
-    assertThat(info.maxObjectSizeLimit.inheritedValue).isNull();
+    assertThat(info.maxObjectSizeLimit.summary).isNull();
+  }
+
+  @Test
+  public void maxObjectSizeOverridesParentProjectWhenNotSetOnParent() throws Exception {
+    Project.NameKey child = createProject(name("child"), project);
+
+    ConfigInfo info = setMaxObjectSize("0");
+    assertThat(info.maxObjectSizeLimit.value).isNull();
+    assertThat(info.maxObjectSizeLimit.configuredValue).isNull();
+    assertThat(info.maxObjectSizeLimit.summary).isNull();
+
+    info = setMaxObjectSize(child, "100k");
+    assertThat(info.maxObjectSizeLimit.value).isEqualTo("102400");
+    assertThat(info.maxObjectSizeLimit.configuredValue).isEqualTo("100k");
+    assertThat(info.maxObjectSizeLimit.summary).isNull();
+  }
+
+  @Test
+  public void maxObjectSizeOverridesParentProjectWhenLower() throws Exception {
+    Project.NameKey child = createProject(name("child"), project);
+
+    ConfigInfo info = setMaxObjectSize("200k");
+    assertThat(info.maxObjectSizeLimit.value).isEqualTo("204800");
+    assertThat(info.maxObjectSizeLimit.configuredValue).isEqualTo("200k");
+    assertThat(info.maxObjectSizeLimit.summary).isNull();
+
+    info = setMaxObjectSize(child, "100k");
+    assertThat(info.maxObjectSizeLimit.value).isEqualTo("102400");
+    assertThat(info.maxObjectSizeLimit.configuredValue).isEqualTo("100k");
+    assertThat(info.maxObjectSizeLimit.summary).isNull();
+  }
+
+  @Test
+  @GerritConfig(name = "receive.inheritProjectMaxObjectSizeLimit", value = "true")
+  public void maxObjectSizeDoesNotOverrideParentProjectWhenHigher() throws Exception {
+    Project.NameKey child = createProject(name("child"), project);
+
+    ConfigInfo info = setMaxObjectSize("100k");
+    assertThat(info.maxObjectSizeLimit.value).isEqualTo("102400");
+    assertThat(info.maxObjectSizeLimit.configuredValue).isEqualTo("100k");
+    assertThat(info.maxObjectSizeLimit.summary).isNull();
+
+    info = setMaxObjectSize(child, "200k");
+    assertThat(info.maxObjectSizeLimit.value).isEqualTo("102400");
+    assertThat(info.maxObjectSizeLimit.configuredValue).isEqualTo("200k");
+    assertThat(info.maxObjectSizeLimit.summary)
+        .isEqualTo(String.format(OVERRIDDEN_BY_PARENT, project));
   }
 
   @Test
   @GerritConfig(name = "receive.maxObjectSizeLimit", value = "200k")
   public void maxObjectSizeIsInheritedFromGlobalConfig() throws Exception {
+    Project.NameKey child = createProject(name("child"), project);
+
     ConfigInfo info = getConfig();
     assertThat(info.maxObjectSizeLimit.value).isEqualTo("204800");
     assertThat(info.maxObjectSizeLimit.configuredValue).isNull();
-    assertThat(info.maxObjectSizeLimit.inheritedValue).isEqualTo("200k");
+    assertThat(info.maxObjectSizeLimit.summary).isEqualTo(INHERITED_FROM_GLOBAL);
+
+    info = getConfig(child);
+    assertThat(info.maxObjectSizeLimit.value).isEqualTo("204800");
+    assertThat(info.maxObjectSizeLimit.configuredValue).isNull();
+    assertThat(info.maxObjectSizeLimit.summary).isEqualTo(INHERITED_FROM_GLOBAL);
   }
 
   @Test
@@ -263,16 +338,40 @@
     ConfigInfo info = setMaxObjectSize("100k");
     assertThat(info.maxObjectSizeLimit.value).isEqualTo("102400");
     assertThat(info.maxObjectSizeLimit.configuredValue).isEqualTo("100k");
-    assertThat(info.maxObjectSizeLimit.inheritedValue).isEqualTo("200k");
+    assertThat(info.maxObjectSizeLimit.summary).isNull();
+  }
+
+  @Test
+  @GerritConfig(name = "receive.maxObjectSizeLimit", value = "300k")
+  public void inheritedMaxObjectSizeOverridesGlobalConfigWhenLower() throws Exception {
+    Project.NameKey child = createProject(name("child"), project);
+
+    ConfigInfo info = setMaxObjectSize("200k");
+    assertThat(info.maxObjectSizeLimit.value).isEqualTo("204800");
+    assertThat(info.maxObjectSizeLimit.configuredValue).isEqualTo("200k");
+    assertThat(info.maxObjectSizeLimit.summary).isNull();
+
+    info = setMaxObjectSize(child, "100k");
+    assertThat(info.maxObjectSizeLimit.value).isEqualTo("102400");
+    assertThat(info.maxObjectSizeLimit.configuredValue).isEqualTo("100k");
+    assertThat(info.maxObjectSizeLimit.summary).isNull();
   }
 
   @Test
   @GerritConfig(name = "receive.maxObjectSizeLimit", value = "200k")
+  @GerritConfig(name = "receive.inheritProjectMaxObjectSizeLimit", value = "true")
   public void maxObjectSizeDoesNotOverrideGlobalConfigWhenHigher() throws Exception {
+    Project.NameKey child = createProject(name("child"), project);
+
     ConfigInfo info = setMaxObjectSize("300k");
     assertThat(info.maxObjectSizeLimit.value).isEqualTo("204800");
     assertThat(info.maxObjectSizeLimit.configuredValue).isEqualTo("300k");
-    assertThat(info.maxObjectSizeLimit.inheritedValue).isEqualTo("200k");
+    assertThat(info.maxObjectSizeLimit.summary).isEqualTo(OVERRIDDEN_BY_GLOBAL);
+
+    info = getConfig(child);
+    assertThat(info.maxObjectSizeLimit.value).isEqualTo("204800");
+    assertThat(info.maxObjectSizeLimit.configuredValue).isNull();
+    assertThat(info.maxObjectSizeLimit.summary).isEqualTo(OVERRIDDEN_BY_GLOBAL);
   }
 
   @Test
@@ -308,9 +407,13 @@
   }
 
   private ConfigInfo setMaxObjectSize(String value) throws Exception {
+    return setMaxObjectSize(project, value);
+  }
+
+  private ConfigInfo setMaxObjectSize(Project.NameKey name, String value) throws Exception {
     ConfigInput input = new ConfigInput();
     input.maxObjectSizeLimit = value;
-    return setConfig(input);
+    return setConfig(name, input);
   }
 
   private ConfigInfo getConfig(Project.NameKey name) throws Exception {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java
index 47acb4b..9579e19 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java
@@ -47,13 +47,8 @@
   }
 
   @ConfigSuite.Config
-  public static Config elasticsearchV6_2() {
-    return getConfig(ElasticVersion.V6_2);
-  }
-
-  @ConfigSuite.Config
-  public static Config elasticsearchV6_3() {
-    return getConfig(ElasticVersion.V6_3);
+  public static Config elasticsearchV6() {
+    return getConfig(ElasticVersion.V6_4);
   }
 
   @Override
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/ElasticIndexIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/ElasticIndexIT.java
index 3426ba45..602c50e 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/ElasticIndexIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/ElasticIndexIT.java
@@ -46,13 +46,8 @@
   }
 
   @ConfigSuite.Config
-  public static Config elasticsearchV6_2() {
-    return getConfig(ElasticVersion.V6_2);
-  }
-
-  @ConfigSuite.Config
-  public static Config elasticsearchV6_3() {
-    return getConfig(ElasticVersion.V6_3);
+  public static Config elasticsearchV6() {
+    return getConfig(ElasticVersion.V6_4);
   }
 
   @Override
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
index 47d09b5..6d988aa 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
@@ -48,6 +48,7 @@
 import org.apache.http.HttpStatus;
 import org.apache.http.entity.ContentType;
 import org.apache.http.nio.entity.NStringEntity;
+import org.elasticsearch.client.Request;
 import org.elasticsearch.client.Response;
 
 abstract class AbstractElasticIndex<K, V> implements Index<K, V> {
@@ -146,10 +147,10 @@
   public void deleteAll() throws IOException {
     // Delete the index, if it exists.
     String endpoint = indexName + client.adapter().indicesExistParam();
-    Response response = client.get().performRequest("HEAD", endpoint);
+    Response response = client.get().performRequest(new Request("HEAD", endpoint));
     int statusCode = response.getStatusLine().getStatusCode();
     if (statusCode == HttpStatus.SC_OK) {
-      response = client.get().performRequest("DELETE", indexName);
+      response = client.get().performRequest(new Request("DELETE", indexName));
       statusCode = response.getStatusLine().getStatusCode();
       if (statusCode != HttpStatus.SC_OK) {
         throw new IOException(
@@ -238,8 +239,12 @@
 
   private Response performRequest(
       String method, Object payload, String uri, Map<String, String> params) throws IOException {
+    Request request = new Request(method, uri);
     String payloadStr = payload instanceof String ? (String) payload : payload.toString();
-    HttpEntity entity = new NStringEntity(payloadStr, ContentType.APPLICATION_JSON);
-    return client.get().performRequest(method, uri, params, entity);
+    request.setEntity(new NStringEntity(payloadStr, ContentType.APPLICATION_JSON));
+    for (Map.Entry<String, String> entry : params.entrySet()) {
+      request.addParameter(entry.getKey(), entry.getValue());
+    }
+    return client.get().performRequest(request);
   }
 }
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexVersionDiscovery.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexVersionDiscovery.java
index 6293566..249e93c 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexVersionDiscovery.java
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexVersionDiscovery.java
@@ -23,7 +23,7 @@
 import java.util.List;
 import org.apache.http.HttpStatus;
 import org.apache.http.StatusLine;
-import org.apache.http.client.methods.HttpGet;
+import org.elasticsearch.client.Request;
 import org.elasticsearch.client.Response;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -41,10 +41,8 @@
 
   List<String> discover(String prefix, String indexName) throws IOException {
     String name = prefix + indexName + "_";
-    Response response =
-        client
-            .get()
-            .performRequest(HttpGet.METHOD_NAME, client.adapter().getVersionDiscoveryUrl(name));
+    Request request = new Request("GET", client.adapter().getVersionDiscoveryUrl(name));
+    Response response = client.get().performRequest(request);
 
     StatusLine statusLine = response.getStatusLine();
     if (statusLine.getStatusCode() != HttpStatus.SC_OK) {
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticQueryAdapter.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticQueryAdapter.java
index 6d80552..64f6252 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticQueryAdapter.java
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticQueryAdapter.java
@@ -31,13 +31,14 @@
 
   ElasticQueryAdapter(ElasticVersion version) {
     this.ignoreUnmapped = version == ElasticVersion.V2_4;
-    this.usePostV5Type = isV6(version);
-    this.versionDiscoveryUrl = isV6(version) ? "%s*" : "%s*/_aliases";
+    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:
         this.searchFilteringName = "_source";
         this.indicesExistParam = "?allow_no_indices=false";
         this.exactFieldType = "keyword";
@@ -55,10 +56,6 @@
     }
   }
 
-  private boolean isV6(ElasticVersion version) {
-    return version == ElasticVersion.V6_2 || version == ElasticVersion.V6_3;
-  }
-
   void setIgnoreUnmapped(JsonObject properties) {
     if (ignoreUnmapped) {
       properties.addProperty("ignore_unmapped", true);
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticRestClientProvider.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticRestClientProvider.java
index 5323ec8..0a32886 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticRestClientProvider.java
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticRestClientProvider.java
@@ -28,6 +28,7 @@
 import org.apache.http.client.CredentialsProvider;
 import org.apache.http.impl.client.BasicCredentialsProvider;
 import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
+import org.elasticsearch.client.Request;
 import org.elasticsearch.client.Response;
 import org.elasticsearch.client.RestClient;
 import org.elasticsearch.client.RestClientBuilder;
@@ -106,7 +107,7 @@
 
   private ElasticVersion getVersion() throws ElasticException {
     try {
-      Response response = client.performRequest("GET", "");
+      Response response = client.performRequest(new Request("GET", ""));
       StatusLine statusLine = response.getStatusLine();
       if (statusLine.getStatusCode() != HttpStatus.SC_OK) {
         throw new FailedToGetVersion(statusLine);
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticVersion.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticVersion.java
index 610a212..dfa5d21 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticVersion.java
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticVersion.java
@@ -21,7 +21,8 @@
   V2_4("2.4.*"),
   V5_6("5.6.*"),
   V6_2("6.2.*"),
-  V6_3("6.3.*");
+  V6_3("6.3.*"),
+  V6_4("6.4.*");
 
   private final String version;
   private final Pattern pattern;
@@ -31,29 +32,33 @@
     this.pattern = Pattern.compile(version);
   }
 
-  public static class InvalidVersion extends ElasticException {
+  public static class UnsupportedVersion extends ElasticException {
     private static final long serialVersionUID = 1L;
 
-    InvalidVersion(String version) {
+    UnsupportedVersion(String version) {
       super(
           String.format(
-              "Invalid version: [%s]. Supported versions: %s", version, supportedVersions()));
+              "Unsupported version: [%s]. Supported versions: %s", version, supportedVersions()));
     }
   }
 
-  public static ElasticVersion forVersion(String version) throws InvalidVersion {
+  public static ElasticVersion forVersion(String version) throws UnsupportedVersion {
     for (ElasticVersion value : ElasticVersion.values()) {
       if (value.pattern.matcher(version).matches()) {
         return value;
       }
     }
-    throw new InvalidVersion(version);
+    throw new UnsupportedVersion(version);
   }
 
   public static String supportedVersions() {
     return Joiner.on(", ").join(ElasticVersion.values());
   }
 
+  public boolean isV6() {
+    return version.startsWith("6.");
+  }
+
   @Override
   public String toString() {
     return version;
diff --git a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticContainer.java b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticContainer.java
index 92b1d95..91e0521 100644
--- a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticContainer.java
+++ b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticContainer.java
@@ -45,11 +45,13 @@
       case V2_4:
         return "elasticsearch:2.4.6-alpine";
       case V5_6:
-        return "docker.elastic.co/elasticsearch/elasticsearch:5.6.10";
+        return "docker.elastic.co/elasticsearch/elasticsearch:5.6.11";
       case V6_2:
         return "docker.elastic.co/elasticsearch/elasticsearch-oss:6.2.4";
       case V6_3:
         return "docker.elastic.co/elasticsearch/elasticsearch-oss:6.3.2";
+      case V6_4:
+        return "docker.elastic.co/elasticsearch/elasticsearch-oss:6.4.0";
     }
     throw new IllegalStateException("No tests for version: " + version.name());
   }
diff --git a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticV6QueryAccountsTest.java b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticV6QueryAccountsTest.java
index e0ee8c9..059585d 100644
--- a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticV6QueryAccountsTest.java
+++ b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticV6QueryAccountsTest.java
@@ -34,7 +34,7 @@
       return;
     }
 
-    container = ElasticContainer.createAndStart(ElasticVersion.V6_3);
+    container = ElasticContainer.createAndStart(ElasticVersion.V6_4);
     nodeInfo = new ElasticNodeInfo(container.getHttpHost().getPort());
   }
 
diff --git a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticV6QueryChangesTest.java b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticV6QueryChangesTest.java
index b69c4d9..fa3b6c4 100644
--- a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticV6QueryChangesTest.java
+++ b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticV6QueryChangesTest.java
@@ -35,7 +35,7 @@
       return;
     }
 
-    container = ElasticContainer.createAndStart(ElasticVersion.V6_3);
+    container = ElasticContainer.createAndStart(ElasticVersion.V6_4);
     nodeInfo = new ElasticNodeInfo(container.getHttpHost().getPort());
   }
 
diff --git a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticV6QueryGroupsTest.java b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticV6QueryGroupsTest.java
index 5999d05..ee88ee4 100644
--- a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticV6QueryGroupsTest.java
+++ b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticV6QueryGroupsTest.java
@@ -34,7 +34,7 @@
       return;
     }
 
-    container = ElasticContainer.createAndStart(ElasticVersion.V6_3);
+    container = ElasticContainer.createAndStart(ElasticVersion.V6_4);
     nodeInfo = new ElasticNodeInfo(container.getHttpHost().getPort());
   }
 
diff --git a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticVersionTest.java b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticVersionTest.java
index 860dca6..b598a0a 100644
--- a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticVersionTest.java
+++ b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticVersionTest.java
@@ -29,21 +29,31 @@
     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.9")).isEqualTo(ElasticVersion.V5_6);
-    assertThat(ElasticVersion.forVersion("5.6.10")).isEqualTo(ElasticVersion.V5_6);
+    assertThat(ElasticVersion.forVersion("5.6.11")).isEqualTo(ElasticVersion.V5_6);
 
     assertThat(ElasticVersion.forVersion("6.2.0")).isEqualTo(ElasticVersion.V6_2);
     assertThat(ElasticVersion.forVersion("6.2.4")).isEqualTo(ElasticVersion.V6_2);
 
     assertThat(ElasticVersion.forVersion("6.3.0")).isEqualTo(ElasticVersion.V6_3);
-    assertThat(ElasticVersion.forVersion("6.3.1")).isEqualTo(ElasticVersion.V6_3);
+    assertThat(ElasticVersion.forVersion("6.3.2")).isEqualTo(ElasticVersion.V6_3);
+
+    assertThat(ElasticVersion.forVersion("6.4.0")).isEqualTo(ElasticVersion.V6_4);
+    assertThat(ElasticVersion.forVersion("6.4.1")).isEqualTo(ElasticVersion.V6_4);
   }
 
   @Test
   public void unsupportedVersion() throws Exception {
-    exception.expect(ElasticVersion.InvalidVersion.class);
+    exception.expect(ElasticVersion.UnsupportedVersion.class);
     exception.expectMessage(
-        "Invalid version: [4.0.0]. Supported versions: " + ElasticVersion.supportedVersions());
+        "Unsupported version: [4.0.0]. Supported versions: " + ElasticVersion.supportedVersions());
     ElasticVersion.forVersion("4.0.0");
   }
+
+  @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();
+  }
 }
diff --git a/gerrit-extension-api/pom.xml b/gerrit-extension-api/pom.xml
index 0c03216..ba8a07a 100644
--- a/gerrit-extension-api/pom.xml
+++ b/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.15.3</version>
+  <version>2.15.4-SNAPSHOT</version>
   <packaging>jar</packaging>
   <name>Gerrit Code Review - Extension API</name>
   <description>API for Gerrit Extensions</description>
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/access/ProjectAccessInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/access/ProjectAccessInfo.java
index 9678253..bc5daf6 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/access/ProjectAccessInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/access/ProjectAccessInfo.java
@@ -27,6 +27,7 @@
   public Set<String> ownerOf;
   public Boolean canUpload;
   public Boolean canAdd;
+  public Boolean canAddTags;
   public Boolean configVisible;
   public Map<String, GroupInfo> groups;
 }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ConfigInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ConfigInfo.java
index bcbf3e7..1460899 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ConfigInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ConfigInfo.java
@@ -54,14 +54,17 @@
   }
 
   public static class MaxObjectSizeLimitInfo {
-    /* The effective value. Null if not set. */
+    /** The effective value in bytes. Null if not set. */
     @Nullable public String value;
 
-    /* The value configured on the project. Null if not set. */
+    /** The value configured explicitly on the project as a formatted string. Null if not set. */
     @Nullable public String configuredValue;
 
-    /* The value configured globally. Null if not set. */
-    @Nullable public String inheritedValue;
+    /**
+     * Whether the value was inherited or overridden from the project's parent hierarchy or global
+     * config. Null if not inherited or overridden.
+     */
+    @Nullable public String summary;
   }
 
   public static class ConfigParameterInfo {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/access/ProjectAccessInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/access/ProjectAccessInfo.java
index b115c7d..88635df 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/access/ProjectAccessInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/access/ProjectAccessInfo.java
@@ -19,6 +19,8 @@
 public class ProjectAccessInfo extends JavaScriptObject {
   public final native boolean canAddRefs() /*-{ return this.can_add ? true : false; }-*/;
 
+  public final native boolean canAddTagRefs() /*-{ return this.can_add_tags ? true : false; }-*/;
+
   public final native boolean isOwner() /*-{ return this.is_owner ? true : false; }-*/;
 
   public final native boolean configVisible() /*-{ return this.config_visible ? true : false; }-*/;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/UsernameField.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/UsernameField.java
index a893c5a..def29b2 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/UsernameField.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/UsernameField.java
@@ -23,7 +23,6 @@
 import com.google.gerrit.client.rpc.RestApi;
 import com.google.gerrit.client.ui.OnEditEnabler;
 import com.google.gerrit.extensions.client.AccountFieldName;
-import com.google.gerrit.reviewdb.client.Account;
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.ClickHandler;
 import com.google.gwt.event.dom.client.KeyCodes;
@@ -38,6 +37,12 @@
 import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
 
 class UsernameField extends Composite {
+  // If these regular expressions are modified the same modifications should be done to the
+  // corresponding regular expressions in the
+  // com.google.gerrit.server.account.ExternalId class.
+  private static final String USER_NAME_PATTERN_FIRST_REGEX = "[a-zA-Z0-9]";
+  private static final String USER_NAME_PATTERN_REST_REGEX = "[a-zA-Z0-9.!#$%&’*+=?^_`\\{|\\}~@-]";
+
   private CopyableLabel userNameLbl;
   private NpTextBox userNameTxt;
   private Button setUserName;
@@ -185,9 +190,9 @@
           final TextBox box = (TextBox) event.getSource();
           final String re;
           if (box.getCursorPos() == 0) {
-            re = Account.USER_NAME_PATTERN_FIRST;
+            re = USER_NAME_PATTERN_FIRST_REGEX;
           } else {
-            re = Account.USER_NAME_PATTERN_REST;
+            re = USER_NAME_PATTERN_REST_REGEX;
           }
           if (!String.valueOf(code).matches("^" + re + "$")) {
             event.preventDefault();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.java
index fe27e9c..7b18a39 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.java
@@ -36,8 +36,6 @@
 
   String effectiveMaxObjectSizeLimit(String effectiveMaxObjectSizeLimit);
 
-  String globalMaxObjectSizeLimit(String globalMaxObjectSizeLimit);
-
   String noMaxObjectSizeLimit();
 
   String pluginProjectOptionsTitle(String pluginName);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.properties
index f746365..c9aa987 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.properties
@@ -6,7 +6,6 @@
 deletedReference = Reference {0} was deleted
 deletedSection = Section {0} was deleted
 effectiveMaxObjectSizeLimit = effective: {0} bytes
-globalMaxObjectSizeLimit = The global max object size limit is set to {0}. The limit cannot be increased on project level.
 noMaxObjectSizeLimit = No max object size limit is set.
 pluginProjectOptionsTitle = {0} Plugin Options
 pluginProjectOptionsTitle = {0} Plugin
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
index 568be85..c54a41b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
@@ -432,9 +432,8 @@
     if (result.maxObjectSizeLimit().value() != null) {
       effectiveMaxObjectSizeLimit.setText(
           AdminMessages.I.effectiveMaxObjectSizeLimit(result.maxObjectSizeLimit().value()));
-      if (result.maxObjectSizeLimit().inheritedValue() != null) {
-        effectiveMaxObjectSizeLimit.setTitle(
-            AdminMessages.I.globalMaxObjectSizeLimit(result.maxObjectSizeLimit().inheritedValue()));
+      if (result.maxObjectSizeLimit().summary() != null) {
+        effectiveMaxObjectSizeLimit.setTitle(result.maxObjectSizeLimit().summary());
       }
     } else {
       effectiveMaxObjectSizeLimit.setText(AdminMessages.I.noMaxObjectSizeLimit());
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectTagsScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectTagsScreen.java
index 18e4176..22c331d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectTagsScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectTagsScreen.java
@@ -94,7 +94,7 @@
         new GerritCallback<ProjectAccessInfo>() {
           @Override
           public void onSuccess(ProjectAccessInfo result) {
-            addPanel.setVisible(result.canAddRefs());
+            addPanel.setVisible(result.canAddTagRefs());
           }
         });
     query = new Query(match).start(start).run();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfo.java
index 98dcbb6..a6a7ce6 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfo.java
@@ -173,10 +173,10 @@
   public static class MaxObjectSizeLimitInfo extends JavaScriptObject {
     public final native String value() /*-{ return this.value; }-*/;
 
-    public final native String inheritedValue() /*-{ return this.inherited_value; }-*/;
-
     public final native String configuredValue() /*-{ return this.configured_value }-*/;
 
+    public final native String summary() /*-{ return this.summary; }-*/;
+
     protected MaxObjectSizeLimitInfo() {}
   }
 
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
index 5c5dc6e..7684bec 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
@@ -1160,10 +1160,18 @@
 
   private void checkRequiresCapability(ViewData d)
       throws AuthException, PermissionBackendException {
-    globals
-        .permissionBackend
-        .user(globals.currentUser)
-        .checkAny(GlobalPermission.fromAnnotation(d.pluginName, d.view.getClass()));
+    try {
+      globals
+          .permissionBackend
+          .user(globals.currentUser)
+          .check(GlobalPermission.ADMINISTRATE_SERVER);
+    } catch (AuthException e) {
+      // Skiping
+      globals
+          .permissionBackend
+          .user(globals.currentUser)
+          .checkAny(GlobalPermission.fromAnnotation(d.pluginName, d.view.getClass()));
+    }
   }
 
   private static long handleException(
diff --git a/gerrit-httpd/src/test/java/com/google/gerrit/httpd/raw/IndexServletTest.java b/gerrit-httpd/src/test/java/com/google/gerrit/httpd/raw/IndexServletTest.java
index d106eec..abf890e 100644
--- a/gerrit-httpd/src/test/java/com/google/gerrit/httpd/raw/IndexServletTest.java
+++ b/gerrit-httpd/src/test/java/com/google/gerrit/httpd/raw/IndexServletTest.java
@@ -15,13 +15,14 @@
 package com.google.gerrit.httpd.raw;
 
 import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 import com.google.template.soy.data.SoyMapData;
 import java.net.URISyntaxException;
 import org.junit.Test;
 
 public class IndexServletTest {
-  class TestIndexServlet extends IndexServlet {
+  static class TestIndexServlet extends IndexServlet {
     private static final long serialVersionUID = 1L;
 
     TestIndexServlet(String canonicalURL, String cdnPath) throws URISyntaxException {
@@ -29,7 +30,7 @@
     }
 
     String getIndexSource() {
-      return new String(indexSource);
+      return new String(indexSource, UTF_8);
     }
   }
 
diff --git a/gerrit-index/src/main/java/com/google/gerrit/index/SiteIndexer.java b/gerrit-index/src/main/java/com/google/gerrit/index/SiteIndexer.java
index 4ad0827..9e41262 100644
--- a/gerrit-index/src/main/java/com/google/gerrit/index/SiteIndexer.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/SiteIndexer.java
@@ -15,11 +15,13 @@
 package com.google.gerrit.index;
 
 import static com.google.common.base.Preconditions.checkNotNull;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 import com.google.common.base.Stopwatch;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.MoreExecutors;
 import java.io.OutputStream;
+import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.RejectedExecutionException;
@@ -65,7 +67,7 @@
 
   protected int totalWork = -1;
   protected OutputStream progressOut = NullOutputStream.INSTANCE;
-  protected PrintWriter verboseWriter = new PrintWriter(NullOutputStream.INSTANCE);
+  protected PrintWriter verboseWriter = newPrintWriter(NullOutputStream.INSTANCE);
 
   public void setTotalWork(int num) {
     totalWork = num;
@@ -76,7 +78,7 @@
   }
 
   public void setVerboseOut(OutputStream out) {
-    verboseWriter = new PrintWriter(checkNotNull(out));
+    verboseWriter = newPrintWriter(checkNotNull(out));
   }
 
   public abstract Result indexAll(I index);
@@ -87,6 +89,10 @@
         new ErrorListener(future, desc, progress, ok), MoreExecutors.directExecutor());
   }
 
+  protected PrintWriter newPrintWriter(OutputStream out) {
+    return new PrintWriter(new OutputStreamWriter(out, UTF_8));
+  }
+
   private static class ErrorListener implements Runnable {
     private final ListenableFuture<?> future;
     private final String desc;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/MigrateToNoteDb.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/MigrateToNoteDb.java
index 0b44ccf..ff23747 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/MigrateToNoteDb.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/MigrateToNoteDb.java
@@ -16,6 +16,7 @@
 
 import static com.google.common.base.MoreObjects.firstNonNull;
 import static com.google.gerrit.server.schema.DataSourceProvider.Context.MULTI_USER;
+import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.stream.Collectors.joining;
 import static java.util.stream.Collectors.toList;
 
@@ -38,6 +39,7 @@
 import com.google.inject.Inject;
 import com.google.inject.Injector;
 import com.google.inject.Provider;
+import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
@@ -137,7 +139,7 @@
           migrator.migrate();
         }
       }
-      try (PrintWriter w = new PrintWriter(System.out, true)) {
+      try (PrintWriter w = new PrintWriter(new OutputStreamWriter(System.out, UTF_8), true)) {
         gcAllUsers.run(w);
       }
     } finally {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/ProjectQoSFilter.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/ProjectQoSFilter.java
index f85a27a..4d2bb41 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/ProjectQoSFilter.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/ProjectQoSFilter.java
@@ -147,7 +147,7 @@
   @Override
   public void destroy() {}
 
-  private final class Listener implements ContinuationListener {
+  private static final class Listener implements ContinuationListener {
     final Future<?> future;
 
     Listener(Future<?> future) {
diff --git a/gerrit-plugin-api/pom.xml b/gerrit-plugin-api/pom.xml
index 1784ded..2cb31be 100644
--- a/gerrit-plugin-api/pom.xml
+++ b/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.15.3</version>
+  <version>2.15.4-SNAPSHOT</version>
   <packaging>jar</packaging>
   <name>Gerrit Code Review - Plugin API</name>
   <description>API for Gerrit Plugins</description>
diff --git a/gerrit-plugin-gwtui/pom.xml b/gerrit-plugin-gwtui/pom.xml
index 73d6a96..0034b8f 100644
--- a/gerrit-plugin-gwtui/pom.xml
+++ b/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.15.3</version>
+  <version>2.15.4-SNAPSHOT</version>
   <packaging>jar</packaging>
   <name>Gerrit Code Review - Plugin GWT UI</name>
   <description>Common Classes for Gerrit GWT UI Plugins</description>
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Account.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Account.java
index b7506e5..a1c16e1 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Account.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Account.java
@@ -46,31 +46,6 @@
  * </ul>
  */
 public final class Account {
-  public static final String USER_NAME_PATTERN_FIRST = "[a-zA-Z0-9]";
-  public static final String USER_NAME_PATTERN_REST = "[a-zA-Z0-9._@-]";
-  public static final String USER_NAME_PATTERN_LAST = "[a-zA-Z0-9]";
-
-  /** Regular expression that {@link #userName} must match. */
-  public static final String USER_NAME_PATTERN =
-      "^"
-          + //
-          "("
-          + //
-          USER_NAME_PATTERN_FIRST
-          + //
-          USER_NAME_PATTERN_REST
-          + "*"
-          + //
-          USER_NAME_PATTERN_LAST
-          + //
-          "|"
-          + //
-          USER_NAME_PATTERN_FIRST
-          + //
-          ")"
-          + //
-          "$";
-
   /** Key local to Gerrit to identify a user. */
   public static class Id extends IntKey<com.google.gwtorm.client.Key<?>> {
     private static final long serialVersionUID = 1L;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountResolver.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountResolver.java
index a1cca06..5eee8d1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountResolver.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountResolver.java
@@ -17,6 +17,7 @@
 import static java.util.stream.Collectors.toSet;
 
 import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.server.account.externalids.ExternalId;
 import com.google.gerrit.server.query.account.InternalAccountQuery;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -109,7 +110,7 @@
       return Collections.emptySet();
     }
 
-    if (nameOrEmail.matches(Account.USER_NAME_PATTERN)) {
+    if (ExternalId.isValidUsername(nameOrEmail)) {
       AccountState who = byId.getByUsername(nameOrEmail);
       if (who != null) {
         return Collections.singleton(who.getAccount().getId());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/ChangeUserName.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/ChangeUserName.java
index 5b7aead..aa6baa1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/ChangeUserName.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/ChangeUserName.java
@@ -18,7 +18,6 @@
 
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.errors.NameAlreadyUsedException;
-import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.externalids.ExternalId;
 import com.google.gerrit.server.account.externalids.ExternalIds;
@@ -32,7 +31,6 @@
 import java.io.IOException;
 import java.util.Collection;
 import java.util.concurrent.Callable;
-import java.util.regex.Pattern;
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -40,7 +38,6 @@
 /** Operation to change the username of an account. */
 public class ChangeUserName implements Callable<VoidResult> {
   private static final Logger log = LoggerFactory.getLogger(ChangeUserName.class);
-  private static final Pattern USER_NAME_PATTERN = Pattern.compile(Account.USER_NAME_PATTERN);
 
   public static final String USERNAME_CANNOT_BE_CHANGED = "Username cannot be changed.";
 
@@ -84,7 +81,7 @@
 
     ExternalIdsUpdate externalIdsUpdate = externalIdsUpdateFactory.create();
     if (newUsername != null && !newUsername.isEmpty()) {
-      if (!USER_NAME_PATTERN.matcher(newUsername).matches()) {
+      if (!ExternalId.isValidUsername(newUsername)) {
         throw new InvalidUserNameException();
       }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java
index 11b7bd8..ef1e8cc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java
@@ -120,9 +120,8 @@
       throw new BadRequestException("username must match URL");
     }
 
-    if (!username.matches(Account.USER_NAME_PATTERN)) {
-      throw new BadRequestException(
-          "Username '" + username + "' must contain only letters, numbers, _, - or .");
+    if (!ExternalId.isValidUsername(username)) {
+      throw new BadRequestException("Invalid username '" + username + "'");
     }
 
     Set<AccountGroup.UUID> groups = parseGroups(input.groups);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/InvalidUserNameException.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/InvalidUserNameException.java
index d60b7af..e8d0df7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/InvalidUserNameException.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/InvalidUserNameException.java
@@ -14,9 +14,7 @@
 
 package com.google.gerrit.server.account;
 
-import com.google.gerrit.reviewdb.client.Account;
-
-/** Error indicating the SSH user name does not match {@link Account#USER_NAME_PATTERN} pattern. */
+/** Error indicating the SSH user name does not match the expected pattern. */
 public class InvalidUserNameException extends Exception {
 
   private static final long serialVersionUID = 1L;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/externalids/ExternalId.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/externalids/ExternalId.java
index d89fd9b..85401c5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/externalids/ExternalId.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/externalids/ExternalId.java
@@ -30,6 +30,7 @@
 import java.io.Serializable;
 import java.util.Objects;
 import java.util.Set;
+import java.util.regex.Pattern;
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.Constants;
@@ -37,6 +38,36 @@
 
 @AutoValue
 public abstract class ExternalId implements Serializable {
+  // If these regular expressions are modified the same modifications should be done to the
+  // corresponding regular expressions in the
+  // com.google.gerrit.client.account.UsernameField class.
+  private static final String USER_NAME_PATTERN_FIRST_REGEX = "[a-zA-Z0-9]";
+  private static final String USER_NAME_PATTERN_REST_REGEX = "[a-zA-Z0-9.!#$%&’*+=?^_`\\{|\\}~@-]";
+  private static final String USER_NAME_PATTERN_LAST_REGEX = "[a-zA-Z0-9]";
+
+  /** Regular expression that a username must match. */
+  private static final String USER_NAME_PATTERN_REGEX =
+      "^("
+          + //
+          USER_NAME_PATTERN_FIRST_REGEX
+          + //
+          USER_NAME_PATTERN_REST_REGEX
+          + "*"
+          + //
+          USER_NAME_PATTERN_LAST_REGEX
+          + //
+          "|"
+          + //
+          USER_NAME_PATTERN_FIRST_REGEX
+          + //
+          ")$";
+
+  private static final Pattern USER_NAME_PATTERN = Pattern.compile(USER_NAME_PATTERN_REGEX);
+
+  public static boolean isValidUsername(String username) {
+    return USER_NAME_PATTERN.matcher(username).matches();
+  }
+
   private static final long serialVersionUID = 1L;
 
   private static final String EXTERNAL_ID_SECTION = "externalId";
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/args4j/AccountIdHandler.java b/gerrit-server/src/main/java/com/google/gerrit/server/args4j/AccountIdHandler.java
index 988b9df7..c7d3f73 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/args4j/AccountIdHandler.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/args4j/AccountIdHandler.java
@@ -20,6 +20,7 @@
 import com.google.gerrit.server.account.AccountManager;
 import com.google.gerrit.server.account.AccountResolver;
 import com.google.gerrit.server.account.AuthRequest;
+import com.google.gerrit.server.account.externalids.ExternalId;
 import com.google.gerrit.server.config.AuthConfig;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -90,7 +91,7 @@
   }
 
   private Account.Id createAccountByLdap(String user) throws CmdLineException, IOException {
-    if (!user.matches(Account.USER_NAME_PATTERN)) {
+    if (!ExternalId.isValidUsername(user)) {
       throw new CmdLineException(owner, "user \"" + user + "\" not found");
     }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
index 218bd82..ca61f80 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
@@ -408,6 +408,10 @@
     return commentLinkSections;
   }
 
+  public void addCommentLinkSection(CommentLinkInfoImpl commentLink) {
+    commentLinkSections.add(commentLink);
+  }
+
   public ConfiguredMimeTypes getMimeTypes() {
     return mimeTypes;
   }
@@ -967,7 +971,6 @@
                     rc.getString(COMMENTLINK, name, KEY_MATCH), name, e.getMessage())));
       }
     }
-    commentLinkSections = ImmutableList.copyOf(commentLinkSections);
   }
 
   private void loadSubscribeSections(Config rc) throws ConfigInvalidException {
@@ -1160,6 +1163,7 @@
     savePluginSections(rc, keepGroups);
     groupList.retainUUIDs(keepGroups);
     saveLabelSections(rc);
+    saveCommentLinkSections(rc);
     saveSubscribeSections(rc);
 
     saveConfig(PROJECT_CONFIG, rc);
@@ -1445,6 +1449,23 @@
     }
   }
 
+  private void saveCommentLinkSections(Config rc) {
+    if (commentLinkSections != null) {
+      for (CommentLinkInfoImpl cm : commentLinkSections) {
+        rc.setString(COMMENTLINK, cm.name, KEY_MATCH, cm.match);
+        if (!Strings.isNullOrEmpty(cm.html)) {
+          rc.setString(COMMENTLINK, cm.name, KEY_HTML, cm.html);
+        }
+        if (!Strings.isNullOrEmpty(cm.link)) {
+          rc.setString(COMMENTLINK, cm.name, KEY_LINK, cm.link);
+        }
+        if (cm.enabled != null && !cm.enabled) {
+          rc.setBoolean(COMMENTLINK, cm.name, KEY_ENABLED, cm.enabled);
+        }
+      }
+    }
+  }
+
   private static void setBooleanConfigKey(
       Config rc, String section, String name, String key, boolean value, boolean defaultValue) {
     if (value == defaultValue) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/TransferConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/TransferConfig.java
index f85f24b..8c93833 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/TransferConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/TransferConfig.java
@@ -28,6 +28,7 @@
   private final PackConfig packConfig;
   private final long maxObjectSizeLimit;
   private final String maxObjectSizeLimitFormatted;
+  private final boolean inheritProjectMaxObjectSizeLimit;
 
   @Inject
   TransferConfig(@GerritServerConfig Config cfg) {
@@ -42,6 +43,8 @@
                 TimeUnit.SECONDS);
     maxObjectSizeLimit = cfg.getLong("receive", "maxObjectSizeLimit", 0);
     maxObjectSizeLimitFormatted = cfg.getString("receive", null, "maxObjectSizeLimit");
+    inheritProjectMaxObjectSizeLimit =
+        cfg.getBoolean("receive", "inheritProjectMaxObjectSizeLimit", false);
 
     packConfig = new PackConfig();
     packConfig.setDeltaCompress(false);
@@ -65,4 +68,8 @@
   public String getFormattedMaxObjectSizeLimit() {
     return maxObjectSizeLimitFormatted;
   }
+
+  public boolean getInheritProjectMaxObjectSizeLimit() {
+    return inheritProjectMaxObjectSizeLimit;
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java
index d6fb161..654341d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java
@@ -215,7 +215,7 @@
     rp.setAllowNonFastForwards(true);
     rp.setRefLogIdent(user.newRefLogIdent());
     rp.setTimeout(transferConfig.getTimeout());
-    rp.setMaxObjectSizeLimit(state.getEffectiveMaxObjectSizeLimit());
+    rp.setMaxObjectSizeLimit(state.getEffectiveMaxObjectSizeLimit().value);
     rp.setCheckReceivedObjects(state.getConfig().getCheckReceivedObjects());
     rp.setRefFilter(new ReceiveRefFilter());
     rp.setAllowPushOptions(true);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/AddMembers.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/AddMembers.java
index b6bcb3b..6e0e512 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/AddMembers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/AddMembers.java
@@ -38,6 +38,7 @@
 import com.google.gerrit.server.account.AccountsCollection;
 import com.google.gerrit.server.account.AuthRequest;
 import com.google.gerrit.server.account.GroupControl;
+import com.google.gerrit.server.account.externalids.ExternalId;
 import com.google.gerrit.server.config.AuthConfig;
 import com.google.gerrit.server.group.AddMembers.Input;
 import com.google.gwtorm.server.OrmException;
@@ -181,7 +182,7 @@
   }
 
   private Account createAccountByLdap(String user) throws IOException {
-    if (!user.matches(Account.USER_NAME_PATTERN)) {
+    if (!ExternalId.isValidUsername(user)) {
       return null;
     }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AllAccountsIndexer.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AllAccountsIndexer.java
index b6a95b7..c66ef30 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AllAccountsIndexer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AllAccountsIndexer.java
@@ -29,7 +29,6 @@
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import java.io.IOException;
-import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
@@ -60,7 +59,7 @@
 
   @Override
   public SiteIndexer.Result indexAll(AccountIndex index) {
-    ProgressMonitor progress = new TextProgressMonitor(new PrintWriter(progressOut));
+    ProgressMonitor progress = new TextProgressMonitor(newPrintWriter(progressOut));
     progress.start(2);
     Stopwatch sw = Stopwatch.createStarted();
     List<Account.Id> ids;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/group/AllGroupsIndexer.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/AllGroupsIndexer.java
index 2b59675..3584961 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/group/AllGroupsIndexer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/group/AllGroupsIndexer.java
@@ -32,7 +32,6 @@
 import com.google.gwtorm.server.SchemaFactory;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
-import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
@@ -67,7 +66,7 @@
 
   @Override
   public SiteIndexer.Result indexAll(GroupIndex index) {
-    ProgressMonitor progress = new TextProgressMonitor(new PrintWriter(progressOut));
+    ProgressMonitor progress = new TextProgressMonitor(newPrintWriter(progressOut));
     progress.start(2);
     Stopwatch sw = Stopwatch.createStarted();
     List<AccountGroup.UUID> uuids;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ListMailFilter.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ListMailFilter.java
index 21347cb..2e7c828 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ListMailFilter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ListMailFilter.java
@@ -54,7 +54,8 @@
     }
 
     boolean match = mailPattern.matcher(message.from().email).find();
-    if (mode == ListFilterMode.WHITELIST && !match || mode == ListFilterMode.BLACKLIST && match) {
+    if ((mode == ListFilterMode.WHITELIST && !match)
+        || (mode == ListFilterMode.BLACKLIST && match)) {
       log.info("Mail message from " + message.from() + " rejected by list filter");
       return false;
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java
index a9663c7..a2e0997 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java
@@ -454,7 +454,7 @@
       excludeOrigSubj = true;
       String aTopic = trimOrNull(a.getTopic());
       excludeTopic =
-          Objects.equals(aTopic, b.getTopic()) || "".equals(aTopic) && b.getTopic() == null;
+          Objects.equals(aTopic, b.getTopic()) || ("".equals(aTopic) && b.getTopic() == null);
       aUpdated = bundleA.getLatestTimestamp();
     } else if (bundleA.source == NOTE_DB && bundleB.source == REVIEW_DB) {
       boolean createdOnMatchesFirstPs =
@@ -472,7 +472,7 @@
       excludeOrigSubj = true;
       String bTopic = trimOrNull(b.getTopic());
       excludeTopic =
-          Objects.equals(bTopic, a.getTopic()) || a.getTopic() == null && "".equals(bTopic);
+          Objects.equals(bTopic, a.getTopic()) || (a.getTopic() == null && "".equals(bTopic));
       bUpdated = bundleB.getLatestTimestamp();
     }
 
@@ -776,7 +776,8 @@
         excludePostSubmit = a.getValue() == 0 && b.isPostSubmit();
       } else if (bundleA.source == NOTE_DB && bundleB.source == REVIEW_DB) {
         excludeGranted =
-            tb.before(psb.getCreatedOn()) && ta.equals(psa.getCreatedOn()) || tb.compareTo(ta) < 0;
+            (tb.before(psb.getCreatedOn()) && ta.equals(psa.getCreatedOn()))
+                || (tb.compareTo(ta) < 0);
         excludePostSubmit = b.getValue() == 0 && a.isPostSubmit();
       }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/permissions/ProjectPermission.java b/gerrit-server/src/main/java/com/google/gerrit/server/permissions/ProjectPermission.java
index d0abf9a..5e8bbc4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/permissions/ProjectPermission.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/permissions/ProjectPermission.java
@@ -58,6 +58,21 @@
   CREATE_REF,
 
   /**
+   * Can create at least one tag reference in the project.
+   *
+   * <p>This project level permission only validates the user may create some tag reference within
+   * the project. The exact reference name must be checked at creation:
+   *
+   * <pre>permissionBackend
+   *    .user(user)
+   *    .project(proj)
+   *    .ref(ref)
+   *    .check(RefPermission.CREATE);
+   * </pre>
+   */
+  CREATE_TAG_REF,
+
+  /**
    * Can create at least one change in the project.
    *
    * <p>This project level permission only validates the user may create a change for some branch
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfoImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfoImpl.java
index 158d410..9913693 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfoImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfoImpl.java
@@ -30,7 +30,7 @@
 import com.google.gerrit.server.config.PluginConfigFactory;
 import com.google.gerrit.server.config.ProjectConfigEntry;
 import com.google.gerrit.server.extensions.webui.UiActions;
-import com.google.gerrit.server.git.TransferConfig;
+import com.google.gerrit.server.project.ProjectState.EffectiveMaxObjectSizeLimit;
 import java.util.Arrays;
 import java.util.LinkedHashMap;
 import java.util.Map;
@@ -40,7 +40,6 @@
   public ConfigInfoImpl(
       boolean serverEnableSignedPush,
       ProjectControl control,
-      TransferConfig transferConfig,
       DynamicMap<ProjectConfigEntry> pluginConfigEntries,
       PluginConfigFactory cfgFactory,
       AllProjectsName allProjects,
@@ -114,7 +113,7 @@
     this.privateByDefault = privateByDefault;
     this.workInProgressByDefault = workInProgressByDefault;
 
-    this.maxObjectSizeLimit = getMaxObjectSizeLimit(projectState, transferConfig, p);
+    this.maxObjectSizeLimit = getMaxObjectSizeLimit(projectState, p);
 
     this.submitType = p.getSubmitType();
     this.state =
@@ -139,13 +138,13 @@
     this.extensionPanelNames = projectState.getConfig().getExtensionPanelSections();
   }
 
-  private MaxObjectSizeLimitInfo getMaxObjectSizeLimit(
-      ProjectState projectState, TransferConfig transferConfig, Project p) {
+  private MaxObjectSizeLimitInfo getMaxObjectSizeLimit(ProjectState projectState, Project p) {
     MaxObjectSizeLimitInfo info = new MaxObjectSizeLimitInfo();
-    long value = projectState.getEffectiveMaxObjectSizeLimit();
+    EffectiveMaxObjectSizeLimit limit = projectState.getEffectiveMaxObjectSizeLimit();
+    long value = limit.value;
     info.value = value == 0 ? null : String.valueOf(value);
     info.configuredValue = p.getMaxObjectSizeLimit();
-    info.inheritedValue = transferConfig.getFormattedMaxObjectSizeLimit();
+    info.summary = limit.summary;
     return info;
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetAccess.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetAccess.java
index 07c52f9..caa7606 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetAccess.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetAccess.java
@@ -16,6 +16,7 @@
 
 import static com.google.gerrit.server.permissions.GlobalPermission.ADMINISTRATE_SERVER;
 import static com.google.gerrit.server.permissions.ProjectPermission.CREATE_REF;
+import static com.google.gerrit.server.permissions.ProjectPermission.CREATE_TAG_REF;
 import static com.google.gerrit.server.permissions.RefPermission.CREATE_CHANGE;
 import static com.google.gerrit.server.permissions.RefPermission.READ;
 import static java.util.stream.Collectors.toMap;
@@ -248,6 +249,7 @@
             pc.isOwner()
                 || (checkReadConfig && perm.ref(RefNames.REFS_CONFIG).testOrFalse(CREATE_CHANGE)));
     info.canAdd = toBoolean(perm.testOrFalse(CREATE_REF));
+    info.canAddTags = toBoolean(perm.testOrFalse(CREATE_TAG_REF));
     info.configVisible = checkReadConfig || pc.isOwner();
 
     info.groups =
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetConfig.java
index 760bd57..3995e1f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetConfig.java
@@ -23,14 +23,12 @@
 import com.google.gerrit.server.config.PluginConfigFactory;
 import com.google.gerrit.server.config.ProjectConfigEntry;
 import com.google.gerrit.server.extensions.webui.UiActions;
-import com.google.gerrit.server.git.TransferConfig;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
 @Singleton
 public class GetConfig implements RestReadView<ProjectResource> {
   private final boolean serverEnableSignedPush;
-  private final TransferConfig transferConfig;
   private final DynamicMap<ProjectConfigEntry> pluginConfigEntries;
   private final PluginConfigFactory cfgFactory;
   private final AllProjectsName allProjects;
@@ -40,14 +38,12 @@
   @Inject
   public GetConfig(
       @EnableSignedPush boolean serverEnableSignedPush,
-      TransferConfig transferConfig,
       DynamicMap<ProjectConfigEntry> pluginConfigEntries,
       PluginConfigFactory cfgFactory,
       AllProjectsName allProjects,
       UiActions uiActions,
       DynamicMap<RestView<ProjectResource>> views) {
     this.serverEnableSignedPush = serverEnableSignedPush;
-    this.transferConfig = transferConfig;
     this.pluginConfigEntries = pluginConfigEntries;
     this.allProjects = allProjects;
     this.cfgFactory = cfgFactory;
@@ -60,7 +56,6 @@
     return new ConfigInfoImpl(
         serverEnableSignedPush,
         resource.getControl(),
-        transferConfig,
         pluginConfigEntries,
         cfgFactory,
         allProjects,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
index 1b28177..e97411e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.server.project;
 
 import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.gerrit.reviewdb.client.RefNames.REFS_TAGS;
 
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
@@ -251,6 +252,10 @@
     return (canPerformOnAnyRef(Permission.CREATE) || isAdmin());
   }
 
+  private boolean canAddTagRefs() {
+    return (canPerformOnTagRef(Permission.CREATE) || isAdmin());
+  }
+
   private boolean canCreateChanges() {
     for (SectionMatcher matcher : access()) {
       AccessSection section = matcher.section;
@@ -281,6 +286,26 @@
     return declaredOwner;
   }
 
+  private boolean canPerformOnTagRef(String permissionName) {
+    for (SectionMatcher matcher : access()) {
+      AccessSection section = matcher.section;
+
+      if (section.getName().startsWith(REFS_TAGS)) {
+        Permission permission = section.getPermission(permissionName);
+        if (permission == null) {
+          continue;
+        }
+
+        Boolean can = canPerform(permissionName, section, permission);
+        if (can != null) {
+          return can;
+        }
+      }
+    }
+
+    return false;
+  }
+
   private boolean canPerformOnAnyRef(String permissionName) {
     for (SectionMatcher matcher : access()) {
       AccessSection section = matcher.section;
@@ -289,25 +314,33 @@
         continue;
       }
 
-      for (PermissionRule rule : permission.getRules()) {
-        if (rule.isBlock() || rule.isDeny() || !match(rule)) {
-          continue;
-        }
-
-        // Being in a group that was granted this permission is only an
-        // approximation.  There might be overrides and doNotInherit
-        // that would render this to be false.
-        //
-        if (controlForRef(section.getName()).canPerform(permissionName)) {
-          return true;
-        }
-        break;
+      Boolean can = canPerform(permissionName, section, permission);
+      if (can != null) {
+        return can;
       }
     }
 
     return false;
   }
 
+  private Boolean canPerform(String permissionName, AccessSection section, Permission permission) {
+    for (PermissionRule rule : permission.getRules()) {
+      if (rule.isBlock() || rule.isDeny() || !match(rule)) {
+        continue;
+      }
+
+      // Being in a group that was granted this permission is only an
+      // approximation.  There might be overrides and doNotInherit
+      // that would render this to be false.
+      //
+      if (controlForRef(section.getName()).canPerform(permissionName)) {
+        return true;
+      }
+      break;
+    }
+    return null;
+  }
+
   private boolean canPerformOnAllRefs(String permission, Set<String> ignore) {
     boolean canPerform = false;
     Set<String> patterns = allRefPatterns(permission);
@@ -463,6 +496,8 @@
 
         case CREATE_REF:
           return canAddRefs();
+        case CREATE_TAG_REF:
+          return canAddTagRefs();
         case CREATE_CHANGE:
           return canCreateChanges();
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
index 86d84a3..08c3d9f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
@@ -17,6 +17,7 @@
 import static com.google.gerrit.common.data.PermissionRule.Action.ALLOW;
 import static java.nio.charset.StandardCharsets.UTF_8;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.FluentIterable;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
@@ -97,6 +98,7 @@
   private final Map<String, ProjectLevelConfig> configs;
   private final Set<AccountGroup.UUID> localOwners;
   private final long globalMaxObjectSizeLimit;
+  private final boolean inheritProjectMaxObjectSizeLimit;
 
   /** Prolog rule state. */
   private volatile PrologMachineCopy rulesMachine;
@@ -147,6 +149,7 @@
             ? limitsFactory.create(config.getAccessSection(AccessSection.GLOBAL_CAPABILITIES))
             : null;
     this.globalMaxObjectSizeLimit = transferConfig.getMaxObjectSizeLimit();
+    this.inheritProjectMaxObjectSizeLimit = transferConfig.getInheritProjectMaxObjectSizeLimit();
 
     if (isAllProjects && !Permission.canBeOnAllProjects(AccessSection.ALL, Permission.OWNER)) {
       localOwners = Collections.emptySet();
@@ -258,13 +261,58 @@
     return cfg;
   }
 
-  public long getEffectiveMaxObjectSizeLimit() {
-    long local = config.getMaxObjectSizeLimit();
-    if (globalMaxObjectSizeLimit > 0 && local > 0) {
-      return Math.min(globalMaxObjectSizeLimit, local);
+  public static class EffectiveMaxObjectSizeLimit {
+    public long value;
+    public String summary;
+  }
+
+  private static final String MAY_NOT_SET = "This project may not set a higher limit.";
+
+  @VisibleForTesting
+  public static final String INHERITED_FROM_PARENT = "Inherited from parent project '%s'.";
+
+  @VisibleForTesting
+  public static final String OVERRIDDEN_BY_PARENT =
+      "Overridden by parent project '%s'. " + MAY_NOT_SET;
+
+  @VisibleForTesting
+  public static final String INHERITED_FROM_GLOBAL = "Inherited from the global config.";
+
+  @VisibleForTesting
+  public static final String OVERRIDDEN_BY_GLOBAL =
+      "Overridden by the global config. " + MAY_NOT_SET;
+
+  public EffectiveMaxObjectSizeLimit getEffectiveMaxObjectSizeLimit() {
+    EffectiveMaxObjectSizeLimit result = new EffectiveMaxObjectSizeLimit();
+
+    result.value = config.getMaxObjectSizeLimit();
+
+    if (inheritProjectMaxObjectSizeLimit) {
+      for (ProjectState parent : parents()) {
+        long parentValue = parent.config.getMaxObjectSizeLimit();
+        if (parentValue > 0 && result.value > 0) {
+          if (parentValue < result.value) {
+            result.value = parentValue;
+            result.summary = String.format(OVERRIDDEN_BY_PARENT, parent.config.getName());
+          }
+        } else if (parentValue > 0) {
+          result.value = parentValue;
+          result.summary = String.format(INHERITED_FROM_PARENT, parent.config.getName());
+        }
+      }
     }
-    // zero means "no limit", in this case the max is more limiting
-    return Math.max(globalMaxObjectSizeLimit, local);
+
+    if (globalMaxObjectSizeLimit > 0 && result.value > 0) {
+      if (globalMaxObjectSizeLimit < result.value) {
+        result.value = globalMaxObjectSizeLimit;
+        result.summary = OVERRIDDEN_BY_GLOBAL;
+      }
+    } else if (globalMaxObjectSizeLimit > result.value) {
+      // zero means "no limit", in this case the max is more limiting
+      result.value = globalMaxObjectSizeLimit;
+      result.summary = INHERITED_FROM_GLOBAL;
+    }
+    return result;
   }
 
   /** Get the sections that pertain only to this project. */
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java
index 8b52085..38dce09 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java
@@ -39,7 +39,6 @@
 import com.google.gerrit.server.extensions.webui.UiActions;
 import com.google.gerrit.server.git.MetaDataUpdate;
 import com.google.gerrit.server.git.ProjectConfig;
-import com.google.gerrit.server.git.TransferConfig;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
@@ -61,7 +60,6 @@
   private final Provider<MetaDataUpdate.User> metaDataUpdateFactory;
   private final ProjectCache projectCache;
   private final ProjectState.Factory projectStateFactory;
-  private final TransferConfig transferConfig;
   private final DynamicMap<ProjectConfigEntry> pluginConfigEntries;
   private final PluginConfigFactory cfgFactory;
   private final AllProjectsName allProjects;
@@ -75,7 +73,6 @@
       Provider<MetaDataUpdate.User> metaDataUpdateFactory,
       ProjectCache projectCache,
       ProjectState.Factory projectStateFactory,
-      TransferConfig transferConfig,
       DynamicMap<ProjectConfigEntry> pluginConfigEntries,
       PluginConfigFactory cfgFactory,
       AllProjectsName allProjects,
@@ -86,7 +83,6 @@
     this.metaDataUpdateFactory = metaDataUpdateFactory;
     this.projectCache = projectCache;
     this.projectStateFactory = projectStateFactory;
-    this.transferConfig = transferConfig;
     this.pluginConfigEntries = pluginConfigEntries;
     this.cfgFactory = cfgFactory;
     this.allProjects = allProjects;
@@ -197,7 +193,6 @@
       return new ConfigInfoImpl(
           serverEnableSignedPush,
           state.controlFor(user.get()),
-          transferConfig,
           pluginConfigEntries,
           cfgFactory,
           allProjects,
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/git/ProjectConfigTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/git/ProjectConfigTest.java
index 23e485b..cae8fec 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/git/ProjectConfigTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/git/ProjectConfigTest.java
@@ -29,6 +29,7 @@
 import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.config.PluginConfig;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.project.CommentLinkInfoImpl;
 import com.google.gwtorm.client.KeyUtil;
 import com.google.gwtorm.server.StandardKeyEncoder;
 import java.io.IOException;
@@ -484,6 +485,19 @@
                 + "\n");
   }
 
+  @Test
+  public void addCommentLink() throws Exception {
+    RevCommit rev = util.commit().create();
+    update(rev);
+
+    ProjectConfig cfg = read(rev);
+    CommentLinkInfoImpl cm = new CommentLinkInfoImpl("Test", "abc.*", null, "<a>link</a>", true);
+    cfg.addCommentLinkSection(cm);
+    rev = commit(cfg);
+    assertThat(text(rev, "project.config"))
+        .isEqualTo("[commentlink \"Test\"]\n\tmatch = abc.*\n\thtml = <a>link</a>\n");
+  }
+
   private ProjectConfig read(RevCommit rev) throws IOException, ConfigInvalidException {
     ProjectConfig cfg = new ProjectConfig(new Project.NameKey("test"));
     cfg.load(db, rev);
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
index df4cb95..1fc95c1 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
@@ -895,7 +895,7 @@
     return all.get(local.getProject().getNameKey());
   }
 
-  private class MockUser extends CurrentUser {
+  private static class MockUser extends CurrentUser {
     private final String username;
     private final GroupMembership groups;
 
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/ConfigSuite.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/ConfigSuite.java
index 7a4b2b0..255cd3e 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/ConfigSuite.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/ConfigSuite.java
@@ -148,7 +148,7 @@
 
     @Override
     public Object createTest() throws Exception {
-      Object test = getTestClass().getJavaClass().newInstance();
+      Object test = getTestClass().getJavaClass().getDeclaredConstructor().newInstance();
       parameterField.set(test, new org.eclipse.jgit.lib.Config(cfg));
       if (nameField != null) {
         nameField.set(test, name);
diff --git a/gerrit-util-http/src/test/java/com/google/gerrit/util/http/testutil/FakeHttpServletResponse.java b/gerrit-util-http/src/test/java/com/google/gerrit/util/http/testutil/FakeHttpServletResponse.java
index 2b1a07e..f6b3e30 100644
--- a/gerrit-util-http/src/test/java/com/google/gerrit/util/http/testutil/FakeHttpServletResponse.java
+++ b/gerrit-util-http/src/test/java/com/google/gerrit/util/http/testutil/FakeHttpServletResponse.java
@@ -25,6 +25,7 @@
 import com.google.common.net.HttpHeaders;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
 import java.nio.charset.Charset;
 import java.util.Collection;
@@ -106,7 +107,7 @@
   public synchronized PrintWriter getWriter() {
     checkState(outputStream == null, "getOutputStream() already called");
     if (writer == null) {
-      writer = new PrintWriter(actualBody);
+      writer = new PrintWriter(new OutputStreamWriter(actualBody, UTF_8));
     }
     return writer;
   }
diff --git a/gerrit-war/pom.xml b/gerrit-war/pom.xml
index 198fc13..0155b04 100644
--- a/gerrit-war/pom.xml
+++ b/gerrit-war/pom.xml
@@ -2,7 +2,7 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.google.gerrit</groupId>
   <artifactId>gerrit-war</artifactId>
-  <version>2.15.3</version>
+  <version>2.15.4-SNAPSHOT</version>
   <packaging>war</packaging>
   <name>Gerrit Code Review - WAR</name>
   <description>Gerrit WAR</description>
diff --git a/lib/jgit/jgit.bzl b/lib/jgit/jgit.bzl
index 55d5e48..cb169cb 100644
--- a/lib/jgit/jgit.bzl
+++ b/lib/jgit/jgit.bzl
@@ -1,6 +1,6 @@
 load("//tools/bzl:maven_jar.bzl", "GERRIT", "MAVEN_CENTRAL", "MAVEN_LOCAL", "maven_jar")
 
-_JGIT_VERS = "4.9.3.201807311005-r"
+_JGIT_VERS = "4.9.4.201809090327-r"
 
 _DOC_VERS = _JGIT_VERS  # Set to _JGIT_VERS unless using a snapshot
 
@@ -35,28 +35,28 @@
         name = "jgit-lib",
         artifact = "org.eclipse.jgit:org.eclipse.jgit:" + _JGIT_VERS,
         repository = _JGIT_REPO,
-        sha1 = "b063719602ce9aaa058421e5beafb26b4950532b",
-        src_sha1 = "c666721021b61465d3e140b8eef37b475c29eeb8",
+        sha1 = "a04cb44514344619bb9cef3db9323298cfda78fd",
+        src_sha1 = "e8cf23009f5ef776fcbb6d18cddf455cf0e956f6",
         unsign = True,
     )
     maven_jar(
         name = "jgit-servlet",
         artifact = "org.eclipse.jgit:org.eclipse.jgit.http.server:" + _JGIT_VERS,
         repository = _JGIT_REPO,
-        sha1 = "0b7408658db0067cdaebeb9c8dda6cadf639b84a",
+        sha1 = "a7e14f47af07b74d72841498e4d24dbafc2c0026",
         unsign = True,
     )
     maven_jar(
         name = "jgit-archive",
         artifact = "org.eclipse.jgit:org.eclipse.jgit.archive:" + _JGIT_VERS,
         repository = _JGIT_REPO,
-        sha1 = "e86ef418c398a38dda59abfbdb21e014dc94fb18",
+        sha1 = "006dbb35cc0be258929f5a72814e4d47ba61e084",
     )
     maven_jar(
         name = "jgit-junit",
         artifact = "org.eclipse.jgit:org.eclipse.jgit.junit:" + _JGIT_VERS,
         repository = _JGIT_REPO,
-        sha1 = "0a061070690a57a855fa5963b71f1f9995dbf8cb",
+        sha1 = "855cc13b23772151894966d88b1def7544955b0a",
         unsign = True,
     )
 
diff --git a/plugins/singleusergroup b/plugins/singleusergroup
index 578bc67..64032d7 160000
--- a/plugins/singleusergroup
+++ b/plugins/singleusergroup
@@ -1 +1 @@
-Subproject commit 578bc671ec38eba7c2aab38644fd1c4deacad4dd
+Subproject commit 64032d745bf818201f0d41dfb8a355d15e1bdc9f
diff --git a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.js b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.js
index dd0bccc..82664f8 100644
--- a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.js
+++ b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.js
@@ -62,7 +62,7 @@
     },
 
     _computeBlankItems(permittedLabels, label, side) {
-      if (!permittedLabels || !permittedLabels[label] ||
+      if (!permittedLabels || !permittedLabels[label] || !this.labelValues ||
           !Object.keys(this.labelValues).length) {
         return [];
       }
diff --git a/tools/BUILD b/tools/BUILD
index 060cbd8..cd34fe9 100644
--- a/tools/BUILD
+++ b/tools/BUILD
@@ -1,6 +1,154 @@
+load("@bazel_tools//tools/jdk:default_java_toolchain.bzl", "default_java_toolchain")
+
 py_binary(
     name = "merge_jars",
     srcs = ["merge_jars.py"],
     main = "merge_jars.py",
     visibility = ["//visibility:public"],
 )
+
+# TODO(davido): remove this when minimum suported Bazel version >= 0.17
+# Copied from tools/jdk/default_java_toolchain.bzl to make Bazel 0.16
+# and later Bazel released to work as expected. See this issue for context:
+# https://github.com/bazelbuild/bazel/issues/6009
+JDK9_JVM_OPTS = [
+    # Allow JavaBuilder to access internal javac APIs.
+    "--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED",
+    "--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
+    "--add-exports=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED",
+    "--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED",
+    "--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED",
+    "--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
+    "--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
+    "--add-opens=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED",
+
+    # override the javac in the JDK.
+    "--patch-module=java.compiler=$(location @bazel_tools//third_party/java/jdk/langtools:java_compiler_jar)",
+    "--patch-module=jdk.compiler=$(location @bazel_tools//third_party/java/jdk/langtools:jdk_compiler_jar)",
+
+    # quiet warnings from com.google.protobuf.UnsafeUtil,
+    # see: https://github.com/google/protobuf/issues/3781
+    "--add-opens=java.base/java.nio=ALL-UNNAMED",
+]
+
+# See https://github.com/bazelbuild/bazel/issues/3427 for more context
+default_java_toolchain(
+    name = "error_prone_warnings_toolchain_bazel_0.16",
+    bootclasspath = ["@bazel_tools//tools/jdk:platformclasspath.jar"],
+    jvm_opts = JDK9_JVM_OPTS,
+    package_configuration = [
+        ":error_prone",
+    ],
+    visibility = ["//visibility:public"],
+)
+
+default_java_toolchain(
+    name = "error_prone_warnings_toolchain",
+    bootclasspath = ["@bazel_tools//tools/jdk:platformclasspath.jar"],
+    package_configuration = [
+        ":error_prone",
+    ],
+    visibility = ["//visibility:public"],
+)
+
+# This EP warnings list is based on:
+# https://github.com/bazelbuild/BUILD_file_generator/blob/master/tools/bazel_defs/java.bzl
+java_package_configuration(
+    name = "error_prone",
+    javacopts = [
+        "-XepDisableWarningsInGeneratedCode",
+        "-Xep:MissingCasesInEnumSwitch:ERROR",
+        "-Xep:ReferenceEquality:WARN",
+        "-Xep:StringEquality:WARN",
+        "-Xep:WildcardImport:WARN",
+        "-Xep:AmbiguousMethodReference:WARN",
+        "-Xep:BadAnnotationImplementation:WARN",
+        "-Xep:BadComparable:WARN",
+        "-Xep:BoxedPrimitiveConstructor:ERROR",
+        "-Xep:CannotMockFinalClass:WARN",
+        "-Xep:ClassCanBeStatic:WARN",
+        "-Xep:ClassNewInstance:WARN",
+        "-Xep:DefaultCharset:ERROR",
+        "-Xep:DoubleCheckedLocking:WARN",
+        "-Xep:ElementsCountedInLoop:WARN",
+        "-Xep:EqualsHashCode:WARN",
+        "-Xep:EqualsIncompatibleType:WARN",
+        "-Xep:ExpectedExceptionChecker:ERROR",
+        "-Xep:Finally:WARN",
+        "-Xep:FloatingPointLiteralPrecision:WARN",
+        "-Xep:FragmentInjection:WARN",
+        "-Xep:FragmentNotInstantiable:WARN",
+        "-Xep:FunctionalInterfaceClash:WARN",
+        "-Xep:FutureReturnValueIgnored:WARN",
+        "-Xep:GetClassOnEnum:WARN",
+        "-Xep:ImmutableAnnotationChecker:WARN",
+        "-Xep:ImmutableEnumChecker:WARN",
+        "-Xep:IncompatibleModifiers:WARN",
+        "-Xep:InjectOnConstructorOfAbstractClass:WARN",
+        "-Xep:InputStreamSlowMultibyteRead:WARN",
+        "-Xep:IterableAndIterator:WARN",
+        "-Xep:JUnit3FloatingPointComparisonWithoutDelta:WARN",
+        "-Xep:JUnitAmbiguousTestClass:WARN",
+        "-Xep:LiteralClassName:WARN",
+        "-Xep:MissingFail:WARN",
+        "-Xep:MissingOverride:WARN",
+        "-Xep:MutableConstantField:WARN",
+        "-Xep:NarrowingCompoundAssignment:WARN",
+        "-Xep:NonAtomicVolatileUpdate:WARN",
+        "-Xep:NonOverridingEquals:WARN",
+        "-Xep:NullableConstructor:WARN",
+        "-Xep:NullablePrimitive:WARN",
+        "-Xep:NullableVoid:WARN",
+        "-Xep:OperatorPrecedence:WARN",
+        "-Xep:OverridesGuiceInjectableMethod:WARN",
+        "-Xep:PreconditionsInvalidPlaceholder:WARN",
+        "-Xep:ProtoFieldPreconditionsCheckNotNull:WARN",
+        "-Xep:ProtocolBufferOrdinal:WARN",
+        "-Xep:RequiredModifiers:WARN",
+        "-Xep:ShortCircuitBoolean:WARN",
+        "-Xep:SimpleDateFormatConstant:WARN",
+        "-Xep:StaticGuardedByInstance:WARN",
+        "-Xep:SynchronizeOnNonFinalField:WARN",
+        "-Xep:TruthConstantAsserts:WARN",
+        "-Xep:TypeParameterShadowing:WARN",
+        "-Xep:TypeParameterUnusedInFormals:WARN",
+        "-Xep:URLEqualsHashCode:WARN",
+        "-Xep:UnsynchronizedOverridesSynchronized:WARN",
+        "-Xep:WaitNotInLoop:WARN",
+    ],
+    packages = ["error_prone_packages"],
+)
+
+package_group(
+    name = "error_prone_packages",
+    packages = [
+        "//gerrit-acceptance-framework/...",
+        "//gerrit-acceptance-tests/...",
+        "//gerrit-cache-h2/...",
+        "//gerrit-cache-mem/...",
+        "//gerrit-common/...",
+        "//gerrit-elasticsearch/...",
+        "//gerrit-extension-api/...",
+        "//gerrit-gpg/...",
+        "//gerrit-httpd/...",
+        "//gerrit-launcher/...",
+        "//gerrit-lucene/...",
+        "//gerrit-main/...",
+        "//gerrit-oauth/...",
+        "//gerrit-openid/...",
+        "//gerrit-patch-commonsnet/...",
+        "//gerrit-patch-jgit/...",
+        "//gerrit-pgm/...",
+        "//gerrit-plugin-api/...",
+        "//gerrit-plugin-gwtui/...",
+        "//gerrit-prettify/...",
+        "//gerrit-reviewdb/...",
+        "//gerrit-server/...",
+        "//gerrit-sshd/...",
+        "//gerrit-test-util/...",
+        "//gerrit-util-cli/...",
+        "//gerrit-util-http/...",
+        "//gerrit-util-ssl/...",
+        "//gerrit-war/...",
+    ],
+)
diff --git a/tools/bzl/gwt.bzl b/tools/bzl/gwt.bzl
index 976e2333..ecb301a 100644
--- a/tools/bzl/gwt.bzl
+++ b/tools/bzl/gwt.bzl
@@ -17,7 +17,7 @@
 load("//tools/bzl:genrule2.bzl", "genrule2")
 load("//tools/bzl:java.bzl", "java_library2")
 
-jar_filetype = FileType([".jar"])
+jar_filetype = [".jar"]
 
 BROWSERS = [
     "chrome",
@@ -224,7 +224,7 @@
             default = Label("@bazel_tools//tools/zip:zipper"),
             cfg = "host",
             executable = True,
-            single_file = True,
+            allow_single_file = True,
         ),
     },
     outputs = {
diff --git a/tools/bzl/js.bzl b/tools/bzl/js.bzl
index b265855..1421caa 100644
--- a/tools/bzl/js.bzl
+++ b/tools/bzl/js.bzl
@@ -415,4 +415,4 @@
 
 def vulcanize(*args, **kwargs):
     """Vulcanize runs vulcanize and crisper on a set of sources."""
-    _vulcanize_rule(*args, pkg = PACKAGE_NAME, **kwargs)
+    _vulcanize_rule(*args, pkg = native.package_name(), **kwargs)
diff --git a/tools/bzl/license-map.py b/tools/bzl/license-map.py
index 74a84cc..4856726 100644
--- a/tools/bzl/license-map.py
+++ b/tools/bzl/license-map.py
@@ -35,7 +35,7 @@
       continue
 
     handled_rules.append(rule_name)
-    for c in child.getchildren():
+    for c in list(child):
       if c.tag != "rule-input":
         continue
 
diff --git a/tools/bzl/license.bzl b/tools/bzl/license.bzl
index f011446..d059216 100644
--- a/tools/bzl/license.bzl
+++ b/tools/bzl/license.bzl
@@ -39,7 +39,7 @@
     if target[0] not in ":/":
         target = ":" + target
     if target[0] != "/":
-        target = "//" + PACKAGE_NAME + target
+        target = "//" + native.package_name() + target
 
     forbidden = "//lib:LICENSE-DO_NOT_DISTRIBUTE"
     native.genquery(
diff --git a/tools/bzl/pkg_war.bzl b/tools/bzl/pkg_war.bzl
index 6d712d5..839c537 100644
--- a/tools/bzl/pkg_war.bzl
+++ b/tools/bzl/pkg_war.bzl
@@ -14,7 +14,7 @@
 
 # War packaging.
 
-jar_filetype = FileType([".jar"])
+jar_filetype = [".jar"]
 
 LIBS = [
     "//gerrit-war:init",
diff --git a/version.bzl b/version.bzl
index 8224ed1..dc7eb94 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.15.3"
+GERRIT_VERSION = "2.15.4-SNAPSHOT"