Merge "DeleteMembers: Do not fail if member input contains null or empty string"
diff --git a/.bazelignore b/.bazelignore
index 30f1613..69c04b1 100644
--- a/.bazelignore
+++ b/.bazelignore
@@ -1 +1,2 @@
 eclipse-out
+node_modules
diff --git a/.bazelversion b/.bazelversion
index 9084fa2..26aaba0 100644
--- a/.bazelversion
+++ b/.bazelversion
@@ -1 +1 @@
-1.1.0
+1.2.0
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index a9d922d..0ad6905 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -1670,7 +1670,7 @@
 when handling very big binary files, such as device firmware or
 CD-ROM ISO images.
 +
-Defaults to 25% of the available JVM heap, limited to 2048m.
+Defaults to 25% of the available JVM heap, limited to 2g.
 +
 Common unit suffixes of 'k', 'm', or 'g' are supported.
 
diff --git a/Documentation/dev-bazel.txt b/Documentation/dev-bazel.txt
index 63dabc4..925d399 100644
--- a/Documentation/dev-bazel.txt
+++ b/Documentation/dev-bazel.txt
@@ -10,7 +10,8 @@
 * Python 2 or 3
 * link:https://github.com/nodesource/distributions/blob/master/README.md[Node.js (including npm)]
 * Bower (`sudo npm install -g bower`)
-* link:https://docs.bazel.build/versions/master/install.html[Bazel]
+* link:https://docs.bazel.build/versions/master/install.html[Bazel] directly
+or through link:https://github.com/bazelbuild/bazelisk[Bazelisk]
 * Maven
 * zip, unzip
 * gcc
diff --git a/Documentation/dev-eclipse.txt b/Documentation/dev-eclipse.txt
index a94dd7b..dfe6aa5 100644
--- a/Documentation/dev-eclipse.txt
+++ b/Documentation/dev-eclipse.txt
@@ -62,7 +62,7 @@
 To format source code, Gerrit uses the
 link:https://github.com/google/google-java-format[`google-java-format`]
 tool (version 1.7), which automatically formats code to follow the
-style guide. See link:dev-contributing.html#style[Code Style] for the
+style guide. See link:dev-crafting-changes.html#style[Code Style] for the
 instruction how to set up command line tool that uses this formatter.
 The Eclipse plugin is provided that allows to format with the same
 formatter from within the Eclipse IDE. See
diff --git a/Documentation/dev-intellij.txt b/Documentation/dev-intellij.txt
index 5077079..81790db 100644
--- a/Documentation/dev-intellij.txt
+++ b/Documentation/dev-intellij.txt
@@ -104,7 +104,7 @@
 *Code -> Reformat Code*, keyboard shortcuts, or the commit dialog will use the
 custom style defined by the `google-java-format` plugin.
 
-Please refer to the documentation on the <<dev-contributing#style,code style>>
+Please refer to the documentation on the <<dev-crafting-changes#style,code style>>
 for which version of `google-java-format` is used with Gerrit.
 
 ==== Code style settings
@@ -159,7 +159,7 @@
 plugin in IntelliJ IDEA.
 
 To simplify the creation of commit messages which are compliant with the
-<<dev-contributing#commit-message,Commit Message>> format, do the following:
+<<dev-crafting-changes#commit-message,Commit Message>> format, do the following:
 
 . Go to *File -> Settings -> Version Control -> Commit Dialog*.
 . In the *Commit message inspections*, activate the three inspections:
@@ -171,7 +171,7 @@
 right margin*.
 
 In addition, you should follow the instructions of
-<<dev-contributing#git_commit_settings,this section>> (if you haven't
+<<dev-crafting-changes#git-commit-settings,this section>> (if you haven't
 done so already):
 
 * Install the Git commit message hook for the `Change-Id` line.
diff --git a/Documentation/js_licenses.txt b/Documentation/js_licenses.txt
index 8861266..c2bdfbb3 100644
--- a/Documentation/js_licenses.txt
+++ b/Documentation/js_licenses.txt
@@ -409,7 +409,7 @@
 [[polymer]]
 polymer
 
-* js:font-roboto
+* js:font-roboto-local
 * js:iron-a11y-announcer
 * js:iron-a11y-keys-behavior
 * js:iron-autogrow-textarea
diff --git a/Documentation/licenses.txt b/Documentation/licenses.txt
index 394886d..949298f 100644
--- a/Documentation/licenses.txt
+++ b/Documentation/licenses.txt
@@ -2666,7 +2666,7 @@
 [[polymer]]
 polymer
 
-* js:font-roboto
+* js:font-roboto-local
 * js:iron-a11y-announcer
 * js:iron-a11y-keys-behavior
 * js:iron-autogrow-textarea
