Merge branch 'stable-2.15'

* stable-2.15:
  ProjectTagsScreen: Base visibility on the create refs/tags/* permission
  Upgrade JGit to 4.9.4.201809090327-r
  Upgrade JGit to 4.7.3.201809090215-r
  Set version to 2.14.13-SNAPSHOT
  ProjectTagsScreen: Base visibility on the create refs/tags/* permission
  Set version to 2.15.4-SNAPSHOT
  Set version to 2.14.12
  [project.config] Allow to add commentLink entries
  ElasticVersionTest: Align tested versions w/ ElasticContainer last ones
  Assume correct relative or absolute URL from Weblink provider
  AbstractSubmit: Remove redundant assertion about null IOException
  ListMailFilter: Fix operator precedence warning raised by ErrorProne
  ListProjects: Fix operator precedence warning raised by ErrorProne
  ChangeBundle: Fix operator precedence warning raised by ErrorProne
  Bazel: fix error_prone_warnings_toolchain rule
  Elastic{Index|ReindexIT} Remove tests for 6.2 and 6.3
  ElasticVersionTest#version6: Add missing test for V6_4
  Allow more email RFC accepted chars in username
  Make inheritance of receive.maxObjectSizeLimit optional
  Allow to inherit receive.maxObjectSizeLimit from parent project
  RestApiServlet: Skip capability check for administrators
  CreateAccount: Simplify error message when username is invalid
  Bazel: Provide toolchain with activated error prone warnings
  Use ExternalId.isValidUsername instead of ExternalId.USER_NAME_PATTERN_REGEX
  Move regular expressions for user name from Account to ExternalId
  AccountIT: Add basic tests for creating user with {in}valid username
  Revert refactoring of Account.USER_NAME_PATTERN
  Fix code that caused changes to break in MS Edge
  Add support for Elasticsearch 6.4.0
  Upgrade elasticsearch-rest-client to 6.4.0
  ElasticVersion: Say 'Unsupported' rather than 'Invalid'
  ElasticQueryAdapter: Move isV6 method to ElasticVersion and simplify
  Account.java: introduce compiled pattern and use where applicable
  Optimize USER_NAME_PATTERN string and its usage
  ElasticContainer: Test against Elasticsearch version 5.6.11
  rest-api-accounts: Fix documentation of "Get Active" response
  GetCapabilities#CheckOne: Return json content type
  ConfigSuite: Instantiate class via getDeclaredConstructor()

Change-Id: I08136f1d27da08ce8a523f2dc062316723e17c45
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 7f32488..0108a04 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -3748,6 +3748,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 b652bda9..cc5386f 100644
--- a/Documentation/config-project-config.txt
+++ b/Documentation/config-project-config.txt
@@ -148,8 +148,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/rest-api-access.txt b/Documentation/rest-api-access.txt
index 65a15ca..c2a7d21 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 edb642e..e28a9c4 100644
--- a/Documentation/rest-api-accounts.txt
+++ b/Documentation/rest-api-accounts.txt
@@ -418,7 +418,10 @@
 .Response
 ----
   HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json; charset=UTF-8
 
+  )]}'
   ok
 ----
 
@@ -1095,7 +1098,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 310ec7b..b517d3c 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -1141,6 +1141,7 @@
     ],
     "can_upload": true,
     "can_add": true,
+    "can_add_tags": true,
     "config_visible": true,
     "groups": {
       "c2ce4749a32ceb82cd6adcce65b8216e12afb41c": {
@@ -1242,6 +1243,7 @@
     ],
     "can_upload": true,
     "can_add": true,
+    "can_add_tags": true,
     "config_visible": true,
     "groups": {
       "global:Anonymous-Users": {
@@ -3442,10 +3444,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 0a6caa2..dca68d3 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -1038,8 +1038,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.8.9"
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 80c6d1a..4fdd067 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
@@ -41,7 +41,7 @@
   // corresponding regular expressions in the
   // com.google.gerrit.server.account.externalids.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 static final String USER_NAME_PATTERN_REST_REGEX = "[a-zA-Z0-9.!#$%&’*+=?^_`\\{|\\}~@-]";
 
   private CopyableLabel userNameLbl;
   private NpTextBox userNameTxt;
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 05a29ac..d10a031 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
@@ -442,9 +442,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 4185ef3..684f8e6 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
@@ -175,10 +175,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/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java b/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
index 630594f..2c1c93a 100644
--- a/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
+++ b/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
@@ -71,6 +71,7 @@
 import org.apache.http.client.methods.HttpPost;
 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> {
@@ -171,10 +172,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(
@@ -307,9 +308,13 @@
 
   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);
   }
 
   protected class ElasticQuerySource implements DataSource<V> {
diff --git a/java/com/google/gerrit/elasticsearch/ElasticIndexVersionDiscovery.java b/java/com/google/gerrit/elasticsearch/ElasticIndexVersionDiscovery.java
index e36ab2d..a777f47 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticIndexVersionDiscovery.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticIndexVersionDiscovery.java
@@ -24,7 +24,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;
 
 @Singleton
@@ -40,10 +40,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/java/com/google/gerrit/elasticsearch/ElasticQueryAdapter.java b/java/com/google/gerrit/elasticsearch/ElasticQueryAdapter.java
index 2beb528..8cb69e0 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticQueryAdapter.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticQueryAdapter.java
@@ -32,13 +32,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";
@@ -58,10 +59,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/java/com/google/gerrit/elasticsearch/ElasticRestClientProvider.java b/java/com/google/gerrit/elasticsearch/ElasticRestClientProvider.java
index 6ceb897..337f2ca 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticRestClientProvider.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticRestClientProvider.java
@@ -29,6 +29,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;
@@ -105,7 +106,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/java/com/google/gerrit/elasticsearch/ElasticVersion.java b/java/com/google/gerrit/elasticsearch/ElasticVersion.java
index 610a212..dfa5d21 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticVersion.java
+++ b/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/java/com/google/gerrit/extensions/api/access/ProjectAccessInfo.java b/java/com/google/gerrit/extensions/api/access/ProjectAccessInfo.java
index 5d8e950..8273d84 100644
--- a/java/com/google/gerrit/extensions/api/access/ProjectAccessInfo.java
+++ b/java/com/google/gerrit/extensions/api/access/ProjectAccessInfo.java
@@ -29,6 +29,7 @@
   public Set<String> ownerOf;
   public Boolean canUpload;
   public Boolean canAdd;
+  public Boolean canAddTags;
   public Boolean configVisible;
   public Map<String, GroupInfo> groups;
   public List<WebLinkInfo> configWebLinks;
diff --git a/java/com/google/gerrit/extensions/api/projects/ConfigInfo.java b/java/com/google/gerrit/extensions/api/projects/ConfigInfo.java
index b5aff67..08ba486 100644
--- a/java/com/google/gerrit/extensions/api/projects/ConfigInfo.java
+++ b/java/com/google/gerrit/extensions/api/projects/ConfigInfo.java
@@ -59,14 +59,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/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
index 10f2638..e0559f1 100644
--- a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
+++ b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
@@ -1392,10 +1392,15 @@
 
   private void checkRequiresCapability(ViewData d)
       throws AuthException, PermissionBackendException {
-    globals
-        .permissionBackend
-        .currentUser()
-        .checkAny(GlobalPermission.fromAnnotation(d.pluginName, d.view.getClass()));
+    try {
+      globals.permissionBackend.currentUser().check(GlobalPermission.ADMINISTRATE_SERVER);
+    } catch (AuthException e) {
+      // Skiping
+      globals
+          .permissionBackend
+          .currentUser()
+          .checkAny(GlobalPermission.fromAnnotation(d.pluginName, d.view.getClass()));
+    }
   }
 
   private static long handleException(
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalId.java b/java/com/google/gerrit/server/account/externalids/ExternalId.java
index db8ea41..96ea0cc 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalId.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalId.java
@@ -47,14 +47,12 @@
   // 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_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
           + //
@@ -67,9 +65,7 @@
           + //
           USER_NAME_PATTERN_FIRST_REGEX
           + //
-          ")"
-          + //
-          "$";
+          ")$";
 
   private static final Pattern USER_NAME_PATTERN = Pattern.compile(USER_NAME_PATTERN_REGEX);
 
diff --git a/java/com/google/gerrit/server/git/TransferConfig.java b/java/com/google/gerrit/server/git/TransferConfig.java
index f85f24b..8c93833 100644
--- a/java/com/google/gerrit/server/git/TransferConfig.java
+++ b/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/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java b/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java
index 7fe0c04..d83ab13 100644
--- a/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java
@@ -224,7 +224,7 @@
     receivePack.setAllowNonFastForwards(true);
     receivePack.setRefLogIdent(user.newRefLogIdent());
     receivePack.setTimeout(transferConfig.getTimeout());
-    receivePack.setMaxObjectSizeLimit(projectState.getEffectiveMaxObjectSizeLimit());
+    receivePack.setMaxObjectSizeLimit(projectState.getEffectiveMaxObjectSizeLimit().value);
     receivePack.setCheckReceivedObjects(projectState.getConfig().getCheckReceivedObjects());
     receivePack.setRefFilter(new ReceiveRefFilter());
     receivePack.setAllowPushOptions(true);
diff --git a/java/com/google/gerrit/server/mail/ListMailFilter.java b/java/com/google/gerrit/server/mail/ListMailFilter.java
index eee8c60..1549f8d 100644
--- a/java/com/google/gerrit/server/mail/ListMailFilter.java
+++ b/java/com/google/gerrit/server/mail/ListMailFilter.java
@@ -53,7 +53,8 @@
     }
 
     boolean match = mailPattern.matcher(message.from().getEmail()).find();
-    if (mode == ListFilterMode.WHITELIST && !match || mode == ListFilterMode.BLACKLIST && match) {
+    if ((mode == ListFilterMode.WHITELIST && !match)
+        || (mode == ListFilterMode.BLACKLIST && match)) {
       logger.atInfo().log("Mail message from %s rejected by list filter", message.from());
       return false;
     }
diff --git a/java/com/google/gerrit/server/notedb/ChangeBundle.java b/java/com/google/gerrit/server/notedb/ChangeBundle.java
index c48a2f8..0ebee1a 100644
--- a/java/com/google/gerrit/server/notedb/ChangeBundle.java
+++ b/java/com/google/gerrit/server/notedb/ChangeBundle.java
@@ -395,7 +395,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 =
@@ -413,7 +413,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();
     }
 
@@ -718,7 +718,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/java/com/google/gerrit/server/permissions/ProjectControl.java b/java/com/google/gerrit/server/permissions/ProjectControl.java
index 67662c7..4a4ea37 100644
--- a/java/com/google/gerrit/server/permissions/ProjectControl.java
+++ b/java/com/google/gerrit/server/permissions/ProjectControl.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.server.permissions;
 
 import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.gerrit.reviewdb.client.RefNames.REFS_TAGS;
 
 import com.google.gerrit.common.data.AccessSection;
 import com.google.gerrit.common.data.Permission;
@@ -212,6 +213,10 @@
     return (canPerformOnAnyRef(Permission.CREATE) || isAdmin());
   }
 
+  private boolean canAddTagRefs() {
+    return (canPerformOnTagRef(Permission.CREATE) || isAdmin());
+  }
+
   private boolean canCreateChanges() {
     for (SectionMatcher matcher : access()) {
       AccessSection section = matcher.getSection();
@@ -233,6 +238,26 @@
     return declaredOwner;
   }
 
+  private boolean canPerformOnTagRef(String permissionName) {
+    for (SectionMatcher matcher : access()) {
+      AccessSection section = matcher.getSection();
+
+      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.getSection();
@@ -241,25 +266,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);
@@ -403,6 +436,8 @@
 
         case CREATE_REF:
           return canAddRefs();
+        case CREATE_TAG_REF:
+          return canAddTagRefs();
         case CREATE_CHANGE:
           return canCreateChanges();
 
diff --git a/java/com/google/gerrit/server/permissions/ProjectPermission.java b/java/com/google/gerrit/server/permissions/ProjectPermission.java
index 3fee6cf..7c58ccb 100644
--- a/java/com/google/gerrit/server/permissions/ProjectPermission.java
+++ b/java/com/google/gerrit/server/permissions/ProjectPermission.java
@@ -51,6 +51,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/java/com/google/gerrit/server/project/ProjectState.java b/java/com/google/gerrit/server/project/ProjectState.java
index 3e379a6..a9b19d9 100644
--- a/java/com/google/gerrit/server/project/ProjectState.java
+++ b/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;
@@ -89,6 +90,7 @@
   private final Map<String, ProjectLevelConfig> configs;
   private final Set<AccountGroup.UUID> localOwners;
   private final long globalMaxObjectSizeLimit;
+  private final boolean inheritProjectMaxObjectSizeLimit;
 
   /** Last system time the configuration's revision was examined. */
   private volatile long lastCheckGeneration;
