Merge "Ignore account visibility when parsing Reviewers Collection"
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 452ef23..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.
 
@@ -2766,6 +2766,22 @@
 If not set or set to a zero, defaults to the number of logical CPUs as returned
 by the JVM. If set to a negative value, defaults to a direct executor.
 
+[[index.change.indexMergeable]]index.change.indexMergeable::
++
+Specifies if `mergeable` should be index or not. Indexing this field enables
+queries that contain the mergeability operator (`is:mergeable`). If enabled,
+Gerrit will check if the change is mergeable into the target branch when
+reindexing a change. This is an expensive operation.
++
+If true, Gerrit will reindex all open changes when the target ref advances.
+Depending on the frequency of updates to the ref and the number of open changes,
+this can be very expensive.
++
+When this setting is changed from `false` to `true`, all changes need to be
+reindexed.
++
+Defaults to true.
+
 [[index.onlineUpgrade]]index.onlineUpgrade::
 +
 Whether to upgrade to new index schema versions while the server is
@@ -2813,19 +2829,6 @@
 +
 Defaults to 1024.
 
-[[index.reindexAfterRefUpdate]]index.reindexAfterRefUpdate::
-+
-Whether to reindex all affected open changes after a ref is updated. This
-includes reindexing all open changes to recompute the "mergeable" bit every time
-the destination branch moves, as well as reindexing changes to take into account
-new project configuration (e.g. label definitions).
-+
-Leaving this enabled may result in fresher results, but may cause performance
-problems if there are lots of open changes on a project whose branches advance
-frequently.
-+
-Defaults to true.
-
 [[index.autoReindexIfStale]]index.autoReindexIfStale::
 +
 Whether to automatically check if a document became stale in the index
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-crafting-changes.txt b/Documentation/dev-crafting-changes.txt
index bf4453c..eb9dee4 100644
--- a/Documentation/dev-crafting-changes.txt
+++ b/Documentation/dev-crafting-changes.txt
@@ -230,6 +230,13 @@
 
   * Tests for new code will greatly help your change get approved.
 
+[[javadoc]]
+== Javadoc
+
+  * Javadocs for new code (especially public classes and
+    public/protected methods) will greatly help your change get
+    approved.
+
 [[change-size]]
 == Change Size/Number of Files Touched
 
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/dev-plugins.txt b/Documentation/dev-plugins.txt
index 223ec71..b7b807f 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -452,8 +452,8 @@
 [source,java]
 ----
 import com.google.gerrit.common.EventDispatcher;
+import com.google.gerrit.exceptions.StorageException;
 import com.google.gerrit.extensions.registration.DynamicItem;
-import com.google.exceptions.StorageException;
 import com.google.inject.Inject;
 
 class MyPlugin {
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 18501b3..949298f 100644
--- a/Documentation/licenses.txt
+++ b/Documentation/licenses.txt
@@ -88,6 +88,8 @@
 * openid:xerces
 * polymer_externs:polymer_closure
 * blame-cache
+* caffeine
+* caffeine-guava
 * gson
 * guava
 * guava-failureaccess
@@ -2664,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/Documentation/note-db.txt b/Documentation/note-db.txt
index 308e045..8725cee 100644
--- a/Documentation/note-db.txt
+++ b/Documentation/note-db.txt
@@ -192,3 +192,10 @@
   of all changes in NoteDb is accurate, and so is only safe once all changes are
   NoteDb primary. Otherwise, reading changes only from NoteDb might result in
   inaccurate results, and writing to NoteDb would compound the problem. +
+
+== NoteDB to ReviewDB rollback
+
+In case of rollback from NoteDB to ReviewDB, all the meta refs and the
+sequence ref need to be removed.
+The [remove-notedb-refs.sh](https://gerrit.googlesource.com/gerrit/+/refs/heads/master/contrib/remove-notedb-refs.sh)
+script has been written to automate this process.
diff --git a/Documentation/rest-api-accounts.txt b/Documentation/rest-api-accounts.txt
index 6ef4e20..85cdace 100644
--- a/Documentation/rest-api-accounts.txt
+++ b/Documentation/rest-api-accounts.txt
@@ -733,8 +733,8 @@
   [
     {
       "seq": 1,
-      "ssh_public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0T...YImydZAw\u003d\u003d john.doe@example.com",
-      "encoded_key": "AAAAB3NzaC1yc2EAAAABIwAAAQEA0T...YImydZAw\u003d\u003d",
+      "ssh_public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0T...YImydZAw== john.doe@example.com",
+      "encoded_key": "AAAAB3NzaC1yc2EAAAABIwAAAQEA0T...YImydZAw==",
       "algorithm": "ssh-rsa",
       "comment": "john.doe@example.com",
       "valid": true
@@ -767,8 +767,8 @@
   )]}'
   {
     "seq": 1,
-    "ssh_public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0T...YImydZAw\u003d\u003d john.doe@example.com",
-    "encoded_key": "AAAAB3NzaC1yc2EAAAABIwAAAQEA0T...YImydZAw\u003d\u003d",
+    "ssh_public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0T...YImydZAw== john.doe@example.com",
+    "encoded_key": "AAAAB3NzaC1yc2EAAAABIwAAAQEA0T...YImydZAw==",
     "algorithm": "ssh-rsa",
     "comment": "john.doe@example.com",
     "valid": true
@@ -791,9 +791,9 @@
 .Request
 ----
   POST /accounts/self/sshkeys HTTP/1.0
-  Content-Type: plain/text
+  Content-Type: text/plain
 
-  AAAAB3NzaC1yc2EAAAABIwAAAQEA0T...YImydZAw\u003d\u003d
+  ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0T...YImydZAw== john.doe@example.com
 ----
 
 As response an link:#ssh-key-info[SshKeyInfo] entity is returned that
@@ -808,8 +808,8 @@
   )]}'
   {
     "seq": 2,
-    "ssh_public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0T...YImydZAw\u003d\u003d john.doe@example.com",
-    "encoded_key": "AAAAB3NzaC1yc2EAAAABIwAAAQEA0T...YImydZAw\u003d\u003d",
+    "ssh_public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA0T...YImydZAw== john.doe@example.com",
+    "encoded_key": "AAAAB3NzaC1yc2EAAAABIwAAAQEA0T...YImydZAw==",
     "algorithm": "ssh-rsa",
     "comment": "john.doe@example.com",
     "valid": true
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 69a7641..0fc733a 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -315,6 +315,11 @@
 the `mergeable` field will always be omitted and `SKIP_MERGEABLE` has no
 effect.
 +
+When link:config-gerrit.html#index.change.indexMergeable[
+`index.change.indexMergeable`] is set to `false` in the `gerrit.config`,
+the `mergeable` field will always be omitted when querying changes and
+`SKIP_MERGEABLE` has no effect.
++
 A change's mergeability can be requested separately by calling the
 link:#get-mergeable[get-mergeable] endpoint.
 --
diff --git a/Documentation/rest-api-groups.txt b/Documentation/rest-api-groups.txt
index 00fd81f..a99c3bb 100644
--- a/Documentation/rest-api-groups.txt
+++ b/Documentation/rest-api-groups.txt
@@ -341,24 +341,19 @@
 [[query-groups]]
 === Query Groups
 --
-'GET /groups/?query2=<query>'
+'GET /groups/?query=<query>'
 --
 
 Queries internal groups visible to the caller. The
 link:user-search-groups.html#_search_operators[query string] must be
-provided by the `query2` parameter. The `start` and `limit` parameters
+provided by the `query` parameter. The `start` and `limit` parameters
 can be used to skip/limit results.
 
 As result a list of link:#group-info[GroupInfo] entities is returned.
 
-[NOTE] `query2` is a temporary name and in future this option may be
-renamed to `query`. `query2` was chosen to maintain backwards
-compatibility with the deprecated `query` parameter on the
-link:#list-groups[List Groups] endpoint.
-
 .Request
 ----
-  GET /groups/?query2=inname:test HTTP/1.0
+  GET /groups/?query=inname:test HTTP/1.0
 ----
 
 .Response
@@ -398,12 +393,12 @@
 
 [[group-query-limit]]
 ==== Group Limit
-The `/groups/?query2=<query>` URL also accepts a limit integer in the
+The `/groups/?query=<query>` URL also accepts a limit integer in the
 `limit` parameter. This limits the results to `limit` groups.
 
 Query the first 25 groups in group list.
 ----
-  GET /groups/?query2=<query>&limit=25 HTTP/1.0
+  GET /groups/?query=<query>&limit=25 HTTP/1.0
 ----
 
 The `/groups/` URL also accepts a start integer in the `start`
@@ -411,7 +406,7 @@
 
 Query 25 groups starting from index 50.
 ----
-  GET /groups/?query2=<query>&limit=25&start=50 HTTP/1.0
+  GET /groups/?query=<query>&limit=25&start=50 HTTP/1.0
 ----
 
 [[group-query-options]]
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index 74ec8d3..c373b96 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -3233,6 +3233,66 @@
   HTTP/1.1 204 No Content
 ----
 
+[[batch-update-labels]]
+=== Batch Update Labels
+--
+'POST /projects/link:#project-name[\{project-name\}]/labels/'
+--
+
+Creates/updates/deletes multiple label definitions in this project at once.
+
+The calling user must have write access to the `refs/meta/config` branch of the
+project.
+
+The updates must be specified in the request body as
+link:#batch-label-input[BatchLabelInput] entity.
+
+The updates are processed in the following order:
+
+1. label deletions
+2. label creations
+3. label updates
+
+.Request
+----
+  POST /projects/My-Project/labels/ HTTP/1.0
+  Content-Type: application/json; charset=UTF-8
+
+  {
+    "commit_message": "Update Labels",
+    "delete": [
+      "Old-Review",
+      "Unused-Review"
+    ],
+    "create": [
+      {
+        "name": "Foo-Review",
+        "values": {
+          " 0": "No score",
+          "-1": "I would prefer this is not merged as is",
+          "-2": "This shall not be merged",
+          "+1": "Looks good to me, but someone else must approve",
+          "+2": "Looks good to me, approved"
+      }
+    ],
+    "update:" {
+      "Bar-Review": {
+        "function": "MaxWithBlock"
+      },
+      "Baz-Review": {
+        "copy_min_score": true
+      }
+    }
+  }
+----
+
+If the label updates were done successfully the response is "`200 OK`".
+
+.Response
+----
+  HTTP/1.1 200 OK
+----
+
 
 [[ids]]
 == IDs
@@ -3645,8 +3705,6 @@
 |`inherited_value` |optional|
 The inherited value of the configuration parameter, only set if
 `inheritable` is true.
-|`permitted_values` |optional|
-The list of permitted values, only set if the `type` is `LIST`.
 |===============================
 
 [[dashboard-info]]
@@ -3876,9 +3934,14 @@
 |Field Name      ||Description
 |`commit_message`|optional|
 Message that should be used to commit the change of the label in the
-`project.config` file to the `refs/meta/config` branch.
+`project.config` file to the `refs/meta/config` branch.+
+Must not be set if this `LabelDefinitionInput` entity is contained in a
+link:#batch-label-input[BatchLabelInput] entity.
 |`name`          |optional|
-The new link:config-labels.html#label_name[name] of the label.
+The new link:config-labels.html#label_name[name] of the label.+
+For label creation the name is required if this `LabelDefinitionInput` entity
+is contained in a link:#batch-label-input[BatchLabelInput]
+entity.
 |`function`      |optional|
 The new link:config-labels.html#label_function[function] of the label (can be
 `MaxWithBlock`, `AnyWithBlock`, `MaxNoBlock`, `NoBlock`, `NoOp` and `PatchSetLock`.
@@ -3961,6 +4024,27 @@
 Not set if not inherited or overridden.
 |===============================
 
+[[batch-label-input]]
+=== BatchLabelInput
+The `BatchLabelInput` entity contains information for batch updating label
+definitions in a project.
+
+[options="header",cols="1,^2,4"]
+|=============================
+|Field Name      ||Description
+|`commit_message`|optional|
+Message that should be used to commit the label updates in the
+`project.config` file to the `refs/meta/config` branch.
+|`delete`        |optional|
+List of labels that should be deleted.
+|`create`        |optional|
+List of link:#label-definition-input[LabelDefinitionInput] entities that
+describe labels that should be created.
+|`update`        |optional|
+Map of label names to link:#label-definition-input[LabelDefinitionInput]
+entities that describe the updates that should be done for the labels.
+|=============================
+
 [[project-access-input]]
 === ProjectAccessInput
 The `ProjectAccessInput` describes changes that should be applied to a project
diff --git a/Jenkinsfile b/Jenkinsfile
index 257b53c..f21c7897 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
+                    }
                 }
             }
         }
@@ -181,9 +185,17 @@
 
 def collectBuilds() {
     def builds = [:]
-    builds["Gerrit-codestyle"] = prepareBuildsForMode("Gerrit-codestyle")
-    Builds.modes.each {
-        builds["Gerrit-verification(${it})"] = prepareBuildsForMode("Gerrit-verifier-bazel", it)
+    if (env.GERRIT_CHANGE_NUMBER == "") {
+       builds["java8"] = { -> build "Gerrit-bazel-${env.BRANCH_NAME}" }
+
+       if (env.BRANCH_NAME == "master") {
+          builds["java11"] = { -> build "Gerrit-bazel-java11-${env.BRANCH_NAME}" }
+       }
+    } else {
+        builds["Gerrit-codestyle"] = prepareBuildsForMode("Gerrit-codestyle")
+        Builds.modes.each {
+            builds["Gerrit-verification(${it})"] = prepareBuildsForMode("Gerrit-verifier-bazel", it)
+        }
     }
     return builds
 }
@@ -270,44 +282,48 @@
 
 node ('master') {
 
-    stage('Preparing'){
-        gerritReview labels: ['Verified': 0, 'Code-Style': 0]
+    if (env.GERRIT_CHANGE_NUMBER != "") {
+        stage('Preparing'){
+            gerritReview labels: ['Verified': 0, 'Code-Style': 0]
 
-        getChangeMetaData()
-        collectBuildModes()
+            getChangeMetaData()
+            collectBuildModes()
+        }
     }
 
     parallel(collectBuilds())
 
-    stage('Retry Flaky Builds'){
-        def flakyBuildsModes = findFlakyBuilds()
-        if (flakyBuildsModes.size() > 0){
-            parallel flakyBuildsModes.collectEntries {
-                ["Gerrit-verification(${it})" :
-                    prepareBuildsForMode("Gerrit-verifier-bazel", it, 3)]
+    if (env.GERRIT_CHANGE_NUMBER != "") {
+        stage('Retry Flaky Builds'){
+            def flakyBuildsModes = findFlakyBuilds()
+            if (flakyBuildsModes.size() > 0){
+                parallel flakyBuildsModes.collectEntries {
+                    ["Gerrit-verification(${it})" :
+                        prepareBuildsForMode("Gerrit-verifier-bazel", it, 3)]
+                }
             }
         }
-    }
 
-    stage('Report to Gerrit'){
-        resCodeStyle = getLabelValue(1, Builds.codeStyle.result)
-        gerritReview(
-            labels: ['Code-Style': resCodeStyle],
-            message: createCodeStyleMsgBody(Builds.codeStyle, resCodeStyle))
-        postCheck(new GerritCheck("codestyle", Change.number, Change.sha1, Builds.codeStyle))
+        stage('Report to Gerrit'){
+            resCodeStyle = getLabelValue(1, Builds.codeStyle.result)
+            gerritReview(
+                labels: ['Code-Style': resCodeStyle],
+                message: createCodeStyleMsgBody(Builds.codeStyle, resCodeStyle))
+            postCheck(new GerritCheck("codestyle", Change.number, Change.sha1, Builds.codeStyle))
 
-        def verificationResults = Builds.verification.collect { k, v -> v }
-        def resVerify = verificationResults.inject(1) {
-            acc, build -> getLabelValue(acc, build.result)
+            def verificationResults = Builds.verification.collect { k, v -> v }
+            def resVerify = verificationResults.inject(1) {
+                acc, build -> getLabelValue(acc, build.result)
+            }
+            gerritReview(
+                labels: ['Verified': resVerify],
+                message: createVerifyMsgBody(Builds.verification))
+
+            Builds.verification.each { type, build -> postCheck(
+                new GerritCheck(type, Change.number, Change.sha1, build)
+            )}
+
+            setResult(resVerify, resCodeStyle)
         }
-        gerritReview(
-            labels: ['Verified': resVerify],
-            message: createVerifyMsgBody(Builds.verification))
-
-        Builds.verification.each { type, build -> postCheck(
-            new GerritCheck(type, Change.number, Change.sha1, build)
-        )}
-
-        setResult(resVerify, resCodeStyle)
     }
 }
diff --git a/WORKSPACE b/WORKSPACE
index 4e2c970..7529d8a 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -241,6 +241,31 @@
     sha1 = "1dcf1de382a0bf95a3d8b0849546c88bac1292c9",
 )
 
+CAFFEINE_VERS = "2.8.0"
+
+maven_jar(
+    name = "caffeine",
+    artifact = "com.github.ben-manes.caffeine:caffeine:" + CAFFEINE_VERS,
+    sha1 = "6000774d7f8412ced005a704188ced78beeed2bb",
+)
+
+# TODO(davido): Rename guava.jar to caffeine-guava.jar on fetch to prevent potential
+# naming collision between caffeine guava adapater and guava library itself.
+# Remove this renaming procedure, once this upstream issue is fixed:
+# https://github.com/ben-manes/caffeine/issues/364.
+http_file(
+    name = "caffeine-guava-renamed",
+    downloaded_file_path = "caffeine-guava-" + CAFFEINE_VERS + ".jar",
+    sha256 = "3a66ee3ec70971dee0bae6e56bda7b8742bc4bedd7489161bfbbaaf7137d89e1",
+    urls = [
+        "https://repo1.maven.org/maven2/com/github/ben-manes/caffeine/guava/" +
+        CAFFEINE_VERS +
+        "/guava-" +
+        CAFFEINE_VERS +
+        ".jar",
+    ],
+)
+
 maven_jar(
     name = "jsch",
     artifact = "com.jcraft:jsch:0.1.54",
diff --git a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index 7edb43a..fd61aa5 100644
--- a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -644,6 +644,14 @@
     return result;
   }
 
+  protected PushOneCommit.Result createChange(TestRepository<InMemoryRepository> repo)
+      throws Exception {
+    PushOneCommit push = pushFactory.create(admin.newIdent(), repo);
+    PushOneCommit.Result result = push.to("refs/for/master");
+    result.assertOkStatus();
+    return result;
+  }
+
   protected PushOneCommit.Result createMergeCommitChange(String ref) throws Exception {
     return createMergeCommitChange(ref, "foo");
   }
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/acceptance/GerritServer.java b/java/com/google/gerrit/acceptance/GerritServer.java
index 73d37be..f90df67 100644
--- a/java/com/google/gerrit/acceptance/GerritServer.java
+++ b/java/com/google/gerrit/acceptance/GerritServer.java
@@ -269,7 +269,7 @@
 
   private static final ImmutableMap<String, Level> LOG_LEVELS =
       ImmutableMap.<String, Level>builder()
-          .put("com.google.gerrit", Level.DEBUG)
+          .put("com.google.gerrit", getGerritLogLevel())
 
           // Silence non-critical messages from MINA SSHD.
           .put("org.apache.mina", Level.WARN)
@@ -307,6 +307,14 @@
           .put("org.eclipse.jgit.util.FS", Level.WARN)
           .build();
 
+  private static Level getGerritLogLevel() {
+    String value = Strings.nullToEmpty(System.getenv("GERRIT_LOG_LEVEL"));
+    if (value.isEmpty()) {
+      value = Strings.nullToEmpty(System.getProperty("gerrit.logLevel"));
+    }
+    return Level.toLevel(value, Level.INFO);
+  }
+
   private static boolean forceLocalDisk() {
     String value = Strings.nullToEmpty(System.getenv("GERRIT_FORCE_LOCAL_DISK"));
     if (value.isEmpty()) {
@@ -563,8 +571,8 @@
     cfg.setInt("sshd", null, "commandStartThreads", 1);
     cfg.setInt("receive", null, "threadPoolSize", 1);
     cfg.setInt("index", null, "threads", 1);
-    if (cfg.getString("index", null, "reindexAfterRefUpdate") == null) {
-      cfg.setBoolean("index", null, "reindexAfterRefUpdate", false);
+    if (cfg.getString("index", "change", "indexMergeable") == null) {
+      cfg.setBoolean("index", "change", "indexMergeable", false);
     }
   }
 
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/elasticsearch/ElasticAccountIndex.java b/java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java
index a06f90f..c3e3264 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.elasticsearch;
 
+import com.google.common.collect.ImmutableSet;
 import com.google.gerrit.elasticsearch.ElasticMapping.MappingProperties;
 import com.google.gerrit.elasticsearch.bulk.BulkRequest;
 import com.google.gerrit.elasticsearch.bulk.IndexRequest;
@@ -74,7 +75,7 @@
   public void replace(AccountState as) {
     BulkRequest bulk =
         new IndexRequest(getId(as), indexName, type, client.adapter())
-            .add(new UpdateRequest<>(schema, as));
+            .add(new UpdateRequest<>(schema, as, ImmutableSet.of()));
 
     String uri = getURI(type, BULK);
     Response response = postRequest(uri, bulk, getRefreshParam());
diff --git a/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java b/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
index 37184cc..084c2ec 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
@@ -22,6 +22,7 @@
 import com.google.common.collect.FluentIterable;
 import com.google.common.collect.ImmutableListMultimap;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Lists;
@@ -48,6 +49,7 @@
 import com.google.gerrit.server.ReviewerByEmailSet;
 import com.google.gerrit.server.ReviewerSet;
 import com.google.gerrit.server.StarredChangesUtil;
+import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.index.IndexUtils;
 import com.google.gerrit.server.index.change.ChangeField;
@@ -65,6 +67,7 @@
 import java.util.Optional;
 import java.util.Set;
 import org.apache.http.HttpStatus;
+import org.eclipse.jgit.lib.Config;
 import org.elasticsearch.client.Response;
 
 /** Secondary index implementation using Elasticsearch. */
@@ -91,6 +94,7 @@
   private final ChangeData.Factory changeDataFactory;
   private final Schema<ChangeData> schema;
   private final FieldDef<ChangeData, ?> idField;
+  private final ImmutableSet<String> skipFields;
 
   @Inject
   ElasticChangeIndex(
@@ -98,6 +102,7 @@
       ChangeData.Factory changeDataFactory,
       SitePaths sitePaths,
       ElasticRestClientProvider clientBuilder,
+      @GerritServerConfig Config gerritConfig,
       @Assisted Schema<ChangeData> schema) {
     super(cfg, sitePaths, schema, clientBuilder, CHANGES);
     this.changeDataFactory = changeDataFactory;
@@ -105,6 +110,10 @@
     this.mapping = new ChangeMapping(schema, client.adapter());
     this.idField =
         this.schema.useLegacyNumericFields() ? ChangeField.LEGACY_ID : ChangeField.LEGACY_ID_STR;
+    this.skipFields =
+        gerritConfig.getBoolean("index", "change", "indexMergeable", true)
+            ? ImmutableSet.of()
+            : ImmutableSet.of(ChangeField.MERGEABLE.getName());
   }
 
   @Override
@@ -123,7 +132,7 @@
     ElasticQueryAdapter adapter = client.adapter();
     BulkRequest bulk =
         new IndexRequest(getId(cd), indexName, adapter.getType(insertIndex), adapter)
-            .add(new UpdateRequest<>(schema, cd));
+            .add(new UpdateRequest<>(schema, cd, skipFields));
     if (adapter.deleteToReplace()) {
       bulk.add(new DeleteRequest(cd.getId().toString(), indexName, deleteIndex, adapter));
     }
@@ -263,7 +272,7 @@
 
     // Mergeable.
     JsonElement mergeableElement = source.get(ChangeField.MERGEABLE.getName());
-    if (mergeableElement != null) {
+    if (mergeableElement != null && !skipFields.contains(ChangeField.MERGEABLE.getName())) {
       String mergeable = mergeableElement.getAsString();
       if ("1".equals(mergeable)) {
         cd.setMergeable(true);
diff --git a/java/com/google/gerrit/elasticsearch/ElasticGroupIndex.java b/java/com/google/gerrit/elasticsearch/ElasticGroupIndex.java
index c215132..ce2025f 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticGroupIndex.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticGroupIndex.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.elasticsearch;
 
+import com.google.common.collect.ImmutableSet;
 import com.google.gerrit.elasticsearch.ElasticMapping.MappingProperties;
 import com.google.gerrit.elasticsearch.bulk.BulkRequest;
 import com.google.gerrit.elasticsearch.bulk.IndexRequest;
@@ -74,7 +75,7 @@
   public void replace(InternalGroup group) {
     BulkRequest bulk =
         new IndexRequest(getId(group), indexName, type, client.adapter())
-            .add(new UpdateRequest<>(schema, group));
+            .add(new UpdateRequest<>(schema, group, ImmutableSet.of()));
 
     String uri = getURI(type, BULK);
     Response response = postRequest(uri, bulk, getRefreshParam());
diff --git a/java/com/google/gerrit/elasticsearch/ElasticProjectIndex.java b/java/com/google/gerrit/elasticsearch/ElasticProjectIndex.java
index 29f8507..b636706 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticProjectIndex.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticProjectIndex.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.elasticsearch;
 
+import com.google.common.collect.ImmutableSet;
 import com.google.gerrit.elasticsearch.ElasticMapping.MappingProperties;
 import com.google.gerrit.elasticsearch.bulk.BulkRequest;
 import com.google.gerrit.elasticsearch.bulk.IndexRequest;
@@ -74,7 +75,7 @@
   public void replace(ProjectData projectState) {
     BulkRequest bulk =
         new IndexRequest(projectState.getProject().getName(), indexName, type, client.adapter())
-            .add(new UpdateRequest<>(schema, projectState));
+            .add(new UpdateRequest<>(schema, projectState, ImmutableSet.of()));
 
     String uri = getURI(type, BULK);
     Response response = postRequest(uri, bulk, getRefreshParam());
diff --git a/java/com/google/gerrit/elasticsearch/bulk/UpdateRequest.java b/java/com/google/gerrit/elasticsearch/bulk/UpdateRequest.java
index 2f0bd01..196b8d6 100644
--- a/java/com/google/gerrit/elasticsearch/bulk/UpdateRequest.java
+++ b/java/com/google/gerrit/elasticsearch/bulk/UpdateRequest.java
@@ -16,6 +16,7 @@
 
 import static java.util.stream.Collectors.toList;
 
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Streams;
 import com.google.gerrit.elasticsearch.builders.XContentBuilder;
@@ -27,17 +28,19 @@
 
   private final Schema<V> schema;
   private final V v;
+  private final ImmutableSet<String> skipFields;
 
-  public UpdateRequest(Schema<V> schema, V v) {
+  public UpdateRequest(Schema<V> schema, V v, ImmutableSet<String> skipFields) {
     this.schema = schema;
     this.v = v;
+    this.skipFields = skipFields;
   }
 
   @Override
   protected String getRequest() {
     try (XContentBuilder closeable = new XContentBuilder()) {
       XContentBuilder builder = closeable.startObject();
-      for (Values<V> values : schema.buildFields(v)) {
+      for (Values<V> values : schema.buildFields(v, skipFields)) {
         String name = values.getField().getName();
         if (values.getField().isRepeatable()) {
           builder.field(name, Streams.stream(values.getValues()).collect(toList()));
diff --git a/java/com/google/gerrit/entities/Comment.java b/java/com/google/gerrit/entities/Comment.java
index 65c642c..55d739a 100644
--- a/java/com/google/gerrit/entities/Comment.java
+++ b/java/com/google/gerrit/entities/Comment.java
@@ -229,12 +229,6 @@
   public String serverId;
   public boolean unresolved;
 
-  /**
-   * Whether the comment was parsed from a JSON representation (false) or the legacy custom notes
-   * format (true).
-   */
-  public transient boolean legacyFormat;
-
   public Comment(Comment c) {
     this(
         new Key(c.key),
diff --git a/java/com/google/gerrit/extensions/api/projects/ProjectApi.java b/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
index 6d02ec4..9873995 100644
--- a/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
+++ b/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
@@ -18,6 +18,7 @@
 import com.google.gerrit.extensions.api.access.ProjectAccessInput;
 import com.google.gerrit.extensions.api.config.AccessCheckInfo;
 import com.google.gerrit.extensions.api.config.AccessCheckInput;
+import com.google.gerrit.extensions.common.BatchLabelInput;
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.common.LabelDefinitionInfo;
 import com.google.gerrit.extensions.common.ProjectInfo;
@@ -221,6 +222,13 @@
   LabelApi label(String labelName) throws RestApiException;
 
   /**
+   * Adds, updates and deletes label definitions in a batch.
+   *
+   * @param input input that describes additions, updates and deletions of label definitions
+   */
+  void labels(BatchLabelInput input) throws RestApiException;
+
+  /**
    * A default implementation which allows source compatibility when adding new methods to the
    * interface.
    */
@@ -404,5 +412,10 @@
     public LabelApi label(String labelName) throws RestApiException {
       throw new NotImplementedException();
     }
+
+    @Override
+    public void labels(BatchLabelInput input) throws RestApiException {
+      throw new NotImplementedException();
+    }
   }
 }
diff --git a/java/com/google/gerrit/extensions/common/BatchLabelInput.java b/java/com/google/gerrit/extensions/common/BatchLabelInput.java
new file mode 100644
index 0000000..eb4c581
--- /dev/null
+++ b/java/com/google/gerrit/extensions/common/BatchLabelInput.java
@@ -0,0 +1,26 @@
+// 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.extensions.common;
+
+import java.util.List;
+import java.util.Map;
+
+/** Input for the REST API that describes additions, updates and deletions of label definitions. */
+public class BatchLabelInput {
+  public String commitMessage;
+  public List<String> delete;
+  public List<LabelDefinitionInput> create;
+  public Map<String, LabelDefinitionInput> update;
+}
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/RestApiMetrics.java b/java/com/google/gerrit/httpd/restapi/RestApiMetrics.java
index a378fa4..aa1b921 100644
--- a/java/com/google/gerrit/httpd/restapi/RestApiMetrics.java
+++ b/java/com/google/gerrit/httpd/restapi/RestApiMetrics.java
@@ -19,7 +19,7 @@
 import com.google.gerrit.extensions.registration.PluginName;
 import com.google.gerrit.httpd.restapi.RestApiServlet.ViewData;
 import com.google.gerrit.metrics.Counter1;
-import com.google.gerrit.metrics.Counter2;
+import com.google.gerrit.metrics.Counter3;
 import com.google.gerrit.metrics.Description;
 import com.google.gerrit.metrics.Description.Units;
 import com.google.gerrit.metrics.Field;
@@ -37,7 +37,7 @@
   };
 
   final Counter1<String> count;
-  final Counter2<String, Integer> errorCount;
+  final Counter3<String, Integer, String> errorCount;
   final Timer1<String> serverLatency;
   final Histogram1<String> responseBytes;
 
@@ -60,6 +60,9 @@
             viewField,
             Field.ofInteger("error_code", Metadata.Builder::httpStatus)
                 .description("HTTP status code")
+                .build(),
+            Field.ofString("cause", Metadata.Builder::cause)
+                .description("The cause of the error.")
                 .build());
 
     serverLatency =
diff --git a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
index 7700740..0535397 100644
--- a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
+++ b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
@@ -17,6 +17,7 @@
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.collect.ImmutableList.toImmutableList;
 import static com.google.common.flogger.LazyArgs.lazy;
 import static com.google.common.net.HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS;
 import static com.google.common.net.HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS;
@@ -103,6 +104,7 @@
 import com.google.gerrit.server.AccessPath;
 import com.google.gerrit.server.AnonymousUser;
 import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.ExceptionHook;
 import com.google.gerrit.server.OptionUtil;
 import com.google.gerrit.server.RequestInfo;
 import com.google.gerrit.server.RequestListener;
@@ -173,7 +175,7 @@
 import java.util.Set;
 import java.util.TreeMap;
 import java.util.concurrent.TimeUnit;
-import java.util.function.Predicate;
+import java.util.concurrent.atomic.AtomicReference;
 import java.util.regex.Pattern;
 import java.util.stream.Stream;
 import java.util.zip.GZIPOutputStream;
@@ -246,6 +248,7 @@
     final DynamicSet<PerformanceLogger> performanceLoggers;
     final ChangeFinder changeFinder;
     final RetryHelper retryHelper;
+    final PluginSetContext<ExceptionHook> exceptionHooks;
 
     @Inject
     Globals(
@@ -260,7 +263,8 @@
         @GerritServerConfig Config config,
         DynamicSet<PerformanceLogger> performanceLoggers,
         ChangeFinder changeFinder,
-        RetryHelper retryHelper) {
+        RetryHelper retryHelper,
+        PluginSetContext<ExceptionHook> exceptionHooks) {
       this.currentUser = currentUser;
       this.webSession = webSession;
       this.paramParser = paramParser;
@@ -273,6 +277,7 @@
       this.performanceLoggers = performanceLoggers;
       this.changeFinder = changeFinder;
       this.retryHelper = retryHelper;
+      this.exceptionHooks = exceptionHooks;
       allowOrigin = makeAllowOrigin(config);
     }
 
@@ -287,7 +292,6 @@
 
   private final Globals globals;
   private final Provider<RestCollection<RestResource, RestResource>> members;
-  private Optional<String> traceId = Optional.empty();
 
   public RestApiServlet(
       Globals globals, RestCollection<? extends RestResource, ? extends RestResource> members) {
@@ -313,6 +317,7 @@
     res.setHeader("X-Content-Type-Options", "nosniff");
     int status = SC_OK;
     long responseBytes = -1;
+    Optional<Exception> cause = Optional.empty();
     Response<?> response = null;
     QueryParams qp = null;
     Object inputRequestBody = null;
@@ -594,22 +599,28 @@
           }
         }
       } catch (MalformedJsonException | JsonParseException e) {
+        cause = Optional.of(e);
         responseBytes =
             replyError(
                 req, res, status = SC_BAD_REQUEST, "Invalid " + JSON_TYPE + " in request", e);
       } catch (BadRequestException e) {
+        cause = Optional.of(e);
         responseBytes =
             replyError(
                 req, res, status = SC_BAD_REQUEST, messageOr(e, "Bad Request"), e.caching(), e);
       } catch (AuthException e) {
+        cause = Optional.of(e);
         responseBytes =
             replyError(req, res, status = SC_FORBIDDEN, messageOr(e, "Forbidden"), e.caching(), e);
       } catch (AmbiguousViewException e) {
+        cause = Optional.of(e);
         responseBytes = replyError(req, res, status = SC_NOT_FOUND, messageOr(e, "Ambiguous"), e);
       } catch (ResourceNotFoundException e) {
+        cause = Optional.of(e);
         responseBytes =
             replyError(req, res, status = SC_NOT_FOUND, messageOr(e, "Not Found"), e.caching(), e);
       } catch (MethodNotAllowedException e) {
+        cause = Optional.of(e);
         responseBytes =
             replyError(
                 req,
@@ -619,9 +630,11 @@
                 e.caching(),
                 e);
       } catch (ResourceConflictException e) {
+        cause = Optional.of(e);
         responseBytes =
             replyError(req, res, status = SC_CONFLICT, messageOr(e, "Conflict"), e.caching(), e);
       } catch (PreconditionFailedException e) {
+        cause = Optional.of(e);
         responseBytes =
             replyError(
                 req,
@@ -631,6 +644,7 @@
                 e.caching(),
                 e);
       } catch (UnprocessableEntityException e) {
+        cause = Optional.of(e);
         responseBytes =
             replyError(
                 req,
@@ -640,19 +654,22 @@
                 e.caching(),
                 e);
       } catch (NotImplementedException e) {
+        cause = Optional.of(e);
         logger.atSevere().withCause(e).log("Error in %s %s", req.getMethod(), uriForLogging(req));
         responseBytes =
             replyError(req, res, status = SC_NOT_IMPLEMENTED, messageOr(e, "Not Implemented"), e);
       } catch (UpdateException e) {
+        cause = Optional.of(e);
         Throwable t = e.getCause();
         if (t instanceof LockFailureException) {
           logger.atSevere().withCause(t).log("Error in %s %s", req.getMethod(), uriForLogging(req));
           responseBytes = replyError(req, res, status = SC_SERVICE_UNAVAILABLE, "Lock failure", e);
         } else {
           status = SC_INTERNAL_SERVER_ERROR;
-          responseBytes = handleException(e, req, res);
+          responseBytes = handleException(traceContext, e, req, res);
         }
       } catch (QuotaException e) {
+        cause = Optional.of(e);
         responseBytes =
             replyError(
                 req,
@@ -662,13 +679,15 @@
                 e.caching(),
                 e);
       } catch (Exception e) {
+        cause = Optional.of(e);
         status = SC_INTERNAL_SERVER_ERROR;
-        responseBytes = handleException(e, req, res);
+        responseBytes = handleException(traceContext, e, req, res);
       } finally {
         String metric = getViewName(viewData);
+        String formattedCause = cause.map(globals.retryHelper::formatCause).orElse("_none");
         globals.metrics.count.increment(metric);
         if (status >= SC_BAD_REQUEST) {
-          globals.metrics.errorCount.increment(metric, status);
+          globals.metrics.errorCount.increment(metric, status, formattedCause);
         }
         if (responseBytes != -1) {
           globals.metrics.responseBytes.record(metric, responseBytes);
@@ -704,8 +723,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 +738,7 @@
         traceContext,
         getViewName(viewData),
         ActionType.REST_READ_REQUEST,
-        () -> view.apply(rsrc),
-        noRetry());
+        () -> view.apply(rsrc));
   }
 
   private Response<?> invokeRestModifyViewWithRetry(
@@ -737,8 +754,7 @@
         traceContext,
         getViewName(viewData),
         ActionType.REST_WRITE_REQUEST,
-        () -> view.apply(rsrc, inputRequestBody),
-        retryOnLockFailure());
+        () -> view.apply(rsrc, inputRequestBody));
   }
 
   private Response<?> invokeRestCollectionCreateViewWithRetry(
@@ -755,8 +771,7 @@
         traceContext,
         getViewName(viewData),
         ActionType.REST_WRITE_REQUEST,
-        () -> view.apply(rsrc, path, inputRequestBody),
-        retryOnLockFailure());
+        () -> view.apply(rsrc, path, inputRequestBody));
   }
 
   private Response<?> invokeRestCollectionDeleteMissingViewWithRetry(
@@ -773,8 +788,7 @@
         traceContext,
         getViewName(viewData),
         ActionType.REST_WRITE_REQUEST,
-        () -> view.apply(rsrc, path, inputRequestBody),
-        retryOnLockFailure());
+        () -> view.apply(rsrc, path, inputRequestBody));
   }
 
   private Response<?> invokeRestCollectionModifyViewWithRetry(
@@ -790,8 +804,7 @@
         traceContext,
         getViewName(viewData),
         ActionType.REST_WRITE_REQUEST,
-        () -> view.apply(rsrc, inputRequestBody),
-        retryOnLockFailure());
+        () -> view.apply(rsrc, inputRequestBody));
   }
 
   private <T> T invokeRestEndpointWithRetry(
@@ -799,9 +812,9 @@
       TraceContext traceContext,
       String caller,
       ActionType actionType,
-      Action<T> action,
-      Predicate<Throwable> retryExceptionPredicate)
+      Action<T> action)
       throws Exception {
+    AtomicReference<Optional<String>> traceId = new AtomicReference<>(Optional.empty());
     RetryHelper.Options.Builder retryOptionsBuilder = RetryHelper.options().caller(caller);
     if (!traceContext.isTracing()) {
       // enable automatic retry with tracing in case of non-recoverable failure
@@ -810,36 +823,28 @@
               .retryWithTrace(t -> !(t instanceof RestApiException))
               .onAutoTrace(
                   autoTraceId -> {
-                    traceId = Optional.of(autoTraceId);
+                    traceId.set(Optional.of(autoTraceId));
 
                     // Include details of the request into the trace.
                     traceRequestData(req);
                   });
     }
     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
       // associated with the same trace ID.
-      traceId.ifPresent(tid -> traceContext.addTag(RequestId.Type.TRACE_ID, tid).forceLogging());
+      traceId
+          .get()
+          .ifPresent(tid -> traceContext.addTag(RequestId.Type.TRACE_ID, tid).forceLogging());
     }
   }
 
-  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";
   }
@@ -1658,13 +1663,25 @@
     }
   }
 
-  private long handleException(Throwable err, HttpServletRequest req, HttpServletResponse res)
+  private long handleException(
+      TraceContext traceContext, Throwable err, HttpServletRequest req, HttpServletResponse res)
       throws IOException {
     logger.atSevere().withCause(err).log("Error in %s %s", req.getMethod(), uriForLogging(req));
     if (!res.isCommitted()) {
       res.reset();
-      traceId.ifPresent(traceId -> res.addHeader(X_GERRIT_TRACE, traceId));
-      return replyError(req, res, SC_INTERNAL_SERVER_ERROR, "Internal server error", err);
+      traceContext.getTraceId().ifPresent(traceId -> res.addHeader(X_GERRIT_TRACE, traceId));
+      StringBuilder msg = new StringBuilder("Internal server error");
+      ImmutableList<String> userMessages =
+          globals.exceptionHooks.stream()
+              .map(h -> h.getUserMessage(err))
+              .filter(Optional::isPresent)
+              .map(Optional::get)
+              .collect(toImmutableList());
+      if (!userMessages.isEmpty()) {
+        msg.append("\n");
+        userMessages.forEach(m -> msg.append("\n* ").append(m));
+      }
+      return replyError(req, res, SC_INTERNAL_SERVER_ERROR, msg.toString(), err);
     }
     return 0;
   }
diff --git a/java/com/google/gerrit/index/Schema.java b/java/com/google/gerrit/index/Schema.java
index f9f8c48..0aa374b 100644
--- a/java/com/google/gerrit/index/Schema.java
+++ b/java/com/google/gerrit/index/Schema.java
@@ -15,11 +15,12 @@
 package com.google.gerrit.index;
 
 import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.collect.ImmutableList.toImmutableList;
 
 import com.google.common.base.MoreObjects;
-import com.google.common.collect.FluentIterable;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.flogger.FluentLogger;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -181,12 +182,17 @@
    * <p>Null values are omitted, as are fields which cause errors, which are logged.
    *
    * @param obj input object.
+   * @param skipFields set of field names to skip when indexing the document
    * @return all non-null field values from the object.
    */
-  public final Iterable<Values<T>> buildFields(T obj) {
-    return FluentIterable.from(fields.values())
-        .transform(
+  public final Iterable<Values<T>> buildFields(T obj, ImmutableSet<String> skipFields) {
+    return fields.values().stream()
+        .map(
             f -> {
+              if (skipFields.contains(f.getName())) {
+                return null;
+              }
+
               Object v;
               try {
                 v = f.get(obj);
@@ -203,7 +209,8 @@
                 return new Values<>(f, Collections.singleton(v));
               }
             })
-        .filter(Objects::nonNull);
+        .filter(Objects::nonNull)
+        .collect(toImmutableList());
   }
 
   @Override
diff --git a/java/com/google/gerrit/index/query/QueryProcessor.java b/java/com/google/gerrit/index/query/QueryProcessor.java
index 9501e52..c05516b 100644
--- a/java/com/google/gerrit/index/query/QueryProcessor.java
+++ b/java/com/google/gerrit/index/query/QueryProcessor.java
@@ -18,7 +18,7 @@
 import static com.google.common.base.Preconditions.checkState;
 import static com.google.common.collect.ImmutableList.toImmutableList;
 import static com.google.common.flogger.LazyArgs.lazy;
-import static java.util.stream.Collectors.toSet;
+import static java.util.stream.Collectors.toList;
 
 import com.google.common.base.Throwables;
 import com.google.common.collect.ImmutableList;
@@ -272,7 +272,7 @@
         ImmutableList<T> matchesList = matches.get(i).toList();
         logger.atFine().log(
             "Matches[%d]:\n%s",
-            i, lazy(() -> matchesList.stream().map(this::formatForLogging).collect(toSet())));
+            i, lazy(() -> matchesList.stream().map(this::formatForLogging).collect(toList())));
         out.add(
             QueryResult.create(
                 queryStrings != null ? queryStrings.get(i) : null,
diff --git a/java/com/google/gerrit/index/query/RangeUtil.java b/java/com/google/gerrit/index/query/RangeUtil.java
index 1f22f36..cfe1929 100644
--- a/java/com/google/gerrit/index/query/RangeUtil.java
+++ b/java/com/google/gerrit/index/query/RangeUtil.java
@@ -106,6 +106,10 @@
         break;
     }
 
+    // Ensure that minValue <= min/max <= maxValue.
+    min = Ints.constrainToRange(min, minValue, maxValue);
+    max = Ints.constrainToRange(max, minValue, maxValue);
+
     return new Range(prefix, min, max);
   }
 
diff --git a/java/com/google/gerrit/lucene/AbstractLuceneIndex.java b/java/com/google/gerrit/lucene/AbstractLuceneIndex.java
index deb3203..5392ab4 100644
--- a/java/com/google/gerrit/lucene/AbstractLuceneIndex.java
+++ b/java/com/google/gerrit/lucene/AbstractLuceneIndex.java
@@ -21,6 +21,7 @@
 import com.google.common.base.Joiner;
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Sets;
 import com.google.common.flogger.FluentLogger;
@@ -98,6 +99,7 @@
   private final SitePaths sitePaths;
   private final Directory dir;
   private final String name;
+  private final ImmutableSet<String> skipFields;
   private final ListeningExecutorService writerThread;
   private final IndexWriter writer;
   private final ReferenceManager<IndexSearcher> searcherManager;
@@ -110,6 +112,7 @@
       SitePaths sitePaths,
       Directory dir,
       String name,
+      ImmutableSet<String> skipFields,
       String subIndex,
       GerritIndexWriterConfig writerConfig,
       SearcherFactory searcherFactory)
@@ -118,6 +121,7 @@
     this.sitePaths = sitePaths;
     this.dir = dir;
     this.name = name;
+    this.skipFields = skipFields;
     String index = Joiner.on('_').skipNulls().join(name, subIndex);
     long commitPeriod = writerConfig.getCommitWithinMs();
 
@@ -311,7 +315,7 @@
 
   Document toDocument(V obj) {
     Document result = new Document();
-    for (Values<V> vs : schema.buildFields(obj)) {
+    for (Values<V> vs : schema.buildFields(obj, skipFields)) {
       if (vs.getValues() != null) {
         add(result, vs);
       }
diff --git a/java/com/google/gerrit/lucene/ChangeSubIndex.java b/java/com/google/gerrit/lucene/ChangeSubIndex.java
index fd439f1..e51a91a7 100644
--- a/java/com/google/gerrit/lucene/ChangeSubIndex.java
+++ b/java/com/google/gerrit/lucene/ChangeSubIndex.java
@@ -20,6 +20,7 @@
 import static com.google.gerrit.lucene.LuceneChangeIndex.UPDATED_SORT_FIELD;
 import static com.google.gerrit.server.index.change.ChangeSchemaDefinitions.NAME;
 
+import com.google.common.collect.ImmutableSet;
 import com.google.gerrit.entities.Change;
 import com.google.gerrit.index.FieldDef;
 import com.google.gerrit.index.QueryOptions;
@@ -48,6 +49,7 @@
       Schema<ChangeData> schema,
       SitePaths sitePaths,
       Path path,
+      ImmutableSet<String> skipFields,
       GerritIndexWriterConfig writerConfig,
       SearcherFactory searcherFactory)
       throws IOException {
@@ -56,6 +58,7 @@
         sitePaths,
         FSDirectory.open(path),
         path.getFileName().toString(),
+        skipFields,
         writerConfig,
         searcherFactory);
   }
@@ -65,10 +68,11 @@
       SitePaths sitePaths,
       Directory dir,
       String subIndex,
+      ImmutableSet<String> skipFields,
       GerritIndexWriterConfig writerConfig,
       SearcherFactory searcherFactory)
       throws IOException {
-    super(schema, sitePaths, dir, NAME, subIndex, writerConfig, searcherFactory);
+    super(schema, sitePaths, dir, NAME, skipFields, subIndex, writerConfig, searcherFactory);
   }
 
   @Override
diff --git a/java/com/google/gerrit/lucene/LuceneAccountIndex.java b/java/com/google/gerrit/lucene/LuceneAccountIndex.java
index efd7ea3..242cffd 100644
--- a/java/com/google/gerrit/lucene/LuceneAccountIndex.java
+++ b/java/com/google/gerrit/lucene/LuceneAccountIndex.java
@@ -20,6 +20,7 @@
 import static com.google.gerrit.server.index.account.AccountField.ID_STR;
 import static com.google.gerrit.server.index.account.AccountField.PREFERRED_EMAIL_EXACT;
 
+import com.google.common.collect.ImmutableSet;
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.exceptions.StorageException;
 import com.google.gerrit.index.FieldDef;
@@ -100,6 +101,7 @@
         sitePaths,
         dir(schema, cfg, sitePaths),
         ACCOUNTS,
+        ImmutableSet.of(),
         null,
         new GerritIndexWriterConfig(cfg, ACCOUNTS),
         new SearcherFactory());