diff --git a/Jenkinsfile b/Jenkinsfile
index 257b53c..0193df3 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -154,18 +154,19 @@
 }
 
 def prepareBuildsForMode(buildName, mode="notedb", retryTimes = 1) {
-    def propagate = retryTimes == 1 ? false : true
     return {
         stage("${buildName}/${mode}") {
-            catchError{
-                retry(retryTimes){
-                    def slaveBuild = build job: "${buildName}", parameters: [
+            def slaveBuild = null
+            for (int i = 1; i <= retryTimes; i++) {
+                try {
+                    slaveBuild = build job: "${buildName}", parameters: [
                         string(name: 'REFSPEC', value: Change.ref),
                         string(name: 'BRANCH', value: Change.sha1),
                         string(name: 'CHANGE_URL', value: Change.url),
                         string(name: 'MODE', value: mode),
                         string(name: 'TARGET_BRANCH', value: Change.branch)
-                    ], propagate: propagate
+                    ], propagate: false
+                } finally {
                     if (buildName == "Gerrit-codestyle"){
                         Builds.codeStyle = new Build(
                             slaveBuild.getAbsoluteUrl(), slaveBuild.getResult())
@@ -173,6 +174,9 @@
                         Builds.verification[mode] = new Build(
                             slaveBuild.getAbsoluteUrl(), slaveBuild.getResult())
                     }
+                    if (slaveBuild.getResult() == "SUCCESS") {
+                        break
+                    }
                 }
             }
         }
diff --git a/java/com/google/gerrit/acceptance/BUILD b/java/com/google/gerrit/acceptance/BUILD
index b68130b..cb3524a 100644
--- a/java/com/google/gerrit/acceptance/BUILD
+++ b/java/com/google/gerrit/acceptance/BUILD
@@ -122,7 +122,6 @@
         "//java/com/google/gerrit/lucene",
         "//java/com/google/gerrit/mail",
         "//java/com/google/gerrit/metrics",
-        "//java/com/google/gerrit/pgm/init",
         "//java/com/google/gerrit/server",
         "//java/com/google/gerrit/server/audit",
         "//java/com/google/gerrit/server/git/receive",
diff --git a/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java b/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
index 864839a..5f412ce 100644
--- a/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
+++ b/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
@@ -26,6 +26,7 @@
 import com.google.common.collect.Lists;
 import com.google.common.collect.Streams;
 import com.google.common.flogger.FluentLogger;
+import com.google.common.io.BaseEncoding;
 import com.google.common.io.CharStreams;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.elasticsearch.ElasticMapping.MappingProperties;
@@ -68,7 +69,6 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.function.Function;
-import org.apache.commons.codec.binary.Base64;
 import org.apache.http.HttpEntity;
 import org.apache.http.HttpStatus;
 import org.apache.http.StatusLine;
@@ -88,7 +88,7 @@
   protected static final String SETTINGS = "settings";
 
   protected static byte[] decodeBase64(String base64String) {
-    return Base64.decodeBase64(base64String);
+    return BaseEncoding.base64().decode(base64String);
   }
 
   protected static <T> List<T> decodeProtos(
@@ -268,7 +268,7 @@
         } else if (type == FieldType.TIMESTAMP) {
           rawFields.put(element.getKey(), new Timestamp(inner.getAsLong()));
         } else if (type == FieldType.STORED_ONLY) {
-          rawFields.put(element.getKey(), Base64.decodeBase64(inner.getAsString()));
+          rawFields.put(element.getKey(), decodeBase64(inner.getAsString()));
         } else {
           throw FieldType.badFieldType(type);
         }
diff --git a/java/com/google/gerrit/elasticsearch/BUILD b/java/com/google/gerrit/elasticsearch/BUILD
index edbd82c..8bab80b 100644
--- a/java/com/google/gerrit/elasticsearch/BUILD
+++ b/java/com/google/gerrit/elasticsearch/BUILD
@@ -19,7 +19,6 @@
         "//lib:guava",
         "//lib:jgit",
         "//lib:protobuf",
-        "//lib/commons:codec",
         "//lib/commons:lang",
         "//lib/elasticsearch-rest-client",
         "//lib/flogger:api",
diff --git a/java/com/google/gerrit/httpd/BUILD b/java/com/google/gerrit/httpd/BUILD
index bcb2a2a..d6a43b91 100644
--- a/java/com/google/gerrit/httpd/BUILD
+++ b/java/com/google/gerrit/httpd/BUILD
@@ -38,7 +38,6 @@
         "//lib:soy",
         "//lib/auto:auto-value",
         "//lib/auto:auto-value-annotations",
-        "//lib/commons:codec",
         "//lib/commons:lang",
         "//lib/flogger:api",
         "//lib/guice",
diff --git a/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java b/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java
index 88a3f0a..d43fcc7 100644
--- a/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java
+++ b/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java
@@ -20,6 +20,7 @@
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Strings;
 import com.google.common.flogger.FluentLogger;
+import com.google.common.io.BaseEncoding;
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.extensions.client.GitBasicAuthPolicy;
 import com.google.gerrit.extensions.registration.DynamicItem;
@@ -48,7 +49,6 @@
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpServletResponseWrapper;
-import org.apache.commons.codec.binary.Base64;
 
 /**
  * Authenticates the current user by HTTP basic authentication.
@@ -110,7 +110,7 @@
       return true;
     }
 
-    final byte[] decoded = Base64.decodeBase64(hdr.substring(LIT_BASIC.length()));
+    final byte[] decoded = BaseEncoding.base64().decode(hdr.substring(LIT_BASIC.length()));
     String usernamePassword = new String(decoded, encoding(req));
     int splitPos = usernamePassword.indexOf(':');
     if (splitPos < 1) {
diff --git a/java/com/google/gerrit/httpd/ProjectOAuthFilter.java b/java/com/google/gerrit/httpd/ProjectOAuthFilter.java
index 4b5742d..693232f 100644
--- a/java/com/google/gerrit/httpd/ProjectOAuthFilter.java
+++ b/java/com/google/gerrit/httpd/ProjectOAuthFilter.java
@@ -22,6 +22,7 @@
 import com.google.common.base.Strings;
 import com.google.common.collect.Iterables;
 import com.google.common.flogger.FluentLogger;
+import com.google.common.io.BaseEncoding;
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.extensions.auth.oauth.OAuthLoginProvider;
 import com.google.gerrit.extensions.registration.DynamicItem;
@@ -53,7 +54,6 @@
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpServletResponseWrapper;
-import org.apache.commons.codec.binary.Base64;
 import org.eclipse.jgit.lib.Config;
 
 /**
@@ -225,7 +225,7 @@
 
   private AuthInfo extractAuthInfo(String hdr, String encoding)
       throws UnsupportedEncodingException {
-    byte[] decoded = Base64.decodeBase64(hdr.substring(BASIC.length()));
+    byte[] decoded = BaseEncoding.base64().decode(hdr.substring(BASIC.length()));
     String usernamePassword = new String(decoded, encoding);
     int splitPos = usernamePassword.indexOf(':');
     if (splitPos < 1 || splitPos == usernamePassword.length() - 1) {
diff --git a/java/com/google/gerrit/httpd/RemoteUserUtil.java b/java/com/google/gerrit/httpd/RemoteUserUtil.java
index a02b5a0..84954dc 100644
--- a/java/com/google/gerrit/httpd/RemoteUserUtil.java
+++ b/java/com/google/gerrit/httpd/RemoteUserUtil.java
@@ -18,8 +18,8 @@
 import static com.google.common.net.HttpHeaders.AUTHORIZATION;
 import static java.nio.charset.StandardCharsets.UTF_8;
 
+import com.google.common.io.BaseEncoding;
 import javax.servlet.http.HttpServletRequest;
-import org.eclipse.jgit.util.Base64;
 
 public class RemoteUserUtil {
   /**
@@ -70,7 +70,7 @@
 
     } else if (auth.startsWith("Basic ")) {
       auth = auth.substring("Basic ".length());
-      auth = new String(Base64.decode(auth), UTF_8);
+      auth = new String(BaseEncoding.base64().decode(auth), UTF_8);
       final int c = auth.indexOf(':');
       return c > 0 ? auth.substring(0, c) : null;
 
diff --git a/java/com/google/gerrit/httpd/auth/oauth/BUILD b/java/com/google/gerrit/httpd/auth/oauth/BUILD
index dd4549e..11c9295 100644
--- a/java/com/google/gerrit/httpd/auth/oauth/BUILD
+++ b/java/com/google/gerrit/httpd/auth/oauth/BUILD
@@ -16,7 +16,6 @@
         "//lib:guava",
         "//lib:jgit",
         "//lib:servlet-api",
-        "//lib/commons:codec",
         "//lib/flogger:api",
         "//lib/guice",
         "//lib/guice:guice-servlet",
diff --git a/java/com/google/gerrit/httpd/auth/oauth/OAuthSession.java b/java/com/google/gerrit/httpd/auth/oauth/OAuthSession.java
index 84dee6e..c7b65d0 100644
--- a/java/com/google/gerrit/httpd/auth/oauth/OAuthSession.java
+++ b/java/com/google/gerrit/httpd/auth/oauth/OAuthSession.java
@@ -19,6 +19,7 @@
 import com.google.common.base.CharMatcher;
 import com.google.common.base.Strings;
 import com.google.common.flogger.FluentLogger;
+import com.google.common.io.BaseEncoding;
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider;
 import com.google.gerrit.extensions.auth.oauth.OAuthToken;
@@ -45,7 +46,6 @@
 import javax.servlet.ServletRequest;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-import org.apache.commons.codec.binary.Base64;
 import org.eclipse.jgit.errors.ConfigInvalidException;
 
 @SessionScoped
@@ -244,7 +244,7 @@
   private static String generateRandomState() {
     byte[] state = new byte[32];
     randomState.nextBytes(state);
-    return Base64.encodeBase64URLSafeString(state);
+    return BaseEncoding.base64Url().encode(state);
   }
 
   @Override
diff --git a/java/com/google/gerrit/httpd/auth/openid/BUILD b/java/com/google/gerrit/httpd/auth/openid/BUILD
index 94f436b..29841aa 100644
--- a/java/com/google/gerrit/httpd/auth/openid/BUILD
+++ b/java/com/google/gerrit/httpd/auth/openid/BUILD
@@ -17,7 +17,6 @@
         "//java/com/google/gerrit/server",
         "//lib:guava",
         "//lib:servlet-api",
-        "//lib/commons:codec",
         "//lib/flogger:api",
         "//lib/guice",
         "//lib/guice:guice-servlet",
diff --git a/java/com/google/gerrit/httpd/auth/openid/OAuthSessionOverOpenID.java b/java/com/google/gerrit/httpd/auth/openid/OAuthSessionOverOpenID.java
index bbdb0c4..f9e6286 100644
--- a/java/com/google/gerrit/httpd/auth/openid/OAuthSessionOverOpenID.java
+++ b/java/com/google/gerrit/httpd/auth/openid/OAuthSessionOverOpenID.java
@@ -18,6 +18,7 @@
 
 import com.google.common.base.Strings;
 import com.google.common.flogger.FluentLogger;
+import com.google.common.io.BaseEncoding;
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.extensions.auth.oauth.OAuthServiceProvider;
 import com.google.gerrit.extensions.auth.oauth.OAuthToken;
@@ -43,7 +44,6 @@
 import javax.servlet.ServletRequest;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
-import org.apache.commons.codec.binary.Base64;
 import org.eclipse.jgit.errors.ConfigInvalidException;
 
 /** OAuth protocol implementation */
@@ -229,7 +229,7 @@
   private static String generateRandomState() {
     byte[] state = new byte[32];
     randomState.nextBytes(state);
-    return Base64.encodeBase64URLSafeString(state);
+    return BaseEncoding.base64Url().encode(state);
   }
 
   @Override
diff --git a/java/com/google/gerrit/httpd/init/BUILD b/java/com/google/gerrit/httpd/init/BUILD
index 149dee8..222041a 100644
--- a/java/com/google/gerrit/httpd/init/BUILD
+++ b/java/com/google/gerrit/httpd/init/BUILD
@@ -6,7 +6,6 @@
     visibility = ["//visibility:public"],
     deps = [
         "//java/com/google/gerrit/elasticsearch",
-        "//java/com/google/gerrit/entities",
         "//java/com/google/gerrit/extensions:api",
         "//java/com/google/gerrit/gpg",
         "//java/com/google/gerrit/httpd",
diff --git a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
index 7700740..1514df8 100644
--- a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
+++ b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
@@ -173,7 +173,6 @@
 import java.util.Set;
 import java.util.TreeMap;
 import java.util.concurrent.TimeUnit;
-import java.util.function.Predicate;
 import java.util.regex.Pattern;
 import java.util.stream.Stream;
 import java.util.zip.GZIPOutputStream;
@@ -704,8 +703,7 @@
         traceContext,
         globals.metrics.view(restCollection.getClass(), pluginName) + "#parse",
         ActionType.REST_READ_REQUEST,
-        () -> restCollection.parse(parentResource, id),
-        noRetry());
+        () -> restCollection.parse(parentResource, id));
   }
 
   private Response<?> invokeRestReadViewWithRetry(
@@ -720,8 +718,7 @@
         traceContext,
         getViewName(viewData),
         ActionType.REST_READ_REQUEST,
-        () -> view.apply(rsrc),
-        noRetry());
+        () -> view.apply(rsrc));
   }
 
   private Response<?> invokeRestModifyViewWithRetry(
@@ -737,8 +734,7 @@
         traceContext,
         getViewName(viewData),
         ActionType.REST_WRITE_REQUEST,
-        () -> view.apply(rsrc, inputRequestBody),
-        retryOnLockFailure());
+        () -> view.apply(rsrc, inputRequestBody));
   }
 
   private Response<?> invokeRestCollectionCreateViewWithRetry(
@@ -755,8 +751,7 @@
         traceContext,
         getViewName(viewData),
         ActionType.REST_WRITE_REQUEST,
-        () -> view.apply(rsrc, path, inputRequestBody),
-        retryOnLockFailure());
+        () -> view.apply(rsrc, path, inputRequestBody));
   }
 
   private Response<?> invokeRestCollectionDeleteMissingViewWithRetry(
@@ -773,8 +768,7 @@
         traceContext,
         getViewName(viewData),
         ActionType.REST_WRITE_REQUEST,
-        () -> view.apply(rsrc, path, inputRequestBody),
-        retryOnLockFailure());
+        () -> view.apply(rsrc, path, inputRequestBody));
   }
 
   private Response<?> invokeRestCollectionModifyViewWithRetry(
@@ -790,8 +784,7 @@
         traceContext,
         getViewName(viewData),
         ActionType.REST_WRITE_REQUEST,
-        () -> view.apply(rsrc, inputRequestBody),
-        retryOnLockFailure());
+        () -> view.apply(rsrc, inputRequestBody));
   }
 
   private <T> T invokeRestEndpointWithRetry(
@@ -799,8 +792,7 @@
       TraceContext traceContext,
       String caller,
       ActionType actionType,
-      Action<T> action,
-      Predicate<Throwable> retryExceptionPredicate)
+      Action<T> action)
       throws Exception {
     RetryHelper.Options.Builder retryOptionsBuilder = RetryHelper.options().caller(caller);
     if (!traceContext.isTracing()) {
@@ -817,8 +809,11 @@
                   });
     }
     try {
+      // ExceptionHookImpl controls on which exceptions we retry.
+      // The passed in exceptionPredicate allows to define additional exceptions on which retry
+      // should happen, but here we have none (hence pass in "t -> false" as exceptionPredicate).
       return globals.retryHelper.execute(
-          actionType, action, retryOptionsBuilder.build(), retryExceptionPredicate);
+          actionType, action, retryOptionsBuilder.build(), t -> false);
     } finally {
       // If auto-tracing got triggered due to a non-recoverable failure, also trace the rest of
       // this request. This means logging is forced for all further log statements and the logs are
@@ -827,19 +822,6 @@
     }
   }
 
-  private static Predicate<Throwable> noRetry() {
-    return t -> false;
-  }
-
-  private static Predicate<Throwable> retryOnLockFailure() {
-    return t -> {
-      if (t instanceof UpdateException) {
-        t = t.getCause();
-      }
-      return t instanceof LockFailureException;
-    };
-  }
-
   private String getViewName(ViewData viewData) {
     return viewData != null && viewData.view != null ? globals.metrics.view(viewData) : "_unknown";
   }
diff --git a/java/com/google/gerrit/server/BUILD b/java/com/google/gerrit/server/BUILD
index dbaf9c3..979ab6f 100644
--- a/java/com/google/gerrit/server/BUILD
+++ b/java/com/google/gerrit/server/BUILD
@@ -107,7 +107,6 @@
         "//lib/auto:auto-value-annotations",
         "//lib/bouncycastle:bcpkix-neverlink",
         "//lib/bouncycastle:bcprov-neverlink",
-        "//lib/commons:codec",
         "//lib/commons:compress",
         "//lib/commons:dbcp",
         "//lib/commons:lang",
diff --git a/java/com/google/gerrit/server/ExceptionHookImpl.java b/java/com/google/gerrit/server/ExceptionHookImpl.java
new file mode 100644
index 0000000..b5edb24
--- /dev/null
+++ b/java/com/google/gerrit/server/ExceptionHookImpl.java
@@ -0,0 +1,47 @@
+// Copyright (C) 2019 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.exceptions.StorageException;
+import com.google.gerrit.git.LockFailureException;
+import com.google.gerrit.server.update.UpdateException;
+import java.util.Optional;
+import org.eclipse.jgit.lib.RefUpdate;
+
+/**
+ * Class to detect and handle exceptions that are caused by temporary errors, and hence should cause
+ * a retry of the failed operation.
+ */
+public class ExceptionHookImpl implements ExceptionHook {
+  @Override
+  public boolean shouldRetry(Throwable throwable) {
+    return isLockFailure(throwable);
+  }
+
+  @Override
+  public Optional<String> formatCause(Throwable throwable) {
+    if (isLockFailure(throwable)) {
+      return Optional.of(RefUpdate.Result.LOCK_FAILURE.name());
+    }
+    return Optional.empty();
+  }
+
+  private static boolean isLockFailure(Throwable throwable) {
+    if (throwable instanceof UpdateException || throwable instanceof StorageException) {
+      throwable = throwable.getCause();
+    }
+    return throwable instanceof LockFailureException;
+  }
+}
diff --git a/java/com/google/gerrit/server/PublishCommentUtil.java b/java/com/google/gerrit/server/PublishCommentUtil.java
index c446c92..f1c2f82 100644
--- a/java/com/google/gerrit/server/PublishCommentUtil.java
+++ b/java/com/google/gerrit/server/PublishCommentUtil.java
@@ -125,8 +125,8 @@
     ImmutableList.Builder<CommentValidationFailure> commentValidationFailures =
         new ImmutableList.Builder<>();
     commentValidators.runEach(
-        listener ->
-            commentValidationFailures.addAll(listener.validateComments(commentsForValidation)));
+        validator ->
+            commentValidationFailures.addAll(validator.validateComments(commentsForValidation)));
     return commentValidationFailures.build();
   }
 }
