Merge "Remove 'data' field from SubmitRequirement"
diff --git a/WORKSPACE b/WORKSPACE
index d07ab5b..015675a 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -213,24 +213,24 @@
     sha1 = "42aa5155a54a87d70af32d4b0d06bf43779de0e2",
 )
 
-FLOGGER_VERS = "0.4"
+FLOGGER_VERS = "0.5"
 
 maven_jar(
     name = "flogger",
     artifact = "com.google.flogger:flogger:" + FLOGGER_VERS,
-    sha1 = "9c8863dcc913b56291c0c88e6d4ca9715b43df98",
+    sha1 = "dd7449aa4aba72969ed409b96996d19906d534d7",
 )
 
 maven_jar(
     name = "flogger-log4j-backend",
     artifact = "com.google.flogger:flogger-log4j-backend:" + FLOGGER_VERS,
-    sha1 = "17aa5e31daa1354187e14b6978597d630391c028",
+    sha1 = "5e2794b75c88223f263f1c1a9d7ea51e2dc45732",
 )
 
 maven_jar(
     name = "flogger-system-backend",
     artifact = "com.google.flogger:flogger-system-backend:" + FLOGGER_VERS,
-    sha1 = "287b569d76abcd82f9de87fe41829fbc7ebd8ac9",
+    sha1 = "b66d3bedb14da604828a8693bb24fd78e36b0e9e",
 )
 
 maven_jar(
diff --git a/e2e-tests/load-tests/.gitignore b/e2e-tests/.gitignore
similarity index 100%
rename from e2e-tests/load-tests/.gitignore
rename to e2e-tests/.gitignore
diff --git a/e2e-tests/load-tests/Dockerfile b/e2e-tests/Dockerfile
similarity index 100%
rename from e2e-tests/load-tests/Dockerfile
rename to e2e-tests/Dockerfile
diff --git a/e2e-tests/load-tests/README.md b/e2e-tests/README.md
similarity index 100%
rename from e2e-tests/load-tests/README.md
rename to e2e-tests/README.md
diff --git a/e2e-tests/load-tests/build.sbt b/e2e-tests/build.sbt
similarity index 100%
rename from e2e-tests/load-tests/build.sbt
rename to e2e-tests/build.sbt
diff --git a/e2e-tests/load-tests/project/Dependencies.scala b/e2e-tests/project/Dependencies.scala
similarity index 100%
rename from e2e-tests/load-tests/project/Dependencies.scala
rename to e2e-tests/project/Dependencies.scala
diff --git a/e2e-tests/load-tests/project/build.properties b/e2e-tests/project/build.properties
similarity index 100%
rename from e2e-tests/load-tests/project/build.properties
rename to e2e-tests/project/build.properties
diff --git a/e2e-tests/load-tests/project/plugins.sbt b/e2e-tests/project/plugins.sbt
similarity index 100%
rename from e2e-tests/load-tests/project/plugins.sbt
rename to e2e-tests/project/plugins.sbt
diff --git a/e2e-tests/load-tests/src/test/resources/application.conf b/e2e-tests/src/test/resources/application.conf
similarity index 100%
rename from e2e-tests/load-tests/src/test/resources/application.conf
rename to e2e-tests/src/test/resources/application.conf
diff --git a/e2e-tests/load-tests/src/test/resources/data/CloneUsingBothProtocols.json b/e2e-tests/src/test/resources/data/CloneUsingBothProtocols.json
similarity index 100%
rename from e2e-tests/load-tests/src/test/resources/data/CloneUsingBothProtocols.json
rename to e2e-tests/src/test/resources/data/CloneUsingBothProtocols.json
diff --git a/e2e-tests/load-tests/src/test/resources/data/ReplayRecordsFromFeeder.json b/e2e-tests/src/test/resources/data/ReplayRecordsFromFeeder.json
similarity index 100%
rename from e2e-tests/load-tests/src/test/resources/data/ReplayRecordsFromFeeder.json
rename to e2e-tests/src/test/resources/data/ReplayRecordsFromFeeder.json
diff --git a/e2e-tests/load-tests/src/test/resources/gatling.conf b/e2e-tests/src/test/resources/gatling.conf
similarity index 100%
rename from e2e-tests/load-tests/src/test/resources/gatling.conf
rename to e2e-tests/src/test/resources/gatling.conf
diff --git a/e2e-tests/load-tests/src/test/resources/hooks/commit-msg b/e2e-tests/src/test/resources/hooks/commit-msg
similarity index 100%
rename from e2e-tests/load-tests/src/test/resources/hooks/commit-msg
rename to e2e-tests/src/test/resources/hooks/commit-msg
diff --git a/e2e-tests/load-tests/src/test/resources/logback.xml b/e2e-tests/src/test/resources/logback.xml
similarity index 100%
rename from e2e-tests/load-tests/src/test/resources/logback.xml
rename to e2e-tests/src/test/resources/logback.xml
diff --git a/e2e-tests/load-tests/src/test/scala/com/google/gerrit/scenarios/CloneUsingBothProtocols.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CloneUsingBothProtocols.scala
similarity index 100%
rename from e2e-tests/load-tests/src/test/scala/com/google/gerrit/scenarios/CloneUsingBothProtocols.scala
rename to e2e-tests/src/test/scala/com/google/gerrit/scenarios/CloneUsingBothProtocols.scala
diff --git a/e2e-tests/load-tests/src/test/scala/com/google/gerrit/scenarios/GitSimulation.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GitSimulation.scala
similarity index 100%
rename from e2e-tests/load-tests/src/test/scala/com/google/gerrit/scenarios/GitSimulation.scala
rename to e2e-tests/src/test/scala/com/google/gerrit/scenarios/GitSimulation.scala
diff --git a/e2e-tests/load-tests/src/test/scala/com/google/gerrit/scenarios/ReplayRecordsFromFeeder.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/ReplayRecordsFromFeeder.scala
similarity index 100%
rename from e2e-tests/load-tests/src/test/scala/com/google/gerrit/scenarios/ReplayRecordsFromFeeder.scala
rename to e2e-tests/src/test/scala/com/google/gerrit/scenarios/ReplayRecordsFromFeeder.scala
diff --git a/java/com/google/gerrit/httpd/raw/AuthorizationCheckServlet.java b/java/com/google/gerrit/httpd/raw/AuthorizationCheckServlet.java
index e8f173c..d92da18 100644
--- a/java/com/google/gerrit/httpd/raw/AuthorizationCheckServlet.java
+++ b/java/com/google/gerrit/httpd/raw/AuthorizationCheckServlet.java
@@ -43,6 +43,7 @@
   @Override
   protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException {
     CacheHeaders.setNotCacheable(res);
+    res.setContentLength(0);
     if (user.get().isIdentifiedUser()) {
       res.setStatus(HttpServletResponse.SC_NO_CONTENT);
     } else {
diff --git a/java/com/google/gerrit/server/ExceptionHookImpl.java b/java/com/google/gerrit/server/ExceptionHookImpl.java
index d0b5f15..8884991 100644
--- a/java/com/google/gerrit/server/ExceptionHookImpl.java
+++ b/java/com/google/gerrit/server/ExceptionHookImpl.java
@@ -21,7 +21,6 @@
 import com.google.gerrit.git.LockFailureException;
 import com.google.gerrit.server.project.ProjectConfig;
 import java.util.Optional;
-import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.lib.RefUpdate;
 
@@ -35,7 +34,9 @@
           + "This may be a temporary issue due to concurrent updates.\n"
           + "Please retry later.";
   private static final String INVALID_PROJECT_CONFIG_USER_MESSAGE =
-      "Invalid " + ProjectConfig.PROJECT_CONFIG + " file.\n" + "Please contact the project owner.";
+      "Invalid " + ProjectConfig.PROJECT_CONFIG + " file.";
+  private static final String CONTACT_PROJECT_OWNER_USER_MESSAGE =
+      "Please contact the project owner.";
 
   @Override
   public boolean shouldRetry(String actionType, String actionName, Throwable throwable) {
@@ -67,7 +68,10 @@
       return ImmutableList.of(LOCK_FAILURE_USER_MESSAGE);
     }
     if (isInvalidProjectConfig(throwable)) {
-      return ImmutableList.of(INVALID_PROJECT_CONFIG_USER_MESSAGE);
+      return ImmutableList.of(
+          getInvalidConfigMessage(throwable).orElse(INVALID_PROJECT_CONFIG_USER_MESSAGE)
+              + "\n"
+              + CONTACT_PROJECT_OWNER_USER_MESSAGE);
     }
     return ImmutableList.of();
   }
@@ -95,9 +99,16 @@
     return isMatching(
         throwable,
         t ->
-            t instanceof ConfigInvalidException
-                && t.getMessage()
-                    .startsWith("Invalid config file " + ProjectConfig.PROJECT_CONFIG));
+            t instanceof InvalidConfigFileException
+                && ProjectConfig.PROJECT_CONFIG.equals(
+                    ((InvalidConfigFileException) t).getFileName()));
+  }
+
+  private Optional<String> getInvalidConfigMessage(Throwable throwable) {
+    return Throwables.getCausalChain(throwable).stream()
+        .filter(InvalidConfigFileException.class::isInstance)
+        .map(ex -> ex.getMessage())
+        .findFirst();
   }
 
   /**
diff --git a/java/com/google/gerrit/server/InvalidConfigFileException.java b/java/com/google/gerrit/server/InvalidConfigFileException.java
new file mode 100644
index 0000000..f10b618
--- /dev/null
+++ b/java/com/google/gerrit/server/InvalidConfigFileException.java
@@ -0,0 +1,61 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server;
+
+import com.google.gerrit.entities.Project;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.ObjectId;
+
+/** Exception that is thrown if an invalid config file causes an error. */
+public class InvalidConfigFileException extends ConfigInvalidException {
+  private static final long serialVersionUID = 1L;
+
+  private final String fileName;
+
+  public InvalidConfigFileException(
+      Project.NameKey projectName,
+      String branchName,
+      ObjectId revision,
+      String fileName,
+      ConfigInvalidException cause) {
+    super(createMessage(projectName, branchName, revision, fileName, cause), cause);
+    this.fileName = fileName;
+  }
+
+  public String getFileName() {
+    return fileName;
+  }
+
+  private static String createMessage(
+      Project.NameKey projectName,
+      String branchName,
+      ObjectId revision,
+      String fileName,
+      ConfigInvalidException cause) {
+    StringBuilder msg =
+        new StringBuilder("Invalid config file ")
+            .append(fileName)
+            .append(" in project ")
+            .append(projectName.get())
+            .append(" in branch ")
+            .append(branchName)
+            .append(" in commit ")
+            .append(revision.name());
+    if (cause != null) {
+      msg.append(": ").append(cause.getMessage());
+    }
+    return msg.toString();
+  }
+}
diff --git a/java/com/google/gerrit/server/git/UsersSelfAdvertiseRefsHook.java b/java/com/google/gerrit/server/git/UsersSelfAdvertiseRefsHook.java
index 169ae32..6c1879e 100644
--- a/java/com/google/gerrit/server/git/UsersSelfAdvertiseRefsHook.java
+++ b/java/com/google/gerrit/server/git/UsersSelfAdvertiseRefsHook.java
@@ -18,11 +18,11 @@
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.server.CurrentUser;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
 import java.io.IOException;
 import java.util.Map;