diff --git a/java/com/google/gerrit/lucene/LuceneChangeIndex.java b/java/com/google/gerrit/lucene/LuceneChangeIndex.java
index 16d66b6..3b277dd 100644
--- a/java/com/google/gerrit/lucene/LuceneChangeIndex.java
+++ b/java/com/google/gerrit/lucene/LuceneChangeIndex.java
@@ -29,6 +29,7 @@
 import com.google.common.collect.Collections2;
 import com.google.common.collect.FluentIterable;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.MultimapBuilder;
@@ -167,6 +168,7 @@
   private final String idSortFieldName;
   private final IdTerm idTerm;
   private final ChangeIdExtractor extractor;
+  private final ImmutableSet<String> skipFields;
 
   @Inject
   LuceneChangeIndex(
@@ -179,6 +181,10 @@
     this.executor = executor;
     this.changeDataFactory = changeDataFactory;
     this.schema = schema;
+    this.skipFields =
+        cfg.getBoolean("index", "change", "indexMergeable", true)
+            ? ImmutableSet.of()
+            : ImmutableSet.of(ChangeField.MERGEABLE.getName());
 
     GerritIndexWriterConfig openConfig = new GerritIndexWriterConfig(cfg, "changes_open");
     GerritIndexWriterConfig closedConfig = new GerritIndexWriterConfig(cfg, "changes_closed");
@@ -189,18 +195,40 @@
     if (LuceneIndexModule.isInMemoryTest(cfg)) {
       openIndex =
           new ChangeSubIndex(
-              schema, sitePaths, new RAMDirectory(), "ramOpen", openConfig, searcherFactory);
+              schema,
+              sitePaths,
+              new RAMDirectory(),
+              "ramOpen",
+              skipFields,
+              openConfig,
+              searcherFactory);
       closedIndex =
           new ChangeSubIndex(
-              schema, sitePaths, new RAMDirectory(), "ramClosed", closedConfig, searcherFactory);
+              schema,
+              sitePaths,
+              new RAMDirectory(),
+              "ramClosed",
+              skipFields,
+              closedConfig,
+              searcherFactory);
     } else {
       Path dir = LuceneVersionManager.getDir(sitePaths, CHANGES, schema);
       openIndex =
           new ChangeSubIndex(
-              schema, sitePaths, dir.resolve(CHANGES_OPEN), openConfig, searcherFactory);
+              schema,
+              sitePaths,
+              dir.resolve(CHANGES_OPEN),
+              skipFields,
+              openConfig,
+              searcherFactory);
       closedIndex =
           new ChangeSubIndex(
-              schema, sitePaths, dir.resolve(CHANGES_CLOSED), closedConfig, searcherFactory);
+              schema,
+              sitePaths,
+              dir.resolve(CHANGES_CLOSED),
+              skipFields,
+              closedConfig,
+              searcherFactory);
     }
 
     idField = this.schema.useLegacyNumericFields() ? LEGACY_ID : LEGACY_ID_STR;
@@ -565,7 +593,7 @@
 
   private void decodeMergeable(ListMultimap<String, IndexableField> doc, ChangeData cd) {
     IndexableField f = Iterables.getFirst(doc.get(MERGEABLE_FIELD), null);
-    if (f != null) {
+    if (f != null && !skipFields.contains(MERGEABLE_FIELD)) {
       String mergeable = f.stringValue();
       if ("1".equals(mergeable)) {
         cd.setMergeable(true);
diff --git a/java/com/google/gerrit/lucene/LuceneGroupIndex.java b/java/com/google/gerrit/lucene/LuceneGroupIndex.java
index 99cd40d..3d1d471 100644
--- a/java/com/google/gerrit/lucene/LuceneGroupIndex.java
+++ b/java/com/google/gerrit/lucene/LuceneGroupIndex.java
@@ -17,6 +17,7 @@
 import static com.google.common.collect.Iterables.getOnlyElement;
 import static com.google.gerrit.server.index.group.GroupField.UUID;
 
+import com.google.common.collect.ImmutableSet;
 import com.google.gerrit.entities.AccountGroup;
 import com.google.gerrit.exceptions.StorageException;
 import com.google.gerrit.index.FieldDef;
@@ -90,6 +91,7 @@
         sitePaths,
         dir(schema, cfg, sitePaths),
         GROUPS,
+        ImmutableSet.of(),
         null,
         new GerritIndexWriterConfig(cfg, GROUPS),
         new SearcherFactory());
diff --git a/java/com/google/gerrit/lucene/LuceneProjectIndex.java b/java/com/google/gerrit/lucene/LuceneProjectIndex.java
index 97454c7..a3a0d9c 100644
--- a/java/com/google/gerrit/lucene/LuceneProjectIndex.java
+++ b/java/com/google/gerrit/lucene/LuceneProjectIndex.java
@@ -17,6 +17,7 @@
 import static com.google.common.collect.Iterables.getOnlyElement;
 import static com.google.gerrit.index.project.ProjectField.NAME;
 
+import com.google.common.collect.ImmutableSet;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.exceptions.StorageException;
 import com.google.gerrit.index.FieldDef;
@@ -90,6 +91,7 @@
         sitePaths,
         dir(schema, cfg, sitePaths),
         PROJECTS,
+        ImmutableSet.of(),
         null,
         new GerritIndexWriterConfig(cfg, PROJECTS),
         new SearcherFactory());
diff --git a/java/com/google/gerrit/pgm/init/AccountsOnInit.java b/java/com/google/gerrit/pgm/init/AccountsOnInit.java
index 4ffe942..536ddcd 100644
--- a/java/com/google/gerrit/pgm/init/AccountsOnInit.java
+++ b/java/com/google/gerrit/pgm/init/AccountsOnInit.java
@@ -14,9 +14,8 @@
 
 package com.google.gerrit.pgm.init;
 
-import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkState;
 import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Objects.requireNonNull;
 
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.entities.RefNames;
@@ -129,9 +128,10 @@
 
   private File getPath() {
     Path basePath = site.resolve(flags.cfg.getString("gerrit", null, "basePath"));
-    checkArgument(basePath != null, "gerrit.basePath must be configured");
-    File file = FileKey.resolve(basePath.resolve(allUsers).toFile(), FS.DETECTED);
-    checkState(file != null, "%s does not exist", file.getAbsolutePath());
-    return file;
+    requireNonNull(basePath, "gerrit.basePath must be configured");
+    File file = basePath.resolve(allUsers).toFile();
+    File resolvedFile = FileKey.resolve(file, FS.DETECTED);
+    requireNonNull(resolvedFile, () -> String.format("%s does not exist", file.getAbsolutePath()));
+    return resolvedFile;
   }
 }
diff --git a/java/com/google/gerrit/server/BUILD b/java/com/google/gerrit/server/BUILD
index dbaf9c3..45d037a 100644
--- a/java/com/google/gerrit/server/BUILD
+++ b/java/com/google/gerrit/server/BUILD
@@ -107,12 +107,12 @@
         "//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",
         "//lib/commons:net",
         "//lib/commons:validator",
+        "//lib/errorprone:annotations",
         "//lib/flogger:api",
         "//lib/guice",
         "//lib/guice:guice-assistedinject",
diff --git a/java/com/google/gerrit/server/CreateGroupPermissionSyncer.java b/java/com/google/gerrit/server/CreateGroupPermissionSyncer.java
index 996257c..5e7919f 100644
--- a/java/com/google/gerrit/server/CreateGroupPermissionSyncer.java
+++ b/java/com/google/gerrit/server/CreateGroupPermissionSyncer.java
@@ -115,11 +115,12 @@
                 .collect(toList()));
         config.replace(createGroupAccessSection);
       } else {
-        Permission createGroupPermission = new Permission(Permission.CREATE);
-        createGroupAccessSection.addPermission(createGroupPermission);
-        createGroupsGlobal.forEach(createGroupPermission::add);
         // The create permission is managed by Gerrit at this point only so there is no concern of
         // overwriting user-defined permissions here.
+        Permission createGroupPermission = new Permission(Permission.CREATE);
+        createGroupAccessSection.remove(createGroupPermission);
+        createGroupAccessSection.addPermission(createGroupPermission);
+        createGroupsGlobal.forEach(createGroupPermission::add);
         config.replace(createGroupAccessSection);
       }
 
diff --git a/java/com/google/gerrit/server/ExceptionHook.java b/java/com/google/gerrit/server/ExceptionHook.java
index 6f05814..db44b4b 100644
--- a/java/com/google/gerrit/server/ExceptionHook.java
+++ b/java/com/google/gerrit/server/ExceptionHook.java
@@ -53,4 +53,15 @@
   default Optional<String> formatCause(Throwable throwable) {
     return Optional.empty();
   }
+
+  /**
+   * Returns an error message that should be returned to the user.
+   *
+   * @param throwable throwable that was thrown while executing an operation
+   * @return error message that should be returned to the user, {@link Optional#empty()} if no
+   *     message should be returned to the user
+   */
+  default Optional<String> getUserMessage(Throwable throwable) {
+    return Optional.empty();
+  }
 }
diff --git a/java/com/google/gerrit/server/ExceptionHookImpl.java b/java/com/google/gerrit/server/ExceptionHookImpl.java
new file mode 100644
index 0000000..9613b29
--- /dev/null
+++ b/java/com/google/gerrit/server/ExceptionHookImpl.java
@@ -0,0 +1,68 @@
+// 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.common.base.Predicate;
+import com.google.common.base.Throwables;
+import com.google.gerrit.git.LockFailureException;
+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 {
+  private static final String LOCK_FAILURE_USER_MESSAGE =
+      "Updating a ref failed with LOCK_FAILURE.\n"
+          + "This may be a temporary issue due to concurrent updates.\n"
+          + "Please retry later.";
+
+  @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();
+  }
+
+  @Override
+  public Optional<String> getUserMessage(Throwable throwable) {
+    if (isLockFailure(throwable)) {
+      return Optional.of(LOCK_FAILURE_USER_MESSAGE);
+    }
+    return Optional.empty();
+  }
+
+  private static boolean isLockFailure(Throwable throwable) {
+    return isMatching(throwable, t -> t instanceof LockFailureException);
+  }
+
+  /**
+   * Check whether the given exception or any of its causes matches the given predicate.
+   *
+   * @param throwable Exception that should be tested
+   * @param predicate predicate to check if a throwable matches
+   * @return {@code true} if the given exception or any of its causes matches the given predicate
+   */
+  private static boolean isMatching(Throwable throwable, Predicate<Throwable> predicate) {
+    return Throwables.getCausalChain(throwable).stream().anyMatch(predicate);
+  }
+}
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/account/externalids/ExternalIdCacheLoader.java b/java/com/google/gerrit/server/account/externalids/ExternalIdCacheLoader.java
index 25420ee..8887e06 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalIdCacheLoader.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalIdCacheLoader.java
@@ -71,6 +71,7 @@
   private final Counter1<Boolean> reloadCounter;
   private final Timer0 reloadDifferential;
   private final boolean enablePartialReloads;
+  private final boolean isPersistentCache;
 
   @Inject
   ExternalIdCacheLoader(
@@ -101,6 +102,8 @@
                 .setUnit(Units.MILLISECONDS));
     this.enablePartialReloads =
         config.getBoolean("cache", ExternalIdCacheImpl.CACHE_NAME, "enablePartialReloads", true);
+    this.isPersistentCache =
+        config.getInt("cache", ExternalIdCacheImpl.CACHE_NAME, "diskLimit", 0) > 0;
   }
 
   @Override
@@ -156,8 +159,11 @@
         }
       }
       if (oldExternalIds == null) {
-        logger.atWarning().log(
-            "Unable to find an old ExternalId cache state, falling back to full reload");
+        if (isPersistentCache) {
+          // If there is no persistence, this is normal. Don't upset admins reading the logs.
+          logger.atWarning().log(
+              "Unable to find an old ExternalId cache state, falling back to full reload");
+        }
         return reloadAllExternalIds(notesRev);
       }
 
diff --git a/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java b/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
index d7ab91b..6d7fc15 100644
--- a/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
+++ b/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
@@ -43,6 +43,7 @@
 import com.google.gerrit.extensions.api.projects.ProjectInput;
 import com.google.gerrit.extensions.api.projects.TagApi;
 import com.google.gerrit.extensions.api.projects.TagInfo;
+import com.google.gerrit.extensions.common.BatchLabelInput;
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.common.Input;
 import com.google.gerrit.extensions.common.LabelDefinitionInfo;
@@ -77,6 +78,7 @@
 import com.google.gerrit.server.restapi.project.ListDashboards;
 import com.google.gerrit.server.restapi.project.ListLabels;
 import com.google.gerrit.server.restapi.project.ListTags;
+import com.google.gerrit.server.restapi.project.PostLabels;
 import com.google.gerrit.server.restapi.project.ProjectsCollection;
 import com.google.gerrit.server.restapi.project.PutConfig;
 import com.google.gerrit.server.restapi.project.PutDescription;
@@ -131,6 +133,7 @@
   private final Index index;
   private final IndexChanges indexChanges;
   private final Provider<ListLabels> listLabels;
+  private final PostLabels postLabels;
   private final LabelApiImpl.Factory labelApi;
 
   @AssistedInject
@@ -168,6 +171,7 @@
       Index index,
       IndexChanges indexChanges,
       Provider<ListLabels> listLabels,
+      PostLabels postLabels,
       LabelApiImpl.Factory labelApi,
       @Assisted ProjectResource project) {
     this(
@@ -205,6 +209,7 @@
         index,
         indexChanges,
         listLabels,
+        postLabels,
         labelApi,
         null);
   }
@@ -244,6 +249,7 @@
       Index index,
       IndexChanges indexChanges,
       Provider<ListLabels> listLabels,
+      PostLabels postLabels,
       LabelApiImpl.Factory labelApi,
       @Assisted String name) {
     this(
@@ -281,6 +287,7 @@
         index,
         indexChanges,
         listLabels,
+        postLabels,
         labelApi,
         name);
   }
@@ -320,6 +327,7 @@
       Index index,
       IndexChanges indexChanges,
       Provider<ListLabels> listLabels,
+      PostLabels postLabels,
       LabelApiImpl.Factory labelApi,
       String name) {
     this.permissionBackend = permissionBackend;
@@ -357,6 +365,7 @@
     this.index = index;
     this.indexChanges = indexChanges;
     this.listLabels = listLabels;
+    this.postLabels = postLabels;
     this.labelApi = labelApi;
   }
 
@@ -712,4 +721,13 @@
       throw asRestApiException("Cannot parse label", e);
     }
   }
+
+  @Override
+  public void labels(BatchLabelInput input) throws RestApiException {
+    try {
+      postLabels.apply(checkExists(), input);
+    } catch (Exception e) {
+      throw asRestApiException("Cannot update labels", e);
+    }
+  }
 }
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/cache/CacheBackend.java b/java/com/google/gerrit/server/cache/CacheBackend.java
new file mode 100644
index 0000000..ec9876f
--- /dev/null
+++ b/java/com/google/gerrit/server/cache/CacheBackend.java
@@ -0,0 +1,25 @@
+// 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.cache;
+
+/** Caffeine is used as default cache backend, but can be overridden with Guava backend. */
+public enum CacheBackend {
+  CAFFEINE,
+  GUAVA;
+
+  public boolean isLegacyBackend() {
+    return this == GUAVA;
+  }
+}
diff --git a/java/com/google/gerrit/server/cache/CacheModule.java b/java/com/google/gerrit/server/cache/CacheModule.java
index 2878624..0fdc6f5 100644
--- a/java/com/google/gerrit/server/cache/CacheModule.java
+++ b/java/com/google/gerrit/server/cache/CacheModule.java
@@ -68,7 +68,8 @@
    */
   protected <K, V> CacheBinding<K, V> cache(
       String name, TypeLiteral<K> keyType, TypeLiteral<V> valType) {
-    CacheProvider<K, V> m = new CacheProvider<>(this, name, keyType, valType);
+    CacheProvider<K, V> m =
+        new CacheProvider<>(this, name, keyType, valType, CacheBackend.CAFFEINE);
     bindCache(m, name, keyType, valType);
     return m;
   }