diff --git a/java/com/google/gerrit/server/audit/BUILD b/java/com/google/gerrit/server/audit/BUILD
index 95929d3..a7ecde4 100644
--- a/java/com/google/gerrit/server/audit/BUILD
+++ b/java/com/google/gerrit/server/audit/BUILD
@@ -57,7 +57,6 @@
         "//lib/auto:auto-value-annotations",
         "//lib/bouncycastle:bcpkix-neverlink",
         "//lib/bouncycastle:bcprov-neverlink",
-        "//lib/commons:codec",
         "//lib/commons:compress",
         "//lib/commons:dbcp",
         "//lib/commons:lang",
diff --git a/java/com/google/gerrit/server/config/GerritGlobalModule.java b/java/com/google/gerrit/server/config/GerritGlobalModule.java
index 3b9c40e..2a0466f 100644
--- a/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -79,6 +79,7 @@
 import com.google.gerrit.server.CreateGroupPermissionSyncer;
 import com.google.gerrit.server.DynamicOptions;
 import com.google.gerrit.server.ExceptionHook;
+import com.google.gerrit.server.ExceptionHookImpl;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.RequestListener;
 import com.google.gerrit.server.TraceRequestListener;
@@ -393,6 +394,7 @@
     DynamicSet.bind(binder(), RequestListener.class).to(TraceRequestListener.class);
     DynamicSet.setOf(binder(), ChangeETagComputation.class);
     DynamicSet.setOf(binder(), ExceptionHook.class);
+    DynamicSet.bind(binder(), ExceptionHook.class).to(ExceptionHookImpl.class);
     DynamicSet.setOf(binder(), MailSoyTemplateProvider.class);
 
     DynamicMap.mapOf(binder(), MailFilter.class);
diff --git a/java/com/google/gerrit/server/mail/SignedToken.java b/java/com/google/gerrit/server/mail/SignedToken.java
index 436b854..ba064df 100644
--- a/java/com/google/gerrit/server/mail/SignedToken.java
+++ b/java/com/google/gerrit/server/mail/SignedToken.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.mail;
 
+import com.google.common.io.BaseEncoding;
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
 import java.security.SecureRandom;
@@ -21,7 +22,6 @@
 import javax.crypto.Mac;
 import javax.crypto.ShortBufferException;
 import javax.crypto.spec.SecretKeySpec;
-import org.apache.commons.codec.binary.Base64;
 
 /**
  * Utility function to compute and verify XSRF tokens.
@@ -164,11 +164,11 @@
   }
 
   private static byte[] decodeBase64(final String s) {
-    return Base64.decodeBase64(toBytes(s));
+    return BaseEncoding.base64().decode(s);
   }
 
   private static String encodeBase64(final byte[] buf) {
-    return toString(Base64.encodeBase64(buf));
+    return BaseEncoding.base64().encode(buf);
   }
 
   private static void encodeInt(final byte[] buf, final int o, final int v) {
@@ -202,12 +202,4 @@
     }
     return r;
   }
-
-  private static String toString(final byte[] b) {
-    final StringBuilder r = new StringBuilder(b.length);
-    for (int i = 0; i < b.length; i++) {
-      r.append((char) b[i]);
-    }
-    return r.toString();
-  }
 }
diff --git a/java/com/google/gerrit/server/mail/SignedTokenEmailTokenVerifier.java b/java/com/google/gerrit/server/mail/SignedTokenEmailTokenVerifier.java
index 8b5cc92..8ae06f8 100644
--- a/java/com/google/gerrit/server/mail/SignedTokenEmailTokenVerifier.java
+++ b/java/com/google/gerrit/server/mail/SignedTokenEmailTokenVerifier.java
@@ -17,6 +17,7 @@
 import static com.google.common.base.Preconditions.checkState;
 import static java.nio.charset.StandardCharsets.UTF_8;
 
+import com.google.common.io.BaseEncoding;
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.server.config.AuthConfig;
 import com.google.gerrit.server.mail.send.RegisterNewEmailSender;
@@ -25,7 +26,6 @@
 import com.google.inject.Singleton;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
-import org.eclipse.jgit.util.Base64;
 
 /** Verifies the token sent by {@link RegisterNewEmailSender}. */
 @Singleton
@@ -50,7 +50,7 @@
     try {
       String payload = String.format("%s:%s", accountId, emailAddress);
       byte[] utf8 = payload.getBytes(UTF_8);
-      String base64 = Base64.encodeBytes(utf8);
+      String base64 = BaseEncoding.base64().encode(utf8);
       return emailRegistrationToken.newToken(base64);
     } catch (XsrfException e) {
       throw new IllegalArgumentException(e);
@@ -70,7 +70,7 @@
       throw new InvalidTokenException();
     }
 
