Merge "Preload edit and drafts REST requests"
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index eea0cc3..5425774 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -121,6 +121,27 @@
 +
 Default is `ALL`.
 
+[[accounts.defaultDisplayName]]accounts.defaultDisplayName::
++
+If a user account does not have a display name set, which is the normal
+case, then this configuration value chooses the strategy how to choose
+the display name. Note that this strategy is not applied by the backend.
+If the AccountInfo has the display name unset, then the client has to
+apply this strategy.
++
+If `FULL_NAME`, then the (full) name of the user is chosen from
+link:rest-api-accounts.html#account-info[AccountInfo].
++
+If `FIRST_NAME`, then the first word (i.e. everything until first
+whitespace character) of the (full) name of the user is chosen from
+link:rest-api-accounts.html#account-info[AccountInfo].
++
+If `USERNAME`, then the username of the user is chosen from
+link:rest-api-accounts.html#account-info[AccountInfo]. If that is not
+set, then the (full) name will be used.
++
+Default is `FULL_NAME`.
+
 [[addreviewer]]
 === Section addreviewer
 
diff --git a/Documentation/dev-bazel.txt b/Documentation/dev-bazel.txt
index 9b43a29..103ae0a 100644
--- a/Documentation/dev-bazel.txt
+++ b/Documentation/dev-bazel.txt
@@ -376,6 +376,21 @@
 Note that Bazel currently does not show
 link:https://github.com/bazelbuild/bazel/issues/3476[the skipped tests,role=external,window=_blank].
 
+[[debug]]
+=== Index Query Tests
+
+The `DEBUG` log level can optionally be enabled for the index query tests. That log level applies to
+both Elasticsearch and Lucene tests.
+
+In Eclipse, set `-Ddebug=true` as a VM argument under the Run Configuration's `Arguments` tab.
+
+With `bazel`, here is an example for the Lucene `account` test:
+
+----
+bazel test --jvmopt='-Ddebug=true' \
+javatests/com/google/gerrit/server/query/account:lucene_query_test
+----
+
 == Dependencies
 
 Dependency JARs are normally downloaded as needed, but you can
diff --git a/Documentation/dev-e2e-tests.txt b/Documentation/dev-e2e-tests.txt
index 628a0ec..9a62f01 100644
--- a/Documentation/dev-e2e-tests.txt
+++ b/Documentation/dev-e2e-tests.txt
@@ -106,12 +106,14 @@
 
 Valid commands are:
 
+* `clone`
 * `fetch`
 * `pull`
 * `push`
-* `clone`
 
-The example above assumes that the `loadtest-repo` project exists in the Gerrit under test.
+The example above assumes that the `loadtest-repo` project exists in the Gerrit under test. The
+`HTTP Credentials` or password obtained from test user's `Settings` (in Gerrit) may be required, in
+`src/test/resources/application.conf`, depending on the above commands used.
 
 == How to run tests
 
diff --git a/Documentation/dev-eclipse.txt b/Documentation/dev-eclipse.txt
index 5f69cd3..9596a55 100644
--- a/Documentation/dev-eclipse.txt
+++ b/Documentation/dev-eclipse.txt
@@ -81,6 +81,13 @@
 
 == Testing
 
+=== PolyGerrit UI is served by `server.go` process. To launch it,
+run this command:
+
+----
+  $ bazel run polygerrit-ui:devserver
+----
+
 === Running the Daemon
 
 Duplicate the existing launch configuration:
diff --git a/Documentation/js_licenses.txt b/Documentation/js_licenses.txt
index 05f2929..bd26190 100644
--- a/Documentation/js_licenses.txt
+++ b/Documentation/js_licenses.txt
@@ -664,6 +664,9 @@
 * @polymer/neon-animation
 * @polymer/paper-behaviors
 * @polymer/paper-button
+* @polymer/paper-dialog
+* @polymer/paper-dialog-behavior
+* @polymer/paper-dialog-scrollable
 * @polymer/paper-icon-button
 * @polymer/paper-input
 * @polymer/paper-item
diff --git a/Documentation/licenses.txt b/Documentation/licenses.txt
index dfb66ec..db2cd7d 100644
--- a/Documentation/licenses.txt
+++ b/Documentation/licenses.txt
@@ -3620,6 +3620,9 @@
 * @polymer/neon-animation
 * @polymer/paper-behaviors
 * @polymer/paper-button
+* @polymer/paper-dialog
+* @polymer/paper-dialog-behavior
+* @polymer/paper-dialog-scrollable
 * @polymer/paper-icon-button
 * @polymer/paper-input
 * @polymer/paper-item
diff --git a/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt
index 828674f..2f72340 100644
--- a/Documentation/rest-api-config.txt
+++ b/Documentation/rest-api-config.txt
@@ -1419,10 +1419,13 @@
 
 [options="header",cols="1,6"]
 |=============================
-|Field Name           |Description
-|`visibility`         |
+|Field Name            |Description
+|`visibility`          |
 link:config-gerrit.html#accounts.visibility[Visibility setting for
 accounts].