@@ -123,7 +124,20 @@
    */
   protected <K, V> PersistentCacheBinding<K, V> persist(
       String name, Class<K> keyType, Class<V> valType) {
-    return persist(name, TypeLiteral.get(keyType), TypeLiteral.get(valType));
+    return persist(name, TypeLiteral.get(keyType), TypeLiteral.get(valType), CacheBackend.CAFFEINE);
+  }
+
+  /**
+   * Declare a named in-memory/on-disk cache.
+   *
+   * @param <K> type of key used to lookup entries.
+   * @param <V> type of value stored by the cache.
+   * @param backend cache backend.
+   * @return binding to describe the cache.
+   */
+  protected <K, V> PersistentCacheBinding<K, V> persist(
+      String name, Class<K> keyType, Class<V> valType, CacheBackend backend) {
+    return persist(name, TypeLiteral.get(keyType), TypeLiteral.get(valType), backend);
   }
 
   /**
@@ -135,7 +149,7 @@
    */
   protected <K, V> PersistentCacheBinding<K, V> persist(
       String name, Class<K> keyType, TypeLiteral<V> valType) {
-    return persist(name, TypeLiteral.get(keyType), valType);
+    return persist(name, TypeLiteral.get(keyType), valType, CacheBackend.CAFFEINE);
   }
 
   /**
@@ -146,8 +160,9 @@
    * @return binding to describe the cache.
    */
   protected <K, V> PersistentCacheBinding<K, V> persist(
-      String name, TypeLiteral<K> keyType, TypeLiteral<V> valType) {
-    PersistentCacheProvider<K, V> m = new PersistentCacheProvider<>(this, name, keyType, valType);
+      String name, TypeLiteral<K> keyType, TypeLiteral<V> valType, CacheBackend backend) {
+    PersistentCacheProvider<K, V> m =
+        new PersistentCacheProvider<>(this, name, keyType, valType, backend);
     bindCache(m, name, keyType, valType);
 
     Type cacheDefType =
diff --git a/java/com/google/gerrit/server/cache/CacheProvider.java b/java/com/google/gerrit/server/cache/CacheProvider.java
index b1a9b91..fe4244c 100644
--- a/java/com/google/gerrit/server/cache/CacheProvider.java
+++ b/java/com/google/gerrit/server/cache/CacheProvider.java
@@ -30,6 +30,7 @@
 
 class CacheProvider<K, V> implements Provider<Cache<K, V>>, CacheBinding<K, V>, CacheDef<K, V> {
   private final CacheModule module;
+  private final CacheBackend backend;
   final String name;
   private final TypeLiteral<K> keyType;
   private final TypeLiteral<V> valType;
@@ -44,11 +45,17 @@
   private MemoryCacheFactory memoryCacheFactory;
   private boolean frozen;
 
-  CacheProvider(CacheModule module, String name, TypeLiteral<K> keyType, TypeLiteral<V> valType) {
+  CacheProvider(
+      CacheModule module,
+      String name,
+      TypeLiteral<K> keyType,
+      TypeLiteral<V> valType,
+      CacheBackend backend) {
     this.module = module;
     this.name = name;
     this.keyType = keyType;
     this.valType = valType;
+    this.backend = backend;
   }
 
   @Inject(optional = true)
@@ -159,7 +166,9 @@
   public Cache<K, V> get() {
     freeze();
     CacheLoader<K, V> ldr = loader();
-    return ldr != null ? memoryCacheFactory.build(this, ldr) : memoryCacheFactory.build(this);
+    return ldr != null
+        ? memoryCacheFactory.build(this, ldr, backend)
+        : memoryCacheFactory.build(this, backend);
   }
 
   protected void checkNotFrozen() {
diff --git a/java/com/google/gerrit/server/cache/MemoryCacheFactory.java b/java/com/google/gerrit/server/cache/MemoryCacheFactory.java
index fc55753..558380d 100644
--- a/java/com/google/gerrit/server/cache/MemoryCacheFactory.java
+++ b/java/com/google/gerrit/server/cache/MemoryCacheFactory.java
@@ -19,7 +19,8 @@
 import com.google.common.cache.LoadingCache;
 
 public interface MemoryCacheFactory {
-  <K, V> Cache<K, V> build(CacheDef<K, V> def);
+  <K, V> Cache<K, V> build(CacheDef<K, V> def, CacheBackend backend);
 
-  <K, V> LoadingCache<K, V> build(CacheDef<K, V> def, CacheLoader<K, V> loader);
+  <K, V> LoadingCache<K, V> build(
+      CacheDef<K, V> def, CacheLoader<K, V> loader, CacheBackend backend);
 }
diff --git a/java/com/google/gerrit/server/cache/PersistentCacheFactory.java b/java/com/google/gerrit/server/cache/PersistentCacheFactory.java
index 27fa9ca..93f91ef 100644
--- a/java/com/google/gerrit/server/cache/PersistentCacheFactory.java
+++ b/java/com/google/gerrit/server/cache/PersistentCacheFactory.java
@@ -19,9 +19,10 @@
 import com.google.common.cache.LoadingCache;
 
 public interface PersistentCacheFactory {
-  <K, V> Cache<K, V> build(PersistentCacheDef<K, V> def);
+  <K, V> Cache<K, V> build(PersistentCacheDef<K, V> def, CacheBackend backend);
 
-  <K, V> LoadingCache<K, V> build(PersistentCacheDef<K, V> def, CacheLoader<K, V> loader);
+  <K, V> LoadingCache<K, V> build(
+      PersistentCacheDef<K, V> def, CacheLoader<K, V> loader, CacheBackend backend);
 
   void onStop(String plugin);
 }
diff --git a/java/com/google/gerrit/server/cache/PersistentCacheProvider.java b/java/com/google/gerrit/server/cache/PersistentCacheProvider.java
index 59d66e3..4fc107f 100644
--- a/java/com/google/gerrit/server/cache/PersistentCacheProvider.java
+++ b/java/com/google/gerrit/server/cache/PersistentCacheProvider.java
@@ -30,6 +30,7 @@
 
 class PersistentCacheProvider<K, V> extends CacheProvider<K, V>
     implements Provider<Cache<K, V>>, PersistentCacheBinding<K, V>, PersistentCacheDef<K, V> {
+  private final CacheBackend backend;
   private int version;
   private long diskLimit;
   private CacheSerializer<K> keySerializer;
@@ -39,9 +40,19 @@
 
   PersistentCacheProvider(
       CacheModule module, String name, TypeLiteral<K> keyType, TypeLiteral<V> valType) {
-    super(module, name, keyType, valType);
+    this(module, name, keyType, valType, CacheBackend.CAFFEINE);
+  }
+
+  PersistentCacheProvider(
+      CacheModule module,
+      String name,
+      TypeLiteral<K> keyType,
+      TypeLiteral<V> valType,
+      CacheBackend backend) {
+    super(module, name, keyType, valType, backend);
     version = -1;
     diskLimit = 128 << 20;
+    this.backend = backend;
   }
 
   @Inject(optional = true)
@@ -130,8 +141,8 @@
     freeze();
     CacheLoader<K, V> ldr = loader();
     return ldr != null
-        ? persistentCacheFactory.build(this, ldr)
-        : persistentCacheFactory.build(this);
+        ? persistentCacheFactory.build(this, ldr, backend)
+        : persistentCacheFactory.build(this, backend);
   }
 
   private static <T> void checkSerializer(
diff --git a/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java b/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java
index af1228d..2b068aa 100644
--- a/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java
+++ b/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java
@@ -21,6 +21,7 @@
 import com.google.common.util.concurrent.ThreadFactoryBuilder;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.server.cache.CacheBackend;
 import com.google.gerrit.server.cache.MemoryCacheFactory;
 import com.google.gerrit.server.cache.PersistentCacheDef;
 import com.google.gerrit.server.cache.PersistentCacheFactory;
@@ -156,18 +157,21 @@
 
   @SuppressWarnings({"unchecked"})
   @Override
-  public <K, V> Cache<K, V> build(PersistentCacheDef<K, V> in) {
+  public <K, V> Cache<K, V> build(PersistentCacheDef<K, V> in, CacheBackend backend) {
     long limit = config.getLong("cache", in.configKey(), "diskLimit", in.diskLimit());
 
     if (cacheDir == null || limit <= 0) {
-      return memCacheFactory.build(in);
+      return memCacheFactory.build(in, backend);
     }
 
     H2CacheDefProxy<K, V> def = new H2CacheDefProxy<>(in);
     SqlStore<K, V> store = newSqlStore(def, limit);
     H2CacheImpl<K, V> cache =
         new H2CacheImpl<>(
-            executor, store, def.keyType(), (Cache<K, ValueHolder<V>>) memCacheFactory.build(def));
+            executor,
+            store,
+            def.keyType(),
+            (Cache<K, ValueHolder<V>>) memCacheFactory.build(def, backend));
     synchronized (caches) {
       caches.add(cache);
     }
@@ -176,11 +180,12 @@
 
   @SuppressWarnings("unchecked")
   @Override
-  public <K, V> LoadingCache<K, V> build(PersistentCacheDef<K, V> in, CacheLoader<K, V> loader) {
+  public <K, V> LoadingCache<K, V> build(
+      PersistentCacheDef<K, V> in, CacheLoader<K, V> loader, CacheBackend backend) {
     long limit = config.getLong("cache", in.configKey(), "diskLimit", in.diskLimit());
 
     if (cacheDir == null || limit <= 0) {
-      return memCacheFactory.build(in, loader);
+      return memCacheFactory.build(in, loader, backend);
     }
 
     H2CacheDefProxy<K, V> def = new H2CacheDefProxy<>(in);
@@ -188,7 +193,9 @@
     Cache<K, ValueHolder<V>> mem =
         (Cache<K, ValueHolder<V>>)
             memCacheFactory.build(
-                def, (CacheLoader<K, V>) new H2CacheImpl.Loader<>(executor, store, loader));
+                def,
+                (CacheLoader<K, V>) new H2CacheImpl.Loader<>(executor, store, loader),
+                backend);
     H2CacheImpl<K, V> cache = new H2CacheImpl<>(executor, store, def.keyType(), mem);
     synchronized (caches) {
       caches.add(cache);
diff --git a/java/com/google/gerrit/server/cache/mem/BUILD b/java/com/google/gerrit/server/cache/mem/BUILD
index d805e1f..a666df7 100644
--- a/java/com/google/gerrit/server/cache/mem/BUILD
+++ b/java/com/google/gerrit/server/cache/mem/BUILD
@@ -8,6 +8,8 @@
         "//java/com/google/gerrit/common:annotations",
         "//java/com/google/gerrit/extensions:api",
         "//java/com/google/gerrit/server",
+        "//lib:caffeine",
+        "//lib:caffeine-guava",
         "//lib:guava",
         "//lib:jgit",
         "//lib/guice",
diff --git a/java/com/google/gerrit/server/cache/mem/DefaultMemoryCacheFactory.java b/java/com/google/gerrit/server/cache/mem/DefaultMemoryCacheFactory.java
index f76b8db..9906b3d 100644
--- a/java/com/google/gerrit/server/cache/mem/DefaultMemoryCacheFactory.java
+++ b/java/com/google/gerrit/server/cache/mem/DefaultMemoryCacheFactory.java
@@ -17,13 +17,18 @@
 import static java.util.concurrent.TimeUnit.NANOSECONDS;
 import static java.util.concurrent.TimeUnit.SECONDS;
 
+import com.github.benmanes.caffeine.cache.Caffeine;
+import com.github.benmanes.caffeine.cache.RemovalListener;
+import com.github.benmanes.caffeine.cache.Weigher;
+import com.github.benmanes.caffeine.guava.CaffeinatedGuava;
 import com.google.common.base.Strings;
 import com.google.common.cache.Cache;
 import com.google.common.cache.CacheBuilder;
 import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
-import com.google.common.cache.Weigher;
+import com.google.common.cache.RemovalNotification;
 import com.google.gerrit.common.Nullable;
+import com.google.gerrit.server.cache.CacheBackend;
 import com.google.gerrit.server.cache.CacheDef;
 import com.google.gerrit.server.cache.ForwardingRemovalListener;
 import com.google.gerrit.server.cache.MemoryCacheFactory;
@@ -46,25 +51,30 @@
   }
 
   @Override
-  public <K, V> Cache<K, V> build(CacheDef<K, V> def) {
-    return create(def).build();
+  public <K, V> Cache<K, V> build(CacheDef<K, V> def, CacheBackend backend) {
+    return backend.isLegacyBackend()
+        ? createLegacy(def).build()
+        : CaffeinatedGuava.build(create(def));
   }
 
   @Override
-  public <K, V> LoadingCache<K, V> build(CacheDef<K, V> def, CacheLoader<K, V> loader) {
-    return create(def).build(loader);
+  public <K, V> LoadingCache<K, V> build(
+      CacheDef<K, V> def, CacheLoader<K, V> loader, CacheBackend backend) {
+    return backend.isLegacyBackend()
+        ? createLegacy(def).build(loader)
+        : CaffeinatedGuava.build(create(def), loader);
   }
 
   @SuppressWarnings("unchecked")
-  private <K, V> CacheBuilder<K, V> create(CacheDef<K, V> def) {
-    CacheBuilder<K, V> builder = newCacheBuilder();
+  private <K, V> CacheBuilder<K, V> createLegacy(CacheDef<K, V> def) {
+    CacheBuilder<K, V> builder = newLegacyCacheBuilder();
     builder.recordStats();
     builder.maximumWeight(
         cfg.getLong("cache", def.configKey(), "memoryLimit", def.maximumWeight()));
 
     builder = builder.removalListener(forwardingRemovalListenerFactory.create(def.name()));
 
-    Weigher<K, V> weigher = def.weigher();
+    com.google.common.cache.Weigher<K, V> weigher = def.weigher();
     if (weigher == null) {
       weigher = unitWeight();
     }
@@ -98,6 +108,42 @@
     return builder;
   }
 
+  private <K, V> Caffeine<K, V> create(CacheDef<K, V> def) {
+    Caffeine<K, V> builder = newCacheBuilder();
+    builder.recordStats();
+    builder.maximumWeight(
+        cfg.getLong("cache", def.configKey(), "memoryLimit", def.maximumWeight()));
+    builder = builder.removalListener(newRemovalListener(def.name()));
+    builder.weigher(newWeigher(def.weigher()));
+
+    Duration expireAfterWrite = def.expireAfterWrite();
+    if (has(def.configKey(), "maxAge")) {
+      builder.expireAfterWrite(
+          ConfigUtil.getTimeUnit(
+              cfg, "cache", def.configKey(), "maxAge", toSeconds(expireAfterWrite), SECONDS),
+          SECONDS);
+    } else if (expireAfterWrite != null) {
+      builder.expireAfterWrite(expireAfterWrite.toNanos(), NANOSECONDS);
+    }
+
+    Duration expireAfterAccess = def.expireFromMemoryAfterAccess();
+    if (has(def.configKey(), "expireFromMemoryAfterAccess")) {
+      builder.expireAfterAccess(
+          ConfigUtil.getTimeUnit(
+              cfg,
+              "cache",
+              def.configKey(),
+              "expireFromMemoryAfterAccess",
+              toSeconds(expireAfterAccess),
+              SECONDS),
+          SECONDS);
+    } else if (expireAfterAccess != null) {
+      builder.expireAfterAccess(expireAfterAccess.toNanos(), NANOSECONDS);
+    }
+
+    return builder;
+  }
+
   private static long toSeconds(@Nullable Duration duration) {
     return duration != null ? duration.getSeconds() : 0;
   }
@@ -107,11 +153,31 @@
   }
 
   @SuppressWarnings("unchecked")
-  private static <K, V> CacheBuilder<K, V> newCacheBuilder() {
+  private static <K, V> CacheBuilder<K, V> newLegacyCacheBuilder() {
     return (CacheBuilder<K, V>) CacheBuilder.newBuilder();
   }
 
-  private static <K, V> Weigher<K, V> unitWeight() {
+  private static <K, V> com.google.common.cache.Weigher<K, V> unitWeight() {
     return (key, value) -> 1;
   }
+
+  @SuppressWarnings("unchecked")
+  private static <K, V> Caffeine<K, V> newCacheBuilder() {
+    return (Caffeine<K, V>) Caffeine.newBuilder();
+  }
+
+  @SuppressWarnings("unchecked")
+  private <V, K> RemovalListener<K, V> newRemovalListener(String cacheName) {
+    return (k, v, cause) ->
+        forwardingRemovalListenerFactory
+            .create(cacheName)
+            .onRemoval(
+                RemovalNotification.create(
+                    k, v, com.google.common.cache.RemovalCause.valueOf(cause.name())));
+  }
+
+  private static <K, V> Weigher<K, V> newWeigher(
+      com.google.common.cache.Weigher<K, V> guavaWeigher) {
+    return guavaWeigher == null ? Weigher.singletonWeigher() : (k, v) -> guavaWeigher.weigh(k, v);
+  }
 }
diff --git a/java/com/google/gerrit/server/change/ChangeInserter.java b/java/com/google/gerrit/server/change/ChangeInserter.java
index 1bf5103..4263373 100644
--- a/java/com/google/gerrit/server/change/ChangeInserter.java
+++ b/java/com/google/gerrit/server/change/ChangeInserter.java
@@ -55,6 +55,7 @@
 import com.google.gerrit.server.git.GroupCollector;
 import com.google.gerrit.server.git.validators.CommitValidationException;
 import com.google.gerrit.server.git.validators.CommitValidators;
+import com.google.gerrit.server.logging.RequestId;
 import com.google.gerrit.server.mail.send.CreateChangeSender;
 import com.google.gerrit.server.notedb.ChangeUpdate;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
@@ -386,7 +387,7 @@
         psUtil.insert(
             ctx.getRevWalk(), update, psId, commitId, newGroups, pushCert, patchSetDescription);
 
-    /* TODO: fixStatus is used here because the tests
+    /* TODO: fixStatusToMerged is used here because the tests
      * (byStatusClosed() in AbstractQueryChangesTest)
      * insert changes that are already merged,
      * and setStatus may not be used to set the Status to merged
@@ -394,7 +395,11 @@
      * is it possible to make the tests use the merge code path,
      * instead of setting the status directly?
      */
-    update.fixStatus(change.getStatus());
+    if (change.getStatus() == Change.Status.MERGED) {
+      update.fixStatusToMerged(new RequestId(ctx.getChange().getId().toString()));
+    } else {
+      update.setStatus(change.getStatus());
+    }
 
     reviewerAdditions =
         reviewerAdder.prepare(ctx.getNotes(), ctx.getUser(), getReviewerInputs(), true);
diff --git a/java/com/google/gerrit/server/change/ChangeJson.java b/java/com/google/gerrit/server/change/ChangeJson.java
index 9937fd0..21ee28a 100644
--- a/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/java/com/google/gerrit/server/change/ChangeJson.java
@@ -138,6 +138,7 @@
           COMMIT_FOOTERS,
           CURRENT_ACTIONS,
           CURRENT_COMMIT,
+          DETAILED_LABELS, // may need to load ChangeNotes to check remove reviewer permissions
           MESSAGES);
 
   @Singleton
diff --git a/java/com/google/gerrit/server/change/ConsistencyChecker.java b/java/com/google/gerrit/server/change/ConsistencyChecker.java
index 0374a1c..19db5ee 100644
--- a/java/com/google/gerrit/server/change/ConsistencyChecker.java
+++ b/java/com/google/gerrit/server/change/ConsistencyChecker.java
@@ -44,6 +44,7 @@
 import com.google.gerrit.server.PatchSetUtil;
 import com.google.gerrit.server.account.Accounts;
 import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.logging.RequestId;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.notedb.PatchSetState;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
@@ -576,7 +577,8 @@
     @Override
     public boolean updateChange(ChangeContext ctx) {
       ctx.getChange().setStatus(Change.Status.MERGED);
-      ctx.getUpdate(ctx.getChange().currentPatchSetId()).fixStatus(Change.Status.MERGED);
+      ctx.getUpdate(ctx.getChange().currentPatchSetId())
+          .fixStatusToMerged(new RequestId(ctx.getChange().getId().toString()));
       p.status = Status.FIXED;
       p.outcome = "Marked change as merged";
       return true;
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/git/MergedByPushOp.java b/java/com/google/gerrit/server/git/MergedByPushOp.java
index 858a55a..9aebebf 100644
--- a/java/com/google/gerrit/server/git/MergedByPushOp.java
+++ b/java/com/google/gerrit/server/git/MergedByPushOp.java
@@ -26,6 +26,7 @@
 import com.google.gerrit.server.PatchSetUtil;
 import com.google.gerrit.server.config.SendEmailExecutor;
 import com.google.gerrit.server.extensions.events.ChangeMerged;
+import com.google.gerrit.server.logging.RequestId;
 import com.google.gerrit.server.mail.send.MergedSender;
 import com.google.gerrit.server.notedb.ChangeUpdate;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
@@ -51,6 +52,7 @@
     MergedByPushOp create(
         RequestScopePropagator requestScopePropagator,
         PatchSet.Id psId,
+        @Assisted RequestId submissionId,
         @Assisted("refName") String refName,
         @Assisted("mergeResultRevId") String mergeResultRevId);
   }
@@ -64,6 +66,7 @@
   private final ChangeMerged changeMerged;
 
   private final PatchSet.Id psId;
+  private final RequestId submissionId;
   private final String refName;
   private final String mergeResultRevId;
 
@@ -83,6 +86,7 @@
       ChangeMerged changeMerged,
       @Assisted RequestScopePropagator requestScopePropagator,
       @Assisted PatchSet.Id psId,
+      @Assisted RequestId submissionId,
       @Assisted("refName") String refName,
       @Assisted("mergeResultRevId") String mergeResultRevId) {
     this.patchSetInfoFactory = patchSetInfoFactory;
@@ -92,6 +96,7 @@
     this.sendEmailExecutor = sendEmailExecutor;
     this.changeMerged = changeMerged;
     this.requestScopePropagator = requestScopePropagator;
+    this.submissionId = submissionId;
     this.psId = psId;
     this.refName = refName;
     this.mergeResultRevId = mergeResultRevId;
@@ -132,9 +137,10 @@
     }
     change.setCurrentPatchSet(info);
     change.setStatus(Change.Status.MERGED);
+    change.setSubmissionId(submissionId.toStringForStorage());
     // we cannot reconstruct the submit records for when this change was
-    // submitted, this is why we must fix the status
-    update.fixStatus(Change.Status.MERGED);
+    // submitted, this is why we must fix the status and other details.
+    update.fixStatusToMerged(submissionId);
     update.setCurrentPatchSet();
     if (change.isWorkInProgress()) {
       change.setWorkInProgress(false);
diff --git a/java/com/google/gerrit/server/git/TracingHook.java b/java/com/google/gerrit/server/git/TracingHook.java
index 63d8bc6..56eded0 100644
--- a/java/com/google/gerrit/server/git/TracingHook.java
+++ b/java/com/google/gerrit/server/git/TracingHook.java
@@ -14,8 +14,6 @@
 
 package com.google.gerrit.server.git;
 
-import static com.google.common.base.Preconditions.checkState;
-
 import com.google.gerrit.server.logging.TraceContext;
 import java.util.List;
 import java.util.Optional;
@@ -59,7 +57,10 @@
    * @param serverOptionList list of provided server options
    */
   private void maybeStartTrace(List<String> serverOptionList) {
-    checkState(traceContext == null, "Trace was already started.");
+    if (traceContext != null) {
+      // Trace was already started
+      return;
+    }
 
     Optional<String> traceOption = parseTraceOption(serverOptionList);
     traceContext =
diff --git a/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java b/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java
index 7038736..7767fe2 100644
--- a/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java
@@ -310,7 +310,8 @@
 
     allRefsWatcher = new AllRefsWatcher();
     receivePack.setAdvertiseRefsHook(
-        ReceiveCommitsAdvertiseRefsHookChain.create(allRefsWatcher, queryProvider, projectName));
+        ReceiveCommitsAdvertiseRefsHookChain.create(
+            allRefsWatcher, queryProvider, projectName, user.getAccountId()));
     resultChangeIds = new ResultChangeIds();
     receiveCommits =
         factory.create(
diff --git a/java/com/google/gerrit/server/git/receive/BUILD b/java/com/google/gerrit/server/git/receive/BUILD
index 2b04d4d..7402a37 100644
--- a/java/com/google/gerrit/server/git/receive/BUILD
+++ b/java/com/google/gerrit/server/git/receive/BUILD
@@ -15,6 +15,7 @@
         "//java/com/google/gerrit/exceptions",
         "//java/com/google/gerrit/extensions:api",
         "//java/com/google/gerrit/git",
+        "//java/com/google/gerrit/index",
         "//java/com/google/gerrit/metrics",
         "//java/com/google/gerrit/server",
         "//java/com/google/gerrit/server/logging",
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
index 7dd21e1..cec9e4e 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
@@ -3281,6 +3281,7 @@
 
                 int existingPatchSets = 0;
                 int newPatchSets = 0;
+                RequestId submissionId = null;
                 COMMIT:
                 for (RevCommit c; (c = rw.next()) != null; ) {
                   rw.parseBody(c);
@@ -3289,13 +3290,20 @@
                       receivePackRefCache.tipsFromObjectId(c.copy(), RefNames.REFS_CHANGES)) {
                     PatchSet.Id psId = PatchSet.Id.fromRef(ref.getName());
                     Optional<ChangeNotes> notes = getChangeNotes(psId.changeId());
+                    if (submissionId == null) {
+                      submissionId = new RequestId(psId.changeId().toString());
+                    }
                     if (notes.isPresent() && notes.get().getChange().getDest().equals(branch)) {
                       existingPatchSets++;
                       bu.addOp(notes.get().getChangeId(), setPrivateOpFactory.create(false, null));
                       bu.addOp(
                           psId.changeId(),
                           mergedByPushOpFactory.create(
-                              requestScopePropagator, psId, refName, newTip.getId().getName()));
+                              requestScopePropagator,
+                              psId,
+                              submissionId,
+                              refName,
+                              newTip.getId().getName()));
                       continue COMMIT;
                     }
                   }
@@ -3324,13 +3332,20 @@
                     logger.atFine().log("Not closing %s because validation failed", id);
                     continue;
                   }
+                  if (submissionId == null) {
+                    submissionId = new RequestId(id.toString());
+                  }
                   req.addOps(bu, null);
                   bu.addOp(id, setPrivateOpFactory.create(false, null));
                   bu.addOp(
                       id,
                       mergedByPushOpFactory
                           .create(
-                              requestScopePropagator, req.psId, refName, newTip.getId().getName())
+                              requestScopePropagator,
+                              req.psId,
+                              submissionId,
+                              refName,
+                              newTip.getId().getName())
                           .setPatchSetProvider(req.replaceOp::getPatchSet));
                   bu.addOp(id, new ChangeProgressOp(progress));
                   ids.add(id);
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveCommitsAdvertiseRefsHook.java b/java/com/google/gerrit/server/git/receive/ReceiveCommitsAdvertiseRefsHook.java
index 83bf554..6c1f097 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveCommitsAdvertiseRefsHook.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveCommitsAdvertiseRefsHook.java
@@ -18,14 +18,19 @@
 
 import com.google.common.collect.Sets;
 import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Account;
 import com.google.gerrit.entities.PatchSet;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.exceptions.StorageException;
+import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.server.git.HookUtil;
 import com.google.gerrit.server.index.change.ChangeField;
 import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.ChangeStatusPredicate;
 import com.google.gerrit.server.query.change.InternalChangeQuery;
+import com.google.gerrit.server.query.change.OwnerPredicate;
+import com.google.gerrit.server.query.change.ProjectPredicate;
 import com.google.gerrit.server.util.MagicBranch;
 import com.google.inject.Provider;
 import java.io.IOException;
@@ -65,11 +70,13 @@
 
   private final Provider<InternalChangeQuery> queryProvider;
   private final Project.NameKey projectName;
+  private final Account.Id user;
 
   public ReceiveCommitsAdvertiseRefsHook(
-      Provider<InternalChangeQuery> queryProvider, Project.NameKey projectName) {
+      Provider<InternalChangeQuery> queryProvider, Project.NameKey projectName, Account.Id user) {
     this.queryProvider = queryProvider;
     this.projectName = projectName;
+    this.user = user;
   }
 
   @Override
@@ -90,7 +97,9 @@
 
   private Set<ObjectId> advertiseOpenChanges(Repository repo)
       throws ServiceMayNotContinueException {
-    // Advertise some recent open changes, in case a commit is based on one.
+    // Advertise the user's most recent open changes. It's likely that the user has one of these in
+    // their local repo and they can serve as starting points to figure out the common ancestor of
+    // what the client and server have in common.
     int limit = 32;
     try {
       Set<ObjectId> r = Sets.newHashSetWithExpectedSize(limit);
@@ -105,7 +114,11 @@
                   ChangeField.PATCH_SET)
               .enforceVisibility(true)
               .setLimit(limit)
-              .byProjectOpen(projectName)) {
+              .query(
+                  Predicate.and(
+                      new ProjectPredicate(projectName.get()),
+                      ChangeStatusPredicate.open(),
+                      new OwnerPredicate(user)))) {
         PatchSet ps = cd.currentPatchSet();
         if (ps != null) {
           // Ensure we actually observed a patch set ref pointing to this
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveCommitsAdvertiseRefsHookChain.java b/java/com/google/gerrit/server/git/receive/ReceiveCommitsAdvertiseRefsHookChain.java
index 76f6b04..fae1401 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveCommitsAdvertiseRefsHookChain.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveCommitsAdvertiseRefsHookChain.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.server.git.receive;
 
 import com.google.common.annotations.VisibleForTesting;
+import com.google.gerrit.entities.Account;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.server.query.change.InternalChangeQuery;
 import com.google.inject.Provider;
@@ -35,8 +36,9 @@
   public static AdvertiseRefsHook create(
       AllRefsWatcher allRefsWatcher,
       Provider<InternalChangeQuery> queryProvider,
-      Project.NameKey projectName) {
-    return create(allRefsWatcher, queryProvider, projectName, false);
+      Project.NameKey projectName,
+      Account.Id user) {
+    return create(allRefsWatcher, queryProvider, projectName, user, false);
   }
 
   /**
@@ -47,18 +49,19 @@
    */
   @VisibleForTesting
   public static AdvertiseRefsHook createForTest(
-      Provider<InternalChangeQuery> queryProvider, Project.NameKey projectName) {
-    return create(new AllRefsWatcher(), queryProvider, projectName, true);
+      Provider<InternalChangeQuery> queryProvider, Project.NameKey projectName, Account.Id user) {
+    return create(new AllRefsWatcher(), queryProvider, projectName, user, true);
   }
 
   private static AdvertiseRefsHook create(
       AllRefsWatcher allRefsWatcher,
       Provider<InternalChangeQuery> queryProvider,
       Project.NameKey projectName,
+      Account.Id user,
       boolean skipHackPushNegotiateHook) {
     List<AdvertiseRefsHook> advHooks = new ArrayList<>();
     advHooks.add(allRefsWatcher);
-    advHooks.add(new ReceiveCommitsAdvertiseRefsHook(queryProvider, projectName));
+    advHooks.add(new ReceiveCommitsAdvertiseRefsHook(queryProvider, projectName, user));
     if (!skipHackPushNegotiateHook) {
       advHooks.add(new HackPushNegotiateHook());
     }
diff --git a/java/com/google/gerrit/server/git/receive/ReplaceOp.java b/java/com/google/gerrit/server/git/receive/ReplaceOp.java
index e95cf3b..6c0d5d3 100644
--- a/java/com/google/gerrit/server/git/receive/ReplaceOp.java
+++ b/java/com/google/gerrit/server/git/receive/ReplaceOp.java
@@ -61,6 +61,7 @@
 import com.google.gerrit.server.extensions.events.RevisionCreated;
 import com.google.gerrit.server.git.MergedByPushOp;
 import com.google.gerrit.server.git.receive.ReceiveCommits.MagicBranchInput;
+import com.google.gerrit.server.logging.RequestId;
 import com.google.gerrit.server.mail.MailUtil.MailRecipients;
 import com.google.gerrit.server.mail.send.ReplacePatchSetSender;
 import com.google.gerrit.server.notedb.ChangeNotes;
@@ -236,7 +237,11 @@
       if (mergedInto != null) {
         mergedByPushOp =
             mergedByPushOpFactory.create(
-                requestScopePropagator, patchSetId, mergedInto, mergeResultRevId);
+                requestScopePropagator,
+                patchSetId,
+                new RequestId(patchSetId.changeId().toString()),
+                mergedInto,
+                mergeResultRevId);
       }
     }
 
diff --git a/java/com/google/gerrit/server/index/StalenessCheckResult.java b/java/com/google/gerrit/server/index/StalenessCheckResult.java
index cd3f592..fe35e6e 100644
--- a/java/com/google/gerrit/server/index/StalenessCheckResult.java
+++ b/java/com/google/gerrit/server/index/StalenessCheckResult.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.server.index;
 
 import com.google.auto.value.AutoValue;
+import com.google.errorprone.annotations.FormatMethod;
 import java.util.Optional;
 
 /** Structured result of a staleness check. */
@@ -29,6 +30,7 @@
     return new AutoValue_StalenessCheckResult(true, Optional.of(reason));
   }
 
+  @FormatMethod
   public static StalenessCheckResult stale(String reason, Object... args) {
     return stale(String.format(reason, args));
   }
diff --git a/java/com/google/gerrit/server/index/change/ReindexAfterRefUpdate.java b/java/com/google/gerrit/server/index/change/ReindexAfterRefUpdate.java
index f6d3b6f..5efa065 100644
--- a/java/com/google/gerrit/server/index/change/ReindexAfterRefUpdate.java
+++ b/java/com/google/gerrit/server/index/change/ReindexAfterRefUpdate.java
@@ -76,7 +76,7 @@
     this.accountCache = accountCache;
     this.indexer = indexer;
     this.executor = executor;
-    this.enabled = cfg.getBoolean("index", null, "reindexAfterRefUpdate", true);
+    this.enabled = cfg.getBoolean("index", "change", "indexMergeable", true);
   }
 
   @Override
diff --git a/java/com/google/gerrit/server/logging/Metadata.java b/java/com/google/gerrit/server/logging/Metadata.java
index 64d156f..d312530 100644
--- a/java/com/google/gerrit/server/logging/Metadata.java
+++ b/java/com/google/gerrit/server/logging/Metadata.java
@@ -95,9 +95,6 @@
   // The version of a secondary index.
   public abstract Optional<Integer> indexVersion();
 
-  // The number of inputs to an operation, eg. Reachable.fromRefs.
-  public abstract Optional<Integer> inputSize();
-
   // The name of the implementation method.
   public abstract Optional<String> methodName();
 
@@ -303,8 +300,6 @@
 
     public abstract Builder indexVersion(int indexVersion);
 
-    public abstract Builder inputSize(int size);
-
     public abstract Builder methodName(@Nullable String methodName);
 
     public abstract Builder multiple(boolean multiple);
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/AbstractChangeNotes.java b/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
index 0acf20e..9a1ba35 100644
--- a/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
+++ b/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
@@ -76,6 +76,7 @@
     }
   }
 
+  /** An {@link AutoCloseable} for parsing a single commit into ChangeNotesCommits. */
   public static class LoadHandle implements AutoCloseable {
     private final Repository repo;
     private final ObjectId id;
diff --git a/java/com/google/gerrit/server/notedb/ChangeDraftUpdate.java b/java/com/google/gerrit/server/notedb/ChangeDraftUpdate.java
index 971b537..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,
@@ -206,9 +203,9 @@
       cache.get(k.commitId()).deleteComment(k.key());
     }
 
+    // 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();
@@ -219,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);
       }
@@ -232,20 +228,13 @@
       return NO_OP_UPDATE;
     }
 
-    // If we touched every revision and there are no comments left, tell the
+    // If there are no comments left, tell the
     // caller to delete the entire ref.
-    boolean touchedAllRevs = updatedCommits.equals(rnm.revisionNotes.keySet());
-    if (touchedAllRevs && !hasComments) {
+    if (!rnm.noteMap.iterator().hasNext()) {
       return null;
     }
 
     ObjectId treeId = rnm.noteMap.writeTree(ins);
-    if (!rnm.noteMap.iterator().hasNext()) {
-      logger.atSevere().log(
-          "building draft update without content: hasComments=%s "
-              + "touchedAllRevs=%s updateCommits=%d revisionNotes=%d",
-          hasComments, touchedAllRevs, updatedCommits.size(), rnm.revisionNotes.size());
-    }
     cb.setTreeId(treeId);
     return cb;
   }
diff --git a/java/com/google/gerrit/server/notedb/ChangeNotesCommit.java b/java/com/google/gerrit/server/notedb/ChangeNotesCommit.java
index 78f6afc..71cb8c9 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNotesCommit.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNotesCommit.java
@@ -43,6 +43,8 @@
  * </ul>
  */
 public class ChangeNotesCommit extends RevCommit {
+
+  /** A {@link RevWalk} producing {@link ChangeNotesCommit}s. */
   public static ChangeNotesRevWalk newRevWalk(Repository repo) {
     return new ChangeNotesRevWalk(repo);
   }
@@ -62,6 +64,7 @@
     };
   }
 