-import javax.inject.Inject;
-import javax.inject.Provider;
-import javax.inject.Singleton;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.RefDatabase;
 import org.eclipse.jgit.lib.SymbolicRef;
diff --git a/java/com/google/gerrit/server/git/meta/VersionedMetaData.java b/java/com/google/gerrit/server/git/meta/VersionedMetaData.java
index 8ab2779..feb038a 100644
--- a/java/com/google/gerrit/server/git/meta/VersionedMetaData.java
+++ b/java/com/google/gerrit/server/git/meta/VersionedMetaData.java
@@ -23,6 +23,7 @@
 import com.google.gerrit.git.GitUpdateFailureException;
 import com.google.gerrit.git.LockFailureException;
 import com.google.gerrit.git.ObjectIds;
+import com.google.gerrit.server.InvalidConfigFileException;
 import com.google.gerrit.server.logging.Metadata;
 import com.google.gerrit.server.logging.TraceContext;
 import com.google.gerrit.server.logging.TraceContext.TraceTimer;
@@ -471,15 +472,7 @@
       try {
         rc.fromText(text);
       } catch (ConfigInvalidException err) {
-        StringBuilder msg =
-            new StringBuilder("Invalid config file ")
-                .append(fileName)
-                .append(" in commit ")
-                .append(revision.name());
-        if (err.getCause() != null) {
-          msg.append(": ").append(err.getCause());
-        }
-        throw new ConfigInvalidException(msg.toString(), err);
+        throw new InvalidConfigFileException(projectName, getRefName(), revision, fileName, err);
       }
     }
     return rc;