-    String payload = new String(Base64.decode(token.getData()), UTF_8);
+    String payload = new String(BaseEncoding.base64().decode(token.getData()), UTF_8);
     Matcher matcher = Pattern.compile("^([0-9]+):(.+@.+)$").matcher(payload);
     if (!matcher.matches()) {
       throw new InvalidTokenException();
diff --git a/java/com/google/gerrit/server/mail/send/FromAddressGeneratorProvider.java b/java/com/google/gerrit/server/mail/send/FromAddressGeneratorProvider.java
index bdfe2e8..6a34786 100644
--- a/java/com/google/gerrit/server/mail/send/FromAddressGeneratorProvider.java
+++ b/java/com/google/gerrit/server/mail/send/FromAddressGeneratorProvider.java
@@ -16,6 +16,7 @@
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 
+import com.google.common.io.BaseEncoding;
 import com.google.gerrit.common.data.ParameterizedString;
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.mail.Address;
@@ -32,7 +33,6 @@
 import java.security.NoSuchAlgorithmException;
 import java.util.Optional;
 import java.util.regex.Pattern;
-import org.apache.commons.codec.binary.Base64;
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.PersonIdent;
 
@@ -232,7 +232,7 @@
     try {
       MessageDigest hash = MessageDigest.getInstance("MD5");
       byte[] bytes = hash.digest(data.getBytes(UTF_8));
-      return Base64.encodeBase64URLSafeString(bytes);
+      return BaseEncoding.base64Url().encode(bytes);
     } catch (NoSuchAlgorithmException e) {
       throw new RuntimeException("No MD5 available", e);
     }
diff --git a/java/com/google/gerrit/server/notedb/ChangeDraftUpdate.java b/java/com/google/gerrit/server/notedb/ChangeDraftUpdate.java
index 7ca0b86..72a460c 100644
--- a/java/com/google/gerrit/server/notedb/ChangeDraftUpdate.java
+++ b/java/com/google/gerrit/server/notedb/ChangeDraftUpdate.java
@@ -20,7 +20,6 @@
 
 import com.google.auto.value.AutoValue;
 import com.google.common.collect.Sets;
-import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.entities.Change;
 import com.google.gerrit.entities.Comment;
@@ -56,8 +55,6 @@
  * <p>This class is not thread safe.
  */
 public class ChangeDraftUpdate extends AbstractChangeUpdate {
-  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
-
   public interface Factory {
     ChangeDraftUpdate create(
         ChangeNotes notes,
@@ -209,7 +206,6 @@
     // keyed by commit ID.
     Map<ObjectId, RevisionNoteBuilder> builders = cache.getBuilders();
     boolean touchedAnyRevs = false;
-    boolean hasComments = false;
     for (Map.Entry<ObjectId, RevisionNoteBuilder> e : builders.entrySet()) {
       updatedCommits.add(e.getKey());
       ObjectId id = e.getKey();
@@ -220,7 +216,6 @@
       if (data.length == 0) {
         rnm.noteMap.remove(id);
       } else {
-        hasComments = true;
         ObjectId dataBlob = ins.insert(OBJ_BLOB, data);
         rnm.noteMap.set(id, dataBlob);
       }
diff --git a/java/com/google/gerrit/server/restapi/BUILD b/java/com/google/gerrit/server/restapi/BUILD
index fd341e9..720e7d2 100644
--- a/java/com/google/gerrit/server/restapi/BUILD
+++ b/java/com/google/gerrit/server/restapi/BUILD
@@ -34,7 +34,6 @@
         "//lib:servlet-api",
         "//lib/auto:auto-value",
         "//lib/auto:auto-value-annotations",
-        "//lib/commons:codec",
         "//lib/commons:compress",
         "//lib/commons:lang",
         "//lib/flogger:api",
diff --git a/java/com/google/gerrit/server/restapi/account/PutHttpPassword.java b/java/com/google/gerrit/server/restapi/account/PutHttpPassword.java
index 7b89b9c..36a0c71 100644
--- a/java/com/google/gerrit/server/restapi/account/PutHttpPassword.java
+++ b/java/com/google/gerrit/server/restapi/account/PutHttpPassword.java
@@ -15,9 +15,11 @@
 package com.google.gerrit.server.restapi.account;
 
 import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_USERNAME;
+import static java.nio.charset.StandardCharsets.UTF_8;
 
 import com.google.common.base.Strings;
 import com.google.common.flogger.FluentLogger;
+import com.google.common.io.BaseEncoding;
 import com.google.gerrit.common.UsedAt;
 import com.google.gerrit.exceptions.EmailException;
 import com.google.gerrit.extensions.common.HttpPasswordInput;
@@ -43,7 +45,6 @@
 import java.security.NoSuchAlgorithmException;
 import java.security.SecureRandom;
 import java.util.Optional;
-import org.apache.commons.codec.binary.Base64;
 import org.eclipse.jgit.errors.ConfigInvalidException;
 
 public class PutHttpPassword implements RestModifyView<AccountResource, HttpPasswordInput> {
@@ -142,7 +143,7 @@
     byte[] rand = new byte[LEN];
     rng.nextBytes(rand);
 
-    byte[] enc = Base64.encodeBase64(rand, false);
+    byte[] enc = BaseEncoding.base64().encode(rand).getBytes(UTF_8);
     StringBuilder r = new StringBuilder(enc.length);
     for (int i = 0; i < enc.length; i++) {
       if (enc[i] == '=') {
diff --git a/java/com/google/gerrit/server/restapi/group/GroupsCollection.java b/java/com/google/gerrit/server/restapi/group/GroupsCollection.java
index b92a464..65a7f4f 100644
--- a/java/com/google/gerrit/server/restapi/group/GroupsCollection.java
+++ b/java/com/google/gerrit/server/restapi/group/GroupsCollection.java
@@ -14,10 +14,13 @@
 
 package com.google.gerrit.server.restapi.group;
 
+import com.google.common.collect.ListMultimap;
 import com.google.gerrit.common.data.GroupDescription;
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.NeedsParams;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.RestCollection;
 import com.google.gerrit.extensions.restapi.RestView;
@@ -30,21 +33,27 @@
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
-public class GroupsCollection implements RestCollection<TopLevelResource, GroupResource> {
+public class GroupsCollection
+    implements RestCollection<TopLevelResource, GroupResource>, NeedsParams {
   private final DynamicMap<RestView<GroupResource>> views;
+  private final Provider<ListGroups> list;
   private final Provider<QueryGroups> queryGroups;
   private final GroupControl.Factory groupControlFactory;
   private final GroupResolver groupResolver;
   private final Provider<CurrentUser> self;
 
+  private boolean hasQuery;
+
   @Inject
   public GroupsCollection(
       DynamicMap<RestView<GroupResource>> views,
+      Provider<ListGroups> list,
       Provider<QueryGroups> queryGroups,
       GroupControl.Factory groupControlFactory,
       GroupResolver groupResolver,
       Provider<CurrentUser> self) {
     this.views = views;
+    this.list = list;
     this.queryGroups = queryGroups;
     this.groupControlFactory = groupControlFactory;
     this.groupResolver = groupResolver;
@@ -52,6 +61,11 @@
   }
 
   @Override
+  public void setParams(ListMultimap<String, String> params) throws BadRequestException {
+    this.hasQuery = params.containsKey("query");
+  }
+
+  @Override
   public RestView<TopLevelResource> list() throws ResourceNotFoundException, AuthException {
     final CurrentUser user = self.get();
     if (user instanceof AnonymousUser) {
@@ -59,7 +73,12 @@
     } else if (!(user.isIdentifiedUser())) {
       throw new ResourceNotFoundException();
     }
-    return queryGroups.get();
+
+    if (hasQuery) {
+      return queryGroups.get();
+    }
+
+    return list.get();
   }
 
   @Override
diff --git a/java/com/google/gerrit/server/update/RetryHelper.java b/java/com/google/gerrit/server/update/RetryHelper.java
index f8cf4cf..6f8ef12 100644
--- a/java/com/google/gerrit/server/update/RetryHelper.java
+++ b/java/com/google/gerrit/server/update/RetryHelper.java
@@ -34,7 +34,6 @@
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.exceptions.StorageException;
 import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.git.LockFailureException;
 import com.google.gerrit.metrics.Counter1;
 import com.google.gerrit.metrics.Counter2;
 import com.google.gerrit.metrics.Counter3;
@@ -57,7 +56,6 @@
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.RefUpdate;
 
 @Singleton
 public class RetryHelper {
@@ -284,15 +282,7 @@
       throws RestApiException, UpdateException {
     try {
       return execute(
-          ActionType.CHANGE_UPDATE,
-          () -> changeAction.call(updateFactory),
-          opts,
-          t -> {
-            if (t instanceof UpdateException || t instanceof StorageException) {
-              t = t.getCause();
-            }
-            return t instanceof LockFailureException;
-          });
+          ActionType.CHANGE_UPDATE, () -> changeAction.call(updateFactory), opts, t -> false);
     } catch (Throwable t) {
       Throwables.throwIfUnchecked(t);
       Throwables.throwIfInstanceOf(t, UpdateException.class);
@@ -390,9 +380,6 @@
       return formattedCause.get();
     }
 
-    if (t instanceof LockFailureException) {
-      return RefUpdate.Result.LOCK_FAILURE.name();
-    }
     return t.getClass().getSimpleName();
   }
 
diff --git a/java/com/google/gerrit/sshd/BUILD b/java/com/google/gerrit/sshd/BUILD
index bc9a0ee..f567a3a 100644
--- a/java/com/google/gerrit/sshd/BUILD
+++ b/java/com/google/gerrit/sshd/BUILD
@@ -32,7 +32,6 @@
         "//lib/auto:auto-value",
         "//lib/auto:auto-value-annotations",
         "//lib/bouncycastle:bcprov-neverlink",
-        "//lib/commons:codec",
         "//lib/dropwizard:dropwizard-core",
         "//lib/flogger:api",
         "//lib/guice",
diff --git a/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java b/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java
index 6c0f3af..916775d 100644
--- a/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java
+++ b/java/com/google/gerrit/sshd/DatabasePubKeyAuth.java
@@ -19,6 +19,7 @@
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 import com.google.common.flogger.FluentLogger;
+import com.google.common.io.BaseEncoding;
 import com.google.gerrit.common.FileUtil;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.PeerDaemonUser;
@@ -39,7 +40,6 @@
 import java.util.HashSet;
 import java.util.Locale;
 import java.util.Set;
-import org.apache.commons.codec.binary.Base64;
 import org.apache.sshd.common.SshException;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
@@ -198,7 +198,8 @@
           }
 
           try {
-            byte[] bin = Base64.decodeBase64(line.getBytes(ISO_8859_1));
+            byte[] bin =
+                BaseEncoding.base64().decode(new String(line.getBytes(ISO_8859_1), ISO_8859_1));
             keys.add(new ByteArrayBuffer(bin).getRawPublicKey());
           } catch (RuntimeException | SshException e) {
             logBadKey(path, line, e);
diff --git a/java/com/google/gerrit/sshd/SshUtil.java b/java/com/google/gerrit/sshd/SshUtil.java
index 39366f0..eac9737 100644
--- a/java/com/google/gerrit/sshd/SshUtil.java
+++ b/java/com/google/gerrit/sshd/SshUtil.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.sshd;
 
+import com.google.common.io.BaseEncoding;
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
@@ -28,12 +29,10 @@
 import java.security.interfaces.DSAPublicKey;
 import java.security.interfaces.RSAPublicKey;
 import java.security.spec.InvalidKeySpecException;
-import org.apache.commons.codec.binary.Base64;
 import org.apache.sshd.common.SshException;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
 import org.apache.sshd.server.session.ServerSession;
-import org.eclipse.jgit.lib.Constants;
 
 /** Utilities to support SSH operations. */
 public class SshUtil {
@@ -53,7 +52,7 @@
       if (s == null) {
         throw new InvalidKeySpecException("No key string");
       }
-      final byte[] bin = Base64.decodeBase64(Constants.encodeASCII(s));
+      final byte[] bin = BaseEncoding.base64().decode(s);
       return new ByteArrayBuffer(bin).getRawPublicKey();
     } catch (RuntimeException | SshException e) {
       throw new InvalidKeySpecException("Cannot parse key", e);
@@ -91,8 +90,7 @@
       }
 
       final PublicKey key =
-          new ByteArrayBuffer(Base64.decodeBase64(Constants.encodeASCII(strBuf.toString())))
-              .getRawPublicKey();
+          new ByteArrayBuffer(BaseEncoding.base64().decode(strBuf.toString())).getRawPublicKey();
       if (key instanceof RSAPublicKey) {
         strBuf.insert(0, KeyPairProvider.SSH_RSA + " ");
 
diff --git a/java/org/apache/commons/net/BUILD b/java/org/apache/commons/net/BUILD
index c322ecd..d83d8ec 100644
--- a/java/org/apache/commons/net/BUILD
+++ b/java/org/apache/commons/net/BUILD
@@ -6,7 +6,7 @@
     visibility = ["//visibility:public"],
     deps = [
         "//java/com/google/gerrit/util/ssl",
-        "//lib/commons:codec",
+        "//lib:guava",
         "//lib/commons:net",
     ],
 )
diff --git a/java/org/apache/commons/net/smtp/AuthSMTPClient.java b/java/org/apache/commons/net/smtp/AuthSMTPClient.java
index 33dd609..85e4dbf 100644
--- a/java/org/apache/commons/net/smtp/AuthSMTPClient.java
+++ b/java/org/apache/commons/net/smtp/AuthSMTPClient.java
@@ -16,6 +16,7 @@
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 
+import com.google.common.io.BaseEncoding;
 import com.google.gerrit.util.ssl.BlindSSLSocketFactory;
 import java.io.BufferedReader;
 import java.io.BufferedWriter;
@@ -33,7 +34,6 @@
 import javax.net.ssl.SSLParameters;
 import javax.net.ssl.SSLSocket;
 import javax.net.ssl.SSLSocketFactory;
-import org.apache.commons.codec.binary.Base64;
 
 public class AuthSMTPClient extends SMTPClient {
   private String authTypes;
@@ -134,7 +134,7 @@
     }
 
     final String enc = getReplyStrings()[0].split(" ", 2)[1];
-    final byte[] nonce = Base64.decodeBase64(enc.getBytes(UTF_8));
+    final byte[] nonce = BaseEncoding.base64().decode(enc);
     final String sec;
     try {
       Mac mac = Mac.getInstance(macName);
@@ -187,6 +187,6 @@
   }
 
   private static String encodeBase64(byte[] data) {
-    return new String(Base64.encodeBase64(data), UTF_8);
+    return BaseEncoding.base64().encode(data);
   }
 }
diff --git a/javatests/com/google/gerrit/acceptance/api/group/BUILD b/javatests/com/google/gerrit/acceptance/api/group/BUILD
index 9e08069..1ba1138 100644
--- a/javatests/com/google/gerrit/acceptance/api/group/BUILD
+++ b/javatests/com/google/gerrit/acceptance/api/group/BUILD
@@ -19,7 +19,6 @@
     name = "util",
     srcs = ["GroupAssert.java"],
     deps = [
-        "//java/com/google/gerrit/entities",
         "//java/com/google/gerrit/extensions:api",
         "//java/com/google/gerrit/server",
         "//lib/truth",
diff --git a/javatests/com/google/gerrit/acceptance/rest/config/ListCachesIT.java b/javatests/com/google/gerrit/acceptance/rest/config/ListCachesIT.java
index ae17be0..8baeffc 100644
--- a/javatests/com/google/gerrit/acceptance/rest/config/ListCachesIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/config/ListCachesIT.java
@@ -18,6 +18,7 @@
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 import com.google.common.collect.Ordering;
+import com.google.common.io.BaseEncoding;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.RestResponse;
 import com.google.gerrit.server.restapi.config.ListCaches.CacheInfo;
@@ -26,7 +27,6 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
-import org.eclipse.jgit.util.Base64;
 import org.junit.Test;
 
 public class ListCachesIT extends AbstractDaemonTest {
@@ -78,7 +78,7 @@
   public void listCacheNamesTextList() throws Exception {
     RestResponse r = adminRestSession.get("/config/server/caches/?format=TEXT_LIST");
     r.assertOK();
-    String result = new String(Base64.decode(r.getEntityContent()), UTF_8.name());
+    String result = new String(BaseEncoding.base64().decode(r.getEntityContent()), UTF_8);
     List<String> list = Arrays.asList(result.split("\n"));
     assertThat(list).contains("accounts");
     assertThat(list).contains("projects");
diff --git a/javatests/com/google/gerrit/acceptance/rest/group/ListGroupsIT.java b/javatests/com/google/gerrit/acceptance/rest/group/ListGroupsIT.java
new file mode 100644
index 0000000..d8132b7
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/rest/group/ListGroupsIT.java
@@ -0,0 +1,37 @@
+// Copyright (C) 2019 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.acceptance.rest.group;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.extensions.common.GroupInfo;
+import com.google.gson.reflect.TypeToken;
+import java.util.Map;
+import org.junit.Test;
+
+public class ListGroupsIT extends AbstractDaemonTest {
+  @Test
+  public void listAllGroups() throws Exception {
+    RestResponse response = adminRestSession.get("/groups/");
+    response.assertOK();
+
+    Map<String, GroupInfo> groupMap =
+        newGson()
+            .fromJson(response.getReader(), new TypeToken<Map<String, GroupInfo>>() {}.getType());
+    assertThat(groupMap.keySet()).containsExactly("Administrators", "Non-Interactive Users");
+  }
+}
diff --git a/javatests/com/google/gerrit/acceptance/rest/revision/RevisionIT.java b/javatests/com/google/gerrit/acceptance/rest/revision/RevisionIT.java
index 220254b..df6a264 100644
--- a/javatests/com/google/gerrit/acceptance/rest/revision/RevisionIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/revision/RevisionIT.java
@@ -19,13 +19,13 @@
 import static com.google.gerrit.acceptance.PushOneCommit.FILE_NAME;
 import static java.nio.charset.StandardCharsets.UTF_8;
 
+import com.google.common.io.BaseEncoding;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.acceptance.RestResponse;
 import com.google.gerrit.extensions.api.changes.ReviewInput;
 import com.google.gerrit.extensions.common.ChangeInfo;
 import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.util.Base64;
 import org.junit.Test;
 
 public class RevisionIT extends AbstractDaemonTest {
@@ -50,7 +50,7 @@
                 + FILE_NAME
                 + "/content?parent=1");
     response.assertOK();
-    assertThat(new String(Base64.decode(response.getEntityContent()), UTF_8))
+    assertThat(new String(BaseEncoding.base64().decode(response.getEntityContent()), UTF_8))
         .isEqualTo(parentContent);
   }
 
diff --git a/javatests/com/google/gerrit/common/BUILD b/javatests/com/google/gerrit/common/BUILD
index 4ec9581..c7b21a3 100644
--- a/javatests/com/google/gerrit/common/BUILD
+++ b/javatests/com/google/gerrit/common/BUILD
@@ -7,7 +7,6 @@
     deps = [
         "//java/com/google/gerrit/common:server",
         "//java/com/google/gerrit/common:version",
-        "//java/com/google/gerrit/entities",
         "//java/com/google/gerrit/launcher",
         "//lib:guava",
         "//lib/auto:auto-value",
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java b/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java
index 73e7eca..c15f1a7 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java
+++ b/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java
@@ -51,7 +51,7 @@
       case V6_7:
         return "blacktop/elasticsearch:6.7.2";
       case V6_8:
-        return "blacktop/elasticsearch:6.8.4";
+        return "blacktop/elasticsearch:6.8.5";
       case V7_0:
         return "blacktop/elasticsearch:7.0.1";
       case V7_1:
diff --git a/javatests/com/google/gerrit/mail/BUILD b/javatests/com/google/gerrit/mail/BUILD
index bd2c478..3d63844 100644
--- a/javatests/com/google/gerrit/mail/BUILD
+++ b/javatests/com/google/gerrit/mail/BUILD
@@ -15,7 +15,6 @@
         "//lib:guava-retrying",
         "//lib:jgit",
         "//lib:jgit-junit",
-        "//lib/commons:codec",
         "//lib/guice",
         "//lib/truth",
         "//lib/truth:truth-java8-extension",
diff --git a/javatests/com/google/gerrit/server/BUILD b/javatests/com/google/gerrit/server/BUILD
index fd6c512..59ed018 100644
--- a/javatests/com/google/gerrit/server/BUILD
+++ b/javatests/com/google/gerrit/server/BUILD
@@ -74,7 +74,6 @@
         "//lib:protobuf",
         "//lib/auto:auto-value",
         "//lib/auto:auto-value-annotations",
-        "//lib/commons:codec",
         "//lib/flogger:api",
         "//lib/guice",
         "//lib/mockito",
diff --git a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index 56b3aea..962b691 100644
--- a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -30,7 +30,6 @@
 import static java.util.concurrent.TimeUnit.MINUTES;
 import static java.util.concurrent.TimeUnit.SECONDS;
 import static java.util.stream.Collectors.toList;
-import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.fail;
 
 import com.google.common.base.MoreObjects;
diff --git a/javatests/com/google/gerrit/server/util/git/BUILD b/javatests/com/google/gerrit/server/util/git/BUILD
index 883898f..b789dff 100644
--- a/javatests/com/google/gerrit/server/util/git/BUILD
+++ b/javatests/com/google/gerrit/server/util/git/BUILD
@@ -18,7 +18,6 @@
         "//lib:protobuf",
         "//lib/auto:auto-value",
         "//lib/auto:auto-value-annotations",
-        "//lib/commons:codec",
         "//lib/guice",
         "//lib/truth",
         "//lib/truth:truth-java8-extension",
diff --git a/lib/js/bower_archives.bzl b/lib/js/bower_archives.bzl
index 34742db..0b2fdef9 100644
--- a/lib/js/bower_archives.bzl
+++ b/lib/js/bower_archives.bzl
@@ -26,10 +26,10 @@
         sha1 = "849ad3ee7c77506548b7b5db603a4e150b9431aa",
     )
     bower_archive(
-        name = "font-roboto",
-        package = "PolymerElements/font-roboto",
+        name = "font-roboto-local",
+        package = "PolymerElements/font-roboto-local",
         version = "1.1.0",
-        sha1 = "ab4218d87b9ce569d6282b01f7642e551879c3d5",
+        sha1 = "de651abf9b1b2d0935f7b264d48131677196412f",
     )
     bower_archive(
         name = "iron-a11y-announcer",
@@ -136,8 +136,10 @@
     bower_archive(
         name = "paper-styles",
         package = "PolymerElements/paper-styles",
-        version = "2.1.0",
-        sha1 = "c143c5491571a6922c06ffe7fdf64ec009ec2eb1",
+        # Basically 2.1.0 but with
+        # https://github.com/PolymerElements/paper-styles/pull/165 applied
+        version = "a6c207e6eee3402fd7a6550e6f9c387ca22ec4c4",
+        sha1 = "6bd17410578b5d4017ccef330393a4b41b1c716e",
     )
     bower_archive(
         name = "shadycss",
diff --git a/lib/js/bower_components.bzl b/lib/js/bower_components.bzl
index 658074e..7fd61c7 100644
--- a/lib/js/bower_components.bzl
+++ b/lib/js/bower_components.bzl
@@ -30,7 +30,7 @@
         seed = True,
     )
     bower_component(
-        name = "font-roboto",
+        name = "font-roboto-local",
         license = "//lib:LICENSE-polymer",
     )
     bower_component(
@@ -283,7 +283,7 @@
         name = "paper-styles",
         license = "//lib:LICENSE-polymer",
         deps = [
-            ":font-roboto",
+            ":font-roboto-local",
             ":iron-flex-layout",
             ":polymer",
         ],
diff --git a/plugins/codemirror-editor b/plugins/codemirror-editor
index 92ce310..9508693 160000
--- a/plugins/codemirror-editor
+++ b/plugins/codemirror-editor
@@ -1 +1 @@
-Subproject commit 92ce310ecf717133601b9e824c38bc5e5eafecba
+Subproject commit 9508693bd0a85560674143256314beac08f2d8ca
diff --git a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.html b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.html
index ffc1b9b..1d6e706 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.html
+++ b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.html
@@ -43,11 +43,6 @@
       .value {
         width: 32em;
       }
-      gr-autocomplete {
-        --gr-autocomplete: {
-          padding: 0 var(--spacing-xs);
-        }
-      }
       .hide {
         display: none;
       }
diff --git a/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.html b/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.html
index 8d03595..b78090c 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.html
+++ b/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog.html
@@ -40,14 +40,7 @@
         width: 20em;
       }
       gr-autocomplete {
-        border: none;
-        --gr-autocomplete: {
-          border: 1px solid var(--border-color);
-          border-radius: var(--border-radius);
-          height: 2em;
-          padding: 0 var(--spacing-xs);
-          width: 20em;
-        }
+        width: 20em;
       }
     </style>
 
diff --git a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.html b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.html
index bc7a109..cf24793 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.html
+++ b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.html
@@ -48,10 +48,6 @@
       }
       gr-autocomplete {
         width: 20em;
-        --gr-autocomplete: {
-          height: 2em;
-          width: 20em;
-        }
       }
       a {
         color: var(--primary-text-color);
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
index a5a5aa5..539b1b6 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
@@ -177,9 +177,10 @@
         <span class="title">Assignee</span>
         <span class="value">
           <gr-account-list
-              max-count="1"
               id="assigneeValue"
               placeholder="Set assignee..."
+              max-count="1"
+              skip-suggest-on-empty
               accounts="{{_assignee}}"
               readonly="[[_computeAssigneeReadOnly(_mutable, change)]]"
               suggestions-provider="[[_getReviewerSuggestionsProvider(change)]]">
diff --git a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.html b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.html
index e20bbd7..4ddc876 100644
--- a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.html
@@ -27,16 +27,13 @@
   <template>
     <style include="shared-styles">
       :host {
-        background-color: var(--dialog-background-color);
         display: block;
+        padding: var(--spacing-m) 0;
       }
       section {
         display: flex;
         padding: var(--spacing-m) var(--spacing-xl);
       }
-      section:not(:first-of-type) {
-        border-top: 1px solid var(--border-color);
-      }
       .flexContainer {
         display: flex;
         justify-content: space-between;
@@ -76,9 +73,9 @@
       }
     </style>
     <section>
-      <span class="title">
+      <h3 class="title">
         Patch set [[patchNum]] of [[_computePatchSetQuantity(change.revisions)]]
-      </span>
+      </h3>
     </section>
     <section class$="[[_computeShowDownloadCommands(_schemes)]]">
       <gr-download-commands
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.html b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.html
index 0cdef8c..1b57ddb 100644
--- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.html
+++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.html
@@ -29,11 +29,9 @@
       }
       gr-autocomplete {
         background-color: var(--view-background-color);
-        border: 1px solid var(--border-color);
         border-radius: var(--border-radius);
         flex: 1;
         outline: none;
-        padding: var(--spacing-xs);
       }
     </style>
     <form>
@@ -45,7 +43,6 @@
           on-commit="_handleInputCommit"
           allow-non-suggested-values
           multi
-          borderless
           threshold="[[_threshold]]"
           tab-complete
           vertical-offset="30"></gr-autocomplete>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-annotation.js b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-annotation.js
index 533eccd..c729bcb 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-annotation.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-annotation.js
@@ -69,7 +69,7 @@
       for (let node of childNodes) {
         const initialNodeLength = this.getLength(node);
         // If the current node is completely before the offset.
-        if (initialNodeLength <= offset) {
+        if (offset > 0 && initialNodeLength <= offset) {
           offset -= initialNodeLength;
           continue;
         }
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-annotation_test.html b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-annotation_test.html
index a032bc2..a2c271b 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-annotation_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-annotation_test.html
@@ -249,6 +249,19 @@
             '0<test-wrapper>1234567890</test-wrapper>123456789');
       });
 
+      test('handles zero-length nodes', () => {
+        const container = document.createElement('div');
+        container.appendChild(document.createTextNode('0123456789'));
+        container.appendChild(document.createElement('span'));
+        container.appendChild(document.createTextNode('0123456789'));
+        GrAnnotation.annotateWithElement(
+            container, 1, 10, {tagName: 'test-wrapper'});
+
+        assert.equal(
+            container.innerHTML,
+            '0<test-wrapper>123456789<span></span>0</test-wrapper>123456789');
+      });
+
       test('sets sanitized attributes', () => {
         const container = document.createElement('div');
         container.textContent = fullText;
diff --git a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.js b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.js
index b0947cd..8ef946c 100644
--- a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.js
+++ b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.js
@@ -100,6 +100,7 @@
     'gr-diff gr-syntax gr-syntax-attribute': true,
     'gr-diff gr-syntax gr-syntax-built_in': true,
     'gr-diff gr-syntax gr-syntax-comment': true,
+    'gr-diff gr-syntax gr-syntax-doctag': true,
     'gr-diff gr-syntax gr-syntax-function': true,
     'gr-diff gr-syntax gr-syntax-keyword': true,
     'gr-diff gr-syntax gr-syntax-link': true,
diff --git a/polygerrit-ui/app/elements/diff/gr-syntax-themes/gr-syntax-theme.html b/polygerrit-ui/app/elements/diff/gr-syntax-themes/gr-syntax-theme.html
index c9f8eb3..e5ae06d 100644
--- a/polygerrit-ui/app/elements/diff/gr-syntax-themes/gr-syntax-theme.html
+++ b/polygerrit-ui/app/elements/diff/gr-syntax-themes/gr-syntax-theme.html
@@ -99,8 +99,11 @@
       .gr-syntax-template-tag {
         color: var(--syntax-template-tag-color);
       }
-      .gr-syntax-param {
-        color: var(--syntax-param-color);
+      .gr-syntax-params {
+        color: var(--syntax-params-color);
+      }
+      .gr-syntax-doctag {
+        font-weight: var(--syntax-doctag-weight);
       }
     </style>
   </template>
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.html b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.html
index 4b9fbac..cb950da 100644
--- a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.html
+++ b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.html
@@ -55,21 +55,13 @@
       gr-dialog .main > iron-input{
         width: 100%;
       }
-      gr-autocomplete {
-        --gr-autocomplete: {
-          border: 1px solid var(--border-color);
-          border-radius: var(--border-radius);
-          height: 2em;
-          padding: 0 var(--spacing-xs);
-        }
-      }
       input {
         border: 1px solid var(--border-color);
         border-radius: var(--border-radius);
-        height: 2em;
         margin: var(--spacing-m) 0;
-        padding: 0 var(--spacing-xs);
+        padding: var(--spacing-s);
         width: 100%;
+        box-sizing: content-box;
       }
       @media screen and (max-width: 50em) {
         gr-dialog {
diff --git a/polygerrit-ui/app/elements/gr-app-element.js b/polygerrit-ui/app/elements/gr-app-element.js
index 4ed0d3a..1e86acc 100644
--- a/polygerrit-ui/app/elements/gr-app-element.js
+++ b/polygerrit-ui/app/elements/gr-app-element.js
@@ -211,7 +211,7 @@
       this.bindShortcut(
           this.Shortcut.TOGGLE_CHANGE_STAR, 's:keyup');
       this.bindShortcut(
-          this.Shortcut.REFRESH_CHANGE_LIST, 'shift+r');
+          this.Shortcut.REFRESH_CHANGE_LIST, 'shift+r:keyup');
       this.bindShortcut(
           this.Shortcut.EDIT_TOPIC, 't');
 
@@ -224,7 +224,7 @@
       this.bindShortcut(
           this.Shortcut.COLLAPSE_ALL_MESSAGES, 'z');
       this.bindShortcut(
-          this.Shortcut.REFRESH_CHANGE, 'shift+r');
+          this.Shortcut.REFRESH_CHANGE, 'shift+r:keyup');
       this.bindShortcut(
           this.Shortcut.UP_TO_DASHBOARD, 'u');
       this.bindShortcut(
diff --git a/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.html b/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.html
index 2027119..22ba457 100644
--- a/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.html
+++ b/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password.html
@@ -18,6 +18,7 @@
 <link rel="import" href="/bower_components/polymer/polymer.html">
 <link rel="import" href="../../../styles/gr-form-styles.html">
 <link rel="import" href="../../shared/gr-button/gr-button.html">
+<link rel="import" href="../../shared/gr-copy-clipboard/gr-copy-clipboard.html">
 <link rel="import" href="../../shared/gr-overlay/gr-overlay.html">
 <link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
 <link rel="import" href="../../../styles/shared-styles.html">
@@ -37,6 +38,9 @@
       #generatedPasswordDisplay {
         margin: var(--spacing-l) 0;
       }
+      #generatedPasswordDisplay .title {
+        width: unset;
+      }
       #generatedPasswordDisplay .value {
         font-family: var(--monospace-font-family);
         font-size: var(--font-size-mono);
@@ -79,6 +83,12 @@
         <section id="generatedPasswordDisplay">
           <span class="title">New Password:</span>
           <span class="value">[[_generatedPassword]]</span>
+          <gr-copy-clipboard
+              has-tooltip
+              button-title="Copy password to clipboard"
+              hide-input
+              text="[[_generatedPassword]]">
+          </gr-copy-clipboard>
         </section>
         <section id="passwordWarning">
           This password will not be displayed again.<br>
diff --git a/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.html b/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.html
index ae656fd..992ea8407 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.html
+++ b/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.html
@@ -40,7 +40,8 @@
         on-commit="_handleInputCommit"
         clear-on-commit
         warn-uncommitted
-        text="{{_inputText}}">
+        text="{{_inputText}}"
+        vertical-offset="24">
     </gr-autocomplete>
   </template>
   <script src="gr-account-entry.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.js b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.js
index 48dddb0..3caec32 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.js
+++ b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.js
@@ -51,16 +51,17 @@
         },
 
         /**
-       * Returns suggestions and convert them to list item
-       * @type {Gerrit.GrSuggestionsProvider}
-       */
+         * Returns suggestions and convert them to list item
+         * @type {Gerrit.GrSuggestionsProvider}
+         */
         suggestionsProvider: {
           type: Object,
         },
 
         /**
-       * Needed for template checking since value is initially set to null.
-       * @type {?Object} */
+         * Needed for template checking since value is initially set to null.
+         * @type {?Object}
+         */
         pendingConfirmation: {
           type: Object,
           value: null,
@@ -71,32 +72,41 @@
           value: false,
         },
         /**
-       * When true, allows for non-suggested inputs to be added.
-       */
+         * When true, allows for non-suggested inputs to be added.
+         */
         allowAnyInput: {
           type: Boolean,
           value: false,
         },
 
         /**
-       * Array of values (groups/accounts) that are removable. When this prop is
-       * undefined, all values are removable.
-       */
+         * Array of values (groups/accounts) that are removable. When this prop is
+         * undefined, all values are removable.
+         */
         removableValues: Array,
         maxCount: {
           type: Number,
           value: 0,
         },
 
-        /** Returns suggestion items
-      * @type {!function(string): Promise<Array<Gerrit.GrSuggestionItem>>}
-      */
+        /**
+         * Returns suggestion items
+         * @type {!function(string): Promise<Array<Gerrit.GrSuggestionItem>>}
+         */
         _querySuggestions: {
           type: Function,
           value() {
             return this._getSuggestions.bind(this);
           },
         },
+
+        /**
+         * Set to true to disable suggestions on empty input.
+         */
+        skipSuggestOnEmpty: {
+          type: Boolean,
+          value: false,
+        },
       };
     }
 
@@ -116,6 +126,9 @@
     }
 
     _getSuggestions(input) {
+      if (this.skipSuggestOnEmpty && !input) {
+        return Promise.resolve([]);
+      }
       const provider = this.suggestionsProvider;
       if (!provider) {
         return Promise.resolve([]);
diff --git a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.html b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.html
index 9521c5f..7fea243 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.html
@@ -382,6 +382,57 @@
       });
     });
 
+    test('suggestion on empty', done => {
+      element.skipSuggestOnEmpty = false;
+      const suggestions = [
+        {
+          email: 'abc@example.com',
+          text: 'abcd',
+        },
+        {
+          email: 'qwe@example.com',
+          text: 'qwer',
+        },
+      ];
+      const getSuggestionsStub =
+          sandbox.stub(suggestionsProvider, 'getSuggestions')
+              .returns(Promise.resolve(suggestions));
+
+      const makeSuggestionItemStub =
+          sandbox.stub(suggestionsProvider, 'makeSuggestionItem', item => item);
+
+      const input = element.$.entry.$.input;
+
+      input.text = '';
+      MockInteractions.focus(input.$.input);
+      input.noDebounce = true;
+      flushAsynchronousOperations();
+      flush(() => {
+        assert.isTrue(getSuggestionsStub.calledOnce);
+        assert.equal(getSuggestionsStub.lastCall.args[0], '');
+        assert.equal(makeSuggestionItemStub.getCalls().length, 2);
+        done();
+      });
+    });
+
+    test('skip suggestion on empty', done => {
+      element.skipSuggestOnEmpty = true;
+      const getSuggestionsStub =
+          sandbox.stub(suggestionsProvider, 'getSuggestions')
+              .returns(Promise.resolve([]));
+
+      const input = element.$.entry.$.input;
+
+      input.text = '';
+      MockInteractions.focus(input.$.input);
+      input.noDebounce = true;
+      flushAsynchronousOperations();
+      flush(() => {
+        assert.isTrue(getSuggestionsStub.notCalled);
+        done();
+      });
+    });
+
     suite('allowAnyInput', () => {
       setup(() => {
         element.allowAnyInput = true;
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.html b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.html
index 64950f2..cf32e28 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.html
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.html
@@ -60,6 +60,7 @@
       .dropdown-content {
         background: var(--dropdown-background-color);
         box-shadow: rgba(0, 0, 0, 0.3) 0 1px 3px;
+        border-radius: var(--border-radius);
         max-height: 50vh;
         overflow: auto;
       }
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.html b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.html
index c9d12ce..e47b661 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.html
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.html
@@ -36,13 +36,14 @@
         margin: 0 var(--spacing-xs);
         vertical-align: top;
       }
-      paper-input:not(.borderless) {
-        border: 1px solid var(--border-color);
+      paper-input.borderless {
+        border: none;
+        padding: 0;
       }
       paper-input {
-        height: var(--line-height-normal);
-        width: 100%;
-        @apply --gr-autocomplete;
+        border: 1px solid var(--border-color);
+        border-radius: var(--border-radius);
+        padding: var(--spacing-s);
         --paper-input-container: {
           padding: 0;
         };
@@ -50,13 +51,25 @@
           font-size: var(--font-size-normal);
           line-height: var(--line-height-normal);
         };
+        /* This is a hack for not being able to set height:0 on the underline
+           of a paper-input 2.2.3 element. All the underline fixes below only
+           actually work in 3.x.x, so the height must be adjusted directly as
+           a workaround until we are on Polymer 3. */
+        height: var(--line-height-normal);
+        --paper-input-container-underline-height: 0;
+        --paper-input-container-underline-wrapper-height: 0;
+        --paper-input-container-underline-focus-height: 0;
+        --paper-input-container-underline-legacy-height: 0;
         --paper-input-container-underline: {
+          height: 0;
           display: none;
         };
         --paper-input-container-underline-focus: {
+          height: 0;
           display: none;
         };
         --paper-input-container-underline-disabled: {
+          height: 0;
           display: none;
         };
       }
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js
index a2c908a..3a60255 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js
@@ -74,9 +74,9 @@
         },
 
         /**
-       * The number of characters that must be typed before suggestions are
-       * made. If threshold is zero, default suggestions are enabled.
-       */
+         * The number of characters that must be typed before suggestions are
+         * made. If threshold is zero, default suggestions are enabled.
+         */
         threshold: {
           type: Number,
           value: 1,
@@ -89,11 +89,15 @@
           type: Boolean,
           value: false,
         },
-        // Vertical offset needed for a 1em font-size with no vertical padding.
-        // Inputs with additional padding will need to increase vertical offset.
+        /**
+         * Vertical offset needed for an element with 20px line-height, 4px
+         * padding and 1px border (30px height total). Plus 1px spacing between
+         * input and dropdown. Inputs with different line-height or padding will
+         * need to tweak vertical offset.
+         */
         verticalOffset: {
           type: Number,
-          value: 20,
+          value: 31,
         },
 
         text: {
@@ -110,10 +114,10 @@
         },
 
         /**
-       * When true, tab key autocompletes but does not fire the commit event.
-       * When false, tab key not caught, and focus is removed from the element.
-       * See Issue 4556, Issue 6645.
-       */
+         * When true, tab key autocompletes but does not fire the commit event.
+         * When false, tab key not caught, and focus is removed from the element.
+         * See Issue 4556, Issue 6645.
+         */
         tabComplete: {
           type: Boolean,
           value: false,
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html
index 68338a4..39329e5 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html
@@ -335,7 +335,7 @@
     });
 
     test('vertical offset overridden by param if it exists', () => {
-      assert.equal(element.$.suggestions.verticalOffset, 20);
+      assert.equal(element.$.suggestions.verticalOffset, 31);
       element.verticalOffset = 30;
       assert.equal(element.$.suggestions.verticalOffset, 30);
     });
diff --git a/polygerrit-ui/app/elements/shared/gr-button/gr-button.js b/polygerrit-ui/app/elements/shared/gr-button/gr-button.js
index 1de704a..8c9ba50 100644
--- a/polygerrit-ui/app/elements/shared/gr-button/gr-button.js
+++ b/polygerrit-ui/app/elements/shared/gr-button/gr-button.js
@@ -91,7 +91,7 @@
 
     _disabledChanged(disabled) {
       if (disabled) {
-        this._enabledTabindex = this.getAttribute('tabindex');
+        this._enabledTabindex = this.getAttribute('tabindex') || '0';
       }
       this.setAttribute('tabindex', disabled ? '-1' : this._enabledTabindex);
       this.updateStyles();
diff --git a/polygerrit-ui/app/elements/shared/gr-button/gr-button_test.html b/polygerrit-ui/app/elements/shared/gr-button/gr-button_test.html
index d8a5443..bcb560a 100644
--- a/polygerrit-ui/app/elements/shared/gr-button/gr-button_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-button/gr-button_test.html
@@ -68,6 +68,21 @@
       assert.isTrue(element.$$('paper-button').disabled);
     });
 
+    test('tabindex should be -1 if disabled', () => {
+      element.disabled = true;
+      assert.isTrue(element.getAttribute('tabindex') === '-1');
+    });
+
+    // Regression tests for Issue: 11969
+    test('tabindex should be reset to 0 if enabled', () => {
+      element.disabled = false;
+      assert.isTrue(element.getAttribute('tabindex') === '0');
+      element.disabled = true;
+      assert.isTrue(element.getAttribute('tabindex') === '-1');
+      element.disabled = false;
+      assert.isTrue(element.getAttribute('tabindex') === '0');
+    });
+
     // 'tap' event is tested so we don't loose backward compatibility with older
     // plugins who didn't move to on-click which is faster and well supported.
     for (const eventName of ['tap', 'click']) {
diff --git a/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog.html b/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog.html
index 2ef5539..e754a55 100644
--- a/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog.html
+++ b/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog.html
@@ -32,36 +32,32 @@
         display: flex;
         flex-direction: column;
         max-height: 90vh;
+        padding: var(--spacing-xl);
       }
       header {
-        border-bottom: 1px solid var(--border-color);
         flex-shrink: 0;
-        font-weight: var(--font-weight-bold);
+        padding-bottom: var(--spacing-xl);
       }
       main {
         display: flex;
         flex-shrink: 1;
         width: 100%;
       }
-      header,
-      main,
-      footer {
-        padding: var(--spacing-m) var(--spacing-xl);
-      }
-      gr-button {
-        margin-left: var(--spacing-l);
-      }
       footer {
         display: flex;
         flex-shrink: 0;
         justify-content: flex-end;
+        padding-top: var(--spacing-xl);
+      }
+      gr-button {
+        margin-left: var(--spacing-l);
       }
       .hidden {
         display: none;
       }
     </style>
     <div class="container" on-keydown="_handleKeydown">
-      <header><slot name="header"></slot></header>
+      <header class="font-h3"><slot name="header"></slot></header>
       <main><slot name="main"></slot></main>
       <footer>
         <gr-button id="cancel" class$="[[_computeCancelClass(cancelLabel)]]" link on-click="_handleCancelTap">
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.html b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.html
index f5f25d2..7fffcff 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.html
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.html
@@ -138,7 +138,7 @@
         id="dropdown"
         vertical-align="top"
         allow-outside-scroll="true"
-        on-tap="_handleDropdownTap">
+        on-click="_handleDropdownClick">
       <paper-listbox
           class="dropdown-content"
           slot="dropdown-content"
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.js b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.js
index 2197733..6272a67 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.js
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.js
@@ -84,7 +84,7 @@
      * Handle a click on the iron-dropdown element.
      * @param {!Event} e
      */
-    _handleDropdownTap(e) {
+    _handleDropdownClick(e) {
       // async is needed so that that the click event is fired before the
       // dropdown closes (This was a bug for touch devices).
       this.async(() => {
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.html b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.html
index 2a05050..d76721f 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.html
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.html
@@ -109,7 +109,7 @@
         vertical-offset="[[verticalOffset]]"
         allow-outside-scroll="true"
         horizontal-align="[[horizontalAlign]]"
-        on-tap="_handleDropdownTap">
+        on-click="_handleDropdownClick">
       <div class="dropdown-content" slot="dropdown-content">
         <ul>
           <template is="dom-if" if="[[topContent]]">
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.js b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.js
index 9725f0c..fc92e76 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.js
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.js
@@ -159,7 +159,7 @@
      * Handle a click on the iron-dropdown element.
      * @param {!Event} e
      */
-    _handleDropdownTap(e) {
+    _handleDropdownClick(e) {
       this._close();
     }
 
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-api-utils.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-api-utils.js
index 2d66cfa..0ec3d6a 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-api-utils.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-api-utils.js
@@ -50,7 +50,11 @@
       return url.pathname;
     }
     const base = Gerrit.BaseUrlBehavior.getBaseUrl();
-    const pathname = url.pathname.replace(base, '');
+    let pathname = url.pathname.replace(base, '');
+    // Load from ASSETS_PATH
+    if (window.ASSETS_PATH && url.href.includes(window.ASSETS_PATH)) {
+      pathname = url.href.replace(window.ASSETS_PATH, '');
+    }
     // Site theme is server from predefined path.
     if (pathname === '/static/gerrit-theme.html') {
       return 'gerrit-theme';
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-api-utils_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-api-utils_test.html
index 128738d..b43796f 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-api-utils_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-api-utils_test.html
@@ -72,6 +72,15 @@
             'gerrit-theme'
         );
       });
+
+      test('with ASSETS_PATH', () => {
+        window.ASSETS_PATH = 'http://cdn.com/2';
+        assert.equal(
+            getPluginNameFromUrl(`${window.ASSETS_PATH}/plugins/a.html`),
+            'a'
+        );
+        window.ASSETS_PATH = undefined;
+      });
     });
   });
 </script>
\ No newline at end of file
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader.js
index 4be38b6..96d8411 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader.js
@@ -113,7 +113,7 @@
       this._pluginListLoaded = true;
 
       plugins.forEach(path => {
-        const url = this._urlFor(path);
+        const url = this._urlFor(path, window.ASSETS_PATH);
         // Skip if preloaded, for bundling.
         if (this.isPluginPreloaded(url)) return;
 
@@ -128,11 +128,11 @@
         });
 
         if (this._isPathEndsWith(url, '.html')) {
-          this._importHtmlPlugin(url, opts && opts[path]);
+          this._importHtmlPlugin(path, opts && opts[path]);
         } else if (this._isPathEndsWith(url, '.js')) {
-          this._loadJsPlugin(url);
+          this._loadJsPlugin(path);
         } else {
-          this._failToLoad(`Unrecognized plugin url ${url}`, url);
+          this._failToLoad(`Unrecognized plugin path ${path}`, path);
         }
       });
 
@@ -181,14 +181,15 @@
         return;
       }
 
-      const pluginObject = this.getPlugin(src);
+      const url = this._urlFor(src);
+      const pluginObject = this.getPlugin(url);
       let plugin = pluginObject && pluginObject.plugin;
       if (!plugin) {
-        plugin = new Plugin(src);
+        plugin = new Plugin(url);
       }
       try {
         callback(plugin);
-        this._pluginInstalled(src, plugin);
+        this._pluginInstalled(url, plugin);
       } catch (e) {
         this._failToLoad(`${e.name}: ${e.message}`, src);
       }
@@ -313,38 +314,79 @@
     }
 
     _importHtmlPlugin(pluginUrl, opts = {}) {
-      // onload (second param) needs to be a function. When null or undefined
-      // were passed, plugins were not loaded correctly.
+      const urlWithAP = this._urlFor(pluginUrl, window.ASSETS_PATH);
+      const urlWithoutAP = this._urlFor(pluginUrl);
+      let onerror = null;
+      if (urlWithAP !== urlWithoutAP) {
+        onerror = () => this._loadHtmlPlugin(urlWithoutAP, opts.sync);
+      }
+      this._loadHtmlPlugin(urlWithAP, opts.sync, onerror);
+    }
+
+    _loadHtmlPlugin(url, sync, onerror) {
+      if (!onerror) {
+        onerror = () => {
+          this._failToLoad(`${pluginUrl} import error`, pluginUrl);
+        };
+      }
+
       (Polymer.importHref || Polymer.Base.importHref)(
-          this._urlFor(pluginUrl), () => {},
-          () => this._failToLoad(`${pluginUrl} import error`, pluginUrl),
-          !opts.sync);
+          url, () => {},
+          onerror,
+          !sync);
     }
 
     _loadJsPlugin(pluginUrl) {
-      this._createScriptTag(this._urlFor(pluginUrl));
+      const urlWithAP = this._urlFor(pluginUrl, window.ASSETS_PATH);
+      const urlWithoutAP = this._urlFor(pluginUrl);
+      let onerror = null;
+      if (urlWithAP !== urlWithoutAP) {
+        onerror = () => this._createScriptTag(urlWithoutAP);
+      }
+
+      this._createScriptTag(urlWithAP, onerror);
     }
 