+|`default_display_name`|The default strategy for choosing the display
+name in the UI, see also
+link:config-gerrit.html#accounts.defaultDisplayName[gerrit.config].
 |=============================
 
 [[auth-info]]
diff --git a/WORKSPACE b/WORKSPACE
index 0d0d405..a56dd8a 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -55,8 +55,8 @@
 
 http_archive(
     name = "build_bazel_rules_nodejs",
-    sha256 = "591d2945b09ecc89fde53e56dd54cfac93322df3bc9d4747cb897ce67ba8cdbf",
-    urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/1.2.0/rules_nodejs-1.2.0.tar.gz"],
+    sha256 = "9473b207f1c5a61b603442cbfeeea8aaf2aa62870673fce2a1c52087f6ff4dc9",
+    urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/1.2.4/rules_nodejs-1.2.4.tar.gz"],
 )
 
 # File is specific to Polymer and copied from the Closure Github -- should be
diff --git a/java/com/google/gerrit/extensions/common/AccountDefaultDisplayName.java b/java/com/google/gerrit/extensions/common/AccountDefaultDisplayName.java
new file mode 100644
index 0000000..44d6c00
--- /dev/null
+++ b/java/com/google/gerrit/extensions/common/AccountDefaultDisplayName.java
@@ -0,0 +1,40 @@
+// 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.extensions.common;
+
+/**
+ * Fallback rule for choosing a display name, if it is not explicitly set. This rule will not be
+ * applied by the backend, but should be applied by the user interface.
+ */
+public enum AccountDefaultDisplayName {
+
+  /**
+   * If the display name for an account is not set, then the (full) name will be used as the display
+   * name in the user interface.
+   */
+  FULL_NAME,
+
+  /**
+   * If the display name for an account is not set, then the first name (i.e. full name until first
+   * whitespace character) will be used as the display name in the user interface.
+   */
+  FIRST_NAME,
+
+  /**
+   * If the display name for an account is not set, then the username will be used as the display
+   * name in the user interface. If the username is also not set, then the (full) name will be used.
+   */
+  USERNAME
+}
diff --git a/java/com/google/gerrit/extensions/common/AccountInfo.java b/java/com/google/gerrit/extensions/common/AccountInfo.java
index 803225e..2a3d260 100644
--- a/java/com/google/gerrit/extensions/common/AccountInfo.java
+++ b/java/com/google/gerrit/extensions/common/AccountInfo.java
@@ -35,9 +35,11 @@
 
   /**
    * The display name of the user. This allows users to control how their name is displayed in the
-   * UI. It will likely be unset for most users. Host admins will just choose a default (full name,
-   * user name, first name, ...) for all users, and this account property is just a way to opt out
-   * of the host wide default strategy of choosing the display name.
+   * UI. It will likely be unset for most users. This account property is just a way to opt out of
+   * the host wide default strategy of choosing the display name, see
+   * accounts.accountDefaultDisplayName in the server config. The default strategy is not applied by
+   * the backend. The display name will just be left unset, and the client has to load and apply the
+   * default strategy.
    */
   public String displayName;
 
diff --git a/java/com/google/gerrit/extensions/common/AccountsInfo.java b/java/com/google/gerrit/extensions/common/AccountsInfo.java
index a2a4826..d669578 100644
--- a/java/com/google/gerrit/extensions/common/AccountsInfo.java
+++ b/java/com/google/gerrit/extensions/common/AccountsInfo.java
@@ -22,4 +22,7 @@
 public class AccountsInfo {
   /** The value of the {@code accounts.visibility} parameter in {@code gerrit.config}. */
   public AccountVisibility visibility;
+
+  /** The value of the {@code accounts.visibility} parameter in {@code gerrit.config}. */
+  public AccountDefaultDisplayName defaultDisplayName;
 }
diff --git a/java/com/google/gerrit/server/config/GerritGlobalModule.java b/java/com/google/gerrit/server/config/GerritGlobalModule.java
index 3914205..276d57c 100644
--- a/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -22,6 +22,7 @@
 import com.google.gerrit.extensions.api.projects.CommentLinkInfo;
 import com.google.gerrit.extensions.auth.oauth.OAuthLoginProvider;
 import com.google.gerrit.extensions.auth.oauth.OAuthTokenEncrypter;
+import com.google.gerrit.extensions.common.AccountDefaultDisplayName;
 import com.google.gerrit.extensions.common.AccountVisibility;
 import com.google.gerrit.extensions.config.CapabilityDefinition;
 import com.google.gerrit.extensions.config.CloneCommand;
@@ -269,6 +270,9 @@
     factory(InboundEmailRejectionSender.Factory.class);
     bind(PermissionCollection.Factory.class);
     bind(AccountVisibility.class).toProvider(AccountVisibilityProvider.class).in(SINGLETON);