diff --git a/java/com/google/gerrit/server/notedb/AllUsersAsyncUpdate.java b/java/com/google/gerrit/server/notedb/AllUsersAsyncUpdate.java
index 030cfb2..5417494 100644
--- a/java/com/google/gerrit/server/notedb/AllUsersAsyncUpdate.java
+++ b/java/com/google/gerrit/server/notedb/AllUsersAsyncUpdate.java
@@ -25,11 +25,11 @@
 import com.google.gerrit.server.FanOutExecutor;
 import com.google.gerrit.server.config.AllUsersName;
 import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.inject.Inject;
 import java.io.IOException;
 import java.util.Map;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
-import javax.inject.Inject;
 import org.eclipse.jgit.lib.BatchRefUpdate;
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.transport.PushCertificate;
diff --git a/java/com/google/gerrit/server/project/ProjectCacheImpl.java b/java/com/google/gerrit/server/project/ProjectCacheImpl.java
index 102da71..9d09eeb 100644
--- a/java/com/google/gerrit/server/project/ProjectCacheImpl.java
+++ b/java/com/google/gerrit/server/project/ProjectCacheImpl.java
@@ -153,7 +153,8 @@
         logger.atFine().log("Cannot find project %s", projectName.get());
         return Optional.empty();
       }
-      throw new StorageException("project state not available", e);
+      throw new StorageException(
+          String.format("project state of project %s not available", projectName.get()), e);
     }
   }
 