-    _createScriptTag(url) {
+    _createScriptTag(url, onerror) {
+      if (!onerror) {
+        onerror = () => this._failToLoad(`${url} load error`, url);
+      }
+
       const el = document.createElement('script');
       el.defer = true;
       el.setAttribute('src', url);
-      el.onerror = () => this._failToLoad(`${url} load error`, url);
+      el.onerror = onerror;
       return document.body.appendChild(el);
     }
 
-    _urlFor(pathOrUrl) {
+    _urlFor(pathOrUrl, assetsPath) {
       if (!pathOrUrl) {
         return pathOrUrl;
       }
+
+      // theme is per host, should always load from assetsPath
+      const isThemeFile = pathOrUrl.endsWith('static/gerrit-theme.html');
+      const shouldTryLoadFromAssetsPathFirst = !isThemeFile && assetsPath;
       if (pathOrUrl.startsWith(PRELOADED_PROTOCOL) ||
           pathOrUrl.startsWith('http')) {
         // Plugins are loaded from another domain or preloaded.
+        if (pathOrUrl.includes(location.host)
+          && shouldTryLoadFromAssetsPathFirst) {
+          // if is loading from host server, try replace with cdn when assetsPath provided
+          return pathOrUrl
+              .replace(location.origin, assetsPath);
+        }
         return pathOrUrl;
       }
+
       if (!pathOrUrl.startsWith('/')) {
         pathOrUrl = '/' + pathOrUrl;
       }
+
+      if (shouldTryLoadFromAssetsPathFirst) {
+        return assetsPath + pathOrUrl;
+      }
+
       return window.location.origin + getBaseUrl() + pathOrUrl;
     }
 
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader_test.html
index 8c1ec96..08e7e18 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader_test.html
@@ -325,11 +325,11 @@
       let loadJsPluginStub;
       setup(() => {
         importHtmlPluginStub = sandbox.stub();
-        sandbox.stub(Gerrit._pluginLoader, '_importHtmlPlugin', url => {
+        sandbox.stub(Gerrit._pluginLoader, '_loadHtmlPlugin', url => {
           importHtmlPluginStub(url);
         });
         loadJsPluginStub = sandbox.stub();
-        sandbox.stub(Gerrit._pluginLoader, '_loadJsPlugin', url => {
+        sandbox.stub(Gerrit._pluginLoader, '_createScriptTag', url => {
           loadJsPluginStub(url);
         });
       });
@@ -346,8 +346,8 @@
 
         assert.isTrue(failToLoadStub.calledOnce);
         assert.isTrue(failToLoadStub.calledWithExactly(
-            `Unrecognized plugin url ${url}/foo/bar`,
-            `${url}/foo/bar`
+            'Unrecognized plugin path foo/bar',
+            'foo/bar'
         ));
       });
 
@@ -407,6 +407,72 @@
       });
     });
 