@@ -132,6 +134,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();
@@ -264,13 +267,58 @@
     }
   }
 
-  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/java/com/google/gerrit/server/restapi/account/CreateAccount.java b/java/com/google/gerrit/server/restapi/account/CreateAccount.java
index 78ec540..0e8eb70 100644
--- a/java/com/google/gerrit/server/restapi/account/CreateAccount.java
+++ b/java/com/google/gerrit/server/restapi/account/CreateAccount.java
@@ -115,8 +115,7 @@
       throw new BadRequestException("username must match URL");
     }
     if (!ExternalId.isValidUsername(username)) {
-      throw new BadRequestException(
-          "Username '" + username + "' must contain only letters, numbers, _, - or .");
+      throw new BadRequestException("Invalid username '" + username + "'");
     }
 
     Set<AccountGroup.UUID> groups = parseGroups(input.groups);
diff --git a/java/com/google/gerrit/server/restapi/account/GetCapabilities.java b/java/com/google/gerrit/server/restapi/account/GetCapabilities.java
index 7889f6e..5c466bf 100644
--- a/java/com/google/gerrit/server/restapi/account/GetCapabilities.java
+++ b/java/com/google/gerrit/server/restapi/account/GetCapabilities.java
@@ -26,8 +26,8 @@
 import com.google.gerrit.extensions.api.access.PluginPermission;
 import com.google.gerrit.extensions.config.CapabilityDefinition;
 import com.google.gerrit.extensions.registration.DynamicMap;