diff --git a/java/com/google/gerrit/server/restapi/project/SetAccessUtil.java b/java/com/google/gerrit/server/restapi/project/SetAccessUtil.java
index ea29fb3..17fba35 100644
--- a/java/com/google/gerrit/server/restapi/project/SetAccessUtil.java
+++ b/java/com/google/gerrit/server/restapi/project/SetAccessUtil.java
@@ -34,9 +34,9 @@
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.group.GroupResolver;
 import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.permissions.PluginPermissionsUtil;
 import com.google.gerrit.server.project.ProjectConfig;
 import com.google.gerrit.server.project.RefPattern;
-import com.google.gerrit.server.restapi.config.ListCapabilities;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
@@ -51,18 +51,18 @@
   private final GroupResolver groupResolver;
   private final AllProjectsName allProjects;
   private final Provider<SetParent> setParent;
-  private final ListCapabilities listCapabilities;
+  private final PluginPermissionsUtil pluginPermissionsUtil;
 
   @Inject
   private SetAccessUtil(
       GroupResolver groupResolver,
       AllProjectsName allProjects,
       Provider<SetParent> setParent,
-      ListCapabilities listCapabilities) {
+      PluginPermissionsUtil pluginPermissionsUtil) {
     this.groupResolver = groupResolver;
     this.allProjects = allProjects;
     this.setParent = setParent;
-    this.listCapabilities = listCapabilities;
+    this.pluginPermissionsUtil = pluginPermissionsUtil;
   }
 
   List<AccessSection> getAccessSections(Map<String, AccessSectionInfo> sectionInfos)
@@ -154,8 +154,7 @@
         // Check all permissions for soundness
         for (Permission p : section.getPermissions()) {
           if (!isCapability(p.getName())) {
-            throw new BadRequestException(
-                "Cannot add non-global capability " + p.getName() + " to global capabilities");
+            throw new BadRequestException("Unknown global capability: " + p.getName());
           }
         }
       }
@@ -244,7 +243,7 @@
     if (GlobalCapability.isGlobalCapability(name)) {
       return true;
     }
-    Set<String> pluginCapabilities = listCapabilities.collectPluginCapabilities().keySet();
+    Set<String> pluginCapabilities = pluginPermissionsUtil.collectPluginCapabilities().keySet();
     return pluginCapabilities.contains(name);
   }
 }
diff --git a/java/com/google/gerrit/sshd/commands/SetTopicCommand.java b/java/com/google/gerrit/sshd/commands/SetTopicCommand.java
index 2345d17..70700f1 100644
--- a/java/com/google/gerrit/sshd/commands/SetTopicCommand.java
+++ b/java/com/google/gerrit/sshd/commands/SetTopicCommand.java
@@ -18,10 +18,8 @@
 import com.google.gerrit.exceptions.StorageException;
 import com.google.gerrit.extensions.api.changes.TopicInput;
 import com.google.gerrit.extensions.restapi.BadRequestException;
-import com.google.gerrit.server.ChangeMessagesUtil;
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.change.ChangeResource;
-import com.google.gerrit.server.extensions.events.TopicEdited;
 import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.restapi.change.SetTopicOp;
 import com.google.gerrit.server.update.BatchUpdate;
@@ -38,8 +36,6 @@
 @CommandMetaData(name = "set-topic", description = "Set the topic for one or more changes")
 public class SetTopicCommand extends SshCommand {
   private final BatchUpdate.Factory updateFactory;
-  private final ChangeMessagesUtil cmUtil;
-  private final TopicEdited topicEdited;
   private final ChangeArgumentParser changeArgumentParser;
   private final SetTopicOp.Factory topicOpFactory;
 
@@ -69,13 +65,9 @@
   @Inject
   SetTopicCommand(
       BatchUpdate.Factory updateFactory,
-      ChangeMessagesUtil cmUtil,
-      TopicEdited topicEdited,
       ChangeArgumentParser changeArgumentParser,
       SetTopicOp.Factory topicOpFactory) {
     this.updateFactory = updateFactory;
-    this.cmUtil = cmUtil;
-    this.topicEdited = topicEdited;
     this.changeArgumentParser = changeArgumentParser;
     this.topicOpFactory = topicOpFactory;
   }
diff --git a/javatests/com/google/gerrit/acceptance/api/change/ChangeSubmitRequirementIT.java b/javatests/com/google/gerrit/acceptance/api/change/ChangeSubmitRequirementIT.java
index 2c972a3..40dd70e 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/ChangeSubmitRequirementIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/ChangeSubmitRequirementIT.java
@@ -27,9 +27,13 @@
 import com.google.gerrit.extensions.config.FactoryModule;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.rules.SubmitRule;
+import com.google.inject.Inject;
 import com.google.inject.Module;
+import com.google.inject.Singleton;
 import java.util.ArrayList;
+import java.util.List;
 import java.util.Optional;
+import java.util.concurrent.atomic.AtomicBoolean;
 import org.junit.Test;
 
 public class ChangeSubmitRequirementIT extends AbstractDaemonTest {
@@ -50,22 +54,63 @@
     };
   }
 