+    AccountDefaultDisplayName accountDefaultDisplayName =
+        cfg.getEnum("accounts", null, "defaultDisplayName", AccountDefaultDisplayName.FULL_NAME);
+    bind(AccountDefaultDisplayName.class).toInstance(accountDefaultDisplayName);
     factory(ProjectOwnerGroupsProvider.Factory.class);
     factory(SubmitRuleEvaluator.Factory.class);
 
diff --git a/java/com/google/gerrit/server/permissions/PermissionCollection.java b/java/com/google/gerrit/server/permissions/PermissionCollection.java
index b23b5a9..1f0370b 100644
--- a/java/com/google/gerrit/server/permissions/PermissionCollection.java
+++ b/java/com/google/gerrit/server/permissions/PermissionCollection.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.server.permissions;
 
 import static com.google.gerrit.common.data.PermissionRule.Action.BLOCK;
+import static com.google.gerrit.server.project.RefPattern.containsParameters;
 import static com.google.gerrit.server.project.RefPattern.isRE;
 import static java.util.stream.Collectors.mapping;
 import static java.util.stream.Collectors.toList;
@@ -131,7 +132,9 @@
         Iterable<SectionMatcher> matcherList, String ref, CurrentUser user) {
       try (Timer0.Context ignored = filterLatency.start()) {
         if (isRE(ref)) {
-          ref = RefPattern.shortestExample(ref);
+          if (!containsParameters(ref)) {
+            ref = RefPattern.shortestExample(ref);
+          }
         } else if (ref.endsWith("/*")) {
           ref = ref.substring(0, ref.length() - 1);
         }
diff --git a/java/com/google/gerrit/server/project/RefPattern.java b/java/com/google/gerrit/server/project/RefPattern.java
index 0e916fb..c52914b 100644
--- a/java/com/google/gerrit/server/project/RefPattern.java
+++ b/java/com/google/gerrit/server/project/RefPattern.java
@@ -18,9 +18,12 @@
 import com.google.common.cache.CacheBuilder;
 import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
+import com.google.common.collect.ImmutableMap;
 import com.google.gerrit.common.data.AccessSection;
+import com.google.gerrit.common.data.ParameterizedString;
 import com.google.gerrit.exceptions.InvalidNameException;
 import dk.brics.automaton.RegExp;
+import java.util.Map;
 import java.util.concurrent.ExecutionException;
 import java.util.regex.Pattern;
 import java.util.regex.PatternSyntaxException;
@@ -69,11 +72,21 @@
     return refPattern.startsWith(AccessSection.REGEX_PREFIX);
   }
 
+  public static boolean containsParameters(String refPattern) {
+    return refPattern.contains("${");
+  }
+
   public static RegExp toRegExp(String refPattern) {
     if (isRE(refPattern)) {
       refPattern = refPattern.substring(1);
     }
-    return new RegExp(refPattern, RegExp.NONE);
+    ParameterizedString template = new ParameterizedString(refPattern);
+    String replacement = "_PLACEHOLDER_";
+    Map<String, String> params =
+        ImmutableMap.of(
+            RefPattern.USERID_SHARDED, replacement,
+            RefPattern.USERNAME, replacement);
+    return new RegExp(template.replace(params), RegExp.NONE);
   }
 
   public static void validate(String refPattern) throws InvalidNameException {
diff --git a/java/com/google/gerrit/server/project/RefPatternMatcher.java b/java/com/google/gerrit/server/project/RefPatternMatcher.java
index f00e98e..b9076b3 100644
--- a/java/com/google/gerrit/server/project/RefPatternMatcher.java
+++ b/java/com/google/gerrit/server/project/RefPatternMatcher.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.server.project;
 
 import static com.google.common.collect.ImmutableSet.toImmutableSet;
+import static com.google.gerrit.server.project.RefPattern.containsParameters;
 import static com.google.gerrit.server.project.RefPattern.isRE;
 
 import com.google.common.collect.ImmutableMap;
@@ -32,7 +33,7 @@
 
 public abstract class RefPatternMatcher {
   public static RefPatternMatcher getMatcher(String pattern) {
-    if (pattern.contains("${")) {
+    if (containsParameters(pattern)) {
       return new ExpandParameters(pattern);
     } else if (isRE(pattern)) {
       return new Regexp(pattern);
@@ -80,7 +81,7 @@
 
     @Override
     public boolean match(String ref, CurrentUser user) {
-      return pattern.matcher(ref).matches();
+      return pattern.matcher(ref).matches() || (isRE(ref) && pattern.pattern().equals(ref));
     }
   }
 
@@ -112,7 +113,11 @@
 
     @Override
     public boolean match(String ref, CurrentUser user) {
-      if (!ref.startsWith(prefix)) {
+      if (isRE(ref)) {
+        if (!ref.substring(1).startsWith(prefix)) {
+          return false;
+        }
+      } else if (!ref.startsWith(prefix)) {
         return false;
       }
 
@@ -142,6 +147,9 @@
     }
 
     public boolean matchPrefix(String ref) {
+      if (isRE(ref)) {
+        return ref.substring(1).startsWith(prefix);
+      }
       return ref.startsWith(prefix);
     }
 
diff --git a/java/com/google/gerrit/server/restapi/config/GetServerInfo.java b/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
index bbe38a3..0f1369c 100644
--- a/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
+++ b/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
@@ -20,6 +20,7 @@
 import com.google.common.base.Strings;
 import com.google.common.collect.Lists;
 import com.google.gerrit.common.data.ContributorAgreement;
+import com.google.gerrit.extensions.common.AccountDefaultDisplayName;
 import com.google.gerrit.extensions.common.AccountsInfo;
 import com.google.gerrit.extensions.common.AuthInfo;
 import com.google.gerrit.extensions.common.ChangeConfigInfo;
@@ -74,6 +75,7 @@
 public class GetServerInfo implements RestReadView<ConfigResource> {
   private final Config config;
   private final AccountVisibilityProvider accountVisibilityProvider;
+  private final AccountDefaultDisplayName accountDefaultDisplayName;
   private final AuthConfig authConfig;
   private final Realm realm;
   private final PluginMapContext<DownloadScheme> downloadSchemes;
@@ -96,6 +98,7 @@
   public GetServerInfo(
       @GerritServerConfig Config config,
       AccountVisibilityProvider accountVisibilityProvider,
+      AccountDefaultDisplayName accountDefaultDisplayName,
       AuthConfig authConfig,
       Realm realm,
       PluginMapContext<DownloadScheme> downloadSchemes,
@@ -115,6 +118,7 @@
       SitePaths sitePaths) {
     this.config = config;
     this.accountVisibilityProvider = accountVisibilityProvider;
+    this.accountDefaultDisplayName = accountDefaultDisplayName;
     this.authConfig = authConfig;
     this.realm = realm;
     this.downloadSchemes = downloadSchemes;
@@ -156,6 +160,7 @@
   private AccountsInfo getAccountsInfo() {
     AccountsInfo info = new AccountsInfo();
     info.visibility = accountVisibilityProvider.get();
+    info.defaultDisplayName = accountDefaultDisplayName;
     return info;
   }
 
diff --git a/java/com/google/gerrit/server/restapi/project/CheckMergeability.java b/java/com/google/gerrit/server/restapi/project/CheckMergeability.java
index 4864fde..da6ff14 100644
--- a/java/com/google/gerrit/server/restapi/project/CheckMergeability.java
+++ b/java/com/google/gerrit/server/restapi/project/CheckMergeability.java
@@ -18,6 +18,7 @@
 import com.google.gerrit.extensions.client.SubmitType;
 import com.google.gerrit.extensions.common.MergeableInfo;
 import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestReadView;
@@ -28,6 +29,7 @@
 import com.google.gerrit.server.project.BranchResource;
 import com.google.inject.Inject;
 import java.io.IOException;
+import org.eclipse.jgit.errors.NoMergeBaseException;
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.lib.Ref;
@@ -78,7 +80,8 @@
 
   @Override
   public Response<MergeableInfo> apply(BranchResource resource)
-      throws IOException, BadRequestException, ResourceNotFoundException {
+      throws IOException, BadRequestException, ResourceNotFoundException,
+          ResourceConflictException {
     if (!(submitType.equals(SubmitType.MERGE_ALWAYS)
         || submitType.equals(SubmitType.MERGE_IF_NECESSARY))) {
       throw new BadRequestException("Submit type: " + submitType + " is not supported");
@@ -123,6 +126,12 @@
       }
     } catch (InvalidMergeStrategyException e) {
       throw new BadRequestException(e.getMessage());
+    } catch (NoMergeBaseException e) {
+      // TODO(ekempin) Rather return MergeableInfo with mergeable = false. But then we need a new
+      // field in MergeableInfo to carry the message to the client and the frontend needs to be
+      // adapted to show the message to the user.
+      throw new ResourceConflictException(
+          String.format("Change cannot be merged: %s", e.getMessage()), e);
     }
     return Response.ok(result);
   }
diff --git a/javatests/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java b/javatests/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java
index 14a04cd..802c5b3 100644
--- a/javatests/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java
@@ -25,6 +25,7 @@
 import com.google.gerrit.extensions.api.plugins.InstallPluginInput;
 import com.google.gerrit.extensions.client.AccountFieldName;
 import com.google.gerrit.extensions.client.AuthType;
+import com.google.gerrit.extensions.common.AccountDefaultDisplayName;
 import com.google.gerrit.extensions.common.AccountVisibility;
 import com.google.gerrit.extensions.common.ServerInfo;
 import com.google.gerrit.server.config.AllProjectsNameProvider;
@@ -41,6 +42,7 @@
   @Test
   // accounts
   @GerritConfig(name = "accounts.visibility", value = "VISIBLE_GROUP")
+  @GerritConfig(name = "accounts.defaultDisplayName", value = "FIRST_NAME")
 
   // auth
   @GerritConfig(name = "auth.type", value = "HTTP")
@@ -82,6 +84,7 @@
 
     // accounts
     assertThat(i.accounts.visibility).isEqualTo(AccountVisibility.VISIBLE_GROUP);
+    assertThat(i.accounts.defaultDisplayName).isEqualTo(AccountDefaultDisplayName.FIRST_NAME);
 
     // auth
     assertThat(i.auth.authType).isEqualTo(AuthType.HTTP);
diff --git a/javatests/com/google/gerrit/server/permissions/RefControlTest.java b/javatests/com/google/gerrit/server/permissions/RefControlTest.java
index 08b008d..43a3f10 100644
--- a/javatests/com/google/gerrit/server/permissions/RefControlTest.java
+++ b/javatests/com/google/gerrit/server/permissions/RefControlTest.java
@@ -477,6 +477,21 @@
   }
 
   @Test
+  public void usernamePatternRegExpCanUploadToAnyRef() throws Exception {
+    projectOperations
+        .project(localKey)
+        .forUpdate()
+        .add(
+            allow(PUSH)
+                .ref("^refs/heads/users/${username}/(public|private)/.+")
+                .group(REGISTERED_USERS))
+        .update();
+    ProjectControl u = user(localKey, "a-registered-user");
+    assertCanUpload(u);
+    assertCanUpdate("refs/heads/users/a-registered-user/private/a", u);
+  }
+
+  @Test
   public void usernamePatternNonRegex() throws Exception {
     projectOperations
         .project(localKey)
@@ -500,6 +515,8 @@
 
     ProjectControl u = user(localKey, "d.v", DEVS);
     ProjectControl d = user(localKey, "dev", DEVS);
+    assertCanAccess(u);
+    assertCanAccess(d);
     assertCannotRead("refs/sb/dev/heads/foobar", u);
     assertCanRead("refs/sb/dev/heads/foobar", d);
   }
@@ -1127,6 +1144,7 @@
     RefPattern.validate("^refs/heads/*");
     RefPattern.validate("^refs/tags/[0-9a-zA-Z-_.]+");
     RefPattern.validate("refs/heads/review/${username}/*");
+    RefPattern.validate("^refs/heads/review/${username}/.+");
   }
 
   @Test
diff --git a/javatests/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java b/javatests/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java
index e7f0812..31d2048 100644
--- a/javatests/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java
+++ b/javatests/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java
@@ -91,10 +91,14 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Optional;
+import org.apache.log4j.Level;
+import org.apache.log4j.LogManager;
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.Repository;
 import org.junit.After;
+import org.junit.AfterClass;
 import org.junit.Before;
+import org.junit.BeforeClass;
 import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
@@ -146,6 +150,20 @@
 
   protected abstract Injector createInjector();
 
+  @BeforeClass
+  public static void setLogLevel() {
+    if (Boolean.getBoolean("debug")) {
+      LogManager.getRootLogger().setLevel(Level.DEBUG);
+    }
+  }
+
+  @AfterClass
+  public static void resetLogLevel() {
+    if (Boolean.getBoolean("debug")) {
+      LogManager.getRootLogger().setLevel(Level.INFO);
+    }
+  }
+
   @Before
   public void setUpInjector() throws Exception {
     lifecycle = new LifecycleManager();
diff --git a/javatests/com/google/gerrit/server/query/account/BUILD b/javatests/com/google/gerrit/server/query/account/BUILD
index 5c910a0..da37f26 100644
--- a/javatests/com/google/gerrit/server/query/account/BUILD
+++ b/javatests/com/google/gerrit/server/query/account/BUILD
@@ -23,8 +23,10 @@
         "//lib:guava",
         "//lib:jgit",
         "//lib/guice",
+        "//lib/log:log4j",
         "//lib/truth",
         "//lib/truth:truth-java8-extension",
+        "//resources:log4j-config",
     ],
 )
 
diff --git a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index 38e4ca4..71bdd69 100644
--- a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -132,6 +132,8 @@
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
+import org.apache.log4j.Level;
+import org.apache.log4j.LogManager;
 import org.eclipse.jgit.junit.TestRepository;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectInserter;
@@ -143,7 +145,9 @@
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.util.SystemReader;
 import org.junit.After;
+import org.junit.AfterClass;
 import org.junit.Before;
+import org.junit.BeforeClass;
 import org.junit.Ignore;
 import org.junit.Test;
 
@@ -206,6 +210,20 @@
 
   protected abstract Injector createInjector();
 
+  @BeforeClass
+  public static void setLogLevel() {
+    if (Boolean.getBoolean("debug")) {
+      LogManager.getRootLogger().setLevel(Level.DEBUG);
+    }
+  }
+
+  @AfterClass
+  public static void resetLogLevel() {
+    if (Boolean.getBoolean("debug")) {
+      LogManager.getRootLogger().setLevel(Level.INFO);
+    }
+  }
+
   @Before
   public void setUpInjector() throws Exception {
     lifecycle = new LifecycleManager();
diff --git a/javatests/com/google/gerrit/server/query/change/BUILD b/javatests/com/google/gerrit/server/query/change/BUILD
index e5b51e7..520e65a 100644
--- a/javatests/com/google/gerrit/server/query/change/BUILD
+++ b/javatests/com/google/gerrit/server/query/change/BUILD
@@ -34,7 +34,9 @@
         "//lib:jgit",
         "//lib:jgit-junit",
         "//lib/guice",
+        "//lib/log:log4j",
         "//lib/truth",
+        "//resources:log4j-config",
     ],
 )
 
diff --git a/javatests/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java b/javatests/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java
index d80eac0..9b01f8d 100644
--- a/javatests/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java
+++ b/javatests/com/google/gerrit/server/query/group/AbstractQueryGroupsTest.java
@@ -68,8 +68,12 @@
 import java.util.List;
 import java.util.Locale;
 import java.util.Optional;
+import org.apache.log4j.Level;
+import org.apache.log4j.LogManager;
 import org.junit.After;
+import org.junit.AfterClass;
 import org.junit.Before;
+import org.junit.BeforeClass;
 import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
@@ -115,6 +119,20 @@
 
   protected abstract Injector createInjector();
 
+  @BeforeClass
+  public static void setLogLevel() {
+    if (Boolean.getBoolean("debug")) {
+      LogManager.getRootLogger().setLevel(Level.DEBUG);
+    }
+  }
+
+  @AfterClass
+  public static void resetLogLevel() {
+    if (Boolean.getBoolean("debug")) {
+      LogManager.getRootLogger().setLevel(Level.INFO);
+    }
+  }
+
   @Before
   public void setUpInjector() throws Exception {
     lifecycle = new LifecycleManager();
diff --git a/javatests/com/google/gerrit/server/query/group/BUILD b/javatests/com/google/gerrit/server/query/group/BUILD
index e14350f..8f6fd6d 100644
--- a/javatests/com/google/gerrit/server/query/group/BUILD
+++ b/javatests/com/google/gerrit/server/query/group/BUILD
@@ -20,8 +20,10 @@
         "//lib:guava",
         "//lib:jgit",
         "//lib/guice",
+        "//lib/log:log4j",
         "//lib/truth",
         "//lib/truth:truth-java8-extension",
+        "//resources:log4j-config",
     ],
 )
 
diff --git a/javatests/com/google/gerrit/server/query/project/AbstractQueryProjectsTest.java b/javatests/com/google/gerrit/server/query/project/AbstractQueryProjectsTest.java
index dfd7928..e142e0c 100644
--- a/javatests/com/google/gerrit/server/query/project/AbstractQueryProjectsTest.java
+++ b/javatests/com/google/gerrit/server/query/project/AbstractQueryProjectsTest.java
@@ -68,8 +68,12 @@
 import java.util.List;
 import java.util.Locale;
 import java.util.Set;
+import org.apache.log4j.Level;
+import org.apache.log4j.LogManager;
 import org.junit.After;
+import org.junit.AfterClass;
 import org.junit.Before;
+import org.junit.BeforeClass;
 import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
@@ -111,6 +115,20 @@
 
   protected abstract Injector createInjector();
 
+  @BeforeClass
+  public static void setLogLevel() {
+    if (Boolean.getBoolean("debug")) {
+      LogManager.getRootLogger().setLevel(Level.DEBUG);
+    }
+  }
+
+  @AfterClass
+  public static void resetLogLevel() {
+    if (Boolean.getBoolean("debug")) {
+      LogManager.getRootLogger().setLevel(Level.INFO);
+    }
+  }
+
   @Before
   public void setUpInjector() throws Exception {
     lifecycle = new LifecycleManager();
diff --git a/javatests/com/google/gerrit/server/query/project/BUILD b/javatests/com/google/gerrit/server/query/project/BUILD
index 984d824..996be2f 100644
--- a/javatests/com/google/gerrit/server/query/project/BUILD
+++ b/javatests/com/google/gerrit/server/query/project/BUILD
@@ -21,7 +21,9 @@
         "//lib:guava",
         "//lib:jgit",
         "//lib/guice",
+        "//lib/log:log4j",
         "//lib/truth",
+        "//resources:log4j-config",
     ],
 )
 
diff --git a/polygerrit-ui/app/node_modules_licenses/licenses.ts b/polygerrit-ui/app/node_modules_licenses/licenses.ts
index f5af70f..fe07569 100644
--- a/polygerrit-ui/app/node_modules_licenses/licenses.ts
+++ b/polygerrit-ui/app/node_modules_licenses/licenses.ts
@@ -197,6 +197,18 @@
     license: SharedLicenses.Polymer2015
   },
   {
+    name: "@polymer/paper-dialog",
+    license: SharedLicenses.Polymer2015
+  },
+  {
+    name: "@polymer/paper-dialog-behavior",
+    license: SharedLicenses.Polymer2015
+  },
+  {
+    name: "@polymer/paper-dialog-scrollable",
+    license: SharedLicenses.Polymer2015
+  },
+  {
     name: "@polymer/paper-icon-button",
     license: SharedLicenses.Polymer2015
   },
diff --git a/polygerrit-ui/app/package.json b/polygerrit-ui/app/package.json
index 06e2764..491920d 100644
--- a/polygerrit-ui/app/package.json
+++ b/polygerrit-ui/app/package.json
@@ -14,6 +14,9 @@
     "@polymer/iron-overlay-behavior": "^3.0.2",
     "@polymer/iron-selector": "^3.0.1",
     "@polymer/paper-button": "^3.0.1",
+    "@polymer/paper-dialog": "^3.0.1",
+    "@polymer/paper-dialog-behavior": "^3.0.1",
+    "@polymer/paper-dialog-scrollable": "^3.0.1",
     "@polymer/paper-input": "^3.0.2",
     "@polymer/paper-item": "^3.0.1",
     "@polymer/paper-listbox": "^3.0.1",
diff --git a/polygerrit-ui/app/yarn.lock b/polygerrit-ui/app/yarn.lock
index ec00b7a..06b0e96 100644
--- a/polygerrit-ui/app/yarn.lock
+++ b/polygerrit-ui/app/yarn.lock
@@ -188,6 +188,35 @@
     "@polymer/paper-styles" "^3.0.0-pre.26"
     "@polymer/polymer" "^3.0.0"
 
+"@polymer/paper-dialog-behavior@^3.0.0-pre.26", "@polymer/paper-dialog-behavior@^3.0.1":
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/@polymer/paper-dialog-behavior/-/paper-dialog-behavior-3.0.1.tgz#819b2fbb9444c1c318ddf55f02819bb29a85657b"
+  integrity sha512-wbI4kCK8le/9MHT+IXzvHjoatxf3kd3Yn0tgozAiAwfSZ7N4Ubpi5MHrK0m9S9PeIxKokAgBYdTUrezSE5378A==
+  dependencies:
+    "@polymer/iron-overlay-behavior" "^3.0.0-pre.27"
+    "@polymer/paper-styles" "^3.0.0-pre.26"
+    "@polymer/polymer" "^3.0.0"
+
+"@polymer/paper-dialog-scrollable@^3.0.1":
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/@polymer/paper-dialog-scrollable/-/paper-dialog-scrollable-3.0.1.tgz#42fd30380320e6dd6d4d68b2ac4e45ee9e5e024f"
+  integrity sha512-1E8B9kNdL58jUrJ/BwqJeOoNVcxNrB559z//d1V0rVHWT5bWCCZegwS3G06iFK5MjxWFbIKzleVTLrT0opiZkA==
+  dependencies:
+    "@polymer/iron-flex-layout" "^3.0.0-pre.26"
+    "@polymer/paper-dialog-behavior" "^3.0.0-pre.26"
+    "@polymer/paper-styles" "^3.0.0-pre.26"
+    "@polymer/polymer" "^3.0.0"
+
+"@polymer/paper-dialog@^3.0.1":
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/@polymer/paper-dialog/-/paper-dialog-3.0.1.tgz#728ebdbfc4d35ec1485e543434cef5dba476f15e"
+  integrity sha512-KvglYbEq7AWJvui2j6WKLnOvgVMeGjovAydGrPRj7kVzCiD49Eq/hpYFJTRV5iDcalWH+mORUpw+jrFnG9+Kgw==
+  dependencies:
+    "@polymer/iron-overlay-behavior" "^3.0.0-pre.27"
+    "@polymer/neon-animation" "^3.0.0-pre.26"
+    "@polymer/paper-dialog-behavior" "^3.0.0-pre.26"
+    "@polymer/polymer" "^3.0.0"
+
 "@polymer/paper-icon-button@^3.0.0-pre.26":
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/@polymer/paper-icon-button/-/paper-icon-button-3.0.2.tgz#a1254faadc2c8dd135ce1ae33bcc161a94c31f65"
diff --git a/polygerrit-ui/server.go b/polygerrit-ui/server.go
index 7fa6eb2..ad03e51 100644
--- a/polygerrit-ui/server.go
+++ b/polygerrit-ui/server.go
@@ -137,7 +137,7 @@
 }
 
 func getFinalPath(redirects []redirects, originalPath string) string {
-	testComponentsPrefix := "/components/";
+	testComponentsPrefix := "/components/"
 	if strings.HasPrefix(originalPath, testComponentsPrefix) {
 		return "/../node_modules/" + originalPath[len(testComponentsPrefix):]
 	}
@@ -179,12 +179,12 @@
 		writer.WriteHeader(500)
 		return
 	}