-import com.google.gerrit.extensions.restapi.BinaryResult;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.server.CurrentUser;
@@ -172,9 +172,9 @@
     }
 
     @Override
-    public BinaryResult apply(Capability resource) throws ResourceNotFoundException {
+    public Response<String> apply(Capability resource) throws ResourceNotFoundException {
       permissionBackend.checkUsesDefaultCapabilities();
-      return BinaryResult.create("ok\n");
+      return Response.ok("ok");
     }
   }
 }
diff --git a/java/com/google/gerrit/server/restapi/project/ConfigInfoImpl.java b/java/com/google/gerrit/server/restapi/project/ConfigInfoImpl.java
index 076bf78..60b5dee 100644
--- a/java/com/google/gerrit/server/restapi/project/ConfigInfoImpl.java
+++ b/java/com/google/gerrit/server/restapi/project/ConfigInfoImpl.java
@@ -33,10 +33,10 @@
 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.BooleanProjectConfigTransformations;
 import com.google.gerrit.server.project.ProjectResource;
 import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.project.ProjectState.EffectiveMaxObjectSizeLimit;
 import java.util.Arrays;
 import java.util.LinkedHashMap;
 import java.util.Map;
@@ -48,7 +48,6 @@
       boolean serverEnableSignedPush,
       ProjectState projectState,
       CurrentUser user,