+  @Inject CustomSubmitRule rule;
+
   @Test
-  public void checkSubmitRequirementIsPropagated() throws Exception {
+  public void submitRequirementIsPropagated() throws Exception {
+    rule.block(false);
     PushOneCommit.Result r = createChange();
 
     ChangeInfo result = gApi.changes().id(r.getChangeId()).get();
+    assertThat(result.requirements).isEmpty();
+
+    rule.block(true);
+    result = gApi.changes().id(r.getChangeId()).get();
     assertThat(result.requirements).containsExactly(reqInfo);
   }
 
+  @Test
+  public void submitRequirementIsPropagatedInQuery() throws Exception {
+    rule.block(false);
+    PushOneCommit.Result r = createChange();
+
+    String query = "status:open project:" + project.get();
+    List<ChangeInfo> result = gApi.changes().query(query).get();
+    assertThat(result).hasSize(1);
+    assertThat(result.get(0).requirements).isEmpty();
+
+    // Submit rule behavior is changed, but the query still returns
+    // the previous result from the index
+    rule.block(true);
+    result = gApi.changes().query(query).get();
+    assertThat(result).hasSize(1);
+    assertThat(result.get(0).requirements).isEmpty();
+
+    // The submit rule result is updated after the change is reindexed
+    gApi.changes().id(r.getChangeId()).index();
+    result = gApi.changes().query(query).get();
+    assertThat(result).hasSize(1);
+    assertThat(result.get(0).requirements).containsExactly(reqInfo);
+  }
+
+  @Singleton
   private static class CustomSubmitRule implements SubmitRule {
+    private final AtomicBoolean block = new AtomicBoolean(true);
+
+    public void block(boolean block) {
+      this.block.set(block);
+    }
+
     @Override
     public Optional<SubmitRecord> evaluate(ChangeData changeData) {
-      SubmitRecord record = new SubmitRecord();
-      record.labels = new ArrayList<>();
-      record.status = SubmitRecord.Status.NOT_READY;
-      record.requirements = ImmutableList.of(req);
-      return Optional.of(record);
+      if (block.get()) {
+        SubmitRecord record = new SubmitRecord();
+        record.labels = new ArrayList<>();
+        record.status = SubmitRecord.Status.NOT_READY;
+        record.requirements = ImmutableList.of(req);
+        return Optional.of(record);
+      }
+      return Optional.empty();
     }
   }
 }
diff --git a/javatests/com/google/gerrit/acceptance/api/change/QueryChangeIT.java b/javatests/com/google/gerrit/acceptance/api/change/QueryChangesIT.java
similarity index 99%
rename from javatests/com/google/gerrit/acceptance/api/change/QueryChangeIT.java
rename to javatests/com/google/gerrit/acceptance/api/change/QueryChangesIT.java
index 92ef02c..448f347 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/QueryChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/QueryChangesIT.java
@@ -46,7 +46,7 @@
 import org.junit.Test;
 
 @NoHttpd
-public class QueryChangeIT extends AbstractDaemonTest {
+public class QueryChangesIT extends AbstractDaemonTest {
   @Inject private AccountOperations accountOperations;
   @Inject private ProjectOperations projectOperations;
   @Inject private Provider<QueryChanges> queryChangesProvider;
diff --git a/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java b/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java
index 5e4beedf..cad20b6 100644
--- a/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java
@@ -65,6 +65,7 @@
 import com.google.gerrit.server.config.ProjectConfigEntry;
 import com.google.gerrit.server.group.SystemGroupBackend;
 import com.google.gerrit.server.project.CommentLinkInfoImpl;
+import com.google.gerrit.server.project.ProjectConfig;
 import com.google.inject.AbstractModule;
 import com.google.inject.Inject;
 import com.google.inject.Module;
@@ -906,7 +907,16 @@
     // We must test this via the REST API since ExceptionHook is not invoked from the Java API.
     RestResponse r = adminRestSession.get("/projects/" + allProjects.get());
     r.assertConflict();
-    assertThat(r.getEntityContent()).contains("Invalid project.config file");
+    assertThat(r.getEntityContent())
+        .contains(
+            String.format(
+                "Invalid config file %s in project %s in branch %s in commit %s: "
+                    + "Bad entry delimiter\n"
+                    + "Please contact the project owner.",
+                ProjectConfig.PROJECT_CONFIG,
+                allProjects.get(),
+                RefNames.REFS_CONFIG,
+                projectOperations.project(allProjects).getHead(RefNames.REFS_CONFIG).name()));
   }
 
   private CommentLinkInfo commentLinkInfo(String name, String match, String link) {
diff --git a/javatests/com/google/gerrit/acceptance/git/PushAccountIT.java b/javatests/com/google/gerrit/acceptance/git/PushAccountIT.java
index 87f3c98..6f7a4c3 100644
--- a/javatests/com/google/gerrit/acceptance/git/PushAccountIT.java
+++ b/javatests/com/google/gerrit/acceptance/git/PushAccountIT.java
@@ -254,11 +254,13 @@
           .contains(
               String.format(
                   "invalid account configuration: commit '%s' has an invalid '%s' file for account"
-                      + " '%s': Invalid config file %s in commit %s",
+                      + " '%s': Invalid config file %s in project %s in branch %s in commit %s",
                   r.getCommit().name(),
                   AccountProperties.ACCOUNT_CONFIG,
                   admin.id(),
                   AccountProperties.ACCOUNT_CONFIG,
+                  allUsers.get(),
+                  userRef,
                   r.getCommit().name()));
     }
   }