+    suite('With ASSETS_PATH', () => {
+      let importHtmlPluginStub;
+      let loadJsPluginStub;
+      setup(() => {
+        window.ASSETS_PATH = 'https://cdn.com';
+        importHtmlPluginStub = sandbox.stub();
+        sandbox.stub(Gerrit._pluginLoader, '_loadHtmlPlugin', url => {
+          importHtmlPluginStub(url);
+        });
+        loadJsPluginStub = sandbox.stub();
+        sandbox.stub(Gerrit._pluginLoader, '_createScriptTag', url => {
+          loadJsPluginStub(url);
+        });
+      });
+
+      teardown(() => {
+        window.ASSETS_PATH = '';
+      });
+
+      test('Should try load plugins from assets path instead', () => {
+        Gerrit._loadPlugins([
+          'foo/bar.js',
+          'foo/bar.html',
+        ]);
+
+        assert.isTrue(importHtmlPluginStub.calledOnce);
+        assert.isTrue(
+            importHtmlPluginStub.calledWithExactly(`https://cdn.com/foo/bar.html`)
+        );
+        assert.isTrue(loadJsPluginStub.calledOnce);
+        assert.isTrue(
+            loadJsPluginStub.calledWithExactly(`https://cdn.com/foo/bar.js`));
+      });
+
+      test('Should honor original path if exists', () => {
+        Gerrit._loadPlugins([
+          'http://e.com/foo/bar.html',
+          'http://e.com/foo/bar.js',
+        ]);
+
+        assert.isTrue(importHtmlPluginStub.calledOnce);
+        assert.isTrue(
+            importHtmlPluginStub.calledWithExactly(`http://e.com/foo/bar.html`)
+        );
+        assert.isTrue(loadJsPluginStub.calledOnce);
+        assert.isTrue(
+            loadJsPluginStub.calledWithExactly(`http://e.com/foo/bar.js`));
+      });
+
+      test('Should try replace current host with assetsPath', () => {
+        const host = window.location.origin;
+        Gerrit._loadPlugins([
+          `${host}/foo/bar.html`,
+          `${host}/foo/bar.js`,
+        ]);
+
+        assert.isTrue(importHtmlPluginStub.calledOnce);
+        assert.isTrue(
+            importHtmlPluginStub.calledWithExactly(`https://cdn.com/foo/bar.html`)
+        );
+        assert.isTrue(loadJsPluginStub.calledOnce);
+        assert.isTrue(
+            loadJsPluginStub.calledWithExactly(`https://cdn.com/foo/bar.js`));
+      });
+    });
+
     test('adds js plugins will call the body', () => {
       Gerrit._loadPlugins([
         'http://e.com/foo/bar.js',
@@ -489,12 +555,10 @@
 
       test('installing preloaded plugin', () => {
         let plugin;
-        window.ASSETS_PATH = 'http://blips.com/chitz';
         Gerrit.install(p => { plugin = p; }, '0.1', 'preloaded:foo');
         assert.strictEqual(plugin.getPluginName(), 'foo');
         assert.strictEqual(plugin.url('/some/thing.html'),
-            'http://blips.com/chitz/plugins/foo/some/thing.html');
-        delete window.ASSETS_PATH;
+            'preloaded:foo/plugins/foo/some/thing.html');
       });
     });
   });
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
index 6c306d9..0aaeaa1 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
@@ -17,8 +17,6 @@
 (function(window) {
   'use strict';
 
-  const PRELOADED_PROTOCOL = 'preloaded:';
-
   const PANEL_ENDPOINTS_MAPPING = {
     CHANGE_SCREEN_BELOW_COMMIT_INFO_BLOCK: 'change-view-integration',
     CHANGE_SCREEN_BELOW_CHANGE_INFO_BLOCK: 'change-metadata-item',
@@ -66,13 +64,6 @@
 
     this._url = new URL(opt_url);
     this._name = getPluginNameFromUrl(this._url);
-    if (this._url.protocol === PRELOADED_PROTOCOL) {
-      // Original plugin URL is used in plugin assets URLs calculation.
-      const assetsBaseUrl = window.ASSETS_PATH ||
-          (window.location.origin + Gerrit.BaseUrlBehavior.getBaseUrl());
-      this._url = new URL(assetsBaseUrl + '/plugins/' + this._name +
-          '/static/' + this._name + '.js');
-    }
   }
 
   Plugin._sharedAPIElement = document.createElement('gr-js-api-interface');
diff --git a/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete.html b/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete.html
index da0b93f..47be6f7 100644
--- a/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete.html
+++ b/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete.html
@@ -38,14 +38,7 @@
       #body {
         display: flex;
       }
-      gr-autocomplete {
-        height: 1.5em;
-        --gr-autocomplete: {
-          border: none;
-        }
-      }
       #trigger {
-        border-left: 1px solid var(--deemphasized-text-color);
         color: var(--deemphasized-text-color);
         cursor: pointer;
         padding-left: var(--spacing-s);
diff --git a/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.html b/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.html
index 2b4b982..46b8fb6 100644
--- a/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.html
+++ b/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay.html
@@ -25,7 +25,10 @@
     <style include="shared-styles">
       :host {
         background: var(--dialog-background-color);
-        box-shadow: rgba(0, 0, 0, 0.3) 0 1px 3px;
+        border-radius: var(--border-radius);
+        box-shadow: 0 11px 15px -7px rgba(0,0,0,.2),
+                    0 24px 38px  3px rgba(0,0,0,.14),
+                    0  9px 46px  8px rgba(0,0,0,.12);
       }
 
       @media screen and (max-width: 50em) {
@@ -35,6 +38,8 @@
           position: fixed;
           right: 0;
           top: 0;
+          border-radius: 0;
+          box-shadow: none;
         }
       }
     </style>