-	if (parsedUrl.Path != "/" && strings.HasSuffix(parsedUrl.Path, "/")) {
+	if parsedUrl.Path != "/" && strings.HasSuffix(parsedUrl.Path, "/") {
 		dirListingMux.ServeHTTP(writer, originalRequest)
-		return;
+		return
 	}
 	if parsedUrl.Path == "/bower_components/web-component-tester/browser.js" {
-		http.Redirect(writer, originalRequest, "/bower_components/wct-browser-legacy/browser.js", 301);
+		http.Redirect(writer, originalRequest, "/bower_components/wct-browser-legacy/browser.js", 301)
 		return
 	}
 
@@ -214,7 +214,7 @@
 }
 
 func readFile(originalPath string, redirectedPath string) ([]byte, error) {
-	pathsToTry := [] string{"app" + redirectedPath}
+	pathsToTry := []string{"app" + redirectedPath}
 	bowerComponentsSuffix := "/bower_components/"
 	nodeModulesPrefix := "/node_modules/"
 
@@ -463,6 +463,7 @@
 		return
 	}
 	w.Header().Set("Content-Encoding", "gzip")
+	addDevHeaders(w)
 	gzw := newGzipResponseWriter(w)
 	defer gzw.Close()
 	http.DefaultServeMux.ServeHTTP(gzw, r)