@@ -480,7 +482,8 @@
     try (Registration registration =
         extensionRegistry.newRegistration().add(accountIndexedCounter)) {
       TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
-      fetch(allUsersRepo, RefNames.refsUsers(admin.id()) + ":userRef");
+      String userRef = RefNames.refsUsers(admin.id());
+      fetch(allUsersRepo, userRef + ":userRef");
       allUsersRepo.reset("userRef");
 
       PushOneCommit.Result r =
@@ -496,11 +499,13 @@
       r.assertMessage(
           String.format(
               "commit '%s' has an invalid '%s' file for account '%s':"
-                  + " Invalid config file %s in commit %s",
+                  + " Invalid config file %s in project %s in branch %s in commit %s",
               r.getCommit().name(),
               AccountProperties.ACCOUNT_CONFIG,
               admin.id(),
               AccountProperties.ACCOUNT_CONFIG,
+              allUsers.get(),
+              userRef,
               r.getCommit().name()));
       accountIndexedCounter.assertNoReindex();
     }
diff --git a/javatests/com/google/gerrit/acceptance/rest/auth/AuthenticationCheckIT.java b/javatests/com/google/gerrit/acceptance/rest/auth/AuthenticationCheckIT.java
index b6ef5a3..9298b43 100644
--- a/javatests/com/google/gerrit/acceptance/rest/auth/AuthenticationCheckIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/auth/AuthenticationCheckIT.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.acceptance.rest.auth;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.RestResponse;
 import com.google.gerrit.acceptance.RestSession;
@@ -24,6 +26,7 @@
   public void authCheck_loggedInUser_returnsOk() throws Exception {
     RestResponse r = adminRestSession.get("/auth-check");
     r.assertNoContent();
+    // Jetty strips Content-Length when status is NO_CONTENT
   }
 
   @Test
@@ -31,5 +34,6 @@
     RestSession anonymous = new RestSession(server, null);
     RestResponse r = anonymous.get("/auth-check");
     r.assertForbidden();
+    assertThat(r.getHeader("Content-Length")).isEqualTo("0");
   }
 }
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/AccessIT.java b/javatests/com/google/gerrit/acceptance/rest/project/AccessIT.java
index 3e9b1f6..08c20ec 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/AccessIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/AccessIT.java
@@ -456,6 +456,44 @@
   }
 
   @Test
+  public void addPermissionAsGlobalCapability() throws Exception {
+    ProjectAccessInput accessInput = newProjectAccessInput();
+    AccessSectionInfo accessSectionInfo = newAccessSectionInfo();
+
+    PermissionInfo push = newPermissionInfo();
+    PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.ALLOW, false);
+    push.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
+    accessSectionInfo.permissions.put(Permission.PUSH, push);
+
+    accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES, accessSectionInfo);
+    BadRequestException ex =
+        assertThrows(
+            BadRequestException.class,
+            () -> gApi.projects().name(allProjects.get()).access(accessInput));
+    assertThat(ex).hasMessageThat().isEqualTo("Unknown global capability: " + Permission.PUSH);
+  }
+
+  @Test
+  public void addInvalidGlobalCapability() throws Exception {
+    ProjectAccessInput accessInput = newProjectAccessInput();
+    AccessSectionInfo accessSectionInfo = createDefaultGlobalCapabilitiesAccessSectionInfo();
+
+    PermissionInfo permissionInfo = newPermissionInfo();
+    PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.ALLOW, false);
+    permissionInfo.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
+    accessSectionInfo.permissions.put("Invalid Global Capability", permissionInfo);
+
+    accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES, accessSectionInfo);
+    BadRequestException ex =
+        assertThrows(
+            BadRequestException.class,
+            () -> gApi.projects().name(allProjects.get()).access(accessInput));
+    assertThat(ex)
+        .hasMessageThat()
+        .isEqualTo("Unknown global capability: Invalid Global Capability");
+  }
+
+  @Test
   public void addGlobalCapabilityForNonRootProject() throws Exception {
     ProjectAccessInput accessInput = newProjectAccessInput();
     AccessSectionInfo accessSectionInfo = createDefaultGlobalCapabilitiesAccessSectionInfo();
diff --git a/plugins/download-commands b/plugins/download-commands
index 1b98be8..5c50f9b 160000
--- a/plugins/download-commands
+++ b/plugins/download-commands
@@ -1 +1 @@
-Subproject commit 1b98be8d371e68237182df0f04361017f2be2ea8
+Subproject commit 5c50f9b17e616fd84d2c561822161fff46bbf902
diff --git a/plugins/replication b/plugins/replication
index 4590b53..041f55c 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit 4590b53e5ef3726ed12b8a3054ae5d4d8363f90d
+Subproject commit 041f55c0ff92e14bea9badacc617a8abb7f37b71
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
index 53e852f..289e3f4 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
@@ -335,10 +335,11 @@
                   aria-label$="[[_computeFileStatusLabel(file.status)]]">
               [[_computeFileStatus(file.status)]]
             </div>
+            <!-- TODO: Remove data-url as it appears its not used -->
             <span
-                data-url="[[_computeDiffURL(change, patchRange.patchNum, patchRange.basePatchNum, file.__path, editMode)]]"
+                data-url="[[_computeDiffURL(change, patchRange, file.__path, editMode)]]"
                 class="path">
-              <a class="pathLink" href$="[[_computeDiffURL(change, patchRange.patchNum, patchRange.basePatchNum, file.__path, editMode)]]">
+              <a class="pathLink" href$="[[_computeDiffURL(change, patchRange, file.__path, editMode)]]">
                 <span title$="[[computeDisplayPath(file.__path)]]"
                     class="fullFileName">
                   [[computeDisplayPath(file.__path)]]
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
index cdc56b2..d2f11c2 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
@@ -798,19 +798,20 @@
       return status || 'M';
     }
 