+  /** A {@link RevWalk} that creates {@link ChangeNotesCommit}s rather than {@link RevCommit}s */
   public static class ChangeNotesRevWalk extends RevWalk {
     private ChangeNotesRevWalk(Repository repo) {
       super(repo);
diff --git a/java/com/google/gerrit/server/notedb/ChangeRevisionNote.java b/java/com/google/gerrit/server/notedb/ChangeRevisionNote.java
index b6443f1..4e52093 100644
--- a/java/com/google/gerrit/server/notedb/ChangeRevisionNote.java
+++ b/java/com/google/gerrit/server/notedb/ChangeRevisionNote.java
@@ -28,6 +28,7 @@
 import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.util.MutableInteger;
 
+/** Implements the parsing of comment data, handling JSON decoding and push certificates. */
 class ChangeRevisionNote extends RevisionNote<Comment> {
   private final ChangeNoteJson noteJson;
   private final Comment.Status status;
diff --git a/java/com/google/gerrit/server/notedb/ChangeUpdate.java b/java/com/google/gerrit/server/notedb/ChangeUpdate.java
index c0cd173..02a4dcc 100644
--- a/java/com/google/gerrit/server/notedb/ChangeUpdate.java
+++ b/java/com/google/gerrit/server/notedb/ChangeUpdate.java
@@ -216,12 +216,14 @@
   }
 
   public void setStatus(Change.Status status) {
-    checkArgument(status != Change.Status.MERGED, "use merge(Iterable<SubmitRecord>)");
+    checkArgument(status != Change.Status.MERGED, "use merge(RequestId, Iterable<SubmitRecord>)");
     this.status = status;
   }
 
-  public void fixStatus(Change.Status status) {
-    this.status = status;
+  public void fixStatusToMerged(RequestId submissionId) {
+    checkArgument(submissionId != null, "submission id must be set for merged changes");
+    this.status = Change.Status.MERGED;
+    this.submissionId = submissionId.toStringForStorage();
   }
 
   public void putApproval(String label, short value) {
diff --git a/java/com/google/gerrit/server/notedb/RevisionNote.java b/java/com/google/gerrit/server/notedb/RevisionNote.java
index ff649a9..cd11e1b 100644
--- a/java/com/google/gerrit/server/notedb/RevisionNote.java
+++ b/java/com/google/gerrit/server/notedb/RevisionNote.java
@@ -26,6 +26,10 @@
 import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.util.MutableInteger;
 
+/**
+ * Data stored in a note, parsed on demand. The data type to parse into is a generic list of type T.
+ * The source of the data is a array of raw bytes
+ */
 @UsedAt(UsedAt.Project.PLUGIN_CHECKS)
 public abstract class RevisionNote<T> {
   static final int MAX_NOTE_SZ = 25 << 20;
@@ -64,6 +68,7 @@
     return entities;
   }
 
+  /** Reads the raw data, and delegates parsing to the {@link #parse(byte[], int)} method. */
   public void parse() throws IOException, ConfigInvalidException {
     raw = reader.open(noteId, OBJ_BLOB).getCachedBytes(MAX_NOTE_SZ);
     MutableInteger p = new MutableInteger();
diff --git a/java/com/google/gerrit/server/notedb/RevisionNoteBuilder.java b/java/com/google/gerrit/server/notedb/RevisionNoteBuilder.java
index e63737c..81273dc 100644
--- a/java/com/google/gerrit/server/notedb/RevisionNoteBuilder.java
+++ b/java/com/google/gerrit/server/notedb/RevisionNoteBuilder.java
@@ -36,6 +36,7 @@
 import org.eclipse.jgit.lib.ObjectId;
 
 class RevisionNoteBuilder {
+  /** Construct a new RevisionNoteMap, seeding it with an existing (immutable) RevisionNoteMap */
   static class Cache {
     private final RevisionNoteMap<? extends RevisionNote<? extends Comment>> revisionNoteMap;
     private final Map<ObjectId, RevisionNoteBuilder> builders;
diff --git a/java/com/google/gerrit/server/notedb/RevisionNoteMap.java b/java/com/google/gerrit/server/notedb/RevisionNoteMap.java
index 3e1bad1..cf16073 100644
--- a/java/com/google/gerrit/server/notedb/RevisionNoteMap.java
+++ b/java/com/google/gerrit/server/notedb/RevisionNoteMap.java
@@ -25,8 +25,16 @@
 import org.eclipse.jgit.notes.Note;
 import org.eclipse.jgit.notes.NoteMap;
 
+/**
+ * A utility class that parses a NoteMap into commit => comment list data.
+ *
+ * @param <T> the RevisionNote for the comment type.
+ */
 class RevisionNoteMap<T extends RevisionNote<? extends Comment>> {
+  // CommitID => blob ID
   final NoteMap noteMap;
+
+  // CommitID => parsed data, immutable map.
   final ImmutableMap<ObjectId, T> revisionNotes;
 
   static RevisionNoteMap<ChangeRevisionNote> parse(
@@ -36,6 +44,7 @@
     for (Note note : noteMap) {
       ChangeRevisionNote rn = new ChangeRevisionNote(noteJson, reader, note.getData(), status);
       rn.parse();
+
       result.put(note.copy(), rn);
     }
     return new RevisionNoteMap<>(noteMap, ImmutableMap.copyOf(result));
diff --git a/java/com/google/gerrit/server/notedb/RobotCommentsRevisionNote.java b/java/com/google/gerrit/server/notedb/RobotCommentsRevisionNote.java
index 97a8ad4..fc4c9fd 100644
--- a/java/com/google/gerrit/server/notedb/RobotCommentsRevisionNote.java
+++ b/java/com/google/gerrit/server/notedb/RobotCommentsRevisionNote.java
@@ -26,6 +26,7 @@
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectReader;
 
+/** Like {@link RevisionNote} but for robot comments. */
 public class RobotCommentsRevisionNote extends RevisionNote<RobotComment> {
   private final ChangeNoteJson noteUtil;
 
diff --git a/java/com/google/gerrit/server/patch/PatchListCacheImpl.java b/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
index 6871652..15fa0f4 100644
--- a/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
+++ b/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
@@ -22,6 +22,7 @@
 import com.google.gerrit.entities.PatchSet;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
+import com.google.gerrit.server.cache.CacheBackend;
 import com.google.gerrit.server.cache.CacheModule;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.inject.Inject;
@@ -45,7 +46,9 @@
       @Override
       protected void configure() {
         factory(PatchListLoader.Factory.class);
-        persist(FILE_NAME, PatchListKey.class, PatchList.class)
+        // TODO(davido): Switch off using legacy cache backend, after fixing PatchListLoader
+        // to be recursion free.
+        persist(FILE_NAME, PatchListKey.class, PatchList.class, CacheBackend.GUAVA)
             .maximumWeight(10 << 20)
             .weigher(PatchListWeigher.class);
 
diff --git a/java/com/google/gerrit/server/permissions/RefControl.java b/java/com/google/gerrit/server/permissions/RefControl.java
index 06fe471..378a512 100644
--- a/java/com/google/gerrit/server/permissions/RefControl.java
+++ b/java/com/google/gerrit/server/permissions/RefControl.java
@@ -440,8 +440,7 @@
     @Override
     public ForChange change(ChangeData cd) {
       try {
-        // TODO(hiesel) Force callers to call database() and use db instead of cd.db()
-        return getProjectControl().controlFor(cd.change()).asForChange(cd);
+        return getProjectControl().controlFor(cd.notes()).asForChange(cd);
       } catch (StorageException e) {
         return FailedPermissionBackend.change("unavailable", e);
       }
diff --git a/java/com/google/gerrit/server/project/ProjectConfig.java b/java/com/google/gerrit/server/project/ProjectConfig.java
index 4d551a2..fa877af 100644
--- a/java/com/google/gerrit/server/project/ProjectConfig.java
+++ b/java/com/google/gerrit/server/project/ProjectConfig.java
@@ -923,9 +923,18 @@
       lowerNames.put(lower, name);
 
       List<LabelValue> values = new ArrayList<>();
+      Set<Short> allValues = new HashSet<>();
       for (String value : rc.getStringList(LABEL, name, KEY_VALUE)) {
         try {
-          values.add(parseLabelValue(value));
+          LabelValue labelValue = parseLabelValue(value);
+          if (allValues.add(labelValue.getValue())) {
+            values.add(labelValue);
+          } else {
+            error(
+                new ValidationError(
+                    PROJECT_CONFIG,
+                    String.format("Duplicate %s \"%s\" for label \"%s\"", KEY_VALUE, value, name)));
+          }
         } catch (IllegalArgumentException notValue) {
           error(
               new ValidationError(
diff --git a/java/com/google/gerrit/server/project/Reachable.java b/java/com/google/gerrit/server/project/Reachable.java
index c30378b..6d28646a 100644
--- a/java/com/google/gerrit/server/project/Reachable.java
+++ b/java/com/google/gerrit/server/project/Reachable.java
@@ -67,7 +67,7 @@
       try (TraceTimer timer =
           TraceContext.newTimer(
               "IncludedInResolver.includedInAny",
-              Metadata.builder().projectName(project.get()).inputSize(refs.size()).build())) {
+              Metadata.builder().projectName(project.get()).resourceCount(refs.size()).build())) {
         return IncludedInResolver.includedInAny(repo, rw, commit, filtered.values());
       }
     } catch (IOException | PermissionBackendException e) {
diff --git a/java/com/google/gerrit/server/query/change/ChangeData.java b/java/com/google/gerrit/server/query/change/ChangeData.java
index c6beac4..78ca0fc 100644
--- a/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -598,7 +598,11 @@
       committer = c.getCommitterIdent();
       parentCount = c.getParentCount();
     } catch (IOException e) {
-      throw new StorageException(e);
+      throw new StorageException(
+          String.format(
+              "Loading commit %s for ps %d of change %d failed.",
+              ps.commitId(), ps.id().get(), ps.id().changeId().get()),
+          e);
     }
     return true;
   }
diff --git a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index c86bd94..61b90f1 100644
--- a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -63,6 +63,7 @@
 import com.google.gerrit.server.change.ChangeTriplet;
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.OperatorAliasConfig;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.index.change.ChangeField;
@@ -94,6 +95,7 @@
 import java.util.regex.Pattern;
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.Repository;
 
 /** Parses a query string meant to be applied to change objects. */
@@ -221,6 +223,7 @@
     final GroupMembers groupMembers;
     final Provider<AnonymousUser> anonymousUserProvider;
     final OperatorAliasConfig operatorAliasConfig;
+    final boolean indexMergeable;
 
     private final Provider<CurrentUser> self;
 
@@ -253,7 +256,8 @@
         AccountCache accountCache,
         GroupMembers groupMembers,
         Provider<AnonymousUser> anonymousUserProvider,
-        OperatorAliasConfig operatorAliasConfig) {
+        OperatorAliasConfig operatorAliasConfig,
+        @GerritServerConfig Config gerritConfig) {
       this(
           queryProvider,
           rewriter,
@@ -281,7 +285,8 @@
           accountCache,
           groupMembers,
           anonymousUserProvider,
-          operatorAliasConfig);
+          operatorAliasConfig,
+          gerritConfig.getBoolean("index", "change", "indexMergeable", true));
     }
 
     private Arguments(
@@ -311,7 +316,8 @@
         AccountCache accountCache,
         GroupMembers groupMembers,
         Provider<AnonymousUser> anonymousUserProvider,
-        OperatorAliasConfig operatorAliasConfig) {
+        OperatorAliasConfig operatorAliasConfig,
+        boolean indexMergeable) {
       this.queryProvider = queryProvider;
       this.rewriter = rewriter;
       this.opFactories = opFactories;
@@ -339,6 +345,7 @@
       this.groupMembers = groupMembers;
       this.anonymousUserProvider = anonymousUserProvider;
       this.operatorAliasConfig = operatorAliasConfig;
+      this.indexMergeable = indexMergeable;
     }
 
     Arguments asUser(CurrentUser otherUser) {
@@ -369,7 +376,8 @@
           accountCache,
           groupMembers,
           anonymousUserProvider,
-          operatorAliasConfig);
+          operatorAliasConfig,
+          indexMergeable);
     }
 
     Arguments asUser(Account.Id otherId) {
@@ -570,6 +578,9 @@
     }
 
     if ("mergeable".equalsIgnoreCase(value)) {
+      if (!args.indexMergeable) {
+        throw new QueryParseException("server does not support 'mergeable'. check configs");
+      }
       return new BooleanPredicate(ChangeField.MERGEABLE);
     }
 
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/change/GetDiff.java b/java/com/google/gerrit/server/restapi/change/GetDiff.java
index 736925d..a6536ce 100644
--- a/java/com/google/gerrit/server/restapi/change/GetDiff.java
+++ b/java/com/google/gerrit/server/restapi/change/GetDiff.java
@@ -146,7 +146,8 @@
     PatchSet.Id pId = resource.getPatchKey().patchSetId();
     String fileName = resource.getPatchKey().fileName();
     logger.atFine().log(
-        "patchSetId = %d, fileName = %s, base = %s", pId.get(), fileName, base, parentNum);
+        "patchSetId = %d, fileName = %s, base = %s, parentNum = %d",
+        pId.get(), fileName, base, parentNum);
     ChangeNotes notes = resource.getRevision().getNotes();
     if (base != null) {
       RevisionResource baseResource =
diff --git a/java/com/google/gerrit/server/restapi/change/QueryChanges.java b/java/com/google/gerrit/server/restapi/change/QueryChanges.java
index 50e1e42..544177f 100644
--- a/java/com/google/gerrit/server/restapi/change/QueryChanges.java
+++ b/java/com/google/gerrit/server/restapi/change/QueryChanges.java
@@ -34,9 +34,11 @@
 import com.google.gerrit.server.query.change.ChangeQueryBuilder;
 import com.google.gerrit.server.query.change.ChangeQueryProcessor;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.EnumSet;
+import java.util.HashMap;
 import java.util.List;
 import org.kohsuke.args4j.Option;
 
@@ -45,8 +47,12 @@
 
   private final ChangeJson.Factory json;
   private final ChangeQueryBuilder qb;
-  private final ChangeQueryProcessor imp;
+  private final Provider<ChangeQueryProcessor> queryProcessorProvider;
+  private final HashMap<String, DynamicOptions.DynamicBean> dynamicBeans = new HashMap<>();
   private EnumSet<ListChangesOption> options;
+  private Integer limit;
+  private Integer start;
+  private Boolean noLimit;
 
   @Option(
       name = "--query",
@@ -61,7 +67,7 @@
       metaVar = "CNT",
       usage = "Maximum number of results to return")
   public void setLimit(int limit) {
-    imp.setUserProvidedLimit(limit);
+    this.limit = limit;
   }
 
   @Option(name = "-o", usage = "Output options per change")
@@ -80,24 +86,27 @@
       metaVar = "CNT",
       usage = "Number of changes to skip")
   public void setStart(int start) {
-    imp.setStart(start);
+    this.start = start;
   }
 
   @Option(name = "--no-limit", usage = "Return all results, overriding the default limit")
   public void setNoLimit(boolean on) {
-    imp.setNoLimit(on);
+    this.noLimit = on;
   }
 
   @Override
   public void setDynamicBean(String plugin, DynamicOptions.DynamicBean dynamicBean) {
-    imp.setDynamicBean(plugin, dynamicBean);
+    dynamicBeans.put(plugin, dynamicBean);
   }
 
   @Inject
-  QueryChanges(ChangeJson.Factory json, ChangeQueryBuilder qb, ChangeQueryProcessor qp) {
+  QueryChanges(
+      ChangeJson.Factory json,
+      ChangeQueryBuilder qb,
+      Provider<ChangeQueryProcessor> queryProcessorProvider) {
     this.json = json;
     this.qb = qb;
-    this.imp = qp;
+    this.queryProcessorProvider = queryProcessorProvider;
 
     options = EnumSet.noneOf(ListChangesOption.class);
   }
@@ -129,9 +138,22 @@
   }
 
   private List<List<ChangeInfo>> query() throws QueryParseException, PermissionBackendException {
-    if (imp.isDisabled()) {
+    ChangeQueryProcessor queryProcessor = queryProcessorProvider.get();
+    if (queryProcessor.isDisabled()) {
       throw new QueryParseException("query disabled");
     }
+
+    if (limit != null) {
+      queryProcessor.setUserProvidedLimit(limit);
+    }
+    if (start != null) {
+      queryProcessor.setStart(start);
+    }
+    if (noLimit != null) {
+      queryProcessor.setNoLimit(noLimit);
+    }
+    dynamicBeans.forEach((p, b) -> queryProcessor.setDynamicBean(p, b));
+
     if (queries == null || queries.isEmpty()) {
       queries = Collections.singletonList("status:open");
     } else if (queries.size() > 10) {
@@ -141,9 +163,9 @@
     }
 
     int cnt = queries.size();
-    List<QueryResult<ChangeData>> results = imp.query(qb.parse(queries));
+    List<QueryResult<ChangeData>> results = queryProcessor.query(qb.parse(queries));
     List<List<ChangeInfo>> res =
-        json.create(options, this.imp.getAttributesFactory()).format(results);
+        json.create(options, queryProcessor.getAttributesFactory()).format(results);
     for (int n = 0; n < cnt; n++) {
       List<ChangeInfo> info = res.get(n);
       if (results.get(n).more() && !info.isEmpty()) {
diff --git a/java/com/google/gerrit/server/restapi/change/RelatedChangesSorter.java b/java/com/google/gerrit/server/restapi/change/RelatedChangesSorter.java
index 8040847..af65483 100644
--- a/java/com/google/gerrit/server/restapi/change/RelatedChangesSorter.java
+++ b/java/com/google/gerrit/server/restapi/change/RelatedChangesSorter.java
@@ -16,6 +16,7 @@
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static java.util.Objects.requireNonNull;
+import static java.util.stream.Collectors.toMap;
 
 import com.google.auto.value.AutoValue;
 import com.google.common.annotations.VisibleForTesting;
@@ -76,7 +77,15 @@
     // Map of all patch sets, keyed by commit SHA-1.
     Map<ObjectId, PatchSetData> byId = collectById(in);
     PatchSetData start = byId.get(startPs.commitId());
-    checkArgument(start != null, "%s not found in %s", startPs, in);
+    requireNonNull(
+        start,
+        () ->
+            String.format(
+                "commit %s of patch set %s not found in %s",
+                startPs.commitId().name(),
+                startPs.id(),
+                byId.entrySet().stream()
+                    .collect(toMap(e -> e.getKey().name(), e -> e.getValue().patchSet().id()))));
 
     // Map of patch set -> immediate parent.
     ListMultimap<PatchSetData, PatchSetData> parents =
diff --git a/java/com/google/gerrit/server/restapi/change/RevertSubmission.java b/java/com/google/gerrit/server/restapi/change/RevertSubmission.java
index 7ba9b98..b44bb29 100644
--- a/java/com/google/gerrit/server/restapi/change/RevertSubmission.java
+++ b/java/com/google/gerrit/server/restapi/change/RevertSubmission.java
@@ -95,11 +95,12 @@
           String.format("change is %s.", ChangeUtil.status(changeResource.getChange())));
     }
 
-    String submissionId =
-        requireNonNull(
-            changeResource.getChange().getSubmissionId(),
-            String.format("merged change %s has no submission ID", changeResource.getId()));
-
+    String submissionId = changeResource.getChange().getSubmissionId();
+    if (submissionId == null) {
+      throw new ResourceConflictException(
+          "This change is merged but doesn't have a submission id,"
+              + " meaning it was not submitted through Gerrit.");
+    }
     List<ChangeData> changeDatas = queryProvider.get().bySubmissionId(submissionId);
 
     for (ChangeData changeData : changeDatas) {
@@ -160,10 +161,21 @@
             "Revert this change and all changes that have been submitted together with this change")
         .setVisible(
             and(
-                change.isMerged() && projectStatePermitsWrite,
+                change.isMerged()
+                    && change.getSubmissionId() != null
+                    && isChangePartOfSubmission(change.getSubmissionId())
+                    && projectStatePermitsWrite,
                 permissionBackend
                     .user(rsrc.getUser())
                     .ref(change.getDest())
                     .testCond(CREATE_CHANGE)));
   }
+
+  /**
+   * @param submissionId the submission id of the change.
+   * @return True if the submission has more than one change, false otherwise.
+   */
+  private Boolean isChangePartOfSubmission(String submissionId) {
+    return (queryProvider.get().setLimit(2).bySubmissionId(submissionId).size() > 1);
+  }
 }
diff --git a/java/com/google/gerrit/server/restapi/group/DeleteMembers.java b/java/com/google/gerrit/server/restapi/group/DeleteMembers.java
index 9bae2e2..fa1c5c6 100644
--- a/java/com/google/gerrit/server/restapi/group/DeleteMembers.java
+++ b/java/com/google/gerrit/server/restapi/group/DeleteMembers.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.restapi.group;
 
+import com.google.common.base.Strings;
 import com.google.common.collect.Sets;
 import com.google.gerrit.common.data.GroupDescription;
 import com.google.gerrit.entities.Account;
@@ -68,6 +69,9 @@
 
     Set<Account.Id> membersToRemove = new HashSet<>();
     for (String nameOrEmail : input.members) {
+      if (Strings.isNullOrEmpty(nameOrEmail)) {
+        continue;
+      }
       membersToRemove.add(accountResolver.resolve(nameOrEmail).asUnique().account().id());
     }
     AccountGroup.UUID groupUuid = internalGroup.getGroupUUID();
diff --git a/java/com/google/gerrit/server/restapi/group/GroupsCollection.java b/java/com/google/gerrit/server/restapi/group/GroupsCollection.java
index 52fe9d0..65a7f4f 100644
--- a/java/com/google/gerrit/server/restapi/group/GroupsCollection.java
+++ b/java/com/google/gerrit/server/restapi/group/GroupsCollection.java
@@ -42,7 +42,7 @@
   private final GroupResolver groupResolver;
   private final Provider<CurrentUser> self;
 
-  private boolean hasQuery2;
+  private boolean hasQuery;
 
   @Inject
   public GroupsCollection(
@@ -62,12 +62,7 @@
 
   @Override
   public void setParams(ListMultimap<String, String> params) throws BadRequestException {
-    if (params.containsKey("query") && params.containsKey("query2")) {
-      throw new BadRequestException("\"query\" and \"query2\" options are mutually exclusive");
-    }
-
-    // The --query2 option is defined in QueryGroups
-    this.hasQuery2 = params.containsKey("query2");
+    this.hasQuery = params.containsKey("query");
   }
 
   @Override
@@ -79,7 +74,7 @@
       throw new ResourceNotFoundException();
     }
 
-    if (hasQuery2) {
+    if (hasQuery) {
       return queryGroups.get();
     }
 
diff --git a/java/com/google/gerrit/server/restapi/group/ListGroups.java b/java/com/google/gerrit/server/restapi/group/ListGroups.java
index 899ed00..adc251c 100644
--- a/java/com/google/gerrit/server/restapi/group/ListGroups.java
+++ b/java/com/google/gerrit/server/restapi/group/ListGroups.java
@@ -129,21 +129,6 @@
     this.owned = owned;
   }
 
-  /**
-   * Add a group to inspect.
-   *
-   * @param uuid UUID of the group
-   * @deprecated use {@link #addGroup(AccountGroup.UUID)}.
-   */
-  @Deprecated
-  @Option(
-      name = "--query",
-      aliases = {"-q"},
-      usage = "group to inspect (deprecated: use --group/-g instead)")
-  void addGroup_Deprecated(AccountGroup.UUID uuid) {
-    addGroup(uuid);
-  }
-
   @Option(
       name = "--group",
       aliases = {"-g"},
diff --git a/java/com/google/gerrit/server/restapi/group/QueryGroups.java b/java/com/google/gerrit/server/restapi/group/QueryGroups.java
index a233111..380d42e 100644
--- a/java/com/google/gerrit/server/restapi/group/QueryGroups.java
+++ b/java/com/google/gerrit/server/restapi/group/QueryGroups.java
@@ -48,12 +48,9 @@
   private int start;
   private EnumSet<ListGroupsOption> options = EnumSet.noneOf(ListGroupsOption.class);
 
-  // TODO(ekempin): --query in ListGroups is marked as deprecated, once it is
-  // removed we want to rename --query2 to --query here.
-  /** --query (-q) is already used by {@link ListGroups} */
   @Option(
-      name = "--query2",
-      aliases = {"-q2"},
+      name = "--query",
+      aliases = {"-q"},
       usage = "group query")
   public void setQuery(String query) {
     this.query = query;
diff --git a/java/com/google/gerrit/server/restapi/project/CreateLabel.java b/java/com/google/gerrit/server/restapi/project/CreateLabel.java
index 03b9452..5d51527 100644
--- a/java/com/google/gerrit/server/restapi/project/CreateLabel.java
+++ b/java/com/google/gerrit/server/restapi/project/CreateLabel.java
@@ -91,85 +91,7 @@
     try (MetaDataUpdate md = updateFactory.create(rsrc.getNameKey())) {
       ProjectConfig config = projectConfigFactory.read(md);
 
-      if (config.getLabelSections().containsKey(id.get())) {
-        throw new ResourceConflictException(String.format("label %s already exists", id.get()));
-      }
-
-      for (String labelName : config.getLabelSections().keySet()) {
-        if (labelName.equalsIgnoreCase(id.get())) {
-          throw new ResourceConflictException(
-              String.format("label %s conflicts with existing label %s", id.get(), labelName));
-        }
-      }
-
-      if (input.values == null || input.values.isEmpty()) {
-        throw new BadRequestException("values are required");
-      }
-
-      List<LabelValue> values = LabelDefinitionInputParser.parseValues(input.values);
-
-      LabelType labelType;
-      try {
-        labelType = new LabelType(id.get(), values);
-      } catch (IllegalArgumentException e) {
-        throw new BadRequestException("invalid name: " + id.get(), e);
-      }
-
-      if (input.function != null && !input.function.trim().isEmpty()) {
-        labelType.setFunction(LabelDefinitionInputParser.parseFunction(input.function));
-      } else {
-        labelType.setFunction(LabelFunction.MAX_WITH_BLOCK);
-      }
-
-      if (input.defaultValue != null) {
-        labelType.setDefaultValue(
-            LabelDefinitionInputParser.parseDefaultValue(labelType, input.defaultValue));
-      }
-
-      if (input.branches != null) {
-        labelType.setRefPatterns(LabelDefinitionInputParser.parseBranches(input.branches));
-      }
-
-      if (input.canOverride != null) {
-        labelType.setCanOverride(input.canOverride);
-      }
-
-      if (input.copyAnyScore != null) {
-        labelType.setCopyAnyScore(input.copyAnyScore);
-      }
-
-      if (input.copyMinScore != null) {
-        labelType.setCopyMinScore(input.copyMinScore);
-      }
-
-      if (input.copyMaxScore != null) {
-        labelType.setCopyMaxScore(input.copyMaxScore);
-      }
-
-      if (input.copyAllScoresIfNoChange != null) {
-        labelType.setCopyAllScoresIfNoChange(input.copyAllScoresIfNoChange);
-      }
-
-      if (input.copyAllScoresIfNoCodeChange != null) {
-        labelType.setCopyAllScoresIfNoCodeChange(input.copyAllScoresIfNoCodeChange);
-      }
-
-      if (input.copyAllScoresOnTrivialRebase != null) {
-        labelType.setCopyAllScoresOnTrivialRebase(input.copyAllScoresOnTrivialRebase);
-      }
-
-      if (input.copyAllScoresOnMergeFirstParentUpdate != null) {
-        labelType.setCopyAllScoresOnMergeFirstParentUpdate(
-            input.copyAllScoresOnMergeFirstParentUpdate);
-      }
-
-      if (input.allowPostSubmit != null) {
-        labelType.setAllowPostSubmit(input.allowPostSubmit);
-      }
-
-      if (input.ignoreSelfApproval != null) {
-        labelType.setIgnoreSelfApproval(input.ignoreSelfApproval);
-      }
+      LabelType labelType = createLabel(config, id.get(), input);
 
       if (input.commitMessage != null) {
         md.setMessage(Strings.emptyToNull(input.commitMessage.trim()));
@@ -177,7 +99,6 @@
         md.setMessage("Update label");
       }
 
-      config.getLabelSections().put(labelType.getName(), labelType);
       config.commit(md);
 
       projectCache.evict(rsrc.getProjectState().getProject());
@@ -185,4 +106,101 @@
       return Response.created(LabelDefinitionJson.format(rsrc.getNameKey(), labelType));
     }
   }
+
+  /**
+   * Creates a new label.
+   *
+   * @param config the project config
+   * @param label the name of the new label
+   * @param input the input that describes the new label
+   * @return the created label type
+   * @throws BadRequestException if there was invalid data in the input
+   * @throws ResourceConflictException if the label cannot be created due to a conflict
+   */
+  public LabelType createLabel(ProjectConfig config, String label, LabelDefinitionInput input)
+      throws BadRequestException, ResourceConflictException {
+    if (config.getLabelSections().containsKey(label)) {
+      throw new ResourceConflictException(String.format("label %s already exists", label));
+    }
+
+    for (String labelName : config.getLabelSections().keySet()) {
+      if (labelName.equalsIgnoreCase(label)) {
+        throw new ResourceConflictException(
+            String.format("label %s conflicts with existing label %s", label, labelName));
+      }
+    }
+
+    if (input.values == null || input.values.isEmpty()) {
+      throw new BadRequestException("values are required");
+    }
+
+    List<LabelValue> values = LabelDefinitionInputParser.parseValues(input.values);
+
+    LabelType labelType;
+    try {
+      labelType = new LabelType(label, values);
+    } catch (IllegalArgumentException e) {
+      throw new BadRequestException("invalid name: " + label, e);
+    }
+
+    if (input.function != null && !input.function.trim().isEmpty()) {
+      labelType.setFunction(LabelDefinitionInputParser.parseFunction(input.function));
+    } else {
+      labelType.setFunction(LabelFunction.MAX_WITH_BLOCK);
+    }
+
+    if (input.defaultValue != null) {
+      labelType.setDefaultValue(
+          LabelDefinitionInputParser.parseDefaultValue(labelType, input.defaultValue));
+    }
+
+    if (input.branches != null) {
+      labelType.setRefPatterns(LabelDefinitionInputParser.parseBranches(input.branches));
+    }
+
+    if (input.canOverride != null) {
+      labelType.setCanOverride(input.canOverride);
+    }
+
+    if (input.copyAnyScore != null) {
+      labelType.setCopyAnyScore(input.copyAnyScore);
+    }
+
+    if (input.copyMinScore != null) {
+      labelType.setCopyMinScore(input.copyMinScore);
+    }
+
+    if (input.copyMaxScore != null) {
+      labelType.setCopyMaxScore(input.copyMaxScore);
+    }
+
+    if (input.copyAllScoresIfNoChange != null) {
+      labelType.setCopyAllScoresIfNoChange(input.copyAllScoresIfNoChange);
+    }
+
+    if (input.copyAllScoresIfNoCodeChange != null) {
+      labelType.setCopyAllScoresIfNoCodeChange(input.copyAllScoresIfNoCodeChange);
+    }
+
+    if (input.copyAllScoresOnTrivialRebase != null) {
+      labelType.setCopyAllScoresOnTrivialRebase(input.copyAllScoresOnTrivialRebase);
+    }
+
+    if (input.copyAllScoresOnMergeFirstParentUpdate != null) {
+      labelType.setCopyAllScoresOnMergeFirstParentUpdate(
+          input.copyAllScoresOnMergeFirstParentUpdate);
+    }
+
+    if (input.allowPostSubmit != null) {
+      labelType.setAllowPostSubmit(input.allowPostSubmit);
+    }
+
+    if (input.ignoreSelfApproval != null) {
+      labelType.setIgnoreSelfApproval(input.ignoreSelfApproval);
+    }
+
+    config.getLabelSections().put(labelType.getName(), labelType);
+
+    return labelType;
+  }
 }
diff --git a/java/com/google/gerrit/server/restapi/project/DeleteLabel.java b/java/com/google/gerrit/server/restapi/project/DeleteLabel.java
index 5464abf..531640c 100644
--- a/java/com/google/gerrit/server/restapi/project/DeleteLabel.java
+++ b/java/com/google/gerrit/server/restapi/project/DeleteLabel.java
@@ -77,12 +77,10 @@
     try (MetaDataUpdate md = updateFactory.create(rsrc.getProject().getNameKey())) {
       ProjectConfig config = projectConfigFactory.read(md);
 
-      if (!config.getLabelSections().containsKey(rsrc.getLabelType().getName())) {
+      if (!deleteLabel(config, rsrc.getLabelType().getName())) {
         throw new ResourceNotFoundException(IdString.fromDecoded(rsrc.getLabelType().getName()));
       }
 
-      config.getLabelSections().remove(rsrc.getLabelType().getName());
-
       if (input.commitMessage != null) {
         md.setMessage(Strings.emptyToNull(input.commitMessage.trim()));
       } else {
@@ -96,4 +94,20 @@
 
     return Response.none();
   }
+
+  /**
+   * Delete the given label from the given project config.
+   *
+   * @param config the project config from which the label should be deleted
+   * @param labelName the name of the label that should be deleted
+   * @return {@code true} if the label was deleted, {@code false} if the label was not found
+   */
+  public boolean deleteLabel(ProjectConfig config, String labelName) {
+    if (!config.getLabelSections().containsKey(labelName)) {
+      return false;
+    }
+
+    config.getLabelSections().remove(labelName);
+    return true;
+  }
 }
diff --git a/java/com/google/gerrit/server/restapi/project/LabelDefinitionInputParser.java b/java/com/google/gerrit/server/restapi/project/LabelDefinitionInputParser.java
index a45c67f..1e288f4 100644
--- a/java/com/google/gerrit/server/restapi/project/LabelDefinitionInputParser.java
+++ b/java/com/google/gerrit/server/restapi/project/LabelDefinitionInputParser.java
@@ -24,10 +24,12 @@
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.server.project.RefPattern;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Optional;
+import java.util.Set;
 
 public class LabelDefinitionInputParser {
   public static LabelFunction parseFunction(String functionString) throws BadRequestException {
@@ -39,6 +41,7 @@
   public static List<LabelValue> parseValues(Map<String, String> values)
       throws BadRequestException {
     List<LabelValue> valueList = new ArrayList<>();
+    Set<Short> allValues = new HashSet<>();
     for (Entry<String, String> e : values.entrySet()) {
       short value;
       try {
@@ -46,6 +49,9 @@
       } catch (NumberFormatException ex) {
         throw new BadRequestException("invalid value: " + e.getKey(), ex);
       }
+      if (!allValues.add(value)) {
+        throw new BadRequestException("duplicate value: " + value);
+      }
       String valueDescription = e.getValue().trim();
       if (valueDescription.isEmpty()) {
         throw new BadRequestException("description for value '" + e.getKey() + "' cannot be empty");
diff --git a/java/com/google/gerrit/server/restapi/project/Module.java b/java/com/google/gerrit/server/restapi/project/Module.java
index 2c76cbd..5b3ea30 100644
--- a/java/com/google/gerrit/server/restapi/project/Module.java
+++ b/java/com/google/gerrit/server/restapi/project/Module.java
@@ -72,6 +72,7 @@
     get(LABEL_KIND).to(GetLabel.class);
     put(LABEL_KIND).to(SetLabel.class);
     delete(LABEL_KIND).to(DeleteLabel.class);
+    postOnCollection(LABEL_KIND).to(PostLabels.class);
 
     get(PROJECT_KIND, "HEAD").to(GetHead.class);
     put(PROJECT_KIND, "HEAD").to(SetHead.class);
diff --git a/java/com/google/gerrit/server/restapi/project/PostLabels.java b/java/com/google/gerrit/server/restapi/project/PostLabels.java
new file mode 100644
index 0000000..8835359
--- /dev/null
+++ b/java/com/google/gerrit/server/restapi/project/PostLabels.java
@@ -0,0 +1,148 @@
+// 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.restapi.project;
+
+import com.google.common.base.Strings;
+import com.google.gerrit.common.data.LabelType;
+import com.google.gerrit.extensions.common.BatchLabelInput;
+import com.google.gerrit.extensions.common.LabelDefinitionInput;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestCollectionModifyView;
+import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.git.meta.MetaDataUpdate;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.permissions.ProjectPermission;
+import com.google.gerrit.server.project.LabelResource;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectConfig;
+import com.google.gerrit.server.project.ProjectResource;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import java.io.IOException;
+import java.util.Map.Entry;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+
+/** REST endpoint that allows to add, update and delete label definitions in a batch. */
+@Singleton
+public class PostLabels
+    implements RestCollectionModifyView<ProjectResource, LabelResource, BatchLabelInput> {
+  private final Provider<CurrentUser> user;
+  private final PermissionBackend permissionBackend;
+  private final MetaDataUpdate.User updateFactory;
+  private final ProjectConfig.Factory projectConfigFactory;
+  private final DeleteLabel deleteLabel;
+  private final CreateLabel createLabel;
+  private final SetLabel setLabel;
+  private final ProjectCache projectCache;
+
+  @Inject
+  public PostLabels(
+      Provider<CurrentUser> user,
+      PermissionBackend permissionBackend,
+      MetaDataUpdate.User updateFactory,
+      ProjectConfig.Factory projectConfigFactory,
+      DeleteLabel deleteLabel,
+      CreateLabel createLabel,
+      SetLabel setLabel,
+      ProjectCache projectCache) {
+    this.user = user;
+    this.permissionBackend = permissionBackend;
+    this.updateFactory = updateFactory;
+    this.projectConfigFactory = projectConfigFactory;
+    this.deleteLabel = deleteLabel;
+    this.createLabel = createLabel;
+    this.setLabel = setLabel;
+    this.projectCache = projectCache;
+  }
+
+  @Override
+  public Response<?> apply(ProjectResource rsrc, BatchLabelInput input)
+      throws AuthException, UnprocessableEntityException, PermissionBackendException, IOException,
+          ConfigInvalidException, BadRequestException, ResourceConflictException {
+    if (!user.get().isIdentifiedUser()) {
+      throw new AuthException("Authentication required");
+    }
+
+    permissionBackend
+        .currentUser()
+        .project(rsrc.getNameKey())
+        .check(ProjectPermission.WRITE_CONFIG);
+
+    if (input == null) {
+      input = new BatchLabelInput();
+    }
+
+    try (MetaDataUpdate md = updateFactory.create(rsrc.getNameKey())) {
+      boolean dirty = false;
+
+      ProjectConfig config = projectConfigFactory.read(md);
+
+      if (input.delete != null && !input.delete.isEmpty()) {
+        for (String labelName : input.delete) {
+          if (!deleteLabel.deleteLabel(config, labelName.trim())) {
+            throw new UnprocessableEntityException(String.format("label %s not found", labelName));
+          }
+        }
+        dirty = true;
+      }
+
+      if (input.create != null && !input.create.isEmpty()) {
+        for (LabelDefinitionInput labelInput : input.create) {
+          if (labelInput.name == null || labelInput.name.trim().isEmpty()) {
+            throw new BadRequestException("label name is required for new label");
+          }
+          if (labelInput.commitMessage != null) {
+            throw new BadRequestException("commit message on label definition input not supported");
+          }
+          createLabel.createLabel(config, labelInput.name.trim(), labelInput);
+        }
+        dirty = true;
+      }
+
+      if (input.update != null && !input.update.isEmpty()) {
+        for (Entry<String, LabelDefinitionInput> e : input.update.entrySet()) {
+          LabelType labelType = config.getLabelSections().get(e.getKey().trim());
+          if (labelType == null) {
+            throw new UnprocessableEntityException(String.format("label %s not found", e.getKey()));
+          }
+          if (e.getValue().commitMessage != null) {
+            throw new BadRequestException("commit message on label definition input not supported");
+          }
+          setLabel.updateLabel(config, labelType, e.getValue());
+        }
+        dirty = true;
+      }
+
+      if (input.commitMessage != null) {
+        md.setMessage(Strings.emptyToNull(input.commitMessage.trim()));
+      } else {
+        md.setMessage("Update labels");
+      }
+
+      if (dirty) {
+        config.commit(md);
+        projectCache.evict(rsrc.getProjectState().getProject());
+      }
+    }
+
+    return Response.ok("");
+  }
+}
diff --git a/java/com/google/gerrit/server/restapi/project/QueryProjects.java b/java/com/google/gerrit/server/restapi/project/QueryProjects.java
index 7066d9a..e4f7df5 100644
--- a/java/com/google/gerrit/server/restapi/project/QueryProjects.java
+++ b/java/com/google/gerrit/server/restapi/project/QueryProjects.java
@@ -31,6 +31,7 @@
 import com.google.gerrit.server.query.project.ProjectQueryBuilder;
 import com.google.gerrit.server.query.project.ProjectQueryProcessor;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 import java.util.ArrayList;
 import java.util.List;
 import org.kohsuke.args4j.Option;
@@ -38,7 +39,7 @@
 public class QueryProjects implements RestReadView<TopLevelResource> {
   private final ProjectIndexCollection indexes;
   private final ProjectQueryBuilder queryBuilder;
-  private final ProjectQueryProcessor queryProcessor;
+  private final Provider<ProjectQueryProcessor> queryProcessorProvider;
   private final ProjectJson json;
 
   private String query;
@@ -78,11 +79,11 @@
   protected QueryProjects(
       ProjectIndexCollection indexes,
       ProjectQueryBuilder queryBuilder,
-      ProjectQueryProcessor queryProcessor,
+      Provider<ProjectQueryProcessor> queryProcessorProvider,
       ProjectJson json) {
     this.indexes = indexes;
     this.queryBuilder = queryBuilder;
-    this.queryProcessor = queryProcessor;
+    this.queryProcessorProvider = queryProcessorProvider;
     this.json = json;
   }
 
@@ -102,6 +103,8 @@
       throw new MethodNotAllowedException("no project index");
     }
 
+    ProjectQueryProcessor queryProcessor = queryProcessorProvider.get();
+
     if (start != 0) {
       queryProcessor.setStart(start);
     }
diff --git a/java/com/google/gerrit/server/restapi/project/SetLabel.java b/java/com/google/gerrit/server/restapi/project/SetLabel.java
index b7cffce..824b4ed 100644
--- a/java/com/google/gerrit/server/restapi/project/SetLabel.java
+++ b/java/com/google/gerrit/server/restapi/project/SetLabel.java
@@ -80,117 +80,9 @@
     LabelType labelType = rsrc.getLabelType();
 
     try (MetaDataUpdate md = updateFactory.create(rsrc.getProject().getNameKey())) {
-      boolean dirty = false;
-
       ProjectConfig config = projectConfigFactory.read(md);
-      config.getLabelSections().remove(labelType.getName());
 
-      if (input.name != null) {
-        String newName = input.name.trim();
-        if (newName.isEmpty()) {
-          throw new BadRequestException("name cannot be empty");
-        }
-        if (!newName.equals(labelType.getName())) {
-          if (config.getLabelSections().containsKey(newName)) {
-            throw new ResourceConflictException(String.format("name %s already in use", newName));
-          }
-
-          for (String labelName : config.getLabelSections().keySet()) {
-            if (labelName.equalsIgnoreCase(newName)) {
-              throw new ResourceConflictException(
-                  String.format("name %s conflicts with existing label %s", newName, labelName));
-            }
-          }
-
-          try {
-            labelType.setName(newName);
-          } catch (IllegalArgumentException e) {
-            throw new BadRequestException("invalid name: " + input.name, e);
-          }
-          dirty = true;
-        }
-      }
-
-      if (input.function != null) {
-        if (input.function.trim().isEmpty()) {
-          throw new BadRequestException("function cannot be empty");
-        }
-        labelType.setFunction(LabelDefinitionInputParser.parseFunction(input.function));
-        dirty = true;
-      }
-
-      if (input.values != null) {
-        if (input.values.isEmpty()) {
-          throw new BadRequestException("values cannot be empty");
-        }
-        labelType.setValues(LabelDefinitionInputParser.parseValues(input.values));
-        dirty = true;
-      }
-
-      if (input.defaultValue != null) {
-        labelType.setDefaultValue(
-            LabelDefinitionInputParser.parseDefaultValue(labelType, input.defaultValue));
-        dirty = true;
-      }
-
-      if (input.branches != null) {
-        labelType.setRefPatterns(LabelDefinitionInputParser.parseBranches(input.branches));
-        dirty = true;
-      }
-
-      if (input.canOverride != null) {
-        labelType.setCanOverride(input.canOverride);
-        dirty = true;
-      }
-
-      if (input.copyAnyScore != null) {
-        labelType.setCopyAnyScore(input.copyAnyScore);
-        dirty = true;
-      }
-
-      if (input.copyMinScore != null) {
-        labelType.setCopyMinScore(input.copyMinScore);
-        dirty = true;
-      }
-
-      if (input.copyMaxScore != null) {
-        labelType.setCopyMaxScore(input.copyMaxScore);
-        dirty = true;
-      }
-
-      if (input.copyAllScoresIfNoChange != null) {
-        labelType.setCopyAllScoresIfNoChange(input.copyAllScoresIfNoChange);
-      }
-
-      if (input.copyAllScoresIfNoCodeChange != null) {
-        labelType.setCopyAllScoresIfNoCodeChange(input.copyAllScoresIfNoCodeChange);
-        dirty = true;
-      }
-
-      if (input.copyAllScoresOnTrivialRebase != null) {
-        labelType.setCopyAllScoresOnTrivialRebase(input.copyAllScoresOnTrivialRebase);
-        dirty = true;
-      }
-
-      if (input.copyAllScoresOnMergeFirstParentUpdate != null) {
-        labelType.setCopyAllScoresOnMergeFirstParentUpdate(
-            input.copyAllScoresOnMergeFirstParentUpdate);
-        dirty = true;
-      }
-
-      if (input.allowPostSubmit != null) {
-        labelType.setAllowPostSubmit(input.allowPostSubmit);
-        dirty = true;
-      }
-
-      if (input.ignoreSelfApproval != null) {
-        labelType.setIgnoreSelfApproval(input.ignoreSelfApproval);
-        dirty = true;
-      }
-
-      if (dirty) {
-        config.getLabelSections().put(labelType.getName(), labelType);
-
+      if (updateLabel(config, labelType, input)) {
         if (input.commitMessage != null) {
           md.setMessage(Strings.emptyToNull(input.commitMessage.trim()));
         } else {
@@ -203,4 +95,128 @@
     }
     return Response.ok(LabelDefinitionJson.format(rsrc.getProject().getNameKey(), labelType));
   }
+
+  /**
+   * Updates the given label.
+   *
+   * @param config the project config
+   * @param labelType the label type that should be updated
+   * @param input the input that describes the label update
+   * @return whether the label type was modified
+   * @throws BadRequestException if there was invalid data in the input
+   * @throws ResourceConflictException if the update cannot be applied due to a conflict
+   */
+  public boolean updateLabel(ProjectConfig config, LabelType labelType, LabelDefinitionInput input)
+      throws BadRequestException, ResourceConflictException {
+    boolean dirty = false;
+
+    config.getLabelSections().remove(labelType.getName());
+
+    if (input.name != null) {
+      String newName = input.name.trim();
+      if (newName.isEmpty()) {
+        throw new BadRequestException("name cannot be empty");
+      }
+      if (!newName.equals(labelType.getName())) {
+        if (config.getLabelSections().containsKey(newName)) {
+          throw new ResourceConflictException(String.format("name %s already in use", newName));
+        }
+
+        for (String labelName : config.getLabelSections().keySet()) {
+          if (labelName.equalsIgnoreCase(newName)) {
+            throw new ResourceConflictException(
+                String.format("name %s conflicts with existing label %s", newName, labelName));
+          }
+        }
+
+        try {
+          labelType.setName(newName);
+        } catch (IllegalArgumentException e) {
+          throw new BadRequestException("invalid name: " + input.name, e);
+        }
+        dirty = true;
+      }
+    }
+
+    if (input.function != null) {
+      if (input.function.trim().isEmpty()) {
+        throw new BadRequestException("function cannot be empty");
+      }
+      labelType.setFunction(LabelDefinitionInputParser.parseFunction(input.function));
+      dirty = true;
+    }
+
+    if (input.values != null) {
+      if (input.values.isEmpty()) {
+        throw new BadRequestException("values cannot be empty");
+      }
+      labelType.setValues(LabelDefinitionInputParser.parseValues(input.values));
+      dirty = true;
+    }
+
+    if (input.defaultValue != null) {
+      labelType.setDefaultValue(
+          LabelDefinitionInputParser.parseDefaultValue(labelType, input.defaultValue));
+      dirty = true;
+    }
+
+    if (input.branches != null) {
+      labelType.setRefPatterns(LabelDefinitionInputParser.parseBranches(input.branches));
+      dirty = true;
+    }
+
+    if (input.canOverride != null) {
+      labelType.setCanOverride(input.canOverride);
+      dirty = true;
+    }
+
+    if (input.copyAnyScore != null) {
+      labelType.setCopyAnyScore(input.copyAnyScore);
+      dirty = true;
+    }
+
+    if (input.copyMinScore != null) {
+      labelType.setCopyMinScore(input.copyMinScore);
+      dirty = true;
+    }
+
+    if (input.copyMaxScore != null) {
+      labelType.setCopyMaxScore(input.copyMaxScore);
+      dirty = true;
+    }
+
+    if (input.copyAllScoresIfNoChange != null) {
+      labelType.setCopyAllScoresIfNoChange(input.copyAllScoresIfNoChange);
+    }
+
+    if (input.copyAllScoresIfNoCodeChange != null) {
+      labelType.setCopyAllScoresIfNoCodeChange(input.copyAllScoresIfNoCodeChange);
+      dirty = true;
+    }
+
+    if (input.copyAllScoresOnTrivialRebase != null) {
+      labelType.setCopyAllScoresOnTrivialRebase(input.copyAllScoresOnTrivialRebase);
+      dirty = true;
+    }
+
+    if (input.copyAllScoresOnMergeFirstParentUpdate != null) {
+      labelType.setCopyAllScoresOnMergeFirstParentUpdate(
+          input.copyAllScoresOnMergeFirstParentUpdate);
+      dirty = true;
+    }
+
+    if (input.allowPostSubmit != null) {
+      labelType.setAllowPostSubmit(input.allowPostSubmit);
+      dirty = true;
+    }
+
+    if (input.ignoreSelfApproval != null) {
+      labelType.setIgnoreSelfApproval(input.ignoreSelfApproval);
+      dirty = true;
+    }
+
+    config.getLabelSections().put(labelType.getName(), labelType);
+
+    return dirty;
+  }
 }
diff --git a/java/com/google/gerrit/server/update/RetryHelper.java b/java/com/google/gerrit/server/update/RetryHelper.java
index 36a609e..4f9a67c 100644
--- a/java/com/google/gerrit/server/update/RetryHelper.java
+++ b/java/com/google/gerrit/server/update/RetryHelper.java
@@ -34,9 +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;
 import com.google.gerrit.metrics.Description;
 import com.google.gerrit.metrics.Field;
@@ -57,7 +54,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 {
@@ -130,8 +126,8 @@
   @VisibleForTesting
   @Singleton
   public static class Metrics {
-    final Counter2<ActionType, String> attemptCounts;
-    final Counter1<ActionType> timeoutCount;
+    final Counter3<ActionType, String, String> attemptCounts;
+    final Counter3<ActionType, String, String> timeoutCount;
     final Counter3<ActionType, String, String> autoRetryCount;
     final Counter3<ActionType, String, String> failuresOnAutoRetryCount;
 
@@ -139,6 +135,19 @@
     Metrics(MetricMaker metricMaker) {
       Field<ActionType> actionTypeField =
           Field.ofEnum(ActionType.class, "action_type", Metadata.Builder::actionType).build();
+      Field<String> operationNameField =
+          Field.ofString("operation_name", Metadata.Builder::operationName)
+              .description("The name of the operation that was retried.")
+              .build();
+      Field<String> lastAttemptCauseField =
+          Field.ofString("cause", Metadata.Builder::cause)
+              .description("The cause for the last attempt.")
+              .build();
+      Field<String> causeField =
+          Field.ofString("cause", Metadata.Builder::cause)
+              .description("The cause for the retry.")
+              .build();
+
       attemptCounts =
           metricMaker.newCounter(
               "action/retry_attempt_count",
@@ -148,9 +157,8 @@
                   .setCumulative()
                   .setUnit("attempts"),
               actionTypeField,
-              Field.ofString("cause", Metadata.Builder::cause)
-                  .description("The cause for the last attempt.")
-                  .build());
+              operationNameField,
+              lastAttemptCauseField);
       timeoutCount =
           metricMaker.newCounter(
               "action/retry_timeout_count",
@@ -158,7 +166,9 @@
                       "Number of action executions of RetryHelper that ultimately timed out")
                   .setCumulative()
                   .setUnit("timeouts"),
-              actionTypeField);
+              actionTypeField,
+              operationNameField,
+              lastAttemptCauseField);
       autoRetryCount =
           metricMaker.newCounter(
               "action/auto_retry_count",
@@ -166,12 +176,8 @@
                   .setCumulative()
                   .setUnit("retries"),
               actionTypeField,
-              Field.ofString("operation_name", Metadata.Builder::operationName)
-                  .description("The name of the operation that was retried.")
-                  .build(),
-              Field.ofString("cause", Metadata.Builder::cause)
-                  .description("The cause for the retry.")
-                  .build());
+              operationNameField,
+              causeField);
       failuresOnAutoRetryCount =
           metricMaker.newCounter(
               "action/failures_on_auto_retry_count",
@@ -179,12 +185,8 @@
                   .setCumulative()
                   .setUnit("failures"),
               actionTypeField,
-              Field.ofString("operation_name", Metadata.Builder::operationName)
-                  .description("The name of the operation that was retried.")
-                  .build(),
-              Field.ofString("cause", Metadata.Builder::cause)
-                  .description("The cause for the retry.")
-                  .build());
+              operationNameField,
+              causeField);
     }
   }
 
@@ -284,15 +286,7 @@
       throws RestApiException, UpdateException {
     try {
       return execute(
-          ActionType.CHANGE_UPDATE,
-          () -> changeAction.call(updateFactory),
-          opts,
-          t -> {
-            if (t instanceof UpdateException) {
-              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);
@@ -365,20 +359,24 @@
                 return false;
               });
       retryerBuilder.withRetryListener(listener);
-      return executeWithTimeoutCount(actionType, action, retryerBuilder.build());
+      return executeWithTimeoutCount(actionType, action, opts, retryerBuilder.build());
     } finally {
       if (listener.getAttemptCount() > 1) {
         logger.atFine().log("%s was attempted %d times", actionType, listener.getAttemptCount());
         metrics.attemptCounts.incrementBy(
             actionType,
+            opts.caller().orElse("N/A"),
             listener.getCause().map(this::formatCause).orElse("_unknown"),
             listener.getAttemptCount() - 1);
       }
     }
   }
 
-  private String formatCause(Throwable t) {
-    if ((t instanceof UpdateException || t instanceof StorageException) && t.getCause() != null) {
+  public String formatCause(Throwable t) {
+    while ((t instanceof UpdateException
+            || t instanceof StorageException
+            || t instanceof ExecutionException)
+        && t.getCause() != null) {
       t = t.getCause();
     }
 
@@ -387,9 +385,6 @@
       return formattedCause.get();
     }
 
-    if (t instanceof LockFailureException) {
-      return RefUpdate.Result.LOCK_FAILURE.name();
-    }
     return t.getClass().getSimpleName();
   }
 
@@ -406,18 +401,22 @@
    *
    * @param actionType the type of the action
    * @param action the action which should be executed and retried on failure
+   * @param opts options for retrying the action on failure
    * @param retryer the retryer
    * @return the result of executing the action
    * @throws Throwable any error or exception that made the action fail, callers are expected to
    *     catch and inspect this Throwable to decide carefully whether it should be re-thrown
    */
-  private <T> T executeWithTimeoutCount(ActionType actionType, Action<T> action, Retryer<T> retryer)
-      throws Throwable {
+  private <T> T executeWithTimeoutCount(
+      ActionType actionType, Action<T> action, Options opts, Retryer<T> retryer) throws Throwable {
     try {
       return retryer.call(action::call);
     } catch (ExecutionException | RetryException e) {
       if (e instanceof RetryException) {
-        metrics.timeoutCount.increment(actionType);
+        metrics.timeoutCount.increment(
+            actionType,
+            opts.caller().orElse("N/A"),
+            e.getCause() != null ? formatCause(e.getCause()) : "_unknown");
       }
       if (e.getCause() != null) {
         throw e.getCause();
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/com/google/gerrit/sshd/commands/SetAccountCommand.java b/java/com/google/gerrit/sshd/commands/SetAccountCommand.java
index e0c4624..df1e3ed 100644
--- a/java/com/google/gerrit/sshd/commands/SetAccountCommand.java
+++ b/java/com/google/gerrit/sshd/commands/SetAccountCommand.java
@@ -275,7 +275,7 @@
       throws RestApiException, IOException, ConfigInvalidException, PermissionBackendException {
     for (String sshKey : sshKeys) {
       SshKeyInput in = new SshKeyInput();
-      in.raw = RawInputUtil.create(sshKey.getBytes(UTF_8), "plain/text");
+      in.raw = RawInputUtil.create(sshKey.getBytes(UTF_8), "text/plain");
       addSshKey.apply(rsrc, in);
     }
   }
diff --git a/java/com/google/gerrit/testing/IndexConfig.java b/java/com/google/gerrit/testing/IndexConfig.java
index 21c49dd..fb6c926 100644
--- a/java/com/google/gerrit/testing/IndexConfig.java
+++ b/java/com/google/gerrit/testing/IndexConfig.java
@@ -23,7 +23,9 @@
 
   public static Config createFromExistingConfig(Config cfg) {
     cfg.setInt("index", null, "maxPages", 10);
-    cfg.setBoolean("index", null, "reindexAfterRefUpdate", false);
+    // To avoid this flakiness indexMergeable is switched off for the tests as it incurs background
+    // reindex calls.
+    cfg.setBoolean("index", "change", "indexMergeable", false);
     cfg.setString("trackingid", "query-bug", "footer", "Bug:");
     cfg.setString("trackingid", "query-bug", "match", "QUERY\\d{2,8}");
     cfg.setString("trackingid", "query-bug", "system", "querytests");
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/change/ChangeIT.java b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
index 5fb99e6..74f753d 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -3409,6 +3409,7 @@
 
     ChangeInfo change = gApi.changes().id(r.getChangeId()).get();
     assertThat(change.status).isEqualTo(ChangeStatus.MERGED);
+    assertThat(change.submissionId).isNotNull();
     assertThat(change.labels.keySet()).containsExactly("Code-Review");
     assertThat(change.permittedLabels.keySet()).containsExactly("Code-Review");
     assertPermitted(change, "Code-Review", 2);
@@ -3569,6 +3570,7 @@
 
     ChangeInfo change = gApi.changes().id(r.getChangeId()).get();
     assertThat(change.status).isEqualTo(ChangeStatus.MERGED);
+    assertThat(change.submissionId).isNotNull();
     assertThat(change.labels.keySet()).containsExactly("Code-Review", "Non-Author-Code-Review");
     assertThat(change.permittedLabels.keySet()).containsExactly("Code-Review");
     assertPermitted(change, "Code-Review", 0, 1, 2);
@@ -3584,11 +3586,34 @@
 
     ChangeInfo change = gApi.changes().id(r.getChangeId()).get();
     assertThat(change.status).isEqualTo(ChangeStatus.MERGED);
+    assertThat(change.submissionId).isNotNull();
     assertThat(change.labels.keySet()).containsExactly("Code-Review");
     assertPermitted(change, "Code-Review", 0, 1, 2);
   }
 
   @Test
+  public void checkSubmissionIdForAutoClosedChange() throws Exception {
+    PushOneCommit.Result first = createChange();
+    PushOneCommit.Result second = createChange();
+
+    PushOneCommit push = pushFactory.create(admin.newIdent(), testRepo);
+
+    PushOneCommit.Result result = push.to("refs/heads/master");
+    result.assertOkStatus();
+
+    ChangeInfo firstChange = gApi.changes().id(first.getChangeId()).get();
+    assertThat(firstChange.status).isEqualTo(ChangeStatus.MERGED);
+    assertThat(firstChange.submissionId).isNotNull();
+
+    ChangeInfo secondChange = gApi.changes().id(second.getChangeId()).get();
+    assertThat(secondChange.status).isEqualTo(ChangeStatus.MERGED);
+    assertThat(secondChange.submissionId).isNotNull();
+
+    assertThat(secondChange.submissionId).isEqualTo(firstChange.submissionId);
+    assertThat(gApi.changes().id(second.getChangeId()).submittedTogether()).hasSize(2);
+  }
+
+  @Test
   public void maxPermittedValueAllowed() throws Exception {
     final int minPermittedValue = -2;
     final int maxPermittedValue = +2;
@@ -4412,6 +4437,20 @@
     }
   }
 
+  @Test
+  @GerritConfig(name = "index.change.indexMergeable", value = "true")
+  public void changeQueryReturnsMergeableWhenGerritIndexMergeable() throws Exception {
+    String changeId = createChange().getChangeId();
+    assertThat(gApi.changes().query(changeId).get().get(0).mergeable).isTrue();
+  }
+
+  @Test
+  @GerritConfig(name = "index.change.indexMergeable", value = "false")
+  public void changeQueryDoesNotReturnMergeableWhenGerritDoesNotIndexMergeable() throws Exception {
+    String changeId = createChange().getChangeId();
+    assertThat(gApi.changes().query(changeId).get().get(0).mergeable).isNull();
+  }
+
   private PushOneCommit.Result createWorkInProgressChange() throws Exception {
     return pushTo("refs/for/master%wip");
   }
diff --git a/javatests/com/google/gerrit/acceptance/api/change/QueryChangeIT.java b/javatests/com/google/gerrit/acceptance/api/change/QueryChangeIT.java
index 2cae04a..b5d673c 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/QueryChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/QueryChangeIT.java
@@ -15,22 +15,32 @@
 package com.google.gerrit.acceptance.api.change;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.block;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static java.util.stream.Collectors.toList;
+import static javax.servlet.http.HttpServletResponse.SC_OK;
 
 import com.google.common.collect.ImmutableList;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.UseClockStep;
 import com.google.gerrit.acceptance.config.GerritConfig;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.Project;
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.restapi.TopLevelResource;
 import com.google.gerrit.server.restapi.change.QueryChanges;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import java.util.List;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.junit.TestRepository;
 import org.junit.Test;
 
 @NoHttpd
 public class QueryChangeIT extends AbstractDaemonTest {
-
+  @Inject private ProjectOperations projectOperations;
   @Inject private Provider<QueryChanges> queryChangesProvider;
 
   @Test
@@ -123,6 +133,68 @@
     assertThat(result.get(2).get(0)._number).isEqualTo(numericId2);
   }
 
+  @Test
+  @UseClockStep
+  @SuppressWarnings("unchecked")
+  public void withPagedResults() throws Exception {
+    // Create 4 visible changes.
+    createChange(testRepo).getChange().getId().get();
+    createChange(testRepo).getChange().getId().get();
+    int changeId3 = createChange(testRepo).getChange().getId().get();
+    int changeId4 = createChange(testRepo).getChange().getId().get();
+
+    // Create hidden project.
+    Project.NameKey hiddenProject = projectOperations.newProject().create();
+    projectOperations
+        .project(hiddenProject)
+        .forUpdate()
+        .add(block(Permission.READ).ref("refs/*").group(REGISTERED_USERS))
+        .update();
+    TestRepository<InMemoryRepository> hiddenRepo = cloneProject(hiddenProject, admin);
+
+    // Create 2 hidden changes.
+    createChange(hiddenRepo);
+    createChange(hiddenRepo);
+
+    // Create a change query that matches all changes (visible and hidden changes).
+    // The index returns the changes ordered by last updated timestamp:
+    // hiddenChange2, hiddenChange1, change4, change3, change2, change1
+    QueryChanges queryChanges = queryChangesProvider.get();
+    queryChanges.addQuery("branch:master");
+
+    // Set a limit on the query so that we need to paginate over the results from the index.
+    queryChanges.setLimit(2);
+
+    // Execute the query and verify the results.
+    // Since the limit is set to 2, at most 2 changes are returned to user, but the index query is
+    // executed with limit 3 (+1 so that we can populate the _more_changes field on the last
+    // result).
+    // This means the index query with limit 3 returns these changes:
+    // hiddenChange2, hiddenChange1, change4
+    // The 2 hidden changes are filtered out because they are not visible to the caller.
+    // This means we have only one matching result (change4) but the limit (3) is not exhausted
+    // yet. Hence the next page is loaded from the index (startIndex is 3 to skip the results
+    // that we already processed, limit is again 3). The results for the next page are:
+    // change3, change2, change1
+    // change2 and change1 are dropped because they are over the limit.
+    List<ChangeInfo> result =
+        (List<ChangeInfo>) queryChanges.apply(TopLevelResource.INSTANCE).value();
+    assertThat(result.stream().map(i -> i._number).collect(toList()))
+        .containsExactly(changeId3, changeId4);
+  }
+
+  @Test
+  public void usingOutOfRangeLabelValuesDoesNotCauseError() throws Exception {
+    for (String operator : ImmutableList.of("=", ">", ">=", "<", "<=")) {
+      QueryChanges queryChanges = queryChangesProvider.get();
+      queryChanges.addQuery("label:Code-Review" + operator + "10");
+      queryChanges.addQuery("label:Code-Review" + operator + "-10");
+      queryChanges.addQuery("Code-Review" + operator + "10");
+      queryChanges.addQuery("Code-Review" + operator + "-10");
+      assertThat(queryChanges.apply(TopLevelResource.INSTANCE).statusCode()).isEqualTo(SC_OK);
+    }
+  }
+
   private static void assertNoChangeHasMoreChangesSet(List<ChangeInfo> results) {
     for (ChangeInfo info : results) {
       assertThat(info._moreChanges).isNull();
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/api/group/GroupsIT.java b/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java
index e543976..988580e 100644
--- a/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java
@@ -187,6 +187,24 @@
   }
 
   @Test
+  public void removeMember_nullInMemberInputDoesNotCauseFailure() throws Exception {
+    AccountGroup.UUID group =
+        groupOperations.newGroup().addMember(admin.id()).addMember(user.id()).create();
+    gApi.groups().id(group.get()).removeMembers(user.id().toString(), null);
+    ImmutableSet<Account.Id> members = groupOperations.group(group).get().members();
+    assertThat(members).containsExactly(admin.id());
+  }
+
+  @Test
+  public void removeMember_emptyStringInMemberInputDoesNotCauseFailure() throws Exception {
+    AccountGroup.UUID group =
+        groupOperations.newGroup().addMember(admin.id()).addMember(user.id()).create();
+    gApi.groups().id(group.get()).removeMembers(user.id().toString(), "");
+    ImmutableSet<Account.Id> members = groupOperations.group(group).get().members();
+    assertThat(members).containsExactly(admin.id());
+  }
+
+  @Test
   public void cachedGroupsForMemberAreUpdatedOnMemberAdditionAndRemoval() throws Exception {
     String username = name("user");
     Account.Id accountId = accountOperations.newAccount().username(username).create();
diff --git a/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java b/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java
index 78c8209..2801b36 100644
--- a/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java
@@ -69,11 +69,16 @@
 import com.google.gerrit.server.group.SystemGroupBackend;
 import com.google.gerrit.server.index.IndexExecutor;
 import com.google.gerrit.server.project.CommentLinkInfoImpl;
+import com.google.gerrit.server.project.ProjectConfig;
 import com.google.inject.AbstractModule;
 import com.google.inject.Inject;
 import com.google.inject.Module;
 import java.util.HashMap;
 import java.util.Map;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.transport.PushResult;
 import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
@@ -851,6 +856,58 @@
     assertCommentLinks(getConfig(project), expected);
   }
 
+  @Test
+  public void cannotPushLabelDefinitionWithDuplicateValues() throws Exception {
+    Config cfg = new Config();
+    cfg.fromText(projectOperations.project(allProjects).getConfig().toText());
+    cfg.setStringList(
+        "label",
+        "Code-Review",
+        "value",
+        ImmutableList.of("+1 LGTM", "1 LGTM", "0 No Value", "-1 Looks Bad"));
+
+    TestRepository<InMemoryRepository> repo = cloneProject(allProjects);
+    GitUtil.fetch(repo, RefNames.REFS_CONFIG + ":" + RefNames.REFS_CONFIG);
+    repo.reset(RefNames.REFS_CONFIG);
+    PushOneCommit.Result r =
+        pushFactory
+            .create(admin.newIdent(), repo, "Subject", "project.config", cfg.toText())
+            .to(RefNames.REFS_CONFIG);
+    r.assertErrorStatus("invalid project configuration");
+    r.assertMessage("project.config: duplicate value \"1 lgtm\" for label \"code-review\"");
+  }
+
+  @Test
+  public void getProjectThatHasLabelDefinitionWithDuplicateValues() throws Exception {
+    // Update the definition of the Code-Review label so that it has the value "+1 LGTM" twice.
+    // This update bypasses all validation checks so that the duplicate label value doesn't get
+    // rejected.
+    Config cfg = new Config();
+    cfg.fromText(projectOperations.project(allProjects).getConfig().toText());
+    cfg.setStringList(
+        "label",
+        "Code-Review",
+        "value",
+        ImmutableList.of("+1 LGTM", "1 LGTM", "0 No Value", "-1 Looks Bad"));
+
+    try (TestRepository<Repository> repo =
+        new TestRepository<>(repoManager.openRepository(allProjects))) {
+      repo.update(
+          RefNames.REFS_CONFIG,
+          repo.commit()
+              .message("Set label with duplicate value")
+              .parent(getHead(repo.getRepository(), RefNames.REFS_CONFIG))
+              .add(ProjectConfig.PROJECT_CONFIG, cfg.toText()));
+    }
+
+    // Verify that project info can be retrieved and that the label value "+1 LGTM" appears only
+    // once.
+    ProjectInfo projectInfo = gApi.projects().name(allProjects.get()).get();
+    assertThat(projectInfo.labels.keySet()).containsExactly("Code-Review");
+    assertThat(projectInfo.labels.get("Code-Review").values)
+        .containsExactly("+1", "LGTM", " 0", "No Value", "-1", "Looks Bad");
+  }
+
   private CommentLinkInfo commentLinkInfo(String name, String match, String link) {
     return new CommentLinkInfoImpl(name, match, link, null /*html*/, null /*enabled*/);
   }
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
index ad73e0f..61d0fd5 100644
--- a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
@@ -46,6 +46,7 @@
 import com.google.gerrit.acceptance.RestResponse;
 import com.google.gerrit.acceptance.TestAccount;
 import com.google.gerrit.acceptance.TestProjectInput;
+import com.google.gerrit.acceptance.config.GerritConfig;
 import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
 import com.google.gerrit.common.data.Permission;
@@ -1062,6 +1063,7 @@
   }
 
   @Test
+  @GerritConfig(name = "index.change.indexMergeable", value = "true")
   public void mergeable() throws Exception {
     ObjectId initial = repo().exactRef(HEAD).getLeaf().getObjectId();
 
diff --git a/javatests/com/google/gerrit/acceptance/git/RefAdvertisementIT.java b/javatests/com/google/gerrit/acceptance/git/RefAdvertisementIT.java
index f0312de..d1d197b 100644
--- a/javatests/com/google/gerrit/acceptance/git/RefAdvertisementIT.java
+++ b/javatests/com/google/gerrit/acceptance/git/RefAdvertisementIT.java
@@ -1446,7 +1446,7 @@
   private TestRefAdvertiser.Result getReceivePackRefs() throws Exception {
     try (Repository repo = repoManager.openRepository(project)) {
       AdvertiseRefsHook adv =
-          ReceiveCommitsAdvertiseRefsHookChain.createForTest(queryProvider, project);
+          ReceiveCommitsAdvertiseRefsHookChain.createForTest(queryProvider, project, admin.id());
       ReceivePack rp = new ReceivePack(repo);
       rp.setAdvertiseRefsHook(adv);
       TestRefAdvertiser advertiser = new TestRefAdvertiser(repo);
diff --git a/javatests/com/google/gerrit/acceptance/rest/binding/ProjectsRestApiBindingsIT.java b/javatests/com/google/gerrit/acceptance/rest/binding/ProjectsRestApiBindingsIT.java
index b8ab752..55eeaf4 100644
--- a/javatests/com/google/gerrit/acceptance/rest/binding/ProjectsRestApiBindingsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/binding/ProjectsRestApiBindingsIT.java
@@ -83,7 +83,8 @@
               .expectedResponseCode(SC_NOT_FOUND)
               .build(),
           RestCall.get("/projects/%s/dashboards"),
-          RestCall.put("/projects/%s/labels/new-label"));
+          RestCall.put("/projects/%s/labels/new-label"),
+          RestCall.post("/projects/%s/labels/"));
 
   /**
    * Child project REST endpoints to be tested, each URL contains placeholders for the parent
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java b/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
index aaf52a1..eff98b3 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
@@ -1205,7 +1205,7 @@
   }
 
   @Test
-  @GerritConfig(name = "index.reindexAfterRefUpdate", value = "true")
+  @GerritConfig(name = "index.change.indexMergeable", value = "true")
   public void submitSchedulesOpenChangesOfSameBranchForReindexing() throws Throwable {
     // Create a merged change.
     PushOneCommit push =
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/ActionsIT.java b/javatests/com/google/gerrit/acceptance/rest/change/ActionsIT.java
index 8b51e7f..911a04d 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/ActionsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/ActionsIT.java
@@ -72,13 +72,28 @@
   }
 
   @Test
-  public void changeActionOneMergedChangeHasReverts() throws Exception {
+  public void changeActionOneMergedChangeHasOnlyNormalRevert() throws Exception {
     String changeId = createChangeWithTopic().getChangeId();
     gApi.changes().id(changeId).current().review(ReviewInput.approve());
     gApi.changes().id(changeId).current().submit();
     Map<String, ActionInfo> actions = getChangeActions(changeId);
     assertThat(actions).containsKey("revert");
-    assertThat(actions).containsKey("revert_submission");
+    assertThat(actions).doesNotContainKey("revert_submission");
+  }
+
+  @Test
+  public void changeActionTwoMergedChangesHaveReverts() throws Exception {
+    String changeId1 = createChangeWithTopic().getChangeId();
+    String changeId2 = createChangeWithTopic().getChangeId();
+    gApi.changes().id(changeId1).current().review(ReviewInput.approve());
+    gApi.changes().id(changeId2).current().review(ReviewInput.approve());
+    gApi.changes().id(changeId2).current().submit();
+    Map<String, ActionInfo> actions1 = getChangeActions(changeId1);
+    assertThat(actions1).containsKey("revert");
+    assertThat(actions1).containsKey("revert_submission");
+    Map<String, ActionInfo> actions2 = getChangeActions(changeId2);
+    assertThat(actions2).containsKey("revert");
+    assertThat(actions2).containsKey("revert_submission");
   }
 
   @Test
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/IndexChangeIT.java b/javatests/com/google/gerrit/acceptance/rest/change/IndexChangeIT.java
index 3030b02..ea3a6a0 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/IndexChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/IndexChangeIT.java
@@ -22,6 +22,7 @@
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.acceptance.config.GerritConfig;
 import com.google.gerrit.acceptance.testsuite.group.GroupOperations;
 import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
@@ -58,6 +59,7 @@
   }
 
   @Test
+  @GerritConfig(name = "index.change.indexMergeable", value = "true")
   public void indexChangeAfterOwnerLosesVisibility() throws Exception {
     // Create a test group with 2 users as members
     TestAccount user2 = accountCreator.user2();
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/GroupsIT.java b/javatests/com/google/gerrit/acceptance/rest/group/GroupsIT.java
deleted file mode 100644
index e153e561..0000000
--- a/javatests/com/google/gerrit/acceptance/rest/group/GroupsIT.java
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright (C) 2017 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 org.junit.Test;
-
-public class GroupsIT extends AbstractDaemonTest {
-  @Test
-  public void invalidQueryOptions() throws Exception {
-    RestResponse r = adminRestSession.put("/groups/?query=foo&query2=bar");
-    r.assertBadRequest();
-    assertThat(r.getEntityContent())
-        .isEqualTo("\"query\" and \"query2\" options are mutually exclusive");
-  }
-}
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/project/AccessIT.java b/javatests/com/google/gerrit/acceptance/rest/project/AccessIT.java
index 91a10ca..3e9b1f6 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/AccessIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/AccessIT.java
@@ -604,7 +604,7 @@
   }
 
   @Test
-  public void syncCreateGroupPermission() throws Exception {
+  public void syncCreateGroupPermission_addAndRemoveCreateGroupCapability() throws Exception {
     // Grant CREATE_GROUP to Registered Users
     ProjectAccessInput accessInput = newProjectAccessInput();
     AccessSectionInfo accessSection = newAccessSectionInfo();
@@ -642,6 +642,44 @@
   }
 
   @Test
+  public void syncCreateGroupPermission_addCreateGroupCapabilityToMultipleGroups()
+      throws Exception {
+    PermissionRuleInfo pri = new PermissionRuleInfo(PermissionRuleInfo.Action.ALLOW, false);
+
+    // Grant CREATE_GROUP to Registered Users
+    ProjectAccessInput accessInput = newProjectAccessInput();
+    AccessSectionInfo accessSection = newAccessSectionInfo();
+    PermissionInfo createGroup = newPermissionInfo();
+    createGroup.rules.put(SystemGroupBackend.REGISTERED_USERS.get(), pri);
+    accessSection.permissions.put(GlobalCapability.CREATE_GROUP, createGroup);
+    accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES, accessSection);
+    gApi.projects().name(allProjects.get()).access(accessInput);
+
+    // Grant CREATE_GROUP to Administrators
+    accessInput = newProjectAccessInput();
+    accessSection = newAccessSectionInfo();
+    createGroup = newPermissionInfo();
+    createGroup.rules.put(adminGroupUuid().get(), pri);
+    accessSection.permissions.put(GlobalCapability.CREATE_GROUP, createGroup);
+    accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES, accessSection);
+    gApi.projects().name(allProjects.get()).access(accessInput);
+
+    // Assert that the permissions were synced from All-Projects (global) to All-Users (ref)
+    Map<String, AccessSectionInfo> local = gApi.projects().name("All-Users").access().local;
+    assertThat(local).isNotNull();
+    assertThat(local).containsKey(RefNames.REFS_GROUPS + "*");
+    Map<String, PermissionInfo> permissions = local.get(RefNames.REFS_GROUPS + "*").permissions;
+    assertThat(permissions).hasSize(2);
+    // READ is the default permission and should be preserved by the syncer
+    assertThat(permissions.keySet()).containsExactly(Permission.READ, Permission.CREATE);
+    Map<String, PermissionRuleInfo> rules = permissions.get(Permission.CREATE).rules;
+    assertThat(rules.keySet())
+        .containsExactly(SystemGroupBackend.REGISTERED_USERS.get(), adminGroupUuid().get());
+    assertThat(rules.get(SystemGroupBackend.REGISTERED_USERS.get())).isEqualTo(pri);
+    assertThat(rules.get(adminGroupUuid().get())).isEqualTo(pri);
+  }
+
+  @Test
   public void addAccessSectionForInvalidRef() throws Exception {
     ProjectAccessInput accessInput = newProjectAccessInput();
     AccessSectionInfo accessSectionInfo = createDefaultAccessSectionInfo();
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/CreateLabelIT.java b/javatests/com/google/gerrit/acceptance/rest/project/CreateLabelIT.java
index 28e8b14..57a1e56 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/CreateLabelIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/CreateLabelIT.java
@@ -169,6 +169,21 @@
   }
 
   @Test
+  public void cannotCreateLabelWithDuplicateValues() throws Exception {
+    LabelDefinitionInput input = new LabelDefinitionInput();
+    // Positive values can be specified as '<value>' or '+<value>'.
+    input.values =
+        ImmutableMap.of(
+            "+1", "Looks Good", "1", "Looks Good", "0", "Don't Know", "-1", "Looks Bad");
+
+    BadRequestException thrown =
+        assertThrows(
+            BadRequestException.class,
+            () -> gApi.projects().name(allProjects.get()).label("Foo").create(input));
+    assertThat(thrown).hasMessageThat().contains("duplicate value: 1");
+  }
+
+  @Test
   public void cannotCreateLabelWithInvalidDefaultValue() throws Exception {
     LabelDefinitionInput input = new LabelDefinitionInput();
     input.values = ImmutableMap.of("+1", "Looks Good", "0", "Don't Know", "-1", "Looks Bad");
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/PostLabelsIT.java b/javatests/com/google/gerrit/acceptance/rest/project/PostLabelsIT.java
new file mode 100644
index 0000000..9e6b051
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/rest/project/PostLabelsIT.java
@@ -0,0 +1,456 @@
+// 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.project;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
+import com.google.gerrit.common.data.LabelFunction;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.extensions.common.BatchLabelInput;
+import com.google.gerrit.extensions.common.LabelDefinitionInfo;
+import com.google.gerrit.extensions.common.LabelDefinitionInput;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
+import com.google.gerrit.server.restapi.project.PostLabels;
+import com.google.inject.Inject;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.Test;
+
+/** Tests for the {@link PostLabels} REST endpoint. */
+public class PostLabelsIT extends AbstractDaemonTest {
+  @Inject private RequestScopeOperations requestScopeOperations;
+  @Inject private ProjectOperations projectOperations;
+
+  @Test
+  public void anonymous() throws Exception {
+    requestScopeOperations.setApiUserAnonymous();
+    AuthException thrown =
+        assertThrows(
+            AuthException.class,
+            () -> gApi.projects().name(allProjects.get()).labels(new BatchLabelInput()));
+    assertThat(thrown).hasMessageThat().contains("Authentication required");
+  }
+
+  @Test
+  public void notAllowed() throws Exception {
+    projectOperations
+        .project(allProjects)
+        .forUpdate()
+        .add(allow(Permission.READ).ref(RefNames.REFS_CONFIG).group(REGISTERED_USERS))
+        .update();
+
+    requestScopeOperations.setApiUser(user.id());
+    AuthException thrown =
+        assertThrows(
+            AuthException.class,
+            () -> gApi.projects().name(allProjects.get()).labels(new BatchLabelInput()));
+    assertThat(thrown).hasMessageThat().contains("write refs/meta/config not permitted");
+  }
+
+  @Test
+  public void deleteNonExistingLabel() throws Exception {
+    BatchLabelInput input = new BatchLabelInput();
+    input.delete = ImmutableList.of("Foo");
+
+    UnprocessableEntityException thrown =
+        assertThrows(
+            UnprocessableEntityException.class,
+            () -> gApi.projects().name(allProjects.get()).labels(input));
+    assertThat(thrown).hasMessageThat().contains("label Foo not found");
+  }
+
+  @Test
+  public void deleteLabels() throws Exception {
+    configLabel("Foo", LabelFunction.NO_OP);
+    configLabel("Bar", LabelFunction.NO_OP);
+    assertThat(gApi.projects().name(project.get()).labels().get()).isNotEmpty();
+
+    BatchLabelInput input = new BatchLabelInput();
+    input.delete = ImmutableList.of("Foo", "Bar");
+    gApi.projects().name(project.get()).labels(input);
+    assertThat(gApi.projects().name(project.get()).labels().get()).isEmpty();
+  }
+
+  @Test
+  public void deleteLabels_labelNamesAreTrimmed() throws Exception {
+    configLabel("Foo", LabelFunction.NO_OP);
+    configLabel("Bar", LabelFunction.NO_OP);
+    assertThat(gApi.projects().name(project.get()).labels().get()).isNotEmpty();
+
+    BatchLabelInput input = new BatchLabelInput();
+    input.delete = ImmutableList.of(" Foo ", " Bar ");
+    gApi.projects().name(project.get()).labels(input);
+    assertThat(gApi.projects().name(project.get()).labels().get()).isEmpty();
+  }
+
+  @Test
+  public void cannotDeleteTheSameLabelTwice() throws Exception {
+    configLabel("Foo", LabelFunction.NO_OP);
+
+    BatchLabelInput input = new BatchLabelInput();
+    input.delete = ImmutableList.of("Foo", "Foo");
+
+    UnprocessableEntityException thrown =
+        assertThrows(
+            UnprocessableEntityException.class,
+            () -> gApi.projects().name(project.get()).labels(input));
+    assertThat(thrown).hasMessageThat().contains("label Foo not found");
+  }
+
+  @Test
+  public void cannotCreateLabelWithNameThatIsAlreadyInUse() throws Exception {
+    LabelDefinitionInput labelInput = new LabelDefinitionInput();
+    labelInput.name = "Code-Review";
+    BatchLabelInput input = new BatchLabelInput();
+    input.create = ImmutableList.of(labelInput);
+
+    ResourceConflictException thrown =
+        assertThrows(
+            ResourceConflictException.class,
+            () -> gApi.projects().name(allProjects.get()).labels(input));
+    assertThat(thrown).hasMessageThat().contains("label Code-Review already exists");
+  }
+
+  @Test
+  public void cannotCreateTwoLabelsWithTheSameName() throws Exception {
+    LabelDefinitionInput fooInput = new LabelDefinitionInput();
+    fooInput.name = "Foo";
+    fooInput.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+
+    BatchLabelInput input = new BatchLabelInput();
+    input.create = ImmutableList.of(fooInput, fooInput);
+
+    ResourceConflictException thrown =
+        assertThrows(
+            ResourceConflictException.class,
+            () -> gApi.projects().name(project.get()).labels(input));
+    assertThat(thrown).hasMessageThat().contains("label Foo already exists");
+  }
+
+  @Test
+  public void cannotCreateTwoLabelsWithNamesThatAreTheSameAfterTrim() throws Exception {
+    LabelDefinitionInput foo1Input = new LabelDefinitionInput();
+    foo1Input.name = "Foo";
+    foo1Input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+
+    LabelDefinitionInput foo2Input = new LabelDefinitionInput();
+    foo2Input.name = " Foo ";
+    foo2Input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+
+    BatchLabelInput input = new BatchLabelInput();
+    input.create = ImmutableList.of(foo1Input, foo2Input);
+
+    ResourceConflictException thrown =
+        assertThrows(
+            ResourceConflictException.class,
+            () -> gApi.projects().name(project.get()).labels(input));
+    assertThat(thrown).hasMessageThat().contains("label Foo already exists");
+  }
+
+  @Test
+  public void cannotCreateTwoLabelsWithConflictingNames() throws Exception {
+    LabelDefinitionInput foo1Input = new LabelDefinitionInput();
+    foo1Input.name = "Foo";
+    foo1Input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+
+    LabelDefinitionInput foo2Input = new LabelDefinitionInput();
+    foo2Input.name = "foo";
+    foo2Input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+
+    BatchLabelInput input = new BatchLabelInput();
+    input.create = ImmutableList.of(foo1Input, foo2Input);
+
+    ResourceConflictException thrown =
+        assertThrows(
+            ResourceConflictException.class,
+            () -> gApi.projects().name(project.get()).labels(input));
+    assertThat(thrown).hasMessageThat().contains("label foo conflicts with existing label Foo");
+  }
+
+  @Test
+  public void createLabels() throws Exception {
+    LabelDefinitionInput fooInput = new LabelDefinitionInput();
+    fooInput.name = "Foo";
+    fooInput.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+
+    LabelDefinitionInput barInput = new LabelDefinitionInput();
+    barInput.name = "Bar";
+    barInput.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+
+    BatchLabelInput input = new BatchLabelInput();
+    input.create = ImmutableList.of(fooInput, barInput);
+
+    gApi.projects().name(allProjects.get()).labels(input);
+    assertThat(gApi.projects().name(allProjects.get()).label("Foo").get()).isNotNull();
+    assertThat(gApi.projects().name(allProjects.get()).label("Bar").get()).isNotNull();
+  }
+
+  @Test
+  public void createLabels_labelNamesAreTrimmed() throws Exception {
+    LabelDefinitionInput fooInput = new LabelDefinitionInput();
+    fooInput.name = " Foo ";
+    fooInput.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+
+    LabelDefinitionInput barInput = new LabelDefinitionInput();
+    barInput.name = " Bar ";
+    barInput.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+
+    BatchLabelInput input = new BatchLabelInput();
+    input.create = ImmutableList.of(fooInput, barInput);
+
+    gApi.projects().name(allProjects.get()).labels(input);
+    assertThat(gApi.projects().name(allProjects.get()).label("Foo").get()).isNotNull();
+    assertThat(gApi.projects().name(allProjects.get()).label("Bar").get()).isNotNull();
+  }
+
+  @Test
+  public void cannotCreateLabelWithoutName() throws Exception {
+    BatchLabelInput input = new BatchLabelInput();
+    input.create = ImmutableList.of(new LabelDefinitionInput());
+
+    BadRequestException thrown =
+        assertThrows(
+            BadRequestException.class, () -> gApi.projects().name(allProjects.get()).labels(input));
+    assertThat(thrown).hasMessageThat().contains("label name is required for new label");
+  }
+
+  @Test
+  public void cannotSetCommitMessageOnLabelDefinitionInputForCreate() throws Exception {
+    LabelDefinitionInput labelInput = new LabelDefinitionInput();
+    labelInput.name = "Foo";
+    labelInput.commitMessage = "Create Label Foo";
+
+    BatchLabelInput input = new BatchLabelInput();
+    input.create = ImmutableList.of(labelInput);
+
+    BadRequestException thrown =
+        assertThrows(
+            BadRequestException.class, () -> gApi.projects().name(allProjects.get()).labels(input));
+    assertThat(thrown)
+        .hasMessageThat()
+        .contains("commit message on label definition input not supported");
+  }
+
+  @Test
+  public void updateNonExistingLabel() throws Exception {
+    BatchLabelInput input = new BatchLabelInput();
+    input.update = ImmutableMap.of("Foo", new LabelDefinitionInput());
+
+    UnprocessableEntityException thrown =
+        assertThrows(
+            UnprocessableEntityException.class,
+            () -> gApi.projects().name(allProjects.get()).labels(input));
+    assertThat(thrown).hasMessageThat().contains("label Foo not found");
+  }
+
+  @Test
+  public void updateLabels() throws Exception {
+    configLabel("Foo", LabelFunction.NO_OP);
+    configLabel("Bar", LabelFunction.NO_OP);
+
+    LabelDefinitionInput fooUpdate = new LabelDefinitionInput();
+    fooUpdate.function = LabelFunction.MAX_WITH_BLOCK.getFunctionName();
+    LabelDefinitionInput barUpdate = new LabelDefinitionInput();
+    barUpdate.name = "Baz";
+
+    BatchLabelInput input = new BatchLabelInput();
+    input.update = ImmutableMap.of("Foo", fooUpdate, "Bar", barUpdate);
+
+    gApi.projects().name(project.get()).labels(input);
+
+    assertThat(gApi.projects().name(project.get()).label("Foo").get().function)
+        .isEqualTo(fooUpdate.function);
+    assertThat(gApi.projects().name(project.get()).label("Baz").get()).isNotNull();
+    assertThrows(
+        ResourceNotFoundException.class,
+        () -> gApi.projects().name(project.get()).label("Bar").get());
+  }
+
+  @Test
+  public void updateLabels_labelNamesAreTrimmed() throws Exception {
+    configLabel("Foo", LabelFunction.NO_OP);
+    configLabel("Bar", LabelFunction.NO_OP);
+
+    LabelDefinitionInput fooUpdate = new LabelDefinitionInput();
+    fooUpdate.function = LabelFunction.MAX_WITH_BLOCK.getFunctionName();
+    LabelDefinitionInput barUpdate = new LabelDefinitionInput();
+    barUpdate.name = "Baz";
+
+    BatchLabelInput input = new BatchLabelInput();
+    input.update = ImmutableMap.of(" Foo ", fooUpdate, " Bar ", barUpdate);
+
+    gApi.projects().name(project.get()).labels(input);
+
+    assertThat(gApi.projects().name(project.get()).label("Foo").get().function)
+        .isEqualTo(fooUpdate.function);
+    assertThat(gApi.projects().name(project.get()).label("Baz").get()).isNotNull();
+    assertThrows(
+        ResourceNotFoundException.class,
+        () -> gApi.projects().name(project.get()).label("Bar").get());
+  }
+
+  @Test
+  public void cannotSetCommitMessageOnLabelDefinitionInputForUpdate() throws Exception {
+    LabelDefinitionInput labelInput = new LabelDefinitionInput();
+    labelInput.commitMessage = "Update label";
+
+    BatchLabelInput input = new BatchLabelInput();
+    input.update = ImmutableMap.of("Code-Review", labelInput);
+
+    BadRequestException thrown =
+        assertThrows(
+            BadRequestException.class, () -> gApi.projects().name(allProjects.get()).labels(input));
+    assertThat(thrown)
+        .hasMessageThat()
+        .contains("commit message on label definition input not supported");
+  }
+
+  @Test
+  public void deleteAndRecreateLabel() throws Exception {
+    configLabel("Foo", LabelFunction.NO_OP);
+
+    LabelDefinitionInput fooInput = new LabelDefinitionInput();
+    fooInput.name = "Foo";
+    fooInput.function = LabelFunction.MAX_NO_BLOCK.getFunctionName();
+    fooInput.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+
+    BatchLabelInput input = new BatchLabelInput();
+    input.delete = ImmutableList.of("Foo");
+    input.create = ImmutableList.of(fooInput);
+
+    gApi.projects().name(project.get()).labels(input);
+
+    LabelDefinitionInfo fooLabel = gApi.projects().name(project.get()).label("Foo").get();
+    assertThat(fooLabel.function).isEqualTo(fooInput.function);
+  }
+
+  @Test
+  public void deleteRecreateAndUpdateLabel() throws Exception {
+    configLabel("Foo", LabelFunction.NO_OP);
+
+    LabelDefinitionInput fooCreateInput = new LabelDefinitionInput();
+    fooCreateInput.name = "Foo";
+    fooCreateInput.function = LabelFunction.MAX_NO_BLOCK.getFunctionName();
+    fooCreateInput.values =
+        ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+
+    LabelDefinitionInput fooUpdateInput = new LabelDefinitionInput();
+    fooUpdateInput.function = LabelFunction.ANY_WITH_BLOCK.getFunctionName();
+
+    BatchLabelInput input = new BatchLabelInput();
+    input.delete = ImmutableList.of("Foo");
+    input.create = ImmutableList.of(fooCreateInput);
+    input.update = ImmutableMap.of("Foo", fooUpdateInput);
+
+    gApi.projects().name(project.get()).labels(input);
+
+    LabelDefinitionInfo fooLabel = gApi.projects().name(project.get()).label("Foo").get();
+    assertThat(fooLabel.function).isEqualTo(fooUpdateInput.function);
+  }
+
+  @Test
+  public void cannotDeleteAndUpdateLabel() throws Exception {
+    configLabel("Foo", LabelFunction.NO_OP);
+
+    LabelDefinitionInput fooInput = new LabelDefinitionInput();
+    fooInput.function = LabelFunction.MAX_NO_BLOCK.getFunctionName();
+
+    BatchLabelInput input = new BatchLabelInput();
+    input.delete = ImmutableList.of("Foo");
+    input.update = ImmutableMap.of("Foo", fooInput);
+
+    UnprocessableEntityException thrown =
+        assertThrows(
+            UnprocessableEntityException.class,
+            () -> gApi.projects().name(project.get()).labels(input));
+    assertThat(thrown).hasMessageThat().contains("label Foo not found");
+  }
+
+  @Test
+  public void createAndUpdateLabel() throws Exception {
+    LabelDefinitionInput fooCreateInput = new LabelDefinitionInput();
+    fooCreateInput.name = "Foo";
+    fooCreateInput.function = LabelFunction.MAX_NO_BLOCK.getFunctionName();
+    fooCreateInput.values =
+        ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+
+    LabelDefinitionInput fooUpdateInput = new LabelDefinitionInput();
+    fooUpdateInput.function = LabelFunction.ANY_WITH_BLOCK.getFunctionName();
+
+    BatchLabelInput input = new BatchLabelInput();
+    input.create = ImmutableList.of(fooCreateInput);
+    input.update = ImmutableMap.of("Foo", fooUpdateInput);
+
+    gApi.projects().name(project.get()).labels(input);
+
+    LabelDefinitionInfo fooLabel = gApi.projects().name(project.get()).label("Foo").get();
+    assertThat(fooLabel.function).isEqualTo(fooUpdateInput.function);
+  }
+
+  @Test
+  public void noOpUpdate() throws Exception {
+    RevCommit refsMetaConfigHead =
+        projectOperations.project(allProjects).getHead(RefNames.REFS_CONFIG);
+
+    gApi.projects().name(allProjects.get()).labels(new BatchLabelInput());
+
+    assertThat(projectOperations.project(allProjects).getHead(RefNames.REFS_CONFIG))
+        .isEqualTo(refsMetaConfigHead);
+  }
+
+  @Test
+  public void defaultCommitMessage() throws Exception {
+    BatchLabelInput input = new BatchLabelInput();
+    input.delete = ImmutableList.of("Code-Review");
+    gApi.projects().name(allProjects.get()).labels(input);
+    assertThat(
+            projectOperations.project(allProjects).getHead(RefNames.REFS_CONFIG).getShortMessage())
+        .isEqualTo("Update labels");
+  }
+
+  @Test
+  public void withCommitMessage() throws Exception {
+    BatchLabelInput input = new BatchLabelInput();
+    input.commitMessage = "Batch Update Labels";
+    input.delete = ImmutableList.of("Code-Review");
+    gApi.projects().name(allProjects.get()).labels(input);
+    assertThat(
+            projectOperations.project(allProjects).getHead(RefNames.REFS_CONFIG).getShortMessage())
+        .isEqualTo(input.commitMessage);
+  }
+
+  @Test
+  public void commitMessageIsTrimmed() throws Exception {
+    BatchLabelInput input = new BatchLabelInput();
+    input.commitMessage = " Batch Update Labels ";
+    input.delete = ImmutableList.of("Code-Review");
+    gApi.projects().name(allProjects.get()).labels(input);
+    assertThat(
+            projectOperations.project(allProjects).getHead(RefNames.REFS_CONFIG).getShortMessage())
+        .isEqualTo("Batch Update Labels");
+  }
+}
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/SetLabelIT.java b/javatests/com/google/gerrit/acceptance/rest/project/SetLabelIT.java
index 9cba930..97b795f 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/SetLabelIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/SetLabelIT.java
@@ -323,6 +323,21 @@
   }
 
   @Test
+  public void cannotSetDuplicateValues() throws Exception {
+    LabelDefinitionInput input = new LabelDefinitionInput();
+    // Positive values can be specified as '<value>' or '+<value>'.
+    input.values =
+        ImmutableMap.of(
+            "+1", "Looks Good", "1", "Looks Good", "0", "Don't Know", "-1", "Looks Bad");
+
+    BadRequestException thrown =
+        assertThrows(
+            BadRequestException.class,
+            () -> gApi.projects().name(allProjects.get()).label("Code-Review").update(input));
+    assertThat(thrown).hasMessageThat().contains("duplicate value: 1");
+  }
+
+  @Test
   public void updateDefaultValue() throws Exception {
     LabelDefinitionInput input = new LabelDefinitionInput();
     input.defaultValue = 1;
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/acceptance/server/change/BUILD b/javatests/com/google/gerrit/acceptance/server/change/BUILD
index 4d1634d..500ab06 100644
--- a/javatests/com/google/gerrit/acceptance/server/change/BUILD
+++ b/javatests/com/google/gerrit/acceptance/server/change/BUILD
@@ -4,5 +4,8 @@
     srcs = glob(["*IT.java"]),
     group = "server_change",
     labels = ["server"],
-    deps = ["//java/com/google/gerrit/server/util/time"],
+    deps = [
+        "//java/com/google/gerrit/server/logging",
+        "//java/com/google/gerrit/server/util/time",
+    ],
 )
diff --git a/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java b/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java
index d85a07b..65f8aa0 100644
--- a/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/change/CommentsIT.java
@@ -29,9 +29,11 @@
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.NoHttpd;
 import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
 import com.google.gerrit.entities.Change;
 import com.google.gerrit.entities.Patch;
+import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.extensions.api.changes.DeleteCommentInput;
 import com.google.gerrit.extensions.api.changes.DraftInput;
 import com.google.gerrit.extensions.api.changes.ReviewInput;
@@ -59,7 +61,6 @@
 import java.sql.Timestamp;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -69,6 +70,7 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.notes.NoteMap;
 import org.eclipse.jgit.revwalk.RevCommit;
@@ -82,6 +84,7 @@
   @Inject private Provider<ChangesCollection> changes;
   @Inject private Provider<PostReview> postReview;
   @Inject private RequestScopeOperations requestScopeOperations;
+  @Inject ProjectOperations projectOperations;
 
   private final Integer[] lines = {0, 1};
 
@@ -315,6 +318,47 @@
   }
 
   @Test
+  public void postCommentsUnreachableData() throws Exception {
+    requestScopeOperations.setApiUser(admin.id());
+
+    String file = "file";
+    PushOneCommit push =
+        pushFactory.create(admin.newIdent(), testRepo, "first subject", file, "l1\nl2\n");
+
+    String dest = "refs/for/master";
+    PushOneCommit.Result r1 = push.to(dest);
+    r1.assertOkStatus();
+    String changeId = r1.getChangeId();
+    String revId = r1.getCommit().getName();
+
+    PushOneCommit.Result r2 = amendChange(r1.getChangeId());
+    r2.assertOkStatus();
+
+    String draftRefName = RefNames.refsDraftComments(r1.getChange().getId(), admin.id());
+
+    DraftInput draft = newDraft(file, Side.REVISION, 1, "comment");
+    addDraft(changeId, "1", draft);
+    ReviewInput reviewInput = new ReviewInput();
+    reviewInput.drafts = DraftHandling.PUBLISH;
+    reviewInput.message = "foo";
+    gApi.changes().id(r1.getChangeId()).revision(1).review(reviewInput);
+
+    addDraft(changeId, "2", newDraft(file, Side.REVISION, 2, "comment2"));
+    reviewInput = new ReviewInput();
+    reviewInput.drafts = DraftHandling.PUBLISH_ALL_REVISIONS;
+    reviewInput.message = "bar";
+    gApi.changes().id(r1.getChangeId()).revision(2).review(reviewInput);
+
+    Map<String, List<CommentInfo>> drafts = getDraftComments(changeId, revId);
+    assertThat(drafts.isEmpty()).isTrue();
+
+    try (Repository repo = repoManager.openRepository(allUsers)) {
+      Ref ref = repo.exactRef(draftRefName);
+      assertThat(ref).isNull();
+    }
+  }
+
+  @Test
   public void listComments() throws Exception {
     String file = "file";
     PushOneCommit push =
@@ -1002,20 +1046,6 @@
     assertThat(getChangeSortedComments(id.get())).hasSize(3);
   }
 
-  @Test
-  public void jsonCommentHasLegacyFormatFalse() throws Exception {
-    PushOneCommit.Result result = createChange();
-    Change.Id changeId = result.getChange().getId();
-    addComment(result.getChangeId(), "comment");
-
-    Collection<com.google.gerrit.entities.Comment> comments =
-        notesFactory.createChecked(project, changeId).getComments().values();
-    assertThat(comments).hasSize(1);
-    com.google.gerrit.entities.Comment comment = comments.iterator().next();
-    assertThat(comment.message).isEqualTo("comment");
-    assertThat(comment.legacyFormat).isFalse();
-  }
-
   private List<CommentInfo> getRevisionComments(String changeId, String revId) throws Exception {
     return getPublishedComments(changeId, revId).values().stream()
         .flatMap(List::stream)
diff --git a/javatests/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java b/javatests/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java
index 1e2d1ba..08719d3 100644
--- a/javatests/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java
@@ -39,6 +39,7 @@
 import com.google.gerrit.server.change.ConsistencyChecker;
 import com.google.gerrit.server.change.NotifyResolver;
 import com.google.gerrit.server.change.PatchSetInserter;
+import com.google.gerrit.server.logging.RequestId;
 import com.google.gerrit.server.notedb.ChangeNoteUtil;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.notedb.Sequences;
@@ -313,7 +314,8 @@
             @Override
             public boolean updateChange(ChangeContext ctx) {
               ctx.getChange().setStatus(Change.Status.MERGED);
-              ctx.getUpdate(ctx.getChange().currentPatchSetId()).fixStatus(Change.Status.MERGED);
+              ctx.getUpdate(ctx.getChange().currentPatchSetId())
+                  .fixStatusToMerged(new RequestId(ctx.getChange().getId().toString()));
               return true;
             }
           });
@@ -862,7 +864,8 @@
             @Override
             public boolean updateChange(ChangeContext ctx) {
               ctx.getChange().setStatus(Change.Status.MERGED);
-              ctx.getUpdate(ctx.getChange().currentPatchSetId()).fixStatus(Change.Status.MERGED);
+              ctx.getUpdate(ctx.getChange().currentPatchSetId())
+                  .fixStatusToMerged(new RequestId(ctx.getChange().getId().toString()));
               return true;
             }
           });
diff --git a/javatests/com/google/gerrit/acceptance/server/change/PatchListCacheIT.java b/javatests/com/google/gerrit/acceptance/server/change/PatchListCacheIT.java
index 7c78d61..b23f9a3 100644
--- a/javatests/com/google/gerrit/acceptance/server/change/PatchListCacheIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/change/PatchListCacheIT.java
@@ -39,7 +39,9 @@
 import com.google.gerrit.server.patch.Text;
 import com.google.inject.Inject;
 import com.google.inject.name.Named;
+import java.lang.reflect.Field;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Optional;
@@ -66,6 +68,25 @@
   private Cache<PatchListKey, PatchList> abstractPatchListCache;
 
   @Test
+  public void ensureLegacyBackendIsUsedForFileCacheBackend() throws Exception {
+    Field fileCacheField = patchListCache.getClass().getDeclaredField("fileCache");
+    fileCacheField.setAccessible(true);
+    // Use the reflection to access "localCache" field that is only present in Guava backend.
+    assertThat(
+            Arrays.stream(fileCacheField.get(patchListCache).getClass().getDeclaredFields())
+                .anyMatch(f -> f.getName().equals("localCache")))
+        .isTrue();
+
+    // intraCache (and all other cache backends) should use Caffeine backend.
+    Field intraCacheField = patchListCache.getClass().getDeclaredField("intraCache");
+    intraCacheField.setAccessible(true);
+    assertThat(
+            Arrays.stream(intraCacheField.get(patchListCache).getClass().getDeclaredFields())
+                .noneMatch(f -> f.getName().equals("localCache")))
+        .isTrue();
+  }
+
+  @Test
   public void listPatchesAgainstBase() throws Exception {
     commitBuilder().add(FILE_D, "4").message(SUBJECT_1).create();
     pushHead(testRepo, "refs/heads/master", false);
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/index/query/RangeUtilTest.java b/javatests/com/google/gerrit/index/query/RangeUtilTest.java
new file mode 100644
index 0000000..681f9d99b
--- /dev/null
+++ b/javatests/com/google/gerrit/index/query/RangeUtilTest.java
@@ -0,0 +1,34 @@
+// 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.index.query;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.index.query.RangeUtil.Range;
+import org.junit.Test;
+
+public class RangeUtilTest {
+  @Test
+  public void getRangeForValueOutsideOfMinMaxRange_minNotGreaterThanMax() {
+    for (String operator : ImmutableList.of("=", ">", ">=", "<", "<=")) {
+      Range range = RangeUtil.getRange("foo", operator, 10, -4, 4);
+      assertThat(range.min).isAtMost(range.max);
+
+      range = RangeUtil.getRange("foo", operator, -10, -4, 4);
+      assertThat(range.min).isAtMost(range.max);
+    }
+  }
+}
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/index/change/FakeQueryBuilder.java b/javatests/com/google/gerrit/server/index/change/FakeQueryBuilder.java
index 9a48a68..3dfbefe 100644
--- a/javatests/com/google/gerrit/server/index/change/FakeQueryBuilder.java
+++ b/javatests/com/google/gerrit/server/index/change/FakeQueryBuilder.java
@@ -18,6 +18,7 @@
 import com.google.gerrit.index.query.Predicate;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.ChangeQueryBuilder;
+import org.eclipse.jgit.lib.Config;
 import org.junit.Ignore;
 
 @Ignore
@@ -26,8 +27,34 @@
     super(
         new ChangeQueryBuilder.Definition<>(FakeQueryBuilder.class),
         new ChangeQueryBuilder.Arguments(
-            null, null, null, null, null, null, null, null, null, null, null, null, null, null,
-            null, null, null, null, indexes, null, null, null, null, null, null, null, null));
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            indexes,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            new Config()));
   }
 
   @Operator
diff --git a/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java b/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java
index 0d7f2bd..6ece894 100644
--- a/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java
+++ b/javatests/com/google/gerrit/server/notedb/ChangeNotesStateTest.java
@@ -938,7 +938,6 @@
                 .put("revId", String.class)
                 .put("serverId", String.class)
                 .put("unresolved", boolean.class)
-                .put("legacyFormat", boolean.class)
                 .build());
   }
 
diff --git a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index 231340d..962b691 100644
--- a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -41,6 +41,7 @@
 import com.google.common.collect.Lists;
 import com.google.common.collect.Streams;
 import com.google.common.truth.ThrowableSubject;
+import com.google.gerrit.acceptance.config.GerritConfig;
 import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.data.LabelType;
@@ -79,6 +80,7 @@
 import com.google.gerrit.index.IndexConfig;
 import com.google.gerrit.index.Schema;
 import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.lifecycle.LifecycleManager;
 import com.google.gerrit.server.AnonymousUser;
 import com.google.gerrit.server.CurrentUser;
@@ -2023,6 +2025,7 @@
   }
 
   @Test
+  @GerritConfig(name = "index.change.indexMergeable", value = "true")
   public void mergeable() throws Exception {
     TestRepository<Repo> repo = createProject("repo");
     RevCommit commit1 = repo.parseBody(repo.commit().add("file1", "contents1").create());
@@ -2040,7 +2043,7 @@
     // If a change gets submitted, the remaining open changes get reindexed asynchronously to update
     // their mergeability information. If the further assertions in this test are done before the
     // asynchronous reindex completed they fail because the mergeability information in the index
-    // was not updated yet. To avoid this flakiness reindexAfterRefUpdate is switched off for the
+    // was not updated yet. To avoid this flakiness indexMergeable is switched off for the
     // tests and we index change2 synchronously here.
     gApi.changes().id(change2.getChangeId()).index();
 
@@ -3079,6 +3082,20 @@
     }
   }
 
+  @Test
+  @GerritConfig(name = "index.change.indexMergeable", value = "false")
+  public void mergeableFailsWhenNotIndexed() throws Exception {
+    TestRepository<Repo> repo = createProject("repo");
+    RevCommit commit1 = repo.parseBody(repo.commit().add("file1", "contents1").create());
+    insert(repo, newChangeForCommit(repo, commit1));
+
+    Throwable thrown = assertThrows(Throwable.class, () -> assertQuery("status:open is:mergeable"));
+    assertThat(thrown.getCause()).isInstanceOf(QueryParseException.class);
+    assertThat(thrown)
+        .hasMessageThat()
+        .contains("server does not support 'mergeable'. check configs");
+  }
+
   protected ChangeInserter newChange(TestRepository<Repo> repo) throws Exception {
     return newChange(repo, null, null, null, null, false);
   }
diff --git a/javatests/com/google/gerrit/server/query/change/BUILD b/javatests/com/google/gerrit/server/query/change/BUILD
index d0162d3..e5b51e7 100644
--- a/javatests/com/google/gerrit/server/query/change/BUILD
+++ b/javatests/com/google/gerrit/server/query/change/BUILD
@@ -16,12 +16,14 @@
         "//prolog:gerrit-prolog-common",
     ],
     deps = [
+        "//java/com/google/gerrit/acceptance/config",
         "//java/com/google/gerrit/acceptance/testsuite/project",
         "//java/com/google/gerrit/common:annotations",
         "//java/com/google/gerrit/common:server",
         "//java/com/google/gerrit/entities",
         "//java/com/google/gerrit/extensions:api",
         "//java/com/google/gerrit/index",
+        "//java/com/google/gerrit/index:query_exception",
         "//java/com/google/gerrit/lifecycle",
         "//java/com/google/gerrit/server",
         "//java/com/google/gerrit/server/project/testing:project-test-util",
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/BUILD b/lib/BUILD
index 13580b1..39c622a 100644
--- a/lib/BUILD
+++ b/lib/BUILD
@@ -1,4 +1,4 @@
-load("@rules_java//java:defs.bzl", "java_library")
+load("@rules_java//java:defs.bzl", "java_import", "java_library")
 
 exports_files(glob([
     "LICENSE-*",
@@ -111,6 +111,29 @@
 )
 
 java_library(
+    name = "caffeine",
+    data = ["//lib:LICENSE-Apache2.0"],
+    visibility = [
+        "//java/com/google/gerrit/server/cache/mem:__pkg__",
+    ],
+    exports = ["@caffeine//jar"],
+)
+
+java_import(
+    name = "caffeine-guava-renamed",
+    jars = ["@caffeine-guava-renamed//file"],
+)
+
+java_library(
+    name = "caffeine-guava",
+    data = ["//lib:LICENSE-Apache2.0"],
+    visibility = [
+        "//java/com/google/gerrit/server/cache/mem:__pkg__",
+    ],
+    exports = [":caffeine-guava-renamed"],
+)
+
+java_library(
     name = "jsch",
     data = ["//lib:LICENSE-jsch"],
     visibility = ["//visibility:public"],
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/modules/jgit b/modules/jgit
index 63fc697..0356613 160000
--- a/modules/jgit
+++ b/modules/jgit
@@ -1 +1 @@
-Subproject commit 63fc6970cc51b712608d93e7cba0b85bb559ac52
+Subproject commit 0356613f48ebee2e3d2d65780e71d9e0b43a752e
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/plugins/hooks b/plugins/hooks
index f4bf0ff..6316be2 160000
--- a/plugins/hooks
+++ b/plugins/hooks
@@ -1 +1 @@
-Subproject commit f4bf0ffbd13a748cc46a3368a8fadcc2cbab6e21
+Subproject commit 6316be2828808dafc546ecd11c055396d0b4951b
diff --git a/polygerrit-ui/app/BUILD b/polygerrit-ui/app/BUILD
index 5ff820b..3a70ac53 100644
--- a/polygerrit-ui/app/BUILD
+++ b/polygerrit-ui/app/BUILD
@@ -55,7 +55,6 @@
         exclude = [
             "bower_components/**",
             "**/*_test.html",
-            "embed/test.html",
             "test/**",
             "samples/**",
         ],
@@ -166,33 +165,6 @@
     ],
 ) for directory in DIRECTORIES]
 
-# Embed bundle
-polygerrit_bundle(
-    name = "polygerrit_embed_ui",
-    srcs = glob(
-        [
-            "**/*.html",
-            "**/*.js",
-        ],
-        exclude = [
-            "bower_components/**",
-            "test/**",
-            "**/*_test.html",
-        ],
-    ),
-    outs = ["polygerrit_embed_ui.zip"],
-    app = "embed/embed.html",
-)
-
-filegroup(
-    name = "embed_test_files",
-    srcs = glob(
-        [
-            "embed/**/*_test.html",
-        ],
-    ),
-)
-
 filegroup(
     name = "template_test_srcs",
     srcs = [
@@ -200,21 +172,3 @@
         "template_test_srcs/template_test.js",
     ],
 )
-
-sh_test(
-    name = "embed_test",
-    size = "small",
-    srcs = ["embed_test.sh"],
-    data = [
-        "embed/test.html",
-        "test/common-test-setup.html",
-        ":embed_test_files",
-        ":pg_code.zip",
-        ":test_components.zip",
-    ],
-    # Should not run sandboxed.
-    tags = [
-        "local",
-        "manual",
-    ],
-)
diff --git a/polygerrit-ui/app/behaviors/async-foreach-behavior/async-foreach-behavior_test.html b/polygerrit-ui/app/behaviors/async-foreach-behavior/async-foreach-behavior_test.html
index 970bfc7..03ba6b1 100644
--- a/polygerrit-ui/app/behaviors/async-foreach-behavior/async-foreach-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/async-foreach-behavior/async-foreach-behavior_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>async-foreach-behavior</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/behaviors/base-url-behavior/base-url-behavior_test.html b/polygerrit-ui/app/behaviors/base-url-behavior/base-url-behavior_test.html
index b61b142..5669bc4 100644
--- a/polygerrit-ui/app/behaviors/base-url-behavior/base-url-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/base-url-behavior/base-url-behavior_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>base-url-behavior</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/behaviors/docs-url-behavior/docs-url-behavior_test.html b/polygerrit-ui/app/behaviors/docs-url-behavior/docs-url-behavior_test.html
index 2c513f3..e554012 100644
--- a/polygerrit-ui/app/behaviors/docs-url-behavior/docs-url-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/docs-url-behavior/docs-url-behavior_test.html
@@ -19,7 +19,7 @@
 <script src="/bower_components/web-component-tester/browser.js"></script>
 <link rel="import" href="../../test/common-test-setup.html"/>
 <title>docs-url-behavior</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <link rel="import" href="docs-url-behavior.html">
diff --git a/polygerrit-ui/app/behaviors/dom-util-behavior/dom-util-behavior_test.html b/polygerrit-ui/app/behaviors/dom-util-behavior/dom-util-behavior_test.html
index 8323ac6..a52e0e2 100644
--- a/polygerrit-ui/app/behaviors/dom-util-behavior/dom-util-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/dom-util-behavior/dom-util-behavior_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>dom-util-behavior</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/behaviors/gr-access-behavior/gr-access-behavior_test.html b/polygerrit-ui/app/behaviors/gr-access-behavior/gr-access-behavior_test.html
index 0d1ee57..48565a6 100644
--- a/polygerrit-ui/app/behaviors/gr-access-behavior/gr-access-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-access-behavior/gr-access-behavior_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>keyboard-shortcut-behavior</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/behaviors/gr-admin-nav-behavior/gr-admin-nav-behavior_test.html b/polygerrit-ui/app/behaviors/gr-admin-nav-behavior/gr-admin-nav-behavior_test.html
index 90593a2..25b75c8 100644
--- a/polygerrit-ui/app/behaviors/gr-admin-nav-behavior/gr-admin-nav-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-admin-nav-behavior/gr-admin-nav-behavior_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>keyboard-shortcut-behavior</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/behaviors/gr-change-table-behavior/gr-change-table-behavior_test.html b/polygerrit-ui/app/behaviors/gr-change-table-behavior/gr-change-table-behavior_test.html
index 791e2af..52931bc 100644
--- a/polygerrit-ui/app/behaviors/gr-change-table-behavior/gr-change-table-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-change-table-behavior/gr-change-table-behavior_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>keyboard-shortcut-behavior</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/behaviors/gr-display-name-behavior/gr-display-name-behavior_test.html b/polygerrit-ui/app/behaviors/gr-display-name-behavior/gr-display-name-behavior_test.html
index 3d4eca1..aa217a1 100644
--- a/polygerrit-ui/app/behaviors/gr-display-name-behavior/gr-display-name-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-display-name-behavior/gr-display-name-behavior_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-display-name-behavior</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/behaviors/gr-list-view-behavior/gr-list-view-behavior_test.html b/polygerrit-ui/app/behaviors/gr-list-view-behavior/gr-list-view-behavior_test.html
index 535483d..9b48cdc 100644
--- a/polygerrit-ui/app/behaviors/gr-list-view-behavior/gr-list-view-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-list-view-behavior/gr-list-view-behavior_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>keyboard-shortcut-behavior</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior_test.html b/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior_test.html
index 3db4084..5e70179 100644
--- a/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior_test.html
@@ -19,7 +19,7 @@
 <script src="/bower_components/web-component-tester/browser.js"></script>
 <link rel="import" href="../../test/common-test-setup.html"/>
 <title>gr-patch-set-behavior</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <link rel="import" href="gr-patch-set-behavior.html">
diff --git a/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior_test.html b/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior_test.html
index 0046290..924c98c 100644
--- a/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior_test.html
@@ -19,7 +19,7 @@
 <script src="/bower_components/web-component-tester/browser.js"></script>
 <link rel="import" href="../../test/common-test-setup.html"/>
 <title>gr-path-list-behavior</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <link rel="import" href="gr-path-list-behavior.html">
diff --git a/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior_test.html b/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior_test.html
index 173c8d4..f3354a4 100644
--- a/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-tooltip-behavior/gr-tooltip-behavior_test.html
@@ -17,7 +17,7 @@
 -->
 
 <title>tooltip-behavior</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior_test.html b/polygerrit-ui/app/behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior_test.html
index 73e51d3..6cf2c68 100644
--- a/polygerrit-ui/app/behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-url-encoding-behavior/gr-url-encoding-behavior_test.html
@@ -17,7 +17,7 @@
 -->
 
 <title>gr-url-encoding-behavior</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior_test.html b/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior_test.html
index 3183c7e..ba143ec 100644
--- a/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>keyboard-shortcut-behavior</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/behaviors/rest-client-behavior/rest-client-behavior_test.html b/polygerrit-ui/app/behaviors/rest-client-behavior/rest-client-behavior_test.html
index a77a01f..cfcc11e 100644
--- a/polygerrit-ui/app/behaviors/rest-client-behavior/rest-client-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/rest-client-behavior/rest-client-behavior_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>keyboard-shortcut-behavior</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/behaviors/safe-types-behavior/safe-types-behavior_test.html b/polygerrit-ui/app/behaviors/safe-types-behavior/safe-types-behavior_test.html
index ab446f1..7351647 100644
--- a/polygerrit-ui/app/behaviors/safe-types-behavior/safe-types-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/safe-types-behavior/safe-types-behavior_test.html
@@ -17,7 +17,7 @@
 -->
 
 <title>safe-types-behavior</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section_test.html b/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section_test.html
index 16c654f..55b2495 100644
--- a/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-access-section</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/page/page.js"></script>
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list_test.html b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list_test.html
index 58c7be4..455b3a2 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-admin-group-list</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/page/page.js"></script>
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view_test.html b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view_test.html
index 984be19..e6eef75 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-admin-view</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog_test.html b/polygerrit-ui/app/elements/admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog_test.html
index 3292cec..c83349b 100644
--- a/polygerrit-ui/app/elements/admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-confirm-delete-item-dialog/gr-confirm-delete-item-dialog_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-confirm-delete-item-dialog</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
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-change-dialog/gr-create-change-dialog_test.html b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog_test.html
index 3a3683f..3367aa3 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-create-change-dialog</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog_test.html b/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog_test.html
index ebca289..3a99526 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-create-group-dialog/gr-create-group-dialog_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-create-group-dialog</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/page/page.js"></script>
@@ -56,7 +56,8 @@
     test('name is updated correctly', done => {
       assert.isFalse(element.hasNewGroupName);
 
-      ironInput(element.root).bindValue = GROUP_NAME;
+      const inputEl = element.root.querySelector('iron-input');
+      inputEl.bindValue = GROUP_NAME;
 
       setTimeout(() => {
         assert.isTrue(element.hasNewGroupName);
diff --git a/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog_test.html b/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog_test.html
index 08e8213..41b8eca 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-create-pointer-dialog/gr-create-pointer-dialog_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-create-pointer-dialog</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
@@ -39,6 +39,10 @@
     let element;
     let sandbox;
 
+    const ironInput = function(element) {
+      return Polymer.dom(element).querySelector('iron-input');
+    };
+
     setup(() => {
       sandbox = sinon.sandbox.create();
       stub('gr-rest-api-interface', {
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-create-repo-dialog/gr-create-repo-dialog_test.html b/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog_test.html
index 7e32c5c..fa481c2 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-create-repo-dialog/gr-create-repo-dialog_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-create-repo-dialog</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log_test.html b/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log_test.html
index 313d465..d517242 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-group-audit-log/gr-group-audit-log_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-group-audit-log</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
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/admin/gr-group-members/gr-group-members_test.html b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_test.html
index bf9113b..e90e6fd 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-group-members</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/admin/gr-group/gr-group_test.html b/polygerrit-ui/app/elements/admin/gr-group/gr-group_test.html
index 1672e85..0d7aae2 100644
--- a/polygerrit-ui/app/elements/admin/gr-group/gr-group_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-group/gr-group_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-group</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_test.html b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_test.html
index 8e57534..6cb7e4a 100644
--- a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-permission</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/page/page.js"></script>
diff --git a/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor_test.html b/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor_test.html
index 39e4ddc..5198ad0 100644
--- a/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-plugin-config-array-editor/gr-plugin-config-array-editor_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-plugin-config-array-editor</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list_test.html b/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list_test.html
index 96fff60..98636a8 100644
--- a/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-plugin-list</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/page/page.js"></script>
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.html b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.html
index 1660088..90eaba5 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-repo-access</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/page/page.js"></script>
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-command/gr-repo-command_test.html b/polygerrit-ui/app/elements/admin/gr-repo-command/gr-repo-command_test.html
index 49d8765..b8ec7e8 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-command/gr-repo-command_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-command/gr-repo-command_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-repo-command</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands_test.html b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands_test.html
index 2976923..3c19175 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-repo-commands</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/page/page.js"></script>
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards_test.html b/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards_test.html
index 4f76983..06496de 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-repo-dashboards</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.html b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.html
index 44d9b27..2b175c7 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-repo-detail-list</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/page/page.js"></script>
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list_test.html b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list_test.html
index c77592c..9569e56 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-repo-list</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/page/page.js"></script>
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config_test.html b/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config_test.html
index 0a6846f..ff0e522 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-repo-plugin-config</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo_test.html b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo_test.html
index 4e81565..102a45e 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-repo</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor_test.html b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor_test.html
index 6d533af..dbf5bb0 100644
--- a/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-rule-editor</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/page/page.js"></script>
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.html b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.html
index b7e615c..f1338fb 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.html
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item.html
@@ -92,7 +92,6 @@
       a {
         color: inherit;
         cursor: pointer;
-        display: inline-block;
         text-decoration: none;
       }
       a:hover {
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html
index 12b4202..3c402d1 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-change-list-item</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_test.html b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_test.html
index 2367aac..08a83af 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-change-list-view</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/page/page.js"></script>
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html
index 817de6f..75fd167 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-change-list</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help_test.html b/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help_test.html
index c43d62a..f10fbf2 100644
--- a/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-create-change-help/gr-create-change-help_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-create-change-help</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog_test.html b/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog_test.html
index 89ad573..2228b50 100644
--- a/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-create-commands-dialog/gr-create-commands-dialog_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-create-commands-dialog</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html
index 41d4192..0d15d13 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-dashboard-view</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header_test.html b/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header_test.html
index 266818e..85eb509 100644
--- a/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-repo-header/gr-repo-header_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-repo-header</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header_test.html b/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header_test.html
index e837a5b..9e04ecd 100644
--- a/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-user-header</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
index 0817762..6703651 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
@@ -269,6 +269,25 @@
           Do you really want to delete the edit?
         </div>
       </gr-dialog>
+      <gr-dialog
+        id="showRevertSubmissionChangesDialog"
+        class="confirmDialog"
+        confirm-label="Close"
+        cancel-label=''
+        on-confirm="_handleShowRevertSubmissionChangesConfirm">
+        <div class="header" slot="header">
+          Reverted Changes
+        </div>
+        <div class="main" slot="main">
+          <template is="dom-repeat" items="[[_revertChanges]]">
+            <div>
+              <a href$="[[item.link]]" target="_blank">
+                Change [[item._number]]
+              </a>
+            </div>
+          </template>
+        </div>
+      </gr-dialog>
     </gr-overlay>
     <gr-js-api-interface id="jsAPI"></gr-js-api-interface>
     <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
index 2ce0d7d..8ad2a06 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
@@ -420,6 +420,10 @@
           type: Boolean,
           value: true,
         },
+        _revertChanges: {
+          type: Array,
+          value: [],
+        },
       };
     }
 
@@ -1258,6 +1262,7 @@
     _handleResponse(action, response) {
       if (!response) { return; }
       return this.$.restAPI.getResponseObject(response).then(obj => {
+        let revertChanges = [];
         switch (action.__key) {
           case ChangeActions.REVERT:
             this._waitForChangeReachable(obj._number)
@@ -1282,6 +1287,29 @@
           case ChangeActions.REBASE_EDIT:
             Gerrit.Nav.navigateToChange(this.change);
             break;
+          case ChangeActions.REVERT_SUBMISSION:
+            revertChanges = obj.revert_changes || [];
+            revertChanges = revertChanges.map(change => {
+              change.link = '/q/' + encodeURIComponent(change.change_id);
+              return change;
+            });
+            // list of reverted changes can never be 0
+            if (revertChanges.length === 1) {
+              // redirect to the change if only 1 change is reverted
+              const change = revertChanges[0];
+              this._waitForChangeReachable(change._number).then(success => {
+                if (success) {
+                  Gerrit.Nav.navigateToChange(change);
+                } else {
+                  console.error('Change ' + change._number + ' not reachable');
+                }
+              });
+            } else {
+              // show multiple reverted changes in a dialog
+              this._revertChanges = revertChanges;
+              this._showActionDialog(this.$.showRevertSubmissionChangesDialog);
+            }
+            break;
           default:
             this.dispatchEvent(new CustomEvent('reload-change',
                 {detail: {action: action.__key}, bubbles: false}));
@@ -1290,6 +1318,10 @@
       });
     }
 
+    _handleShowRevertSubmissionChangesConfirm() {
+      this._hideAllDialogs();
+    }
+
     _handleResponseError(action, response, body) {
       if (action && action.__key === RevisionActions.CHERRYPICK) {
         if (response && response.status === 409 &&
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
index 862b4eb..532c573 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-change-actions</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
@@ -1422,6 +1422,7 @@
       let payload;
       let onShowError;
       let onShowAlert;
+      let getResponseObjectStub;
 
       setup(() => {
         cleanup = sinon.stub();
@@ -1437,12 +1438,18 @@
 
       suite('happy path', () => {
         let sendStub;
-
+        let waitForChangeReachableStub;
         setup(() => {
           sandbox.stub(element, 'fetchChangeUpdates')
               .returns(Promise.resolve({isLatest: true}));
           sendStub = sandbox.stub(element.$.restAPI, 'executeChangeAction')
               .returns(Promise.resolve({}));
+          getResponseObjectStub = sandbox.stub(element.$.restAPI,
+              'getResponseObject');
+          waitForChangeReachableStub = sandbox.stub(element,
+              '_waitForChangeReachable').returns(Promise.resolve(true));
+          sandbox.stub(Gerrit.Nav,
+              'navigateToChange').returns(Promise.resolve(true));
         });
 
         test('change action', () => {
@@ -1455,6 +1462,49 @@
               });
         });
 
+        suite('single changes revert', () => {
+          setup(() => {
+            getResponseObjectStub
+                .returns(Promise.resolve({revert_changes: [
+                  {change_id: 12345},
+                ]}));
+            showActionDialogStub = sandbox.stub(element, '_showActionDialog');
+          });
+
+          test('revert submission single change', done => {
+            element._send('POST', {message: 'Revert submission'},
+                '/revert_submission', false, cleanup).then(res => {
+              element._handleResponse({__key: 'revert_submission'}, {}).
+                  then(() => {
+                    assert.isTrue(waitForChangeReachableStub.called);
+                    done();
+                  });
+            });
+          });
+        });
+
+        suite('multiple changes revert', () => {
+          let showActionDialogStub;
+          setup(() => {
+            getResponseObjectStub
+                .returns(Promise.resolve({revert_changes: [
+                  {change_id: 12345}, {change_id: 23456},
+                ]}));
+            showActionDialogStub = sandbox.stub(element, '_showActionDialog');
+          });
+
+          test('revert submission multiple change', done => {
+            element._send('POST', {message: 'Revert submission'},
+                '/revert_submission', false, cleanup).then(res => {
+              element._handleResponse({__key: 'revert_submission'}, {}).then(
+                  () => {
+                    assert.isTrue(showActionDialogStub.called);
+                    done();
+                  });
+            });
+          });
+        });
+
         test('revision action', () => {
           return element._send('DELETE', payload, '/endpoint', true, cleanup)
               .then(() => {
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata-it_test.html b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata-it_test.html
index b8bea9ca..8d85dce 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata-it_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata-it_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-change-metadata</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
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-change-metadata/gr-change-metadata_test.html b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
index 6f06fc8..148d917 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-change-metadata</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements_test.html b/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements_test.html
index 2ceac39..242fe2c 100644
--- a/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-change-requirements</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
index a697b1a..d9b8464 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-change-view</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list_test.html b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list_test.html
index c18ae8d..97be9b7 100644
--- a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-comment-list</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_test.html b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_test.html
index d3b33d1..2c5b431 100644
--- a/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_test.html
+++ b/polygerrit-ui/app/elements/change/gr-commit-info/gr-commit-info_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-commit-info</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog_test.html b/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog_test.html
index cc4b80e..e9964c5 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-abandon-dialog/gr-confirm-abandon-dialog_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-confirm-abandon-dialog</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog_test.html b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog_test.html
index f411de4..557972a 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-conflict-dialog/gr-confirm-cherrypick-conflict-dialog_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-confirm-cherrypick-conflict-dialog</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.html b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.html
index 22a2aba..42310fb 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-confirm-cherrypick-dialog</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog_test.html b/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog_test.html
index 8d6e029..036950b 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-confirm-move-dialog</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.html b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.html
index cd5b130..b116171 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-confirm-rebase-dialog</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.html b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.html
index 6e41555..dbdfba2 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-dialog/gr-confirm-revert-dialog_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-confirm-revert-dialog</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog_test.html b/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog_test.html
index c0f2dc3..cc4bd54 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-confirm-revert-submission-dialog</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog_test.html b/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog_test.html
index 40fa29a..515147f 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-submit-dialog/gr-confirm-submit-dialog_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-confirm-submit-dialog</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
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/change/gr-download-dialog/gr-download-dialog_test.html b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.html
index 82574808..0f1260f 100644
--- a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-download-dialog</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.html b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.html
index ac626ab..ea7ea8f 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-file-list-header</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
index 05b23fa..0d25846 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-file-list</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_test.html b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_test.html
index 68c77e6..b97e0b4 100644
--- a/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-included-in-dialog/gr-included-in-dialog_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-included-in-dialog</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.html b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.html
index f1a009a..e9c21bc 100644
--- a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.html
+++ b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-label-score-row</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores_test.html b/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores_test.html
index b8d471c..4c489b7 100644
--- a/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores_test.html
+++ b/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-label-scores</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message_test.html b/polygerrit-ui/app/elements/change/gr-message/gr-message_test.html
index ef5a756..01bc691 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message_test.html
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-message</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.html b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.html
index 265cee98..9caf13d 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-messages-list</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.html b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.html
index f04d40e..38b2fb9 100644
--- a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-related-changes-list</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.html b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.html
index a65f76c..7d95323 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.html
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog-it_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-reply-dialog</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
index d8d49cf..badd62a 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-reply-dialog</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_test.html b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_test.html
index e870993..18f7bc5 100644
--- a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-reviewer-list</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.html b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.html
index e736651..b29246d 100644
--- a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-thread-list</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog_test.html b/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog_test.html
index 577b978..76377fb 100644
--- a/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-upload-help-dialog/gr-upload-help-dialog_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-upload-help-dialog</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown_test.html b/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown_test.html
index e29faa8..9dafb74 100644
--- a/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown_test.html
+++ b/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-account-dropdown</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog_test.html b/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog_test.html
index 648f8be..dd4a71a 100644
--- a/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog_test.html
+++ b/polygerrit-ui/app/elements/core/gr-error-dialog/gr-error-dialog_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-error-dialog</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.html b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.html
index 9140c17..84b6717 100644
--- a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.html
+++ b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-error-manager</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/core/gr-key-binding-display/gr-key-binding-display_test.html b/polygerrit-ui/app/elements/core/gr-key-binding-display/gr-key-binding-display_test.html
index 39c8af8..e0d2d40 100644
--- a/polygerrit-ui/app/elements/core/gr-key-binding-display/gr-key-binding-display_test.html
+++ b/polygerrit-ui/app/elements/core/gr-key-binding-display/gr-key-binding-display_test.html
@@ -17,7 +17,7 @@
 -->
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-key-binding-display</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog_test.html b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog_test.html
index 1a3d6c7..b713aa1 100644
--- a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog_test.html
+++ b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog_test.html
@@ -17,7 +17,7 @@
 -->
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-key-binding-display</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.html b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.html
index fe8384e..d29858e 100644
--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.html
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.html
@@ -90,7 +90,7 @@
       }
       gr-smart-search {
         flex-grow: 1;
-        margin-left: var(--spacing-m);
+        margin: 0 var(--spacing-m);
         max-width: 500px;
       }
       gr-dropdown,
diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.html b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.html
index ea0056f..46e7d32 100644
--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.html
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-main-header</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation_test.html b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation_test.html
index 73ce86a..ca44430 100644
--- a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation_test.html
+++ b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-navigation</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html
index 736fa54..c2c0297 100644
--- a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html
+++ b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-reporting</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
index 4bfc35b..fb7f0d6 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-router</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
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/core/gr-search-bar/gr-search-bar_test.html b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.html
index a4927c3..c822b73 100644
--- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.html
+++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-search-bar</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search_test.html b/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search_test.html
index 53036dc..5a4e341 100644
--- a/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search_test.html
+++ b/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-smart-search</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/custom-dark-theme_test.html b/polygerrit-ui/app/elements/custom-dark-theme_test.html
index 4cf35f1..1cd042a 100644
--- a/polygerrit-ui/app/elements/custom-dark-theme_test.html
+++ b/polygerrit-ui/app/elements/custom-dark-theme_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-app-it_test</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/custom-light-theme_test.html b/polygerrit-ui/app/elements/custom-light-theme_test.html
index e346af5..0cabd09 100644
--- a/polygerrit-ui/app/elements/custom-light-theme_test.html
+++ b/polygerrit-ui/app/elements/custom-light-theme_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-app-it_test</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api-mock.js b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api-mock.js
index b7994e6..4009420 100644
--- a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api-mock.js
+++ b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api-mock.js
@@ -17,16 +17,20 @@
 (function() {
   'use strict';
 
-  Polymer({
-    is: 'comment-api-mock',
+  class CommentApiMock extends Polymer.GestureEventListeners(
+      Polymer.LegacyElementMixin(
+          Polymer.Element)) {
+    static get is() { return 'comment-api-mock'; }
 
-    properties: {
-      _changeComments: Object,
-    },
+    static get properties() {
+      return {
+        _changeComments: Object,
+      };
+    }
 
     loadComments() {
       return this._reloadComments();
-    },
+    }
 
     /**
      * For the purposes of the mock, _reloadDrafts is not included because its
@@ -38,13 +42,15 @@
       return this._reloadComments().then(() => {
         return e.detail.resolve();
       });
-    },
+    }
 
     _reloadComments() {
       return this.$.commentAPI.loadAll(this._changeNum)
           .then(comments => {
             this._changeComments = this.$.commentAPI._changeComments;
           });
-    },
-  });
+    }
+  }
+
+  customElements.define(CommentApiMock.is, CommentApiMock);
 })();
diff --git a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api_test.html b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api_test.html
index 47181f9..4261c2d 100644
--- a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-comment-api</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer_test.html b/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer_test.html
index 45a67e1..99c583d 100644
--- a/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-coverage-layer/gr-coverage-layer_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-coverage-layer</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified_test.html b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified_test.html
index 19e017d..4c44414 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>GrDiffBuilderUnified</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html
index 56fb8f5..758798a 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-diff-builder</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.html b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.html
index 0438287..626ab33 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-diff-cursor</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
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 e3724fc..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
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-annotation</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
@@ -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-diff-highlight/gr-diff-highlight_test.html b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.html
index aca7799..3de521d 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-diff-highlight</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.html b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.html
index eb0a68d..ea94441 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-diff</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector_test.html b/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector_test.html
index adeaa15..333a3d6 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-mode-selector/gr-diff-mode-selector_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-diff-mode-selector</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor_test.html b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor_test.html
index c04b066..308428f 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-diff-processor test</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection_test.html b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection_test.html
index 9385658..6fe319d 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-diff-selection</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
index 573d75f..3a5c0bc 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-diff-view</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-group_test.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-group_test.html
index 16e8036..686410c 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-group_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-group_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-diff-group</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/web-component-tester/browser.js"></script>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
index 52a29f9..2805eb4 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-diff</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.html b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.html
index ee893ee..81897cd 100644
--- a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-patch-range-select</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer_test.html b/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer_test.html
index 9d207a5..4ed744b 100644
--- a/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-ranged-comment-layer</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box_test.html b/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box_test.html
index 9add096..57adf32 100644
--- a/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-selection-action-box/gr-selection-action-box_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-selection-action-box</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
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-layer/gr-syntax-layer_test.html b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.html
index 4e3492f..926a6bc 100644
--- a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-syntax-layer</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
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/documentation/gr-documentation-search/gr-documentation-search_test.html b/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search_test.html
index 84298e2..688a9e6 100644
--- a/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search_test.html
+++ b/polygerrit-ui/app/elements/documentation/gr-documentation-search/gr-documentation-search_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-documentation-search</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/page/page.js"></script>
diff --git a/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor_test.html b/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor_test.html
index c986e7c..4635b1c 100644
--- a/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor_test.html
+++ b/polygerrit-ui/app/elements/edit/gr-default-editor/gr-default-editor_test.html
@@ -17,7 +17,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-default-editor</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-constants.html b/polygerrit-ui/app/elements/edit/gr-edit-constants.html
index d526ccd..5895124 100644
--- a/polygerrit-ui/app/elements/edit/gr-edit-constants.html
+++ b/polygerrit-ui/app/elements/edit/gr-edit-constants.html
@@ -22,7 +22,7 @@
 
     // Order corresponds to order in the UI.
     GrEditConstants.Actions = {
-      OPEN: {label: 'Open', id: 'open'},
+      OPEN: {label: 'Add/Open', id: 'open'},
       DELETE: {label: 'Delete', id: 'delete'},
       RENAME: {label: 'Rename', id: 'rename'},
       RESTORE: {label: 'Restore', id: 'restore'},
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 52692a7..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 {
@@ -89,12 +81,12 @@
           id="openDialog"
           class="invisible dialog"
           disabled$="[[!_isValidPath(_path)]]"
-          confirm-label="Open"
+          confirm-label="Confirm"
           confirm-on-enter
           on-confirm="_handleOpenConfirm"
           on-cancel="_handleDialogCancel">
         <div class="header" slot="header">
-          Open an existing or new file
+          Add a new file or open an existing file
         </div>
         <div class="main" slot="main">
           <gr-autocomplete
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.html b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.html
index dd8cb74..2d7dc9c 100644
--- a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.html
+++ b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.html
@@ -17,7 +17,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-edit-controls</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls_test.html b/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls_test.html
index 7979e57..dc26468 100644
--- a/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls_test.html
+++ b/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls_test.html
@@ -17,7 +17,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-edit-file-controls</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.html b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.html
index 226472f..ebd624e 100644
--- a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.html
+++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.html
@@ -17,7 +17,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-editor-view</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
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/gr-app_test.html b/polygerrit-ui/app/elements/gr-app_test.html
index 9f1b7f8..c069f8b 100644
--- a/polygerrit-ui/app/elements/gr-app_test.html
+++ b/polygerrit-ui/app/elements/gr-app_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-app</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/plugins/gr-admin-api/gr-admin-api_test.html b/polygerrit-ui/app/elements/plugins/gr-admin-api/gr-admin-api_test.html
index 2537a37..10ba710 100644
--- a/polygerrit-ui/app/elements/plugins/gr-admin-api/gr-admin-api_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-admin-api/gr-admin-api_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-admin-api</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper_test.html b/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper_test.html
index 0c4149c..c95128b 100644
--- a/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-attribute-helper/gr-attribute-helper_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-attribute-helper</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/plugins/gr-dom-hooks/gr-dom-hooks_test.html b/polygerrit-ui/app/elements/plugins/gr-dom-hooks/gr-dom-hooks_test.html
index 9e657fa..a00dc68 100644
--- a/polygerrit-ui/app/elements/plugins/gr-dom-hooks/gr-dom-hooks_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-dom-hooks/gr-dom-hooks_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-dom-hooks</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.html b/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.html
index b0ad585..58e2074 100644
--- a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-endpoint-decorator</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper_test.html b/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper_test.html
index bd76bd4..a98a0d2 100644
--- a/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-event-helper</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style_test.html b/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style_test.html
index 9566067..9f0c950 100644
--- a/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-external-style/gr-external-style_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-external-style</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host_test.html b/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host_test.html
index 3a8e4d8..1f748b8 100644
--- a/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-plugin-host</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup_test.html b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup_test.html
index 1f1e81e..2ff2aba 100644
--- a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-plugin-popup_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-plugin-popup</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface_test.html b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface_test.html
index 53370e2..0be07d4 100644
--- a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-popup-interface</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-repo-api_test.html b/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-repo-api_test.html
index 0b32f8a..5a47a85 100644
--- a/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-repo-api_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-repo-api_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-repo-api</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/plugins/gr-settings-api/gr-settings-api_test.html b/polygerrit-ui/app/elements/plugins/gr-settings-api/gr-settings-api_test.html
index cbc2de6..6248d78 100644
--- a/polygerrit-ui/app/elements/plugins/gr-settings-api/gr-settings-api_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-settings-api/gr-settings-api_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-settings-api</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api_test.html b/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api_test.html
index 16b7eb8..7d14e21 100644
--- a/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-admin-api</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-theme-api_test.html b/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-theme-api_test.html
index 6332b91..9401a15 100644
--- a/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-theme-api_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-theme-api_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-theme-api</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.html b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.html
index a35c1f0..a9056b91 100644
--- a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-account-info</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list_test.html b/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list_test.html
index 14cf97c..cd5bc09 100644
--- a/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-settings-view</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_test.html b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_test.html
index 29a7081..ec37c03 100644
--- a/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-change-table-editor/gr-change-table-editor_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-settings-view</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_test.html b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_test.html
index 53d6be1..833fa39 100644
--- a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-cla-view</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences_test.html b/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences_test.html
index c1c5c52..8b0bf86 100644
--- a/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-edit-preferences</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor_test.html b/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor_test.html
index 8d3f2d2..e55ac97 100644
--- a/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-email-editor/gr-email-editor_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-email-editor</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor_test.html b/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor_test.html
index 9cfbde5f..575285b 100644
--- a/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-gpg-editor</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list_test.html b/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list_test.html
index 3c3ece36..d8bf888 100644
--- a/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-group-list/gr-group-list_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-settings-view</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
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/settings/gr-http-password/gr-http-password_test.html b/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password_test.html
index 8924058..89da766 100644
--- a/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-http-password/gr-http-password_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-settings-view</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/settings/gr-identities/gr-identities_test.html b/polygerrit-ui/app/elements/settings/gr-identities/gr-identities_test.html
index 1277424..e2f8cad 100644
--- a/polygerrit-ui/app/elements/settings/gr-identities/gr-identities_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-identities/gr-identities_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-identities</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor_test.html b/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor_test.html
index 134e018..380e00a 100644
--- a/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-menu-editor/gr-menu-editor_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-settings-view</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog_test.html b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog_test.html
index d1b5c80..a987a26 100644
--- a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-registration-dialog</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.html b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.html
index 6dcf124..24ee69b 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-settings-view</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor_test.html b/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor_test.html
index d313f5a..912c1fd 100644
--- a/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-ssh-editor/gr-ssh-editor_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-ssh-editor</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.html b/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.html
index 7a238ec..4bdb00f 100644
--- a/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-settings-view</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
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-entry/gr-account-entry_test.html b/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry_test.html
index 6896af9..0bac7e0 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-account-entry</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.html b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.html
index f0620d3..f369ae2 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-account-label</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link_test.html b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link_test.html
index 134c579..e41304f 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-account-link</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.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 202ddf6..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
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-account-list</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
@@ -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-alert/gr-alert_test.html b/polygerrit-ui/app/elements/shared/gr-alert/gr-alert_test.html
index 2338d55..bfcc431 100644
--- a/polygerrit-ui/app/elements/shared/gr-alert/gr-alert_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-alert/gr-alert_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-alert</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
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 2afbd82..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
@@ -17,12 +17,12 @@
 
 <link rel="import" href="/bower_components/polymer/polymer.html">
 
-<link rel="import" href="../../../types/polymer-behaviors.js">
 <link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
 <link rel="import" href="../../../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html">
 <link rel="import" href="/bower_components/iron-dropdown/iron-dropdown.html">
 <link rel="import" href="/bower_components/iron-fit-behavior/iron-fit-behavior.html">
 <link rel="import" href="../../shared/gr-cursor-manager/gr-cursor-manager.html">
+<script src="../../../types/polymer-behaviors.js"></script>
 <script src="../../../scripts/rootElement.js"></script>
 <link rel="import" href="../../../styles/shared-styles.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-dropdown/gr-autocomplete-dropdown_test.html b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown_test.html
index a7b59d7..5f2ce94 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-autocomplete-dropdown</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
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 ea1fd50..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
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-reviewer-list</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
@@ -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-avatar/gr-avatar_test.html b/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar_test.html
index 9c2aba9..98a75dd 100644
--- a/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-avatar</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
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 2593ed1..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
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-button</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
@@ -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-change-star/gr-change-star_test.html b/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star_test.html
index 7ee22a7..60c504a 100644
--- a/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-change-star</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status_test.html b/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status_test.html
index 421c6ab5..96a1840 100644
--- a/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-change-status/gr-change-status_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-change-status</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.html b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.html
index d7510ec..89b28d5 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.html
+++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.html
@@ -58,6 +58,9 @@
       #container.unresolved {
         background-color: var(--unresolved-comment-background-color);
       }
+      #container.robotComment {
+        background-color: var(--robot-comment-background-color);
+      }
       #commentInfoContainer {
         border-top: 1px dotted var(--border-color);
         display: flex;
@@ -82,7 +85,7 @@
         <span class="descriptionText">Patchset [[patchNum]]</span>
       </div>
     </template>
-    <div id="container" class$="[[_computeHostClass(unresolved)]]">
+    <div id="container" class$="[[_computeHostClass(unresolved, isRobotComment)]]">
       <template id="commentList" is="dom-repeat" items="[[_orderedComments]]"
           as="comment">
         <gr-comment
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.js b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.js
index 59e16c2..8687e77 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.js
+++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.js
@@ -137,6 +137,11 @@
         _lastComment: Object,
         _orderedComments: Array,
         _projectConfig: Object,
+        isRobotComment: {
+          type: Boolean,
+          value: false,
+          reflectToAttribute: true,
+        },
       };
     }
 
@@ -221,12 +226,13 @@
         this._lastComment = this._getLastComment();
         this.unresolved = this._lastComment.unresolved;
         this.hasDraft = this._lastComment.__draft;
+        this.isRobotComment = !!(this._lastComment.robot_id);
       }
     }
 
     _shouldDisableAction(_showActions, _lastComment) {
       return !_showActions || !_lastComment || !!_lastComment.__draft;
-    },
+    }
 
     _hideActions(_showActions, _lastComment) {
       return this._shouldDisableAction(_showActions, _lastComment) ||
@@ -498,6 +504,9 @@
     }
 
     _computeHostClass(unresolved) {
+      if (this.isRobotComment) {
+        return 'robotComment';
+      }
       return unresolved ? 'unresolved' : '';
     }
 
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.html b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.html
index a1e20f9..17e0b1f 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-comment-thread</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.html b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.html
index 560f9fa..de75b40 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-comment</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard_test.html b/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard_test.html
index 9cec20e..14490f0 100644
--- a/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-copy-clipboard/gr-copy-clipboard_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-copy-clipboard</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-count-string-formatter/gr-count-string-formatter_test.html b/polygerrit-ui/app/elements/shared/gr-count-string-formatter/gr-count-string-formatter_test.html
index d061ac2..5022766 100644
--- a/polygerrit-ui/app/elements/shared/gr-count-string-formatter/gr-count-string-formatter_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-count-string-formatter/gr-count-string-formatter_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-count-string-formatter</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager_test.html b/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager_test.html
index b51e44c..103b41a 100644
--- a/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-cursor-manager</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter_test.html b/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter_test.html
index 99af4e6..fffd38e 100644
--- a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-date-formatter</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
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-dialog/gr-dialog_test.html b/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog_test.html
index 1456e77..ced925e 100644
--- a/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-dialog/gr-dialog_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-dialog</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences_test.html b/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences_test.html
index 4d2a1a4..fa1ade5 100644
--- a/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-diff-preferences</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_test.html b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_test.html
index 85a5b1f..7d4b341 100644
--- a/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-download-commands/gr-download-commands_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-download-commands</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
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-list/gr-dropdown-list_test.html b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list_test.html
index b5b1408..8d098df 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-dropdown-list</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
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-dropdown/gr-dropdown_test.html b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown_test.html
index 295f746..4b5ccb4 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-dropdown</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content_test.html b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content_test.html
index 24db69f..69c9a00 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-editable-content</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label_test.html b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label_test.html
index 416a499..1690c3a 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-editable-label</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-fixed-panel/gr-fixed-panel_test.html b/polygerrit-ui/app/elements/shared/gr-fixed-panel/gr-fixed-panel_test.html
index 75e9901..419b3e7 100644
--- a/polygerrit-ui/app/elements/shared/gr-fixed-panel/gr-fixed-panel_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-fixed-panel/gr-fixed-panel_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-fixed-panel</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.html b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.html
index 801190a..92fbc74 100644
--- a/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-formatted-text/gr-formatted-text_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-editable-label</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard_test.html b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard_test.html
index 8e79f65..35d736c 100644
--- a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-hovercard</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context_test.html
index 3223636..66e7a74 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-annotation-actions-context</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api_test.html
index 987b551..98ff954 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-annotation-actions-js-api-js-api</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
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 89dfbd7..128738d 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
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-api-interface</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api_test.html
index 7332877..cbe2d37 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-change-actions-js-api</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api_test.html
index 842a2fe..7955aa4 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-reply-js-api_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-change-reply-js-api</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit_test.html
index e81b8aa..7b03308 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-api-interface</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html
index ae12940..537e55b 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-api-interface</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-action-context_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-action-context_test.html
index 6da117f..f7b7cf8 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-action-context_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-action-context_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-plugin-action-context</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints_test.html
index 8ed7f14..b39ed6d 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-plugin-endpoints</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
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 b0a8c4a..8c1ec96 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
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-plugin-host</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api_test.html
index bcbd961..4aae032 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-plugin-rest-api</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info_test.html b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info_test.html
index 35dc772..adf88d9 100644
--- a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info_test.html
@@ -17,7 +17,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-label-info</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
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-labeled-autocomplete/gr-labeled-autocomplete_test.html b/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete_test.html
index b257746..7e8f73f 100644
--- a/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-labeled-autocomplete/gr-labeled-autocomplete_test.html
@@ -17,7 +17,7 @@
 -->
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-labeled-autocomplete</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-lib-loader/gr-lib-loader_test.html b/polygerrit-ui/app/elements/shared/gr-lib-loader/gr-lib-loader_test.html
index 10d1608..832a558 100644
--- a/polygerrit-ui/app/elements/shared/gr-lib-loader/gr-lib-loader_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-lib-loader/gr-lib-loader_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-lib-loader</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text_test.html b/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text_test.html
index 7946bb6..3ba30d1 100644
--- a/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-limited-text</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip_test.html b/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip_test.html
index 22a2eaf..733e897 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-linked-chip</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.html b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.html
index f01a75c..e9477bf 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-linked-text</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view_test.html b/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view_test.html
index c67d8b2..8f87b38 100644
--- a/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-list-view/gr-list-view_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-list-view</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 <script src="/bower_components/page/page.js"></script>
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
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/elements/shared/gr-overlay/gr-overlay_test.html b/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay_test.html
index 08b7497..659823f 100644
--- a/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-overlay/gr-overlay_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-overlay</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/page/page.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav_test.html b/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav_test.html
index b384b47..663d3bd 100644
--- a/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-page-nav</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/page/page.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker_test.html b/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker_test.html
index 1ed9151..e7252d6 100644
--- a/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-repo-branch-picker/gr-repo-branch-picker_test.html
@@ -17,7 +17,7 @@
 -->
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-repo-branch-picker</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-auth_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-auth_test.html
index dc07d0f..6c5c150 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-auth_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-auth_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-auth</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-etag-decorator_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-etag-decorator_test.html
index 76c8c2c..623ea18 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-etag-decorator_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-etag-decorator_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-etag-decorator</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
index a1a7f90..75593e8 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
@@ -1591,7 +1591,7 @@
       );
       const params = {
         O: options,
-        q: 'status:open is:mergeable conflicts:' + changeNum,
+        q: 'status:open conflicts:' + changeNum,
       };
       return this._restApiHelper.fetchJSON({
         url: '/changes/',
@@ -2354,7 +2354,7 @@
         method: 'POST',
         url: '/accounts/self/sshkeys',
         body: key,
-        contentType: 'plain/text',
+        contentType: 'text/plain',
         reportUrlAsIs: true,
       };
       return this._restApiHelper.send(req)
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
index 9f01f48..a598d0d 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-rest-api-interface</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper_test.html
index 4eaf1bc..446cfc8 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-rest-api-helper</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser_test.html
index fdf79af..ad3e4ca 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-reviewer-updates-parser</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-select/gr-select_test.html b/polygerrit-ui/app/elements/shared/gr-select/gr-select_test.html
index b3abe5f..cae61056 100644
--- a/polygerrit-ui/app/elements/shared/gr-select/gr-select_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-select/gr-select_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-select</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command_test.html b/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command_test.html
index 3f2f8ba..a4bdf58 100644
--- a/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-shell-command/gr-shell-command_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-shell-command</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-storage/gr-storage_test.html b/polygerrit-ui/app/elements/shared/gr-storage/gr-storage_test.html
index 0482584..5cd4d3e 100644
--- a/polygerrit-ui/app/elements/shared/gr-storage/gr-storage_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-storage/gr-storage_test.html
@@ -17,7 +17,7 @@
 -->
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-storage</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.html b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.html
index b884ecd..699deab 100644
--- a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-textarea</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content_test.html b/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content_test.html
index f9350c6..4276195 100644
--- a/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content_test.html
@@ -17,7 +17,7 @@
 -->
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-storage</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip_test.html b/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip_test.html
index f59f6e1..95f3922 100644
--- a/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-tooltip/gr-tooltip_test.html
@@ -17,7 +17,7 @@
 -->
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-storage</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/revision-info/revision-info_test.html b/polygerrit-ui/app/elements/shared/revision-info/revision-info_test.html
index 7e5810b..187ce35 100644
--- a/polygerrit-ui/app/elements/shared/revision-info/revision-info_test.html
+++ b/polygerrit-ui/app/elements/shared/revision-info/revision-info_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>revision-info</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/embed/embed.html b/polygerrit-ui/app/embed/embed.html
deleted file mode 100644
index 64e0137..0000000
--- a/polygerrit-ui/app/embed/embed.html
+++ /dev/null
@@ -1,27 +0,0 @@
-<!--
-@license
-Copyright (C) 2017 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.
--->
-<script>
-  window.Gerrit = window.Gerrit || {};
-</script>
-<link rel="import" href="/bower_components/polymer/polymer.html">
-<link rel="import" href="../elements/change/gr-change-view/gr-change-view.html">
-<link rel="import" href="../elements/core/gr-search-bar/gr-search-bar.html">
-<link rel="import" href="../elements/diff/gr-diff-view/gr-diff-view.html">
-<link rel="import" href="../elements/change-list/gr-change-list-view/gr-change-list-view.html">
-<link rel="import" href="../elements/change-list/gr-dashboard-view/gr-dashboard-view.html">
-<link rel="import" href="../elements/change-list/gr-embed-dashboard/gr-embed-dashboard.html">
-<link rel="import" href="../styles/themes/app-theme.html">
diff --git a/polygerrit-ui/app/embed/embed_test.html b/polygerrit-ui/app/embed/embed_test.html
deleted file mode 100644
index 048dffa..0000000
--- a/polygerrit-ui/app/embed/embed_test.html
+++ /dev/null
@@ -1,98 +0,0 @@
-<!DOCTYPE html>
-<!--
-@license
-Copyright (C) 2017 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.
--->
-
-<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
-<title>embed_test</title>
-<script src="/test/common-test-setup.js"></script>
-<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
-
-<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
-<script src="/bower_components/web-component-tester/browser.js"></script>
-<link rel="import" href="embed.html"/>
-
-<script>void(0);</script>
-
-<test-fixture id="change-view">
-  <template>
-    <gr-change-view></gr-change-view>
-  </template>
-</test-fixture>
-
-<test-fixture id="diff-view">
-  <template>
-    <gr-diff-view></gr-diff-view>
-  </template>
-</test-fixture>
-
-<test-fixture id="dashboard-view">
-  <template>
-    <gr-dashboard-view></gr-dashboard-view>
-  </template>
-</test-fixture>
-
-<test-fixture id="change-list-view">
-  <template>
-    <gr-change-list-view></gr-change-list-view>
-  </template>
-</test-fixture>
-
-<test-fixture id="change-list">
-  <template>
-    <gr-change-list></gr-change-list>
-  </template>
-</test-fixture>
-
-<test-fixture id="search-bar">
-  <template>
-    <gr-search-bar></gr-search-bar>
-  </template>
-</test-fixture>
-
-<script>
-  suite('embed test', () => {
-    test('gr-change-view is embedded', () => {
-      const element = fixture('change-view');
-      assert.equal(element.tagName.toLowerCase(), 'gr-change-view');
-    });
-
-    test('diff-view is embedded', () => {
-      const element = fixture('diff-view');
-      assert.equal(element.tagName.toLowerCase(), 'gr-diff-view');
-    });
-
-    test('dashboard-view is embedded', () => {
-      const element = fixture('dashboard-view');
-      assert.equal(element.tagName.toLowerCase(), 'gr-dashboard-view');
-    });
-
-    test('change-list-view is embedded', () => {
-      const element = fixture('change-list-view');
-      assert.equal(element.tagName.toLowerCase(), 'gr-change-list-view');
-    });
-
-    test('change-list is embedded', () => {
-      const element = fixture('change-list');
-      assert.equal(element.tagName.toLowerCase(), 'gr-change-list');
-    });
-
-    test('search-bar is embedded', () => {
-      const element = fixture('search-bar');
-      assert.equal(element.tagName.toLowerCase(), 'gr-search-bar');
-    });
-  });
-</script>
diff --git a/polygerrit-ui/app/embed/test.html b/polygerrit-ui/app/embed/test.html
deleted file mode 100644
index 955eaee..0000000
--- a/polygerrit-ui/app/embed/test.html
+++ /dev/null
@@ -1,26 +0,0 @@
-<!DOCTYPE html>
-<!--
-@license
-Copyright (C) 2017 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.
--->
-
-<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
-<title>Embed Test Runner</title>
-<meta charset="utf-8">
-<script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
-<script src="/bower_components/web-component-tester/browser.js"></script>
-<script>
-  WCT.loadSuites(['../embed/embed_test.html']);
-</script>
diff --git a/polygerrit-ui/app/embed_test.sh b/polygerrit-ui/app/embed_test.sh
deleted file mode 100755
index 0d8f58f..0000000
--- a/polygerrit-ui/app/embed_test.sh
+++ /dev/null
@@ -1,69 +0,0 @@
-#!/bin/sh
-
-set -ex
-
-t=$(mktemp -d || mktemp -d -t wct-XXXXXXXXXX)
-components=$TEST_SRCDIR/gerrit/polygerrit-ui/app/test_components.zip
-code=$TEST_SRCDIR/gerrit/polygerrit-ui/app/pg_code.zip
-
-echo $t
-unzip -qd $t $components
-unzip -qd $t $code
-# Purge test/ directory contents coming from pg_code.zip.
-rm -rf $t/test
-mkdir -p $t/test
-cp $TEST_SRCDIR/gerrit/polygerrit-ui/app/embed/test.html $t/test/
-
-if [ "${WCT_HEADLESS_MODE:-0}" != "0" ]; then
-    CHROME_OPTIONS=[\'start-maximized\',\'headless\',\'disable-gpu\',\'no-sandbox\']
-    FIREFOX_OPTIONS=[\'-headless\']
-else
-    CHROME_OPTIONS=[\'start-maximized\']
-    FIREFOX_OPTIONS=[\'\']
-fi
-
-# For some reason wct tries to install selenium into its node_modules
-# directory on first run. If you've installed into /usr/local and
-# aren't running wct as root, you're screwed. Turning this option off
-# through skipSeleniumInstall seems to still work, so there's that.
-
-# Sauce tests are disabled by default in order to run local tests
-# only.  Run it with (saucelabs.com account required; free for open
-# source): WCT_ARGS='--plugin sauce' ./polygerrit-ui/app/embed_test.sh
-
-cat <<EOF > $t/wct.conf.js
-module.exports = {
-      'suites': ['test'],
-      'webserver': {
-        'pathMappings': [
-          {'/components/bower_components': 'bower_components'}
-        ]
-      },
-      'plugins': {
-        'local': {
-          'skipSeleniumInstall': true,
-          'browserOptions': {
-            'chrome': ${CHROME_OPTIONS},
-            'firefox': ${FIREFOX_OPTIONS}
-          }
-        },
-        'sauce': {
-          'disabled': true,
-          'browsers': [
-            'OS X 10.12/chrome',
-            'Windows 10/chrome',
-            'Linux/firefox',
-            'OS X 10.12/safari',
-            'Windows 10/microsoftedge'
-          ]
-        }
-      }
-    };
-EOF
-
-export PATH="$(dirname $NPM):$PATH"
-
-cd $t
-test -n "${WCT}"
-
-${WCT} ${WCT_ARGS}
diff --git a/polygerrit-ui/app/polymer.json b/polygerrit-ui/app/polymer.json
index 6d6ae7c..411c969 100644
--- a/polygerrit-ui/app/polymer.json
+++ b/polygerrit-ui/app/polymer.json
@@ -4,7 +4,7 @@
     "behaviors/**/*",
     "elements/**/*",
     "scripts/**/*",
-    "styles/**/*",
+    "styles/*",
     "types/**/*"
   ],
   "lint": {
diff --git a/polygerrit-ui/app/run_test.sh b/polygerrit-ui/app/run_test.sh
index e9be18d..76a4fa1 100755
--- a/polygerrit-ui/app/run_test.sh
+++ b/polygerrit-ui/app/run_test.sh
@@ -48,5 +48,4 @@
       --test_env="DISPLAY=${DISPLAY}" \
       --test_env="WCT_HEADLESS_MODE=${WCT_HEADLESS_MODE}" \
       "$@" \
-      //polygerrit-ui/app:embed_test \
       //polygerrit-ui/app:wct_test
diff --git a/polygerrit-ui/app/samples/coverage-plugin.html b/polygerrit-ui/app/samples/coverage-plugin.html
index e4c23aa..fa44a47 100644
--- a/polygerrit-ui/app/samples/coverage-plugin.html
+++ b/polygerrit-ui/app/samples/coverage-plugin.html
@@ -60,6 +60,8 @@
       }).enableToggleCheckbox('Display Coverage', checkbox => {
         // Checkbox is attached so now add the notifier that will be controlled
         // by the checkbox.
+        // Checkbox will only be added to the file diff page, in the top right
+        // section near the "Diff view".
         annotationApi.addNotifier(notifyFunc => {
           new Promise(resolve => setTimeout(resolve, 3000)).then(() => {
             populateWithDummyData(coverageData);
diff --git a/polygerrit-ui/app/scripts/gr-display-name-utils/gr-display-name-utils_test.html b/polygerrit-ui/app/scripts/gr-display-name-utils/gr-display-name-utils_test.html
index b04e324..a35e034 100644
--- a/polygerrit-ui/app/scripts/gr-display-name-utils/gr-display-name-utils_test.html
+++ b/polygerrit-ui/app/scripts/gr-display-name-utils/gr-display-name-utils_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-display-name-utils</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/scripts/gr-email-suggestions-provider/gr-email-suggestions-provider_test.html b/polygerrit-ui/app/scripts/gr-email-suggestions-provider/gr-email-suggestions-provider_test.html
index 0266ab9..ac32efa 100644
--- a/polygerrit-ui/app/scripts/gr-email-suggestions-provider/gr-email-suggestions-provider_test.html
+++ b/polygerrit-ui/app/scripts/gr-email-suggestions-provider/gr-email-suggestions-provider_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-email-suggestions-provider</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/scripts/gr-group-suggestions-provider/gr-group-suggestions-provider_test.html b/polygerrit-ui/app/scripts/gr-group-suggestions-provider/gr-group-suggestions-provider_test.html
index b60aaa9..6908256 100644
--- a/polygerrit-ui/app/scripts/gr-group-suggestions-provider/gr-group-suggestions-provider_test.html
+++ b/polygerrit-ui/app/scripts/gr-group-suggestions-provider/gr-group-suggestions-provider_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-group-suggestions-provider</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
diff --git a/polygerrit-ui/app/scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider_test.html b/polygerrit-ui/app/scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider_test.html
index ca3c277..6fcc4dc 100644
--- a/polygerrit-ui/app/scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider_test.html
+++ b/polygerrit-ui/app/scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider_test.html
@@ -18,7 +18,7 @@
 
 <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 <title>gr-reviewer-suggestions-provider</title>
-<script src="/test/common-test-setup.js"></script>
+
 <script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
 
 <script src="/bower_components/webcomponentsjs/webcomponents-lite.js"></script>
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 41395a3..3a620d2 100644
--- a/polygerrit-ui/app/styles/themes/app-theme.html
+++ b/polygerrit-ui/app/styles/themes/app-theme.html
@@ -14,8 +14,6 @@
 See the License for the specific language governing permissions and
 limitations under the License.
 -->
-<link rel="import" href="/bower_components/polymer/lib/elements/custom-style.html">
-
 <custom-style><style is="custom-style">
 html {
   /**
@@ -47,6 +45,7 @@
   --assignee-highlight-color: #fcfad6;
   --chip-background-color: #eee;
   --comment-background-color: #fcfad6;
+  --robot-comment-background-color: #e8f0fe;
   --default-button-background-color: white;
   --dialog-background-color: white;
   --dropdown-background-color: white;
@@ -76,7 +75,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 */
@@ -144,6 +143,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;
@@ -151,7 +151,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;
@@ -164,10 +164,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 69a787e..4a91774 100644
--- a/polygerrit-ui/app/styles/themes/dark-theme.html
+++ b/polygerrit-ui/app/styles/themes/dark-theme.html
@@ -14,8 +14,6 @@
 See the License for the specific language governing permissions and
 limitations under the License.
 -->
-<link rel="import" href="/bower_components/polymer/lib/elements/custom-style.html">
-
 <dom-module id="dark-theme">
   <custom-style><style is="custom-style">
     html {
@@ -47,6 +45,7 @@
       --assignee-highlight-color: #3a361c;
       --chip-background-color: #131416;
       --comment-background-color: #0b162b;
+      --robot-comment-background-color: #e8f0fe;
       --default-button-background-color: #3c4043;
       --dialog-background-color: #131416;
       --dropdown-background-color: #131416;
@@ -114,6 +113,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;
@@ -121,7 +121,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/polygerrit-ui/app/test/common-test-setup.js b/polygerrit-ui/app/test/common-test-setup.js
deleted file mode 100644
index 7ceff7e..0000000
--- a/polygerrit-ui/app/test/common-test-setup.js
+++ /dev/null
@@ -1,26 +0,0 @@
-/**
- * @license
- * 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.
- */
-
-/**
- * Helps looking up the proper iron-input element during the Polymer 2
- * transition. Polymer 2 uses the <iron-input> element, while Polymer 1 uses
- * the nested <input is="iron-input"> element.
- */
-window.ironInput = function(element) {
-  return Polymer.dom(element).querySelector(
-      Polymer.Element ? 'iron-input' : 'input[is=iron-input]');
-};
diff --git a/polygerrit-ui/server.go b/polygerrit-ui/server.go
index 35bdefd..3b699c2 100644
--- a/polygerrit-ui/server.go
+++ b/polygerrit-ui/server.go
@@ -86,6 +86,9 @@
 		log.Println("Local plugins from", "../plugins")
 	} else {
 		http.HandleFunc("/plugins/", handleProxy)
+		// Serve local plugins from `plugins_`
+		http.Handle("/plugins_/", http.StripPrefix("/plugins_/",
+			http.FileServer(http.Dir("../plugins"))))
 	}
 	log.Println("Serving on port", *port)
 	log.Fatal(http.ListenAndServe(*port, &server{}))
diff --git a/tools/BUILD b/tools/BUILD
index 5531c3e..f0a4ffa 100644
--- a/tools/BUILD
+++ b/tools/BUILD
@@ -53,6 +53,7 @@
         "-Xep:ExpectedExceptionChecker:ERROR",
         "-Xep:Finally:ERROR",
         "-Xep:FloatingPointLiteralPrecision:ERROR",
+        "-Xep:FormatStringAnnotation:ERROR",
         "-Xep:FragmentInjection:ERROR",
         "-Xep:FragmentNotInstantiable:ERROR",
         "-Xep:FunctionalInterfaceClash:ERROR",
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 = []