-      TransferConfig transferConfig,
       DynamicMap<ProjectConfigEntry> pluginConfigEntries,
       PluginConfigFactory cfgFactory,
       AllProjectsName allProjects,
@@ -72,7 +71,7 @@
       this.requireSignedPush = null;
     }
 
-    this.maxObjectSizeLimit = getMaxObjectSizeLimit(projectState, transferConfig, p);
+    this.maxObjectSizeLimit = getMaxObjectSizeLimit(projectState, p);
 
     this.defaultSubmitType = new SubmitTypeInfo();
     this.defaultSubmitType.value = projectState.getSubmitType();
@@ -107,13 +106,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/java/com/google/gerrit/server/restapi/project/GetAccess.java b/java/com/google/gerrit/server/restapi/project/GetAccess.java
index d545f92..a6b9404 100644
--- a/java/com/google/gerrit/server/restapi/project/GetAccess.java
+++ b/java/com/google/gerrit/server/restapi/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 com.google.gerrit.server.permissions.RefPermission.WRITE_CONFIG;
@@ -270,6 +271,7 @@
                     || (canReadConfig
                         && 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 = canReadConfig || canWriteConfig;
 
     info.groups =
diff --git a/java/com/google/gerrit/server/restapi/project/GetConfig.java b/java/com/google/gerrit/server/restapi/project/GetConfig.java
index 7fedc8f..b3ad962 100644
--- a/java/com/google/gerrit/server/restapi/project/GetConfig.java
+++ b/java/com/google/gerrit/server/restapi/project/GetConfig.java
@@ -23,7 +23,6 @@
 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.ProjectResource;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
@@ -31,7 +30,6 @@
 @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;
@@ -41,14 +39,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;
@@ -62,7 +58,6 @@
         serverEnableSignedPush,
         resource.getProjectState(),
         resource.getUser(),
-        transferConfig,
         pluginConfigEntries,
         cfgFactory,
         allProjects,
diff --git a/java/com/google/gerrit/server/restapi/project/PutConfig.java b/java/com/google/gerrit/server/restapi/project/PutConfig.java
index f4eb781..76ea0c9 100644
--- a/java/com/google/gerrit/server/restapi/project/PutConfig.java
+++ b/java/com/google/gerrit/server/restapi/project/PutConfig.java
@@ -38,7 +38,6 @@
 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.git.meta.MetaDataUpdate;
 import com.google.gerrit.server.permissions.PermissionBackend;
 import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -71,7 +70,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;
@@ -86,7 +84,6 @@
       Provider<MetaDataUpdate.User> metaDataUpdateFactory,
       ProjectCache projectCache,
       ProjectState.Factory projectStateFactory,
-      TransferConfig transferConfig,
       DynamicMap<ProjectConfigEntry> pluginConfigEntries,
       PluginConfigFactory cfgFactory,
       AllProjectsName allProjects,
@@ -98,7 +95,6 @@
     this.metaDataUpdateFactory = metaDataUpdateFactory;
     this.projectCache = projectCache;
     this.projectStateFactory = projectStateFactory;
-    this.transferConfig = transferConfig;
     this.pluginConfigEntries = pluginConfigEntries;
     this.cfgFactory = cfgFactory;
     this.allProjects = allProjects;
@@ -173,7 +169,6 @@
           serverEnableSignedPush,
           state,
           user.get(),
-          transferConfig,
           pluginConfigEntries,
           cfgFactory,
           allProjects,
diff --git a/java/com/google/gerrit/testing/ConfigSuite.java b/java/com/google/gerrit/testing/ConfigSuite.java
index ff87fd8..9e45b7c 100644
--- a/java/com/google/gerrit/testing/ConfigSuite.java
+++ b/java/com/google/gerrit/testing/ConfigSuite.java
@@ -159,7 +159,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/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
index 7126354..de66b87 100644
--- a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
@@ -2164,6 +2164,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 allGroupsForAUserAccountCanBeRetrieved() throws Exception {
     String username = name("user1");
     accountOperations.newAccount().username(username).create();
diff --git a/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java b/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java
index f7d3867..18888ea 100644
--- a/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java
+++ b/javatests/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.git.QueueProvider.QueueType.BATCH;
+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 static java.util.stream.Collectors.toSet;
 
 import com.google.common.collect.ImmutableList;
@@ -416,7 +420,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
@@ -425,13 +429,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
@@ -441,21 +462,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
@@ -464,16 +539,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
@@ -487,10 +586,6 @@
     return gApi.projects().name(name.get()).config(input);
   }
 
-  private ConfigInfo setConfig(ConfigInput input) throws Exception {
-    return setConfig(project, input);
-  }
-
   private ConfigInfo getConfig(Project.NameKey name) throws Exception {
     return gApi.projects().name(name.get()).config();
   }
@@ -517,9 +612,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 static class ProjectIndexedCounter implements ProjectIndexedListener {
diff --git a/javatests/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java b/javatests/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java
index c90b3d3..0d5d2cd 100644
--- a/javatests/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java
+++ b/javatests/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/javatests/com/google/gerrit/acceptance/ssh/ElasticIndexIT.java b/javatests/com/google/gerrit/acceptance/ssh/ElasticIndexIT.java
index 95da5a6..9d69955 100644
--- a/javatests/com/google/gerrit/acceptance/ssh/ElasticIndexIT.java
+++ b/javatests/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/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java b/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java
index f5614b4..8b3c08f 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java
+++ b/javatests/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/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryAccountsTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryAccountsTest.java
index 1d17b5b..b8154ce 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryAccountsTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryAccountsTest.java
@@ -41,7 +41,7 @@
       return;
     }
 
-    container = ElasticContainer.createAndStart(ElasticVersion.V6_3);
+    container = ElasticContainer.createAndStart(ElasticVersion.V6_4);
     nodeInfo = new ElasticNodeInfo(container.getHttpHost().getPort());
   }
 
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryChangesTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryChangesTest.java
index 7c5d2e2..3445b36 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryChangesTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryChangesTest.java
@@ -41,7 +41,7 @@
       return;
     }
 