-    _computeDiffURL(change, patchNum, basePatchNum, path, editMode) {
+    _computeDiffURL(change, patchRange, path, editMode) {
       // Polymer 2: check for undefined
-      if ([change, patchNum, basePatchNum, path, editMode]
+      if ([change, patchRange, path, editMode]
           .some(arg => arg === undefined)) {
         return;
       }
       // TODO(kaspern): Fix editing for commit messages and merge lists.
       if (editMode && path !== this.COMMIT_MESSAGE_PATH &&
           path !== this.MERGE_LIST_PATH) {
-        return Gerrit.Nav.getEditUrlForDiff(change, path, patchNum,
-            basePatchNum);
+        return Gerrit.Nav.getEditUrlForDiff(change, path, patchRange.patchNum,
+            patchRange.basePatchNum);
       }
-      return Gerrit.Nav.getUrlForDiff(change, path, patchNum, basePatchNum);
+      return Gerrit.Nav.getUrlForDiff(change, path, patchRange.patchNum,
+          patchRange.basePatchNum);
     }
 
     _formatBytes(bytes) {
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
index 0a1c8ec..af80ca8 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
@@ -1267,6 +1267,42 @@
       });
     });
 
+    suite('diff url file list', () => {
+      test('diff url', () => {
+        const diffStub = sandbox.stub(Gerrit.Nav, 'getUrlForDiff')
+            .returns('/c/gerrit/+/1/1/index.php');
+        const change = {
+          _number: 1,
+          project: 'gerrit',
+        };
+        const path = 'index.php';
+        const patchRange = {
+          patchNum: 1,
+        };
+        assert.equal(
+            element._computeDiffURL(change, patchRange, path, false),
+            '/c/gerrit/+/1/1/index.php');
+        diffStub.restore();
+      });
+
+      test('diff url commit msg', () => {
+        const diffStub = sandbox.stub(Gerrit.Nav, 'getUrlForDiff')
+            .returns('/c/gerrit/+/1/1//COMMIT_MSG');
+        const change = {
+          _number: 1,
+          project: 'gerrit',
+        };
+        const path = '/COMMIT_MSG';
+        const patchRange = {
+          patchNum: 1,
+        };
+        assert.equal(
+            element._computeDiffURL(change, patchRange, path, false),
+            '/c/gerrit/+/1/1//COMMIT_MSG');
+        diffStub.restore();
+      });
+    });
+
     suite('size bars', () => {
       test('_computeSizeBarLayout', () => {
         assert.isUndefined(element._computeSizeBarLayout(null));
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.js b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.js
index efacb97..9e1ec9e 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element.js
@@ -24,6 +24,10 @@
 
   const TRAILING_WHITESPACE_PATTERN = /\s+$/;
 
+  // https://gerrit.googlesource.com/gerrit/+/234616a8627334686769f1de989d286039f4d6a5/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js#740
+  const COMMIT_MSG_PATH = '/COMMIT_MSG';
+  const COMMIT_MSG_LINE_LENGTH = 72;
+
   /**
    * @appliesMixin Gerrit.FireMixin
    */
@@ -271,18 +275,39 @@
         return;
       }
 
+      const localPrefs = Object.assign({}, prefs);
+      if (this.path === COMMIT_MSG_PATH) {
+        // override line_length for commit msg the same way as
+        // in gr-diff
+        localPrefs.line_length = COMMIT_MSG_LINE_LENGTH;
+      }
+
       let builder = null;
       if (this.isImageDiff) {
-        builder = new GrDiffBuilderImage(diff, prefs, this.diffElement,
-            this.baseImage, this.revisionImage);
+        builder = new GrDiffBuilderImage(
+            diff,
+            localPrefs,
+            this.diffElement,
+            this.baseImage,
+            this.revisionImage);
       } else if (diff.binary) {
         // If the diff is binary, but not an image.
-        return new GrDiffBuilderBinary(diff, prefs, this.diffElement);
+        return new GrDiffBuilderBinary(
+            diff,
+            localPrefs,
+            this.diffElement);
       } else if (this.viewMode === DiffViewMode.SIDE_BY_SIDE) {
-        builder = new GrDiffBuilderSideBySide(diff, prefs, this.diffElement,
-            this._layers);
+        builder = new GrDiffBuilderSideBySide(
+            diff,
+            localPrefs,
+            this.diffElement,
+            this._layers
+        );
       } else if (this.viewMode === DiffViewMode.UNIFIED) {
-        builder = new GrDiffBuilderUnified(diff, prefs, this.diffElement,
+        builder = new GrDiffBuilderUnified(
+            diff,
+            localPrefs,
+            this.diffElement,
             this._layers);
       }
       if (!builder) {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_test.html b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_test.html
index d9d515f..3af5522 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-element_test.html
@@ -60,6 +60,11 @@
 </test-fixture>
 
 <script>
+  const DiffViewMode = {
+    SIDE_BY_SIDE: 'SIDE_BY_SIDE',
+    UNIFIED: 'UNIFIED_DIFF',
+  };
+
   suite('gr-diff-builder tests', async () => {
     await readyToTest();
     let prefs;
@@ -192,6 +197,27 @@
       assert.equal(result, expected);
     });
 
+    [DiffViewMode.UNIFIED, DiffViewMode.SIDE_BY_SIDE]
+        .forEach(mode => {
+          test(`line_length used for regular files under ${mode}`, () => {
+            element.path = '/a.txt';
+            element.viewMode = mode;
+            builder = element._getDiffBuilder(
+                {}, {tab_size: 4, line_length: 50}
+            );
+            assert.equal(builder._prefs.line_length, 50);
+          });
+
+          test(`line_length ignored for commit msg under ${mode}`, () => {
+            element.path = '/COMMIT_MSG';
+            element.viewMode = mode;
+            builder = element._getDiffBuilder(
+                {}, {tab_size: 4, line_length: 50}
+            );
+            assert.equal(builder._prefs.line_length, 72);
+          });
+        });
+
     test('_createTextEl linewrap with tabs', () => {
       const text = '\t'.repeat(7) + '!';
       const line = {text, highlights: []};
diff --git a/polygerrit-ui/app/embed/README.md b/polygerrit-ui/app/embed/README.md
new file mode 100644
index 0000000..e147007
--- /dev/null
+++ b/polygerrit-ui/app/embed/README.md
@@ -0,0 +1,13 @@
+This folder contains shared components that can be used independently from Gerrit.
+
+### gr-diff
+
+`gr-diff.html` is the `gr-diff` component used in gerrit to render diff. If you want to use it, feel free to import it and use it in your project as:
+
+```
+<gr-diff></gr-diff>
+```
+
+All supported attributes defined in `polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js`, you can pass them by just assigning them to the `gr-app` element.
+
+To customize the style of the diff, you can use `css variables`, all supported varibled defined in `polygerrit-ui/app/styles/themes/app-theme.html` and `polygerrit-ui/app/styles/themes/dark-theme.html`.
\ No newline at end of file
diff --git a/polygerrit-ui/app/scripts/util.js b/polygerrit-ui/app/scripts/util.js
index 565b9b3..6a8a116 100644
--- a/polygerrit-ui/app/scripts/util.js
+++ b/polygerrit-ui/app/scripts/util.js
@@ -124,6 +124,12 @@
           .filter(child => !!child.shadowRoot)
           .map(child => child.shadowRoot);
       nodes = nodes.concat(allShadowNodes);
+
+      // Add shadowRoot of current node if has one
+      // as its not included in node.querySelectorAll('*')
+      if (node.shadowRoot) {
+        nodes.push(node.shadowRoot);
+      }
     }
     return result;
   };
@@ -154,6 +160,12 @@
           .filter(child => !!child.shadowRoot)
           .map(child => child.shadowRoot);
       nodes = nodes.concat(allShadowNodes);
+
+      // Add shadowRoot of current node if has one
+      // as its not included in node.querySelectorAll('*')
+      if (node.shadowRoot) {
+        nodes.push(node.shadowRoot);
+      }
     }
     return [...results];
   };