diff --git a/polygerrit-ui/app/styles/gr-form-styles.html b/polygerrit-ui/app/styles/gr-form-styles.html
index 3fe0a72..7c9ae0d 100644
--- a/polygerrit-ui/app/styles/gr-form-styles.html
+++ b/polygerrit-ui/app/styles/gr-form-styles.html
@@ -61,7 +61,6 @@
       }
       .gr-form-styles td,
       .gr-form-styles tfoot th {
-        height: 2em;
         padding: var(--spacing-s) 0;
         vertical-align: middle;
       }
@@ -84,8 +83,7 @@
       .gr-form-styles textarea {
         border: 1px solid var(--border-color);
         border-radius: var(--border-radius);
-        height: 2em;
-        padding: 0 var(--spacing-xs);
+        padding: var(--spacing-s);
       }
       .gr-form-styles td:last-child {
         width: 5em;
@@ -97,23 +95,16 @@
       .gr-form-styles iron-autogrow-textarea {
         border: none;
         height: auto;
-        min-height: 2em;
+        min-height: 4em;
         --iron-autogrow-textarea: {
           border: 1px solid var(--border-color);
           border-radius: var(--border-radius);
           box-sizing: border-box;
-          padding: var(--spacing-s) var(--spacing-xs) 0 var(--spacing-xs);
+          padding: var(--spacing-s);
         }
       }
       .gr-form-styles gr-autocomplete {
-        border: none;
-        --gr-autocomplete: {
-          border: 1px solid var(--border-color);
-          border-radius: var(--border-radius);
-          height: 2em;
-          padding: 0 var(--spacing-xs);
-          width: 14em;
-        }
+        width: 14em;
       }
       @media only screen and (max-width: 40em) {
         .gr-form-styles section {
diff --git a/polygerrit-ui/app/styles/shared-styles.html b/polygerrit-ui/app/styles/shared-styles.html
index 5314741..51b92e1 100644
--- a/polygerrit-ui/app/styles/shared-styles.html
+++ b/polygerrit-ui/app/styles/shared-styles.html
@@ -42,10 +42,11 @@
       input {
         background-color: inherit;
         border: 1px solid var(--border-color);
+        border-radius: var(--border-radius);
         box-sizing: border-box;
         color: var(--primary-text-color);
         margin: 0;
-        padding: 0;
+        padding: var(--spacing-s);
       }
       iron-autogrow-textarea {
         background-color: inherit;
diff --git a/polygerrit-ui/app/styles/themes/app-theme.html b/polygerrit-ui/app/styles/themes/app-theme.html
index ccdde2b..0ccb5c5 100644
--- a/polygerrit-ui/app/styles/themes/app-theme.html
+++ b/polygerrit-ui/app/styles/themes/app-theme.html
@@ -74,7 +74,7 @@
 
   /* fonts */
   --font-family: 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
-  --monospace-font-family: 'Roboto Mono', Menlo, 'Lucida Console', Monaco, monospace;
+  --monospace-font-family: 'Roboto Mono', 'SF Mono', 'Lucida Console', Monaco, monospace;
   --font-size-code: 12px;     /* 12px mono */
   --font-size-mono: .929rem;  /* 13px mono */
   --font-size-small: .857rem; /* 12px */
@@ -142,6 +142,7 @@
   --syntax-built_in-color: #30a;
   --syntax-comment-color: #3f7f5f;
   --syntax-default-color: var(--primary-text-color);
+  --syntax-doctag-weight: bold;
   --syntax-function-color: var(--primary-text-color);
   --syntax-keyword-color: #9e0069;
   --syntax-link-color: #219;
@@ -149,7 +150,7 @@
   --syntax-meta-color: #ff1717;
   --syntax-meta-keyword-color: #219;
   --syntax-number-color: #164;
-  --syntax-param-color: var(--primary-text-color);
+  --syntax-params-color: var(--primary-text-color);
   --syntax-regexp-color: #fa8602;
   --syntax-selector-attr-color: #fa8602;
   --syntax-selector-class-color: #164;
@@ -162,10 +163,10 @@
   --syntax-title-color: #0000c0;
   --syntax-type-color: #2a66d9;
   --syntax-variable-color: var(--primary-text-color);
-
   /* misc */
   --border-radius: 4px;
   --reply-overlay-z-index: 1000;
+  --iron-overlay-backdrop-opacity: 0.32;
   --iron-overlay-backdrop: {
     transition: none;
   };
diff --git a/polygerrit-ui/app/styles/themes/dark-theme.html b/polygerrit-ui/app/styles/themes/dark-theme.html
index f3d5e78..513f28a 100644
--- a/polygerrit-ui/app/styles/themes/dark-theme.html
+++ b/polygerrit-ui/app/styles/themes/dark-theme.html
@@ -112,6 +112,7 @@
       --syntax-built_in-color: #f7c369;
       --syntax-comment-color: var(--deemphasized-text-color);
       --syntax-default-color: var(--primary-text-color);
+      --syntax-doctag-weight: bold;
       --syntax-function-color: var(--primary-text-color);
       --syntax-keyword-color: #cd4cf0;
       --syntax-link-color: #c792ea;
@@ -119,7 +120,7 @@
       --syntax-meta-color: #6d7eee;
       --syntax-meta-keyword-color: #eefff7;
       --syntax-number-color: #00998a;
-      --syntax-param-color: var(--primary-text-color);
+      --syntax-params-color: var(--primary-text-color);
       --syntax-regexp-color: #f77669;
       --syntax-selector-attr-color: #80cbbf;
       --syntax-selector-class-color: #ffcb68;
diff --git a/tools/js/bower2bazel.py b/tools/js/bower2bazel.py
index e728cc3..a03f764 100755
--- a/tools/js/bower2bazel.py
+++ b/tools/js/bower2bazel.py
@@ -38,7 +38,7 @@
     "codemirror-minified": "codemirror-minified",
     "es6-promise": "es6-promise",
     "fetch": "fetch",
-    "font-roboto": "polymer",
+    "font-roboto-local": "polymer",
     "iron-a11y-announcer": "polymer",
     "iron-a11y-keys-behavior": "polymer",
     "iron-autogrow-textarea": "polymer",
@@ -77,7 +77,7 @@
     "paper-behaviors": "polymer",
     "paper-ripple": "polymer",
     "iron-checked-element-behavior": "polymer",
-    "font-roboto": "polymer",
+    "font-roboto-local": "polymer",
 }
 
 
diff --git a/tools/maven/package.bzl b/tools/maven/package.bzl
index 11e569d..b25656d 100644
--- a/tools/maven/package.bzl
+++ b/tools/maven/package.bzl
@@ -17,6 +17,14 @@
     "echo \"# this script should run from the root of your workspace.\" >> $@",
     "echo \"set -e\" >> $@",
     "echo \"\" >> $@",
+    "echo 'function bazel_cmd() {' >> $@",
+    "echo '  if [[ `which bazelisk` ]]; then' >> $@",
+    "echo '    bazelisk \"$$@\"' >> $@",
+    "echo '  else' >> $@",
+    "echo '    bazel \"$$@\"' >> $@",
+    "echo '  fi' >> $@",
+    "echo '}' >> $@",
+    "echo \"\" >> $@",
     "echo 'if [[ \"$$VERBOSE\" ]]; then set -x ; fi' >> $@",
     "echo \"\" >> $@",
     "echo %s >> $@",
@@ -32,7 +40,7 @@
         src = {},
         doc = {},
         war = {}):
-    build_cmd = ["bazel", "build"]
+    build_cmd = ["bazel_cmd", "build"]
     mvn_cmd = ["python", "tools/maven/mvn.py", "-v", version]
     api_cmd = mvn_cmd[:]
     api_targets = []