diff --git a/polymer-bridges b/polymer-bridges
index b689bef..855f478 160000
--- a/polymer-bridges
+++ b/polymer-bridges
@@ -1 +1 @@
-Subproject commit b689bef776647b4cef3148e538c211331295caf8
+Subproject commit 855f4781b702de120953a64da5c277ea4908deaa
diff --git a/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy b/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy
index 28d5e73..0bd1780 100644
--- a/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy
+++ b/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy
@@ -104,7 +104,7 @@
 
   // RobotoMono fonts are used in styles/fonts.css
   {if $useGoogleFonts}
-    <link rel="preload" href="https://fonts.googleapis.com/css?family=Roboto+Mono:400,500,700|Roboto:400,500,700&display=swap" as="style">
+    <link rel="preload" as="style" href="https://fonts.googleapis.com/css?family=Roboto+Mono:400,500,700|Roboto:400,500,700&display=swap">
   {else}
     // @see https://github.com/w3c/preload/issues/32 regarding crossorigin
     <link rel="preload" href="{$staticResourcePath}/fonts/RobotoMono-Regular.woff2" as="font" type="font/woff2" crossorigin="anonymous">{\n}
@@ -113,9 +113,9 @@
     <link rel="preload" href="{$staticResourcePath}/fonts/Roboto-Regular.woff" as="font" type="font/woff" crossorigin="anonymous">{\n}
     <link rel="preload" href="{$staticResourcePath}/fonts/Roboto-Medium.woff2" as="font" type="font/woff2" crossorigin="anonymous">{\n}
     <link rel="preload" href="{$staticResourcePath}/fonts/Roboto-Medium.woff" as="font" type="font/woff" crossorigin="anonymous">{\n}