-    container = ElasticContainer.createAndStart(ElasticVersion.V6_3);
+    container = ElasticContainer.createAndStart(ElasticVersion.V6_4);
     nodeInfo = new ElasticNodeInfo(container.getHttpHost().getPort());
   }
 
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryGroupsTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryGroupsTest.java
index 15b58e0..851b27d 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryGroupsTest.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticV6QueryGroupsTest.java
@@ -41,7 +41,7 @@
       return;
     }
 
-    container = ElasticContainer.createAndStart(ElasticVersion.V6_3);
+    container = ElasticContainer.createAndStart(ElasticVersion.V6_4);
     nodeInfo = new ElasticNodeInfo(container.getHttpHost().getPort());
   }
 
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticVersionTest.java b/javatests/com/google/gerrit/elasticsearch/ElasticVersionTest.java
index 860dca6..b598a0a 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticVersionTest.java
+++ b/javatests/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/plugins/singleusergroup b/plugins/singleusergroup
index e4024e9..cc636d7 160000
--- a/plugins/singleusergroup
+++ b/plugins/singleusergroup
@@ -1 +1 @@
-Subproject commit e4024e9d8d8139fc4c658c3af1a5e11e19b2d476
+Subproject commit cc636d7e36afb62455a9f045b125d246fd84afd0