+    <link rel="preload" as="style" href="{$staticResourcePath}/styles/fonts.css">{\n}
   {/if}
-  <link rel="stylesheet" href="{$staticResourcePath}/styles/fonts.css">{\n}
-  <link rel="stylesheet" href="{$staticResourcePath}/styles/main.css">{\n}
+  <link rel="preload" as="style" href="{$staticResourcePath}/styles/main.css">{\n}
 
   <script src="{$staticResourcePath}/bower_components/webcomponentsjs/webcomponents-lite.js"></script>{\n}
 
@@ -131,6 +131,14 @@
 
   <link rel="import" href="{$staticResourcePath}/elements/gr-app.html">{\n}
 
+  // Now use preloaded resources
+  {if $useGoogleFonts}
+    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto+Mono:400,500,700|Roboto:400,500,700&display=swap">{\n}
+  {else}
+    <link rel="stylesheet" href="{$staticResourcePath}/styles/fonts.css">{\n}
+  {/if}
+  <link rel="stylesheet" href="{$staticResourcePath}/styles/main.css">{\n}
+
   <body unresolved>{\n}
   <gr-app id="app"></gr-app>{\n}
 {/template}
diff --git a/tools/eclipse/gerrit_daemon.launch b/tools/eclipse/gerrit_daemon.launch
index d00f7bf..4f6c599 100644
--- a/tools/eclipse/gerrit_daemon.launch
+++ b/tools/eclipse/gerrit_daemon.launch
@@ -11,7 +11,7 @@
 </listAttribute>
 <booleanAttribute key="org.eclipse.jdt.launching.ATTR_USE_START_ON_FIRST_THREAD" value="true"/>
 <stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="Main"/>
-<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="daemon --console-log --show-stack-trace -d ${resource_loc:/gerrit}/../gerrit_testsite"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="daemon --console-log --show-stack-trace --dev-cdn http://localhost:8081 -d ${resource_loc:/gerrit}/../gerrit_testsite"/>
 <stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="gerrit"/>
 <stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Dgerrit.plugin-classes=${resource_loc:/gerrit/eclipse-out}/plugins"/>
 </launchConfiguration>