Merge "ProjectsCollection: Add debug log with project state"
diff --git a/BUILD b/BUILD
index 4bba5a5..d924417 100644
--- a/BUILD
+++ b/BUILD
@@ -2,6 +2,10 @@
 
 load("//tools/bzl:genrule2.bzl", "genrule2")
 load("//tools/bzl:pkg_war.bzl", "pkg_war")
+load(
+    "@bazel_tools//tools/jdk:default_java_toolchain.bzl",
+    "default_java_toolchain",
+)
 
 config_setting(
     name = "java9",
@@ -10,6 +14,43 @@
     },
 )
 
+config_setting(
+    name = "java10",
+    values = {
+        "java_toolchain": ":toolchain_vanilla",
+    },
+)
+
+# TODO(davido): Switch to consuming it from @bazel_tool//tools/jdk:absolute_javabase
+# when new Bazel version is released with this change included:
+# https://github.com/bazelbuild/bazel/issues/6012
+# https://github.com/bazelbuild/bazel/commit/0173bdbf7bdd1874379d4dd3eb70d5321e0f1816
+# As the interim use a hack that works around it by putting the variable reference
+# behind a select
+config_setting(
+    name = "use_absolute_javabase",
+    values = {"define": "USE_ABSOLUTE_JAVABASE=true"},
+)
+
+java_runtime(
+    name = "absolute_javabase",
+    java_home = select({
+        ":use_absolute_javabase": "$(ABSOLUTE_JAVABASE)",
+        "//conditions:default": "",
+    }),
+    visibility = ["//visibility:public"],
+)
+
+# TODO(davido): Switch to consuming it from @bazel_tool//tools/jdk:toolchain_vanilla
+# when my change is included in released Bazel version:
+# https://github.com/bazelbuild/bazel/commit/0bef68e054eccecd690e5d9f46db8a0c4b2d887a
+default_java_toolchain(
+    name = "toolchain_vanilla",
+    forcibly_disable_header_compilation = True,
+    javabuilder = ["@bazel_tools//tools/jdk:VanillaJavaBuilder_deploy.jar"],
+    jvm_opts = [],
+)
+
 genrule(
     name = "gen_version",
     outs = ["version.txt"],
diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt
index 56200c4..d3f5d77 100644
--- a/Documentation/access-control.txt
+++ b/Documentation/access-control.txt
@@ -1583,15 +1583,18 @@
 * The 'force' setting has no effect on label ranges.
 
 * BLOCK specifies the values that a group cannot vote, eg.
++
 ----
   label-Code-Review = block -2..+2 group Anonymous Users
 ----
++
 prevents all users from voting -2 or +2.
 
 * DENY works for votes too, with the same caveats
 
 * The blocked vote range is the union of the all the blocked vote
 ranges across projects, so in
++
 ----
 All-Projects: project.config
      label-Code-Review = block -2..+1 group A
@@ -1599,15 +1602,18 @@
 Child-Project: project-config
      label-Code-Review = block -1..+2 group A
 ----
++
 members of group A cannot vote at all in the Child-Project.
 
 
 * The allowed vote range is the union of vote ranges allowed by all of
 the ALLOW rules. For example, in
++
 ----
      label-Code-Review = -2..+1 group A
      label-Code-Review = -1..+2 group B
 ----
++
 a user that is both in A and B can vote -2..2.
 
 
diff --git a/Documentation/cmd-close-connection.txt b/Documentation/cmd-close-connection.txt
index 973441e..c161541 100644
--- a/Documentation/cmd-close-connection.txt
+++ b/Documentation/cmd-close-connection.txt
@@ -1,7 +1,7 @@
 = gerrit close-connection
 
 == NAME
-gerrit close-connection - Close the specified SSH connection
+gerrit close-connection - Close the specified SSH connection.
 
 == SYNOPSIS
 [verse]
diff --git a/Documentation/cmd-create-account.txt b/Documentation/cmd-create-account.txt
index 62bd0aa..617191f 100644
--- a/Documentation/cmd-create-account.txt
+++ b/Documentation/cmd-create-account.txt
@@ -69,7 +69,7 @@
 the 'Non-Interactive Users' group.
 
 ----
-	$ cat ~/.ssh/id_watcher.pub | ssh -p 29418 review.example.com gerrit create-account --group "'Non-Interactive Users'" --ssh-key - watcher
+$ cat ~/.ssh/id_watcher.pub | ssh -p 29418 review.example.com gerrit create-account --group "'Non-Interactive Users'" --ssh-key - watcher
 ----
 
 GERRIT
diff --git a/Documentation/cmd-create-branch.txt b/Documentation/cmd-create-branch.txt
index 336af56..2060917 100644
--- a/Documentation/cmd-create-branch.txt
+++ b/Documentation/cmd-create-branch.txt
@@ -1,7 +1,7 @@
 = gerrit create-branch
 
 == NAME
-gerrit create-branch - Create a new branch
+gerrit create-branch - Create a new branch.
 
 == SYNOPSIS
 [verse]
@@ -40,7 +40,7 @@
 the project 'myproject'.
 
 ----
-    $ ssh -p 29418 review.example.com gerrit create-branch myproject newbranch master
+$ ssh -p 29418 review.example.com gerrit create-branch myproject newbranch master
 ----
 
 GERRIT
diff --git a/Documentation/cmd-create-group.txt b/Documentation/cmd-create-group.txt
index 7f1f463..2ba611c 100644
--- a/Documentation/cmd-create-group.txt
+++ b/Documentation/cmd-create-group.txt
@@ -68,14 +68,14 @@
 `developer1` and `developer2`.  The group should be owned by itself:
 
 ----
-	$ ssh -p 29418 user@review.example.com gerrit create-group --member developer1 --member developer2 gerritdev
+$ ssh -p 29418 user@review.example.com gerrit create-group --member developer1 --member developer2 gerritdev
 ----
 
 Create a new account group called `Foo` owned by the `Foo-admin` group.
 Put `developer1` as the initial member and include group description:
 
 ----
-	$ ssh -p 29418 user@review.example.com gerrit create-group --owner Foo-admin --member developer1 --description "'Foo description'" Foo
+$ ssh -p 29418 user@review.example.com gerrit create-group --owner Foo-admin --member developer1 --description "'Foo description'" Foo
 ----
 
 Note that it is necessary to quote the description twice.  The local
diff --git a/Documentation/cmd-create-project.txt b/Documentation/cmd-create-project.txt
index e8c3857..026d7b1 100644
--- a/Documentation/cmd-create-project.txt
+++ b/Documentation/cmd-create-project.txt
@@ -1,7 +1,7 @@
 = gerrit create-project
 
 == NAME
-gerrit create-project - Create a new hosted project
+gerrit create-project - Create a new hosted project.
 
 == SYNOPSIS
 [verse]
@@ -61,15 +61,17 @@
 --owner::
 -o::
 	Identifier of the group(s) which will initially own this repository.
++
+--
+This can be:
 
-        This can be:
-
-        * the UUID of the group
-        * the legacy numeric ID of the group
-        * the name of the group if it is unique
-
-	The specified group(s) must already be defined within Gerrit.
-	Several groups can be specified on the command line.
+* the UUID of the group
+* the legacy numeric ID of the group
+* the name of the group if it is unique
+--
++
+The specified group(s) must already be defined within Gerrit.
+Several groups can be specified on the command line.
 +
 Defaults to what is specified by `repository.*.ownerGroup`
 in gerrit.config.
@@ -180,13 +182,13 @@
 Create a new project called `tools/gerrit`:
 
 ----
-	$ ssh -p 29418 review.example.com gerrit create-project tools/gerrit.git
+$ ssh -p 29418 review.example.com gerrit create-project tools/gerrit.git
 ----
 
 Create a new project with a description:
 
 ----
-	$ ssh -p 29418 review.example.com gerrit create-project tool.git --description "'Tools used by build system'"
+$ ssh -p 29418 review.example.com gerrit create-project tool.git --description "'Tools used by build system'"
 ----
 
 Note that it is necessary to quote the description twice.  The local
@@ -199,7 +201,7 @@
 perform remote repository creation by a Bourne shell script:
 
 ----
-  mkdir -p '/base/project.git' && cd '/base/project.git' && git init --bare && git update-ref HEAD refs/heads/master
+mkdir -p '/base/project.git' && cd '/base/project.git' && git init --bare && git update-ref HEAD refs/heads/master
 ----
 
 For this to work successfully the remote system must be able to run
diff --git a/Documentation/cmd-flush-caches.txt b/Documentation/cmd-flush-caches.txt
index 9ba4808..55d9083 100644
--- a/Documentation/cmd-flush-caches.txt
+++ b/Documentation/cmd-flush-caches.txt
@@ -1,7 +1,7 @@
 = gerrit flush-caches
 
 == NAME
-gerrit flush-caches - Flush some/all server caches from memory
+gerrit flush-caches - Flush some/all server caches from memory.
 
 == SYNOPSIS
 [verse]
@@ -58,40 +58,40 @@
 List caches available for flushing:
 
 ----
-	$ ssh -p 29418 review.example.com gerrit flush-caches --list
-	accounts
-	diff
-	groups
-	ldap_groups
-	openid
-	projects
-	sshkeys
-	web_sessions
+$ ssh -p 29418 review.example.com gerrit flush-caches --list
+accounts
+diff
+groups
+ldap_groups
+openid
+projects
+sshkeys
+web_sessions
 ----
 
 Flush all caches known to the server, forcing them to recompute:
 
 ----
-	$ ssh -p 29418 review.example.com gerrit flush-caches --all
+$ ssh -p 29418 review.example.com gerrit flush-caches --all
 ----
 
 or
 
 ----
-	$ ssh -p 29418 review.example.com gerrit flush-caches
+$ ssh -p 29418 review.example.com gerrit flush-caches
 ----
 
 Flush only the "sshkeys" cache, after manually editing an SSH key
 for a user:
 
 ----
-	$ ssh -p 29418 review.example.com gerrit flush-caches --cache sshkeys
+$ ssh -p 29418 review.example.com gerrit flush-caches --cache sshkeys
 ----
 
 Flush "web_sessions", forcing all users to sign-in again:
 
 ----
-	$ ssh -p 29418 review.example.com gerrit flush-caches --cache web_sessions
+$ ssh -p 29418 review.example.com gerrit flush-caches --cache web_sessions
 ----
 
 == SEE ALSO
diff --git a/Documentation/cmd-gc.txt b/Documentation/cmd-gc.txt
index 1d1cc00..390dce1 100644
--- a/Documentation/cmd-gc.txt
+++ b/Documentation/cmd-gc.txt
@@ -1,7 +1,7 @@
 = gerrit gc
 
 == NAME
-gerrit gc - Run the Git garbage collection
+gerrit gc - Run the Git garbage collection.
 
 == SYNOPSIS
 [verse]
@@ -54,19 +54,19 @@
 Run the Git garbage collection for the projects 'myProject' and
 'yourProject':
 ----
-	$ ssh -p 29418 review.example.com gerrit gc myProject yourProject
-	collecting garbage for "myProject":
-	...
-	done.
+$ ssh -p 29418 review.example.com gerrit gc myProject yourProject
+collecting garbage for "myProject":
+...
+done.
 
-	collecting garbage for "yourProject":
-	...
-	done.
+collecting garbage for "yourProject":
+...
+done.
 ----
 
 Run the Git garbage collection for all projects:
 ----
-	$ ssh -p 29418 review.example.com gerrit gc --all
+$ ssh -p 29418 review.example.com gerrit gc --all
 ----
 
 GERRIT
diff --git a/Documentation/cmd-gsql.txt b/Documentation/cmd-gsql.txt
index d2eb783..7f2aaf7 100644
--- a/Documentation/cmd-gsql.txt
+++ b/Documentation/cmd-gsql.txt
@@ -1,7 +1,7 @@
 = gerrit gsql
 
 == NAME
-gerrit gsql - Administrative interface to active database
+gerrit gsql - Administrative interface to active database.
 
 == SYNOPSIS
 [verse]
@@ -42,18 +42,18 @@
 To manually correct a user's SSH user name:
 
 ----
-	$ ssh -p 29418 review.example.com gerrit gsql
-	Welcome to Gerrit Code Review v2.0.25
-	(PostgreSQL 8.3.8)
+$ ssh -p 29418 review.example.com gerrit gsql
+Welcome to Gerrit Code Review v2.0.25
+(PostgreSQL 8.3.8)
 
-	Type '\h' for help.  Type '\r' to clear the buffer.
+Type '\h' for help.  Type '\r' to clear the buffer.
 
-	gerrit> update accounts set ssh_user_name = 'alice' where account_id=1;
-	UPDATE 1; 1 ms
-	gerrit> \q
-	Bye
+gerrit> update accounts set ssh_user_name = 'alice' where account_id=1;
+UPDATE 1; 1 ms
+gerrit> \q
+Bye
 
-	$ ssh -p 29418 review.example.com gerrit flush-caches --cache sshkeys --cache accounts
+$ ssh -p 29418 review.example.com gerrit flush-caches --cache sshkeys --cache accounts
 ----
 
 GERRIT
diff --git a/Documentation/cmd-hook-commit-msg.txt b/Documentation/cmd-hook-commit-msg.txt
index ffdd5da..49d5c17 100644
--- a/Documentation/cmd-hook-commit-msg.txt
+++ b/Documentation/cmd-hook-commit-msg.txt
@@ -20,24 +20,24 @@
 for a project, the hook will modify a commit message such as:
 
 ----
-  Improve foo widget by attaching a bar.
+Improve foo widget by attaching a bar.
 
-  We want a bar, because it improves the foo by providing more
-  wizbangery to the dowhatimeanery.
+We want a bar, because it improves the foo by providing more
+wizbangery to the dowhatimeanery.
 
-  Signed-off-by: A. U. Thor <author@example.com>
+Signed-off-by: A. U. Thor <author@example.com>
 ----
 
 by inserting a new `Change-Id: ` line in the footer:
 
 ----
-  Improve foo widget by attaching a bar.
+Improve foo widget by attaching a bar.
 
-  We want a bar, because it improves the foo by providing more
-  wizbangery to the dowhatimeanery.
+We want a bar, because it improves the foo by providing more
+wizbangery to the dowhatimeanery.
 
-  Change-Id: Ic8aaa0728a43936cd4c6e1ed590e01ba8f0fbf5b
-  Signed-off-by: A. U. Thor <author@example.com>
+Change-Id: Ic8aaa0728a43936cd4c6e1ed590e01ba8f0fbf5b
+Signed-off-by: A. U. Thor <author@example.com>
 ----
 
 The hook implementation is reasonably intelligent at inserting the
@@ -57,31 +57,29 @@
 
 == OBTAINING
 
-
 To obtain the `commit-msg` script use `scp`, `wget` or `curl` to download
 it to your local system from your Gerrit server.
 
 You can use either of the below commands:
 
 ----
-  $ scp -p -P 29418 <your username>@<your Gerrit review server>:hooks/commit-msg <local path to your git>/.git/hooks/
+$ scp -p -P 29418 <your username>@<your Gerrit review server>:hooks/commit-msg <local path to your git>/.git/hooks/
 
-  $ curl -Lo <local path to your git>/.git/hooks/commit-msg <your Gerrit http URL>/tools/hooks/commit-msg
+$ curl -Lo <local path to your git>/.git/hooks/commit-msg <your Gerrit http URL>/tools/hooks/commit-msg
 ----
 
 A specific example of this might look something like this:
 
-.Example
 ----
-  $ scp -p -P 29418 john.doe@review.example.com:hooks/commit-msg ~/duhproject/.git/hooks/
+$ scp -p -P 29418 john.doe@review.example.com:hooks/commit-msg ~/duhproject/.git/hooks/
 
-  $ curl -Lo ~/duhproject/.git/hooks/commit-msg http://review.example.com/tools/hooks/commit-msg
+$ curl -Lo ~/duhproject/.git/hooks/commit-msg http://review.example.com/tools/hooks/commit-msg
 ----
 
 Make sure the hook file is executable:
 
 ----
-  $ chmod u+x ~/duhproject/.git/hooks/commit-msg
+$ chmod u+x ~/duhproject/.git/hooks/commit-msg
 ----
 
 == SEE ALSO
diff --git a/Documentation/cmd-index-activate.txt b/Documentation/cmd-index-activate.txt
index 4428d12..0bbf308 100644
--- a/Documentation/cmd-index-activate.txt
+++ b/Documentation/cmd-index-activate.txt
@@ -1,7 +1,7 @@
 = gerrit index activate
 
 == NAME
-gerrit index activate - Activate the latest index version available
+gerrit index activate - Activate the latest index version available.
 
 == SYNOPSIS
 [verse]
@@ -37,7 +37,7 @@
 Activate the latest change index:
 
 ----
-  $ ssh -p 29418 review.example.com gerrit index activate changes
+$ ssh -p 29418 review.example.com gerrit index activate changes
 ----
 
 GERRIT
diff --git a/Documentation/cmd-index-changes-in-project.txt b/Documentation/cmd-index-changes-in-project.txt
index ec1626f..b2282fc 100644
--- a/Documentation/cmd-index-changes-in-project.txt
+++ b/Documentation/cmd-index-changes-in-project.txt
@@ -26,7 +26,7 @@
 Index all changes in projects MyProject and NiceProject.
 
 ----
-    $ ssh -p 29418 user@review.example.com gerrit index changes-in-project MyProject NiceProject
+$ ssh -p 29418 user@review.example.com gerrit index changes-in-project MyProject NiceProject
 ----
 
 GERRIT
diff --git a/Documentation/cmd-index-changes.txt b/Documentation/cmd-index-changes.txt
index d38c51a..0ee7aab 100644
--- a/Documentation/cmd-index-changes.txt
+++ b/Documentation/cmd-index-changes.txt
@@ -30,7 +30,7 @@
 Index changes with legacy ID numbers 1 and 2.
 
 ----
-    $ ssh -p 29418 user@review.example.com gerrit index changes 1 2
+$ ssh -p 29418 user@review.example.com gerrit index changes 1 2
 ----
 
 GERRIT
diff --git a/Documentation/cmd-index-start.txt b/Documentation/cmd-index-start.txt
index 5a002f3..d1a02b3 100644
--- a/Documentation/cmd-index-start.txt
+++ b/Documentation/cmd-index-start.txt
@@ -1,7 +1,7 @@
 = gerrit index start
 
 == NAME
-gerrit index start - Start the online indexer
+gerrit index start - Start the online indexer.
 
 == SYNOPSIS
 [verse]
@@ -44,7 +44,7 @@
 Start the online indexer for the 'changes' index:
 
 ----
-  $ ssh -p 29418 review.example.com gerrit index start changes
+$ ssh -p 29418 review.example.com gerrit index start changes
 ----
 
 GERRIT
diff --git a/Documentation/cmd-index.txt b/Documentation/cmd-index.txt
index 496c205..25099fa 100644
--- a/Documentation/cmd-index.txt
+++ b/Documentation/cmd-index.txt
@@ -8,11 +8,11 @@
 To download a client command or hook, use scp or an http client:
 
 ----
-  $ scp -p -P 29418 john.doe@review.example.com:bin/gerrit-cherry-pick ~/bin/
-  $ scp -p -P 29418 john.doe@review.example.com:hooks/commit-msg .git/hooks/
+$ scp -p -P 29418 john.doe@review.example.com:bin/gerrit-cherry-pick ~/bin/
+$ scp -p -P 29418 john.doe@review.example.com:hooks/commit-msg .git/hooks/
 
-  $ curl -Lo ~/bin/gerrit-cherry-pick http://review.example.com/tools/bin/gerrit-cherry-pick
-  $ curl -Lo .git/hooks/commit-msg http://review.example.com/tools/hooks/commit-msg
+$ curl -Lo ~/bin/gerrit-cherry-pick http://review.example.com/tools/bin/gerrit-cherry-pick
+$ curl -Lo .git/hooks/commit-msg http://review.example.com/tools/hooks/commit-msg
 ----
 
 For more details on how to determine the correct SSH port number,
@@ -32,7 +32,7 @@
 server.
 
 link:cmd-hook-commit-msg.html[commit-msg]::
-	Automatically generate `Change-Id: ` tags in commit messages.
+	Automatically generate `Change-Id:` tags in commit messages.
 
 
 == Server
diff --git a/Documentation/cmd-kill.txt b/Documentation/cmd-kill.txt
index ac8e802..db5093d 100644
--- a/Documentation/cmd-kill.txt
+++ b/Documentation/cmd-kill.txt
@@ -1,7 +1,7 @@
 = kill
 
 == NAME
-kill - Cancel or abort a background task
+kill - Cancel or abort a background task.
 
 == SYNOPSIS
 [verse]
diff --git a/Documentation/cmd-logging-ls-level.txt b/Documentation/cmd-logging-ls-level.txt
index ee015bb..fb1fb33 100644
--- a/Documentation/cmd-logging-ls-level.txt
+++ b/Documentation/cmd-logging-ls-level.txt
@@ -1,9 +1,9 @@
 = gerrit logging ls-level
 
 == NAME
-gerrit logging ls-level - view the logging level
+gerrit logging ls-level - view the logging level.
 
-gerrit logging ls - view the logging level
+gerrit logging ls - view the logging level.
 
 == SYNOPSIS
 [verse]
@@ -27,13 +27,12 @@
 
 View the logging level of the loggers in the package com.google:
 ----
-    $ssh -p 29418 review.example.com gerrit logging ls-level \
-     com.google.
+$ssh -p 29418 review.example.com gerrit logging ls-level com.google.
 ----
 
 View the logging level of every logger
 ----
-    $ssh -p 29418 review.example.com gerrit logging ls-level
+$ssh -p 29418 review.example.com gerrit logging ls-level
 ----
 
 GERRIT
diff --git a/Documentation/cmd-logging-set-level.txt b/Documentation/cmd-logging-set-level.txt
index 5baa968..d7fc69e 100644
--- a/Documentation/cmd-logging-set-level.txt
+++ b/Documentation/cmd-logging-set-level.txt
@@ -1,9 +1,9 @@
 = gerrit logging set-level
 
 == NAME
-gerrit logging set-level - set the logging level
+gerrit logging set-level - set the logging level.
 
-gerrit logging set - set the logging level
+gerrit logging set - set the logging level.
 
 == SYNOPSIS
 [verse]
@@ -34,14 +34,12 @@
 
 Change the logging level of the loggers in the package com.google to DEBUG.
 ----
-    $ssh -p 29418 review.example.com gerrit logging set-level \
-     debug com.google.
+$ssh -p 29418 review.example.com gerrit logging set-level debug com.google.
 ----
 
 Reset the logging level of every logger to what they were at deployment time.
 ----
-    $ssh -p 29418 review.example.com gerrit logging set-level \
-     reset
+$ssh -p 29418 review.example.com gerrit logging set-level reset
 ----
 
 GERRIT
diff --git a/Documentation/cmd-ls-groups.txt b/Documentation/cmd-ls-groups.txt
index 6d4bdc5..8a4845c 100644
--- a/Documentation/cmd-ls-groups.txt
+++ b/Documentation/cmd-ls-groups.txt
@@ -1,7 +1,7 @@
 = gerrit ls-groups
 
 == NAME
-gerrit ls-groups - List groups visible to caller
+gerrit ls-groups - List groups visible to caller.
 
 == SYNOPSIS
 [verse]
@@ -88,53 +88,53 @@
 
 List visible groups:
 ----
-	$ ssh -p 29418 review.example.com gerrit ls-groups
-	Administrators
-	Anonymous Users
-	MyProject_Committers
-	Project Owners
-	Registered Users
+$ ssh -p 29418 review.example.com gerrit ls-groups
+Administrators
+Anonymous Users
+MyProject_Committers
+Project Owners
+Registered Users
 ----
 
 List all groups for which any permission is set for the project
 "MyProject":
 ----
-	$ ssh -p 29418 review.example.com gerrit ls-groups --project MyProject
-	MyProject_Committers
-	Project Owners
-	Registered Users
+$ ssh -p 29418 review.example.com gerrit ls-groups --project MyProject
+MyProject_Committers
+Project Owners
+Registered Users
 ----
 
 List all groups which are owned by the calling user:
 ----
-	$ ssh -p 29418 review.example.com gerrit ls-groups --owned
-	MyProject_Committers
-	MyProject_Verifiers
+$ ssh -p 29418 review.example.com gerrit ls-groups --owned
+MyProject_Committers
+MyProject_Verifiers
 ----
 
 Check if the calling user owns the group `MyProject_Committers`. If
 `MyProject_Committers` is returned the calling user owns this group.
 If the result is empty, the calling user doesn't own the group.
 ----
-	$ ssh -p 29418 review.example.com gerrit ls-groups --owned -q MyProject_Committers
-	MyProject_Committers
+$ ssh -p 29418 review.example.com gerrit ls-groups --owned -q MyProject_Committers
+MyProject_Committers
 ----
 
 Extract the UUID of the 'Administrators' group:
 
 ----
-	$ ssh -p 29418 review.example.com gerrit ls-groups -v | awk '-F\t' '$1 == "Administrators" {print $2}'
-	ad463411db3eec4e1efb0d73f55183c1db2fd82a
+$ ssh -p 29418 review.example.com gerrit ls-groups -v | awk '-F\t' '$1 == "Administrators" {print $2}'
+ad463411db3eec4e1efb0d73f55183c1db2fd82a
 ----
 
 Extract and expand the multi-line description of the 'Administrators'
 group:
 
 ----
-	$ printf "$(ssh -p 29418 review.example.com gerrit ls-groups -v | awk '-F\t' '$1 == "Administrators" {print $3}')\n"
-	This is a
-	multi-line
-	description.
+$ printf "$(ssh -p 29418 review.example.com gerrit ls-groups -v | awk '-F\t' '$1 == "Administrators" {print $3}')\n"
+This is a
+multi-line
+description.
 ----
 
 GERRIT
diff --git a/Documentation/cmd-ls-members.txt b/Documentation/cmd-ls-members.txt
index 273451b..8f8857c 100644
--- a/Documentation/cmd-ls-members.txt
+++ b/Documentation/cmd-ls-members.txt
@@ -1,7 +1,7 @@
 = gerrit ls-members
 
 == NAME
-gerrit ls-members - Show members of a given group
+gerrit ls-members - Show members of a given group.
 
 == SYNOPSIS
 [verse]
@@ -40,17 +40,17 @@
 
 List members of the Administrators group:
 ----
-	$ ssh -p 29418 review.example.com gerrit ls-members Administrators
-	id      username  full name    email
-	100000  jim     Jim Bob somebody@example.com
-	100001  johnny  John Smith      n/a
-	100002  mrnoname        n/a     someoneelse@example.com
+$ ssh -p 29418 review.example.com gerrit ls-members Administrators
+id      username  full name    email
+100000  jim     Jim Bob somebody@example.com
+100001  johnny  John Smith      n/a
+100002  mrnoname        n/a     someoneelse@example.com
 ----
 
 List members of a non-existent group:
 ----
-	$ ssh -p 29418 review.example.com gerrit ls-members BadlySpelledGroup
-	Group not found or not visible
+$ ssh -p 29418 review.example.com gerrit ls-members BadlySpelledGroup
+Group not found or not visible
 ----
 
 GERRIT
diff --git a/Documentation/cmd-ls-projects.txt b/Documentation/cmd-ls-projects.txt
index 486ca44..3bb8e4f 100644
--- a/Documentation/cmd-ls-projects.txt
+++ b/Documentation/cmd-ls-projects.txt
@@ -1,7 +1,7 @@
 = gerrit ls-projects
 
 == NAME
-gerrit ls-projects - List projects visible to caller
+gerrit ls-projects - List projects visible to caller.
 
 == SYNOPSIS
 [verse]
@@ -115,28 +115,28 @@
 
 List visible projects:
 ----
-	$ ssh -p 29418 review.example.com gerrit ls-projects
-	platform/manifest
-	tools/gerrit
-	tools/gwtorm
+$ ssh -p 29418 review.example.com gerrit ls-projects
+platform/manifest
+tools/gerrit
+tools/gwtorm
 
-	$ curl http://review.example.com/projects/
-	platform/manifest
-	tools/gerrit
-	tools/gwtorm
+$ curl http://review.example.com/projects/
+platform/manifest
+tools/gerrit
+tools/gwtorm
 
-	$ curl http://review.example.com/projects/tools/
-	tools/gerrit
-	tools/gwtorm
+$ curl http://review.example.com/projects/tools/
+tools/gerrit
+tools/gwtorm
 ----
 
 Clone any project visible to the user:
 ----
-	for p in `ssh -p 29418 review.example.com gerrit ls-projects`
-	do
-	  mkdir -p `dirname "$p"`
-	  git clone --bare "ssh://review.example.com:29418/$p.git" "$p.git"
-	done
+for p in `ssh -p 29418 review.example.com gerrit ls-projects`
+do
+  mkdir -p `dirname "$p"`
+  git clone --bare "ssh://review.example.com:29418/$p.git" "$p.git"
+done
 ----
 
 == SEE ALSO
diff --git a/Documentation/cmd-ls-user-refs.txt b/Documentation/cmd-ls-user-refs.txt
index 1a87fc9..cba7d1b 100644
--- a/Documentation/cmd-ls-user-refs.txt
+++ b/Documentation/cmd-ls-user-refs.txt
@@ -1,7 +1,7 @@
 = gerrit ls-user-refs
 
 == NAME
-gerrit ls-user-refs - List refs visible to a specific user
+gerrit ls-user-refs - List refs visible to a specific user.
 
 == SYNOPSIS
 [verse]
@@ -42,7 +42,7 @@
 
 List visible refs for the user "mr.developer" in project "gerrit"
 ----
-	$ ssh -p 29418 review.example.com gerrit ls-user-refs -p gerrit -u mr.developer
+$ ssh -p 29418 review.example.com gerrit ls-user-refs -p gerrit -u mr.developer
 ----
 
 GERRIT
diff --git a/Documentation/cmd-plugin-enable.txt b/Documentation/cmd-plugin-enable.txt
index 9b52736..955267e 100644
--- a/Documentation/cmd-plugin-enable.txt
+++ b/Documentation/cmd-plugin-enable.txt
@@ -32,7 +32,7 @@
 Enable a plugin:
 
 ----
-	ssh -p 29418 localhost gerrit plugin enable my-plugin
+ssh -p 29418 localhost gerrit plugin enable my-plugin
 ----
 
 GERRIT
diff --git a/Documentation/concept-changes.txt b/Documentation/concept-changes.txt
index 7320a50..1d275b4 100644
--- a/Documentation/concept-changes.txt
+++ b/Documentation/concept-changes.txt
@@ -55,7 +55,7 @@
 |An optional topic.
 
 |Strategy
-|The <<submit-strategy>> for the change.
+|The <<submit-strategies,submit strategy>> for the change.
 
 |Code Review
 |Displays the Code Review status for the change.
@@ -84,10 +84,10 @@
 several categories, including:
 
 * Relation Chain. These changes are related by parent-child relationships,
-  regardless of <<topics>>.
+  regardless of <<topic,topic>>.
 * Merge Conflicts. These are changes in which there is a merge conflict with
   the current change.
-* Submitted Together. These are changes that share the same <<topics>>.
+* Submitted Together. These are changes that share the same <<topic,topic>>.
 
 An arrow indicates the change you are currently viewing.
 
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 734397e..9f0bf86 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -690,7 +690,7 @@
 allows to limit the memory used by H2 and thus prevent out-of-memory
 caused by the H2 database using too much memory.
 +
-See <<database.h2.cachesize,database.h2.cachesize>> for a detailed discussion.
+See <<database.h2.cacheSize,database.h2.cacheSize>> for a detailed discussion.
 +
 Default is unset, using up to half of the available memory.
 +
@@ -3684,6 +3684,9 @@
 BLOCK rules).
 +
 Default is false.
++
+This value supports configuration reloads:
+link:cmd-reload-config.html[reload-config]
 
 [[receive.checkReferencedObjectsAreReachable]]receive.checkReferencedObjectsAreReachable::
 +
diff --git a/Documentation/dev-bazel.txt b/Documentation/dev-bazel.txt
index 6331581..0ecc820 100644
--- a/Documentation/dev-bazel.txt
+++ b/Documentation/dev-bazel.txt
@@ -6,7 +6,7 @@
 To build Gerrit from source, you need:
 
 * A Linux or macOS system (Windows is not supported at this time)
-* A JDK for Java 8
+* A JDK for Java 8|9|10
 * Python 2 or 3
 * Node.js
 * link:https://www.bazel.io/versions/master/docs/install.html[Bazel]
@@ -14,6 +14,30 @@
 * zip, unzip
 * gcc
 
+[[Java 10 support]]
+Java 10 is supported through vanilla java toolchain
+link:https://docs.bazel.build/versions/master/toolchains.html[Bazel option].
+To build Gerrit with Java 10, specify vanilla java toolchain and provide
+path to Java 10 home:
+
+```
+  $ bazel build --host_javabase=:absolute_javabase \
+    --define=ABSOLUTE_JAVABASE=<path-to-java-10> \
+    --define=USE_ABSOLUTE_JAVABASE=true \
+    --host_java_toolchain=//:toolchain_vanilla \
+    --java_toolchain=//:toolchain_vanilla \
+    :release
+```
+
+Note that the following options must be added to `container.javaOptions`
+in `$gerrit_site/etc/gerrit.config` to run Gerrit with Java 10:
+
+```
+[container]
+  javaOptions = --add-modules java.activation
+  javaOptions = --add-opens=jdk.management/com.sun.management.internal=ALL-UNNAMED
+```
+
 [[Java 9 support]]
 Java 9 is supported through alternative java toolchain
 link:https://docs.bazel.build/versions/master/toolchains.html[Bazel option].
diff --git a/Documentation/metrics.txt b/Documentation/metrics.txt
index 19d3b41..2647b35 100644
--- a/Documentation/metrics.txt
+++ b/Documentation/metrics.txt
@@ -138,6 +138,13 @@
 * `notedb/read_all_external_ids_latency`: Latency for reading all
 external ID's from NoteDb.
 
+=== Permissions
+
+* `permissions/project_state/computation_latency`: Latency to compute current access
+sections on a project by traversing it's parents.
+* `permissions/permission_collection/filter_latency`: Latency to filter access sections
+by user and ref.
+
 === Reviewer Suggestion
 
 * `reviewer_suggestion/query_accounts`: Latency for querying accounts for
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index b517d3c..2a9dcee 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -3570,6 +3570,12 @@
 `FALSE`, `INHERIT`).
 This property is deprecated and will be removed in
 a future release.
+|`enable_signed_push`                           |`INHERIT` if not set|
+Whether signed push validation is enabled on the project  (`TRUE`,
+`FALSE`, `INHERIT`).
+|`require_signed_push`                          |`INHERIT` if not set|
+Whether signed push validation is required on the project  (`TRUE`,
+`FALSE`, `INHERIT`).
 |`max_object_size_limit`     |optional|
 Max allowed Git object size for this project.
 Common unit suffixes of 'k', 'm', or 'g' are supported.
@@ -3707,8 +3713,6 @@
 The path to the `GerritSiteFooter.html` file.
 |=============================
 
-----
-
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/user-search.txt b/Documentation/user-search.txt
index 41cb380..7c904f5 100644
--- a/Documentation/user-search.txt
+++ b/Documentation/user-search.txt
@@ -166,7 +166,7 @@
 'PROJECT'.
 
 [[repository]]
-repository:'REPOSITORY'::
+repository:'REPOSITORY', repo:'REPOSITORY'::
 +
 Changes occurring in 'REPOSITORY'. If 'REPOSITORY' starts with `^` it
 matches repository names by regular expression.  The
@@ -174,12 +174,12 @@
 library] is used for evaluation of such patterns.
 
 [[repositories]]
-repositories:'PREFIX'::
+repositories:'PREFIX', repos:'PREFIX'::
 +
 Changes occurring in repositories starting with 'PREFIX'.
 
 [[parentrepository]]
-parentrepository:'REPOSITORY'::
+parentrepository:'REPOSITORY', parentrepo:'REPOSITORY'::
 +
 Changes occurring in 'REPOSITORY' or in one of the child repositories of
 'REPOSITORY'.
diff --git a/WORKSPACE b/WORKSPACE
index 0929342..a20e0b1 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -1,5 +1,6 @@
 workspace(name = "gerrit")
 
+load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
 load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive", "http_file")
 load("//tools/bzl:maven_jar.bzl", "GERRIT", "MAVEN_LOCAL", "maven_jar")
 load("//lib/codemirror:cm.bzl", "CM_VERSION", "DIFF_MATCH_PATCH_VERSION")
@@ -24,6 +25,7 @@
 # https://github.com/google/closure-compiler/blob/master/contrib/externs/polymer-1.0.js
 http_file(
     name = "polymer_closure",
+    downloaded_file_path = "polymer_closure.js",
     sha256 = "5a589bdba674e1fec7188e9251c8624ebf2d4d969beb6635f9148f420d1e08b1",
     urls = ["https://raw.githubusercontent.com/google/closure-compiler/775609aad61e14aef289ebec4bfc09ad88877f9e/contrib/externs/polymer-1.0.js"],
 )
@@ -43,6 +45,42 @@
     omit_javax_inject = True,
 )
 
+# Golang support for PolyGerrit local dev server.
+http_archive(
+    name = "io_bazel_rules_go",
+    sha256 = "97cf62bdef33519412167fd1e4b0810a318a7c234f5f8dc4f53e2da86241c492",
+    urls = ["https://github.com/bazelbuild/rules_go/releases/download/0.15.3/rules_go-0.15.3.tar.gz"],
+)
+
+load("@io_bazel_rules_go//go:def.bzl", "go_register_toolchains", "go_rules_dependencies")
+
+go_rules_dependencies()
+
+go_register_toolchains()
+
+http_archive(
+    name = "bazel_gazelle",
+    sha256 = "c0a5739d12c6d05b6c1ad56f2200cb0b57c5a70e03ebd2f7b87ce88cabf09c7b",
+    urls = ["https://github.com/bazelbuild/bazel-gazelle/releases/download/0.14.0/bazel-gazelle-0.14.0.tar.gz"],
+)
+
+load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies", "go_repository")
+
+gazelle_dependencies()
+
+# Dependencies for PolyGerrit local dev server.
+go_repository(
+    name = "com_github_robfig_soy",
+    commit = "82face14ebc0883b4ca9c901b5aaf3738b9f6a24",
+    importpath = "github.com/robfig/soy",
+)
+
+go_repository(
+    name = "com_github_howeyc_fsnotify",
+    commit = "441bbc86b167f3c1f4786afae9931403b99fdacf",
+    importpath = "github.com/howeyc/fsnotify",
+)
+
 ANTLR_VERS = "3.5.2"
 
 maven_jar(
@@ -70,24 +108,24 @@
     sha1 = "83cd2cd674a217ade95a4bb83a8a14f351f48bd0",
 )
 
-GUICE_VERS = "4.2.0"
+GUICE_VERS = "4.2.1"
 
 maven_jar(
     name = "guice-library",
     artifact = "com.google.inject:guice:" + GUICE_VERS,
-    sha1 = "25e1f4c1d528a1cffabcca0d432f634f3132f6c8",
+    sha1 = "f77dfd89318fe3ff293bafceaa75fbf66e4e4b10",
 )
 
 maven_jar(
     name = "guice-assistedinject",
     artifact = "com.google.inject.extensions:guice-assistedinject:" + GUICE_VERS,
-    sha1 = "e7270305960ad7db56f7e30cb9df6be9ff1cfb45",
+    sha1 = "d327e4aee7c96f08cd657c17da231a1f4a8999ac",
 )
 
 maven_jar(
     name = "guice-servlet",
     artifact = "com.google.inject.extensions:guice-servlet:" + GUICE_VERS,
-    sha1 = "f57581625c36c148f088d9f52a568d5bdf12c61d",
+    sha1 = "3927e462f923b0c672fdb045c5645bca4beab5c0",
 )
 
 maven_jar(
@@ -292,8 +330,8 @@
 
 maven_jar(
     name = "args4j-intern",
-    artifact = "args4j:args4j:2.0.29",
-    sha1 = "55ca4ddc4e906ffbaec043113b36bb410a3d909e",
+    artifact = "args4j:args4j:2.33",
+    sha1 = "bd87a75374a6d6523de82fef51fc3cfe9baf9fc9",
 )
 
 maven_jar(
@@ -599,36 +637,36 @@
     sha1 = "05b6f921f1810bdf90e25471968f741f87168b64",
 )
 
-LUCENE_VERS = "5.5.4"
+LUCENE_VERS = "6.6.5"
 
 maven_jar(
     name = "lucene-core",
     artifact = "org.apache.lucene:lucene-core:" + LUCENE_VERS,
-    sha1 = "ab9c77e75cf142aa6e284b310c8395617bd9b19b",
+    sha1 = "2983f80b1037e098209657b0ca9176827892d0c0",
 )
 
 maven_jar(
     name = "lucene-analyzers-common",
     artifact = "org.apache.lucene:lucene-analyzers-common:" + LUCENE_VERS,
-    sha1 = "08ce9d34c8124c80e176e8332ee947480bbb9576",
+    sha1 = "6094f91071d90570b7f5f8ce481d5de7d2d2e9d5",
 )
 
 maven_jar(
     name = "backward-codecs",
     artifact = "org.apache.lucene:lucene-backward-codecs:" + LUCENE_VERS,
-    sha1 = "a933f42e758c54c43083398127ea7342b54d8212",
+    sha1 = "460a19e8d1aa7d31e9614cf528a6cb508c9e823d",
 )
 
 maven_jar(
     name = "lucene-misc",
     artifact = "org.apache.lucene:lucene-misc:" + LUCENE_VERS,
-    sha1 = "a74388857f73614e528ae44d742c60187cb55a5a",
+    sha1 = "ce3a1b7b6a92b9af30791356a4bd46d1cea6cc1e",
 )
 
 maven_jar(
     name = "lucene-queryparser",
     artifact = "org.apache.lucene:lucene-queryparser:" + LUCENE_VERS,
-    sha1 = "8a06fad4675473d98d93b61fea529e3f464bf69e",
+    sha1 = "2db9ca0086a4b8e0b9bc9f08a9b420303168e37c",
 )
 
 maven_jar(
@@ -740,13 +778,10 @@
     sha1 = "d0c46320fbc07be3a24eb13a56cee4e3d38e0c75",
 )
 
-# TODO(davido): Remove exlusion of file system provider, when this issue is fixed:
-# https://issues.apache.org/jira/browse/SSHD-736
 maven_jar(
     name = "sshd",
-    artifact = "org.apache.sshd:sshd-core:1.7.0",
-    exclude = ["META-INF/services/java.nio.file.spi.FileSystemProvider"],
-    sha1 = "2e8b14f6d841b098e46bf407b6fdccab4c19fa41",
+    artifact = "org.apache.sshd:sshd-core:2.0.0",
+    sha1 = "f4275079a2463cfd2bf1548a80e1683288a8e86b",
 )
 
 maven_jar(
@@ -757,8 +792,14 @@
 
 maven_jar(
     name = "mina-core",
-    artifact = "org.apache.mina:mina-core:2.0.16",
-    sha1 = "f720f17643eaa7b0fec07c1d7f6272972c02bba4",
+    artifact = "org.apache.mina:mina-core:2.0.17",
+    sha1 = "7e10ec974760436d931f3e58be507d1957bcc8db",
+)
+
+maven_jar(
+    name = "sshd-mina",
+    artifact = "org.apache.sshd:sshd-mina:2.0.0",
+    sha1 = "50f2669312494f6c1996d8bd0d266c1fca7be6f6",
 )
 
 maven_jar(
@@ -1026,14 +1067,14 @@
 
 maven_jar(
     name = "asciidoctor",
-    artifact = "org.asciidoctor:asciidoctorj:1.5.6",
-    sha1 = "bb757d4b8b0f8438ce2ed781f6688cc6c01d9237",
+    artifact = "org.asciidoctor:asciidoctorj:1.5.7",
+    sha1 = "8e8c1d8fc6144405700dd8df3b177f2801ac5987",
 )
 
 maven_jar(
     name = "jruby",
-    artifact = "org.jruby:jruby-complete:9.1.13.0",
-    sha1 = "8903bf42272062e87a7cbc1d98919e0729a9939f",
+    artifact = "org.jruby:jruby-complete:9.1.17.0",
+    sha1 = "76716d529710fc03d1d429b43e3cedd4419f78d4",
 )
 
 maven_jar(
diff --git a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index 69d603f..030ec2a 100644
--- a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -502,6 +502,8 @@
       in.useSignedOffBy = ann.useSignedOffBy();
       in.useContentMerge = ann.useContentMerge();
       in.rejectEmptyCommit = ann.rejectEmptyCommit();
+      in.enableSignedPush = ann.enableSignedPush();
+      in.requireSignedPush = ann.requireSignedPush();
     } else {
       // Defaults should match TestProjectConfig, omitting nullable values.
       in.createEmptyCommit = true;
@@ -714,7 +716,7 @@
       throws Exception {
     assertThat(topic).isNotEmpty();
     return createCommitAndPush(
-        repo, "refs/for/master/" + name(topic), commitMsg, fileName, content);
+        repo, "refs/for/master%topic=" + name(topic), commitMsg, fileName, content);
   }
 
   protected PushOneCommit.Result createChange(String subject, String fileName, String content)
@@ -733,7 +735,7 @@
       String topic)
       throws Exception {
     PushOneCommit push = pushFactory.create(db, admin.getIdent(), repo, subject, fileName, content);
-    return push.to("refs/for/" + branch + "/" + name(topic));
+    return push.to("refs/for/" + branch + "%topic=" + name(topic));
   }
 
   protected BranchApi createBranch(Branch.NameKey branch) throws Exception {
diff --git a/java/com/google/gerrit/acceptance/AcceptanceTestRequestScope.java b/java/com/google/gerrit/acceptance/AcceptanceTestRequestScope.java
index 3acee77..9a3e811 100644
--- a/java/com/google/gerrit/acceptance/AcceptanceTestRequestScope.java
+++ b/java/com/google/gerrit/acceptance/AcceptanceTestRequestScope.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.acceptance;
 
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.RequestCleanup;
@@ -22,6 +21,7 @@
 import com.google.gerrit.server.util.RequestContext;
 import com.google.gerrit.server.util.ThreadLocalRequestContext;
 import com.google.gerrit.server.util.ThreadLocalRequestScopePropagator;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.testing.DisabledReviewDb;
 import com.google.gwtorm.server.SchemaFactory;
 import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/acceptance/BUILD b/java/com/google/gerrit/acceptance/BUILD
index 0214cea..861372d 100644
--- a/java/com/google/gerrit/acceptance/BUILD
+++ b/java/com/google/gerrit/acceptance/BUILD
@@ -109,6 +109,7 @@
         "//java/com/google/gerrit/server/git/receive",
         "//java/com/google/gerrit/server/restapi",
         "//java/com/google/gerrit/server/schema",
+        "//java/com/google/gerrit/server/util/time",
         "//lib:gson",
         "//lib:guava-retrying",
         "//lib:gwtorm",
diff --git a/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java b/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java
index 83a3874..b23b6e6 100644
--- a/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java
+++ b/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java
@@ -44,16 +44,12 @@
 import com.google.gwtorm.server.SchemaFactory;
 import com.google.inject.Inject;
 import com.google.inject.Key;
-import com.google.inject.Provides;
 import com.google.inject.ProvisionException;
-import com.google.inject.Singleton;
 import com.google.inject.TypeLiteral;
 import com.google.inject.util.Providers;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import org.apache.sshd.common.keyprovider.KeyPairProvider;
-import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
 import org.eclipse.jgit.lib.Config;
 
 class InMemoryTestingDatabaseModule extends LifecycleModule {
@@ -107,23 +103,8 @@
 
     install(new SchemaModule());
     bind(SchemaVersion.class).to(SchemaVersion.C);
-  }
 
-  @Provides
-  @Singleton
-  KeyPairProvider createHostKey() {
-    return getHostKeys();
-  }
-
-  private static SimpleGeneratorHostKeyProvider keys;
-
-  private static synchronized KeyPairProvider getHostKeys() {
-    if (keys == null) {
-      keys = new SimpleGeneratorHostKeyProvider();
-      keys.setAlgorithm("RSA");
-      keys.loadKeys();
-    }
-    return keys;
+    install(new SshdModule());
   }
 
   static class CreateDatabase implements LifecycleListener {
diff --git a/java/com/google/gerrit/acceptance/SshdModule.java b/java/com/google/gerrit/acceptance/SshdModule.java
new file mode 100644
index 0000000..185d6e2
--- /dev/null
+++ b/java/com/google/gerrit/acceptance/SshdModule.java
@@ -0,0 +1,41 @@
+// Copyright (C) 2018 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;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Provides;
+import com.google.inject.Singleton;
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
+import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
+
+public class SshdModule extends AbstractModule {
+
+  @Provides
+  @Singleton
+  KeyPairProvider createHostKey() {
+    return getHostKeys();
+  }
+
+  private static SimpleGeneratorHostKeyProvider keys;
+
+  private static synchronized KeyPairProvider getHostKeys() {
+    if (keys == null) {
+      keys = new SimpleGeneratorHostKeyProvider();
+      keys.setAlgorithm("RSA");
+      keys.loadKeys();
+    }
+    return keys;
+  }
+}
diff --git a/java/com/google/gerrit/acceptance/TestProjectInput.java b/java/com/google/gerrit/acceptance/TestProjectInput.java
index eada6434..0a3686b 100644
--- a/java/com/google/gerrit/acceptance/TestProjectInput.java
+++ b/java/com/google/gerrit/acceptance/TestProjectInput.java
@@ -47,6 +47,10 @@
 
   InheritableBoolean rejectEmptyCommit() default InheritableBoolean.INHERIT;
 
+  InheritableBoolean enableSignedPush() default InheritableBoolean.INHERIT;
+
+  InheritableBoolean requireSignedPush() default InheritableBoolean.INHERIT;
+
   // Fields specific to acceptance test behavior.
 
   /** Username to use for initial clone, passed to {@link AccountCreator}. */
diff --git a/java/com/google/gerrit/asciidoctor/DocIndexer.java b/java/com/google/gerrit/asciidoctor/DocIndexer.java
index ef3ea3f..9517811 100644
--- a/java/com/google/gerrit/asciidoctor/DocIndexer.java
+++ b/java/com/google/gerrit/asciidoctor/DocIndexer.java
@@ -33,8 +33,8 @@
 import java.util.regex.Pattern;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
+import org.apache.lucene.analysis.CharArraySet;
 import org.apache.lucene.analysis.standard.StandardAnalyzer;
-import org.apache.lucene.analysis.util.CharArraySet;
 import org.apache.lucene.document.Document;
 import org.apache.lucene.document.Field;
 import org.apache.lucene.document.StringField;
diff --git a/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java b/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
index 2c1c93a..fec7137 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.flogger.FluentLogger;
 import com.google.common.io.CharStreams;
+import com.google.gerrit.common.Nullable;
 import com.google.gerrit.elasticsearch.ElasticMapping.MappingProperties;
 import com.google.gerrit.elasticsearch.builders.QueryBuilder;
 import com.google.gerrit.elasticsearch.builders.SearchSourceBuilder;
@@ -160,7 +161,7 @@
   @Override
   public void delete(K id) throws IOException {
     String uri = getURI(type, BULK);
-    Response response = postRequest(getDeleteActions(id), uri, getRefreshParam());
+    Response response = postRequest(uri, getDeleteActions(id), getRefreshParam());
     int statusCode = response.getStatusLine().getStatusCode();
     if (statusCode != HttpStatus.SC_OK) {
       throw new IOException(
@@ -172,10 +173,10 @@
   public void deleteAll() throws IOException {
     // Delete the index, if it exists.
     String endpoint = indexName + client.adapter().indicesExistParam();
-    Response response = client.get().performRequest(new Request("HEAD", endpoint));
+    Response response = performRequest("HEAD", endpoint);
     int statusCode = response.getStatusLine().getStatusCode();
     if (statusCode == HttpStatus.SC_OK) {
-      response = client.get().performRequest(new Request("DELETE", indexName));
+      response = performRequest("DELETE", indexName);
       statusCode = response.getStatusLine().getStatusCode();
       if (statusCode != HttpStatus.SC_OK) {
         throw new IOException(
@@ -185,7 +186,7 @@
 
     // Recreate the index.
     String indexCreationFields = concatJsonString(getSettings(), getMappings());
-    response = performRequest("PUT", indexCreationFields, indexName, Collections.emptyMap());
+    response = performRequest("PUT", indexName, indexCreationFields);
     statusCode = response.getStatusLine().getStatusCode();
     if (statusCode != HttpStatus.SC_OK) {
       String error = String.format("Failed to create index %s: %s", indexName, statusCode);
@@ -297,20 +298,36 @@
     return encodedIndexName + "/" + encodedType + "/" + request;
   }
 
-  protected Response postRequest(Object payload, String uri, Map<String, String> params)
+  protected Response postRequest(String uri, Object payload) throws IOException {
+    return performRequest("POST", uri, payload);
+  }
+
+  protected Response postRequest(String uri, Object payload, Map<String, String> params)
       throws IOException {
-    return performRequest("POST", payload, uri, params);
+    return performRequest("POST", uri, payload, params);
   }
 
   private String concatJsonString(String target, String addition) {
     return target.substring(0, target.length() - 1) + "," + addition.substring(1);
   }
 
+  private Response performRequest(String method, String uri) throws IOException {
+    return performRequest(method, uri, null);
+  }
+
+  private Response performRequest(String method, String uri, @Nullable Object payload)
+      throws IOException {
+    return performRequest(method, uri, payload, Collections.emptyMap());
+  }
+
   private Response performRequest(
-      String method, Object payload, String uri, Map<String, String> params) throws IOException {
-    Request request = new Request(method, uri);
-    String payloadStr = payload instanceof String ? (String) payload : payload.toString();
-    request.setEntity(new NStringEntity(payloadStr, ContentType.APPLICATION_JSON));
+      String method, String uri, @Nullable Object payload, Map<String, String> params)
+      throws IOException {
+    Request request = new Request(method, uri.startsWith("/") ? uri : "/" + uri);
+    if (payload != null) {
+      String payloadStr = payload instanceof String ? (String) payload : payload.toString();
+      request.setEntity(new NStringEntity(payloadStr, ContentType.APPLICATION_JSON));
+    }
     for (Map.Entry<String, String> entry : params.entrySet()) {
       request.addParameter(entry.getKey(), entry.getValue());
     }
diff --git a/java/com/google/gerrit/elasticsearch/BUILD b/java/com/google/gerrit/elasticsearch/BUILD
index 31ede79..8d23051 100644
--- a/java/com/google/gerrit/elasticsearch/BUILD
+++ b/java/com/google/gerrit/elasticsearch/BUILD
@@ -3,6 +3,7 @@
     srcs = glob(["**/*.java"]),
     visibility = ["//visibility:public"],
     deps = [
+        "//java/com/google/gerrit/common:annotations",
         "//java/com/google/gerrit/extensions:api",
         "//java/com/google/gerrit/index",
         "//java/com/google/gerrit/index:query_exception",
diff --git a/java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java b/java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java
index d18af42..1b69b6d 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java
@@ -79,7 +79,7 @@
             .add(new UpdateRequest<>(schema, as));
 
     String uri = getURI(type, BULK);
-    Response response = postRequest(bulk, uri, getRefreshParam());
+    Response response = postRequest(uri, bulk, getRefreshParam());
     int statusCode = response.getStatusLine().getStatusCode();
     if (statusCode != HttpStatus.SC_OK) {
       throw new IOException(
diff --git a/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java b/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
index f6af79f..d7c8b00 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
@@ -138,7 +138,7 @@
     }
 
     String uri = getURI(type, BULK);
-    Response response = postRequest(bulk, uri, getRefreshParam());
+    Response response = postRequest(uri, bulk, getRefreshParam());
     int statusCode = response.getStatusLine().getStatusCode();
     if (statusCode != HttpStatus.SC_OK) {
       throw new IOException(
diff --git a/java/com/google/gerrit/elasticsearch/ElasticGroupIndex.java b/java/com/google/gerrit/elasticsearch/ElasticGroupIndex.java
index bf6b962..f694a05 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticGroupIndex.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticGroupIndex.java
@@ -77,7 +77,7 @@
             .add(new UpdateRequest<>(schema, group));
 
     String uri = getURI(type, BULK);
-    Response response = postRequest(bulk, uri, getRefreshParam());
+    Response response = postRequest(uri, bulk, getRefreshParam());
     int statusCode = response.getStatusLine().getStatusCode();
     if (statusCode != HttpStatus.SC_OK) {
       throw new IOException(
diff --git a/java/com/google/gerrit/elasticsearch/ElasticProjectIndex.java b/java/com/google/gerrit/elasticsearch/ElasticProjectIndex.java
index 623f62c..8510559 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticProjectIndex.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticProjectIndex.java
@@ -77,7 +77,7 @@
             .add(new UpdateRequest<>(schema, projectState));
 
     String uri = getURI(type, BULK);
-    Response response = postRequest(bulk, uri, getRefreshParam());
+    Response response = postRequest(uri, bulk, getRefreshParam());
     int statusCode = response.getStatusLine().getStatusCode();
     if (statusCode != HttpStatus.SC_OK) {
       throw new IOException(
diff --git a/java/com/google/gerrit/elasticsearch/ElasticQueryAdapter.java b/java/com/google/gerrit/elasticsearch/ElasticQueryAdapter.java
index 8cb69e0..05fd7a7 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticQueryAdapter.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticQueryAdapter.java
@@ -33,7 +33,7 @@
   ElasticQueryAdapter(ElasticVersion version) {
     this.ignoreUnmapped = version == ElasticVersion.V2_4;
     this.usePostV5Type = version.isV6();
-    this.versionDiscoveryUrl = version.isV6() ? "%s*" : "%s*/_aliases";
+    this.versionDiscoveryUrl = version.isV6() ? "/%s*" : "/%s*/_aliases";
 
     switch (version) {
       case V5_6:
diff --git a/java/com/google/gerrit/elasticsearch/ElasticRestClientProvider.java b/java/com/google/gerrit/elasticsearch/ElasticRestClientProvider.java
index 337f2ca..e9839b7 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticRestClientProvider.java
+++ b/java/com/google/gerrit/elasticsearch/ElasticRestClientProvider.java
@@ -106,7 +106,7 @@
 
   private ElasticVersion getVersion() throws ElasticException {
     try {
-      Response response = client.performRequest(new Request("GET", ""));
+      Response response = client.performRequest(new Request("GET", "/"));
       StatusLine statusLine = response.getStatusLine();
       if (statusLine.getStatusCode() != HttpStatus.SC_OK) {
         throw new FailedToGetVersion(statusLine);
diff --git a/java/com/google/gerrit/extensions/api/projects/ProjectInput.java b/java/com/google/gerrit/extensions/api/projects/ProjectInput.java
index b7079ae..e61d316 100644
--- a/java/com/google/gerrit/extensions/api/projects/ProjectInput.java
+++ b/java/com/google/gerrit/extensions/api/projects/ProjectInput.java
@@ -34,6 +34,8 @@
   public InheritableBoolean requireChangeId;
   public InheritableBoolean createNewChangeForAllNotInTarget;
   public InheritableBoolean rejectEmptyCommit;
+  public InheritableBoolean enableSignedPush;
+  public InheritableBoolean requireSignedPush;
   public String maxObjectSizeLimit;
   public Map<String, Map<String, ConfigValue>> pluginConfigValues;
 }
diff --git a/java/com/google/gerrit/httpd/BUILD b/java/com/google/gerrit/httpd/BUILD
index fae7c6a..2294d0e 100644
--- a/java/com/google/gerrit/httpd/BUILD
+++ b/java/com/google/gerrit/httpd/BUILD
@@ -2,7 +2,10 @@
     name = "httpd",
     srcs = glob(["**/*.java"]),
     resource_strip_prefix = "resources",
-    resources = ["//resources/com/google/gerrit/httpd"],
+    resources = [
+        "//resources/com/google/gerrit/httpd",
+        "//resources/com/google/gerrit/httpd/raw",
+    ],
     visibility = ["//visibility:public"],
     deps = [
         "//java/com/google/gerrit/common:annotations",
@@ -18,6 +21,7 @@
         "//java/com/google/gerrit/server/ioutil",
         "//java/com/google/gerrit/server/logging",
         "//java/com/google/gerrit/server/restapi",
+        "//java/com/google/gerrit/server/util/time",
         "//java/com/google/gerrit/util/cli",
         "//java/com/google/gerrit/util/http",
         "//java/org/eclipse/jgit:server",
diff --git a/java/com/google/gerrit/httpd/HttpLogoutServlet.java b/java/com/google/gerrit/httpd/HttpLogoutServlet.java
index 39a39c6..abfcc22 100644
--- a/java/com/google/gerrit/httpd/HttpLogoutServlet.java
+++ b/java/com/google/gerrit/httpd/HttpLogoutServlet.java
@@ -16,13 +16,13 @@
 
 import com.google.common.base.Strings;
 import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.audit.AuditEvent;
 import com.google.gerrit.server.audit.AuditService;
 import com.google.gerrit.server.config.AuthConfig;
 import com.google.gerrit.server.config.CanonicalWebUrl;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
diff --git a/java/com/google/gerrit/httpd/WebSessionManager.java b/java/com/google/gerrit/httpd/WebSessionManager.java
index 457e65f..cb1e965 100644
--- a/java/com/google/gerrit/httpd/WebSessionManager.java
+++ b/java/com/google/gerrit/httpd/WebSessionManager.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.httpd;
 
-import static com.google.gerrit.common.TimeUtil.nowMs;
 import static com.google.gerrit.httpd.CacheBasedWebSession.MAX_AGE_MINUTES;
 import static com.google.gerrit.server.ioutil.BasicSerialization.readFixInt64;
 import static com.google.gerrit.server.ioutil.BasicSerialization.readString;
@@ -23,6 +22,7 @@
 import static com.google.gerrit.server.ioutil.BasicSerialization.writeFixInt64;
 import static com.google.gerrit.server.ioutil.BasicSerialization.writeString;
 import static com.google.gerrit.server.ioutil.BasicSerialization.writeVarInt32;
+import static com.google.gerrit.server.util.time.TimeUtil.nowMs;
 import static java.util.concurrent.TimeUnit.HOURS;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.concurrent.TimeUnit.MINUTES;
diff --git a/java/com/google/gerrit/httpd/raw/BazelBuild.java b/java/com/google/gerrit/httpd/raw/BazelBuild.java
index 2b390a9..430f0b5 100644
--- a/java/com/google/gerrit/httpd/raw/BazelBuild.java
+++ b/java/com/google/gerrit/httpd/raw/BazelBuild.java
@@ -22,8 +22,8 @@
 import com.google.common.flogger.FluentLogger;
 import com.google.common.html.HtmlEscapers;
 import com.google.common.io.ByteStreams;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.launcher.GerritLauncher;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.util.http.CacheHeaders;
 import java.io.IOException;
 import java.io.InputStream;
diff --git a/java/com/google/gerrit/httpd/raw/DirectoryGwtUiServlet.java b/java/com/google/gerrit/httpd/raw/DirectoryGwtUiServlet.java
index 0f3e342..8ac1601 100644
--- a/java/com/google/gerrit/httpd/raw/DirectoryGwtUiServlet.java
+++ b/java/com/google/gerrit/httpd/raw/DirectoryGwtUiServlet.java
@@ -15,7 +15,7 @@
 package com.google.gerrit.httpd.raw;
 
 import com.google.common.cache.Cache;
-import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.server.util.time.TimeUtil;
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
diff --git a/java/com/google/gerrit/httpd/raw/PolyGerritUiServlet.java b/java/com/google/gerrit/httpd/raw/PolyGerritUiServlet.java
index c508b2d..c7d23de 100644
--- a/java/com/google/gerrit/httpd/raw/PolyGerritUiServlet.java
+++ b/java/com/google/gerrit/httpd/raw/PolyGerritUiServlet.java
@@ -15,7 +15,7 @@
 package com.google.gerrit.httpd.raw;
 
 import com.google.common.cache.Cache;
-import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.server.util.time.TimeUtil;
 import java.io.IOException;
 import java.nio.file.FileSystems;
 import java.nio.file.Path;
diff --git a/java/com/google/gerrit/httpd/raw/WarDocServlet.java b/java/com/google/gerrit/httpd/raw/WarDocServlet.java
index 3f6ff25..27520e3 100644
--- a/java/com/google/gerrit/httpd/raw/WarDocServlet.java
+++ b/java/com/google/gerrit/httpd/raw/WarDocServlet.java
@@ -15,7 +15,7 @@
 package com.google.gerrit.httpd.raw;
 
 import com.google.common.cache.Cache;
-import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.server.util.time.TimeUtil;
 import java.nio.file.FileSystem;
 import java.nio.file.Path;
 import java.nio.file.attribute.FileTime;
diff --git a/java/com/google/gerrit/httpd/raw/WarGwtUiServlet.java b/java/com/google/gerrit/httpd/raw/WarGwtUiServlet.java
index ff27965..5fe7054 100644
--- a/java/com/google/gerrit/httpd/raw/WarGwtUiServlet.java
+++ b/java/com/google/gerrit/httpd/raw/WarGwtUiServlet.java
@@ -15,7 +15,7 @@
 package com.google.gerrit.httpd.raw;
 
 import com.google.common.cache.Cache;
-import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.server.util.time.TimeUtil;
 import java.nio.file.FileSystem;
 import java.nio.file.Path;
 import java.nio.file.attribute.FileTime;
diff --git a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
index e0559f1..6b5fce1 100644
--- a/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
+++ b/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
@@ -67,7 +67,6 @@
 import com.google.common.net.HttpHeaders;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.RawInputUtil;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.registration.PluginName;
@@ -116,6 +115,7 @@
 import com.google.gerrit.server.permissions.PermissionBackend;
 import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.update.UpdateException;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.util.http.CacheHeaders;
 import com.google.gerrit.util.http.RequestUtil;
 import com.google.gson.ExclusionStrategy;
@@ -445,6 +445,7 @@
         }
 
         if (notModified(req, rsrc, viewData.view)) {
+          logger.atFinest().log("REST call succeeded: %d", SC_NOT_MODIFIED);
           res.sendError(SC_NOT_MODIFIED);
           return;
         }
diff --git a/java/com/google/gerrit/httpd/rpc/GerritJsonServlet.java b/java/com/google/gerrit/httpd/rpc/GerritJsonServlet.java
index f5d2216..16e82e9 100644
--- a/java/com/google/gerrit/httpd/rpc/GerritJsonServlet.java
+++ b/java/com/google/gerrit/httpd/rpc/GerritJsonServlet.java
@@ -17,7 +17,6 @@
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.MultimapBuilder;
 import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.audit.Audit;
 import com.google.gerrit.common.auth.SignInRequired;
 import com.google.gerrit.common.errors.NotSignedInException;
@@ -27,6 +26,7 @@
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.audit.AuditService;
 import com.google.gerrit.server.audit.RpcAuditEvent;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gson.GsonBuilder;
 import com.google.gwtjsonrpc.common.RemoteJsonService;
 import com.google.gwtjsonrpc.server.ActiveCall;
diff --git a/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java b/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java
index aee0330..8b2090d 100644
--- a/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java
+++ b/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java
@@ -16,7 +16,6 @@
 
 import com.google.common.base.Throwables;
 import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.data.AccessSection;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.common.data.PermissionRule;
@@ -49,6 +48,7 @@
 import com.google.gerrit.server.restapi.project.SetParent;
 import com.google.gerrit.server.update.BatchUpdate;
 import com.google.gerrit.server.update.UpdateException;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
diff --git a/java/com/google/gerrit/index/project/ProjectSchemaDefinitions.java b/java/com/google/gerrit/index/project/ProjectSchemaDefinitions.java
index 07b5adb..6229041 100644
--- a/java/com/google/gerrit/index/project/ProjectSchemaDefinitions.java
+++ b/java/com/google/gerrit/index/project/ProjectSchemaDefinitions.java
@@ -30,8 +30,12 @@
           ProjectField.NAME_PART,
           ProjectField.ANCESTOR_NAME);
 
+  @Deprecated
   static final Schema<ProjectData> V2 = schema(V1, ProjectField.STATE, ProjectField.REF_STATE);
 
+  // Bump Lucene version requires reindexing
+  static final Schema<ProjectData> V3 = schema(V2);
+
   public static final ProjectSchemaDefinitions INSTANCE = new ProjectSchemaDefinitions();
 
   private ProjectSchemaDefinitions() {
diff --git a/java/com/google/gerrit/lucene/AbstractLuceneIndex.java b/java/com/google/gerrit/lucene/AbstractLuceneIndex.java
index dc293cd..40acf80 100644
--- a/java/com/google/gerrit/lucene/AbstractLuceneIndex.java
+++ b/java/com/google/gerrit/lucene/AbstractLuceneIndex.java
@@ -64,15 +64,14 @@
 import org.apache.lucene.document.Document;
 import org.apache.lucene.document.Field;
 import org.apache.lucene.document.Field.Store;
-import org.apache.lucene.document.IntField;
-import org.apache.lucene.document.LongField;
+import org.apache.lucene.document.LegacyIntField;
+import org.apache.lucene.document.LegacyLongField;
 import org.apache.lucene.document.StoredField;
 import org.apache.lucene.document.StringField;
 import org.apache.lucene.document.TextField;
 import org.apache.lucene.index.IndexWriter;
 import org.apache.lucene.index.IndexableField;
 import org.apache.lucene.index.Term;
-import org.apache.lucene.index.TrackingIndexWriter;
 import org.apache.lucene.search.ControlledRealTimeReopenThread;
 import org.apache.lucene.search.IndexSearcher;
 import org.apache.lucene.search.Query;
@@ -86,6 +85,7 @@
 import org.apache.lucene.store.Directory;
 
 /** Basic Lucene index implementation. */
+@SuppressWarnings("deprecation")
 public abstract class AbstractLuceneIndex<K, V> implements Index<K, V> {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
@@ -98,7 +98,7 @@
   private final Directory dir;
   private final String name;
   private final ListeningExecutorService writerThread;
-  private final TrackingIndexWriter writer;
+  private final IndexWriter writer;
   private final ReferenceManager<IndexSearcher> searcherManager;
   private final ControlledRealTimeReopenThread<IndexSearcher> reopenThread;
   private final Set<NrtFuture> notDoneNrtFutures;
@@ -118,17 +118,16 @@
     this.dir = dir;
     this.name = name;
     String index = Joiner.on('_').skipNulls().join(name, subIndex);
-    IndexWriter delegateWriter;
     long commitPeriod = writerConfig.getCommitWithinMs();
 
     if (commitPeriod < 0) {
-      delegateWriter = new AutoCommitWriter(dir, writerConfig.getLuceneConfig());
+      writer = new AutoCommitWriter(dir, writerConfig.getLuceneConfig());
     } else if (commitPeriod == 0) {
-      delegateWriter = new AutoCommitWriter(dir, writerConfig.getLuceneConfig(), true);
+      writer = new AutoCommitWriter(dir, writerConfig.getLuceneConfig(), true);
     } else {
       final AutoCommitWriter autoCommitWriter =
           new AutoCommitWriter(dir, writerConfig.getLuceneConfig());
-      delegateWriter = autoCommitWriter;
+      writer = autoCommitWriter;
 
       autoCommitExecutor =
           new LoggingContextAwareScheduledExecutorService(
@@ -165,8 +164,7 @@
               commitPeriod,
               MILLISECONDS);
     }
-    writer = new TrackingIndexWriter(delegateWriter);
-    searcherManager = new WrappableSearcherManager(writer.getIndexWriter(), true, searcherFactory);
+    searcherManager = new WrappableSearcherManager(writer, true, searcherFactory);
 
     notDoneNrtFutures = Sets.newConcurrentHashSet();
 
@@ -251,7 +249,7 @@
     }
 
     try {
-      writer.getIndexWriter().close();
+      writer.close();
     } catch (AlreadyClosedException e) {
       // Ignore.
     } catch (IOException e) {
@@ -294,7 +292,7 @@
     writer.deleteAll();
   }
 
-  public TrackingIndexWriter getWriter() {
+  public IndexWriter getWriter() {
     return writer;
   }
 
@@ -325,15 +323,15 @@
 
     if (type == FieldType.INTEGER || type == FieldType.INTEGER_RANGE) {
       for (Object value : values.getValues()) {
-        doc.add(new IntField(name, (Integer) value, store));
+        doc.add(new LegacyIntField(name, (Integer) value, store));
       }
     } else if (type == FieldType.LONG) {
       for (Object value : values.getValues()) {
-        doc.add(new LongField(name, (Long) value, store));
+        doc.add(new LegacyLongField(name, (Long) value, store));
       }
     } else if (type == FieldType.TIMESTAMP) {
       for (Object value : values.getValues()) {
-        doc.add(new LongField(name, ((Timestamp) value).getTime(), store));
+        doc.add(new LegacyLongField(name, ((Timestamp) value).getTime(), store));
       }
     } else if (type == FieldType.EXACT || type == FieldType.PREFIX) {
       for (Object value : values.getValues()) {
diff --git a/java/com/google/gerrit/lucene/AutoCommitWriter.java b/java/com/google/gerrit/lucene/AutoCommitWriter.java
index 7a418aa..2cc7563 100644
--- a/java/com/google/gerrit/lucene/AutoCommitWriter.java
+++ b/java/com/google/gerrit/lucene/AutoCommitWriter.java
@@ -47,58 +47,64 @@
   }
 
   @Override
-  public void addDocument(Iterable<? extends IndexableField> doc) throws IOException {
-    super.addDocument(doc);
+  public long addDocument(Iterable<? extends IndexableField> doc) throws IOException {
+    long ret = super.addDocument(doc);
     autoFlush();
+    return ret;
   }
 
   @Override
-  public void addDocuments(Iterable<? extends Iterable<? extends IndexableField>> docs)
+  public long addDocuments(Iterable<? extends Iterable<? extends IndexableField>> docs)
       throws IOException {
-    super.addDocuments(docs);
+    long ret = super.addDocuments(docs);
     autoFlush();
+    return ret;
   }
 
   @Override
-  public void updateDocuments(
+  public long updateDocuments(
       Term delTerm, Iterable<? extends Iterable<? extends IndexableField>> docs)
       throws IOException {
-    super.updateDocuments(delTerm, docs);
+    long ret = super.updateDocuments(delTerm, docs);
     autoFlush();
+    return ret;
   }
 
   @Override
-  public void deleteDocuments(Term... term) throws IOException {
-    super.deleteDocuments(term);
+  public long deleteDocuments(Term... term) throws IOException {
+    long ret = super.deleteDocuments(term);
     autoFlush();
+    return ret;
   }
 
   @Override
-  public synchronized boolean tryDeleteDocument(IndexReader readerIn, int docID)
-      throws IOException {
-    boolean ret = super.tryDeleteDocument(readerIn, docID);
-    if (ret) {
+  public synchronized long tryDeleteDocument(IndexReader readerIn, int docID) throws IOException {
+    long ret = super.tryDeleteDocument(readerIn, docID);
+    if (ret != -1) {
       autoFlush();
     }
     return ret;
   }
 
   @Override
-  public void deleteDocuments(Query... queries) throws IOException {
-    super.deleteDocuments(queries);
+  public long deleteDocuments(Query... queries) throws IOException {
+    long ret = super.deleteDocuments(queries);
     autoFlush();
+    return ret;
   }
 
   @Override
-  public void updateDocument(Term term, Iterable<? extends IndexableField> doc) throws IOException {
-    super.updateDocument(term, doc);
+  public long updateDocument(Term term, Iterable<? extends IndexableField> doc) throws IOException {
+    long ret = super.updateDocument(term, doc);
     autoFlush();
+    return ret;
   }
 
   @Override
-  public void deleteAll() throws IOException {
-    super.deleteAll();
+  public long deleteAll() throws IOException {
+    long ret = super.deleteAll();
     autoFlush();
+    return ret;
   }
 
   void manualFlush() throws IOException {
diff --git a/java/com/google/gerrit/lucene/GerritIndexWriterConfig.java b/java/com/google/gerrit/lucene/GerritIndexWriterConfig.java
index ada3220..75e03e3 100644
--- a/java/com/google/gerrit/lucene/GerritIndexWriterConfig.java
+++ b/java/com/google/gerrit/lucene/GerritIndexWriterConfig.java
@@ -19,8 +19,8 @@
 
 import com.google.common.collect.ImmutableMap;
 import com.google.gerrit.server.config.ConfigUtil;
+import org.apache.lucene.analysis.CharArraySet;
 import org.apache.lucene.analysis.standard.StandardAnalyzer;
-import org.apache.lucene.analysis.util.CharArraySet;
 import org.apache.lucene.index.IndexWriterConfig;
 import org.apache.lucene.index.IndexWriterConfig.OpenMode;
 import org.eclipse.jgit.lib.Config;
diff --git a/java/com/google/gerrit/lucene/QueryBuilder.java b/java/com/google/gerrit/lucene/QueryBuilder.java
index 6aab7c7..ce5ba98 100644
--- a/java/com/google/gerrit/lucene/QueryBuilder.java
+++ b/java/com/google/gerrit/lucene/QueryBuilder.java
@@ -38,19 +38,20 @@
 import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.index.Term;
 import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.LegacyNumericRangeQuery;
 import org.apache.lucene.search.MatchAllDocsQuery;
-import org.apache.lucene.search.NumericRangeQuery;
 import org.apache.lucene.search.PrefixQuery;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.RegexpQuery;
 import org.apache.lucene.search.TermQuery;
 import org.apache.lucene.util.BytesRefBuilder;
-import org.apache.lucene.util.NumericUtils;
+import org.apache.lucene.util.LegacyNumericUtils;
 
+@SuppressWarnings("deprecation")
 public class QueryBuilder<V> {
   static Term intTerm(String name, int value) {
     BytesRefBuilder builder = new BytesRefBuilder();
-    NumericUtils.intToPrefixCoded(value, 0, builder);
+    LegacyNumericUtils.intToPrefixCoded(value, 0, builder);
     return new Term(name, builder.get());
   }
 
@@ -180,7 +181,8 @@
         // Just fall back to a standard integer query.
         return new TermQuery(intTerm(p.getField().getName(), minimum));
       }
-      return NumericRangeQuery.newIntRange(r.getField().getName(), minimum, maximum, true, true);
+      return LegacyNumericRangeQuery.newIntRange(
+          r.getField().getName(), minimum, maximum, true, true);
     }
     throw new QueryParseException("not an integer range: " + p);
   }
@@ -188,7 +190,7 @@
   private Query timestampQuery(IndexPredicate<V> p) throws QueryParseException {
     if (p instanceof TimestampRangePredicate) {
       TimestampRangePredicate<V> r = (TimestampRangePredicate<V>) p;
-      return NumericRangeQuery.newLongRange(
+      return LegacyNumericRangeQuery.newLongRange(
           r.getField().getName(),
           r.getMinTimestamp().getTime(),
           r.getMaxTimestamp().getTime(),
@@ -200,7 +202,7 @@
 
   private Query notTimestamp(TimestampRangePredicate<V> r) throws QueryParseException {
     if (r.getMinTimestamp().getTime() == 0) {
-      return NumericRangeQuery.newLongRange(
+      return LegacyNumericRangeQuery.newLongRange(
           r.getField().getName(), r.getMaxTimestamp().getTime(), null, true, true);
     }
     throw new QueryParseException("cannot negate: " + r);
diff --git a/java/com/google/gerrit/lucene/WrappableSearcherManager.java b/java/com/google/gerrit/lucene/WrappableSearcherManager.java
index f9ecac3..ba8d7da 100644
--- a/java/com/google/gerrit/lucene/WrappableSearcherManager.java
+++ b/java/com/google/gerrit/lucene/WrappableSearcherManager.java
@@ -81,11 +81,17 @@
   WrappableSearcherManager(
       IndexWriter writer, boolean applyAllDeletes, SearcherFactory searcherFactory)
       throws IOException {
+    // TODO(davido): Make it configurable
+    // If true, new deletes will be written down to index files instead of carried over from writer
+    // to reader directly in heap
+    boolean writeAllDeletes = false;
     if (searcherFactory == null) {
       searcherFactory = new SearcherFactory();
     }
     this.searcherFactory = searcherFactory;
-    current = getSearcher(searcherFactory, DirectoryReader.open(writer, applyAllDeletes));
+    current =
+        getSearcher(
+            searcherFactory, DirectoryReader.open(writer, applyAllDeletes, writeAllDeletes));
   }
 
   /**
diff --git a/java/com/google/gerrit/pgm/BUILD b/java/com/google/gerrit/pgm/BUILD
index ba27991..b34aec0 100644
--- a/java/com/google/gerrit/pgm/BUILD
+++ b/java/com/google/gerrit/pgm/BUILD
@@ -43,6 +43,7 @@
         "//java/com/google/gerrit/server/ioutil",
         "//java/com/google/gerrit/server/restapi",
         "//java/com/google/gerrit/server/schema",
+        "//java/com/google/gerrit/server/util/time",
         "//java/com/google/gerrit/sshd",
         "//java/com/google/gerrit/util/http",
         "//lib:args4j",
diff --git a/java/com/google/gerrit/pgm/http/jetty/BUILD b/java/com/google/gerrit/pgm/http/jetty/BUILD
index b1da011..a6a13dc 100644
--- a/java/com/google/gerrit/pgm/http/jetty/BUILD
+++ b/java/com/google/gerrit/pgm/http/jetty/BUILD
@@ -9,6 +9,7 @@
         "//java/com/google/gerrit/launcher",
         "//java/com/google/gerrit/lifecycle",
         "//java/com/google/gerrit/server",
+        "//java/com/google/gerrit/server/util/time",
         "//java/com/google/gerrit/sshd",
         "//java/com/google/gerrit/util/http",
         "//lib:guava",
diff --git a/java/com/google/gerrit/pgm/http/jetty/HttpLog.java b/java/com/google/gerrit/pgm/http/jetty/HttpLog.java
index d7bc720..1f639a7 100644
--- a/java/com/google/gerrit/pgm/http/jetty/HttpLog.java
+++ b/java/com/google/gerrit/pgm/http/jetty/HttpLog.java
@@ -20,10 +20,10 @@
 import com.google.common.base.Splitter;
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableSet;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.restapi.Url;
 import com.google.gerrit.httpd.GetUserFilter;
 import com.google.gerrit.server.util.SystemLog;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.inject.Inject;
 import java.util.Iterator;
 import org.apache.log4j.AsyncAppender;
diff --git a/java/com/google/gerrit/pgm/init/BUILD b/java/com/google/gerrit/pgm/init/BUILD
index 8a1c08b..7e04e5a 100644
--- a/java/com/google/gerrit/pgm/init/BUILD
+++ b/java/com/google/gerrit/pgm/init/BUILD
@@ -19,6 +19,7 @@
         "//java/com/google/gerrit/server",
         "//java/com/google/gerrit/server/ioutil",
         "//java/com/google/gerrit/server/schema",
+        "//java/com/google/gerrit/server/util/time",
         "//lib:guava",
         "//lib:gwtjsonrpc",
         "//lib:gwtorm",
diff --git a/java/com/google/gerrit/pgm/init/InitAdminUser.java b/java/com/google/gerrit/pgm/init/InitAdminUser.java
index d0aed46..f12fa50 100644
--- a/java/com/google/gerrit/pgm/init/InitAdminUser.java
+++ b/java/com/google/gerrit/pgm/init/InitAdminUser.java
@@ -17,7 +17,6 @@
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 import com.google.common.base.Strings;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.data.GroupReference;
 import com.google.gerrit.common.errors.NoSuchGroupException;
 import com.google.gerrit.extensions.client.AuthType;
@@ -37,6 +36,7 @@
 import com.google.gerrit.server.index.account.AccountIndexCollection;
 import com.google.gerrit.server.index.group.GroupIndex;
 import com.google.gerrit.server.index.group.GroupIndexCollection;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gwtorm.server.SchemaFactory;
 import com.google.inject.Inject;
 import java.io.IOException;
diff --git a/java/com/google/gerrit/pgm/rules/PrologCompiler.java b/java/com/google/gerrit/pgm/rules/PrologCompiler.java
index 4ad7701..2663f42 100644
--- a/java/com/google/gerrit/pgm/rules/PrologCompiler.java
+++ b/java/com/google/gerrit/pgm/rules/PrologCompiler.java
@@ -14,11 +14,11 @@
 
 package com.google.gerrit.pgm.rules;
 
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.Version;
 import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 import com.googlecode.prolog_cafe.compiler.Compiler;
diff --git a/java/com/google/gerrit/server/BUILD b/java/com/google/gerrit/server/BUILD
index e5bc480..0d48bca 100644
--- a/java/com/google/gerrit/server/BUILD
+++ b/java/com/google/gerrit/server/BUILD
@@ -44,6 +44,7 @@
         "//java/com/google/gerrit/server/ioutil",
         "//java/com/google/gerrit/server/logging",
         "//java/com/google/gerrit/server/util/git",
+        "//java/com/google/gerrit/server/util/time",
         "//java/com/google/gerrit/util/cli",
         "//java/com/google/gerrit/util/ssl",
         "//java/org/apache/commons/net",
diff --git a/java/com/google/gerrit/server/account/AccountCacheImpl.java b/java/com/google/gerrit/server/account/AccountCacheImpl.java
index 2cde94c..b6ae216 100644
--- a/java/com/google/gerrit/server/account/AccountCacheImpl.java
+++ b/java/com/google/gerrit/server/account/AccountCacheImpl.java
@@ -21,13 +21,13 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.FanOutExecutor;
 import com.google.gerrit.server.account.externalids.ExternalId;
 import com.google.gerrit.server.account.externalids.ExternalIds;
 import com.google.gerrit.server.cache.CacheModule;
 import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.inject.Inject;
 import com.google.inject.Module;
 import com.google.inject.Singleton;
diff --git a/java/com/google/gerrit/server/account/AccountConfig.java b/java/com/google/gerrit/server/account/AccountConfig.java
index 3bdc71a..5cd18e3 100644
--- a/java/com/google/gerrit/server/account/AccountConfig.java
+++ b/java/com/google/gerrit/server/account/AccountConfig.java
@@ -21,7 +21,6 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.client.DiffPreferencesInfo;
 import com.google.gerrit.extensions.client.EditPreferencesInfo;
 import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
@@ -34,6 +33,7 @@
 import com.google.gerrit.server.git.ValidationError;
 import com.google.gerrit.server.git.meta.MetaDataUpdate;
 import com.google.gerrit.server.git.meta.VersionedMetaData;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gwtorm.server.OrmDuplicateKeyException;
 import java.io.IOException;
 import java.sql.Timestamp;
diff --git a/java/com/google/gerrit/server/audit/AuditEvent.java b/java/com/google/gerrit/server/audit/AuditEvent.java
index 4abdfd9..ff4f72b 100644
--- a/java/com/google/gerrit/server/audit/AuditEvent.java
+++ b/java/com/google/gerrit/server/audit/AuditEvent.java
@@ -19,8 +19,8 @@
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableListMultimap;
 import com.google.common.collect.ListMultimap;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.util.time.TimeUtil;
 
 public class AuditEvent {
 
diff --git a/java/com/google/gerrit/server/audit/BUILD b/java/com/google/gerrit/server/audit/BUILD
index d85668e..6982766 100644
--- a/java/com/google/gerrit/server/audit/BUILD
+++ b/java/com/google/gerrit/server/audit/BUILD
@@ -7,8 +7,6 @@
     resources = ["//resources/com/google/gerrit/server"],
     visibility = ["//visibility:public"],
     deps = [
-        "//java/com/google/gerrit/common:annotations",
-        "//java/com/google/gerrit/common:server",
         "//java/com/google/gerrit/extensions:api",
         "//java/com/google/gerrit/index",
         "//java/com/google/gerrit/index:query_exception",
@@ -19,6 +17,7 @@
         "//java/com/google/gerrit/reviewdb:server",
         "//java/com/google/gerrit/server",
         "//java/com/google/gerrit/server/ioutil",
+        "//java/com/google/gerrit/server/util/time",
         "//java/com/google/gerrit/util/cli",
         "//java/com/google/gerrit/util/ssl",
         "//java/org/apache/commons/net",
diff --git a/java/com/google/gerrit/server/cache/h2/BUILD b/java/com/google/gerrit/server/cache/h2/BUILD
index f6418e3..f85b498 100644
--- a/java/com/google/gerrit/server/cache/h2/BUILD
+++ b/java/com/google/gerrit/server/cache/h2/BUILD
@@ -10,6 +10,7 @@
         "//java/com/google/gerrit/server",
         "//java/com/google/gerrit/server/cache/serialize",
         "//java/com/google/gerrit/server/logging",
+        "//java/com/google/gerrit/server/util/time",
         "//lib:guava",
         "//lib:h2",
         "//lib/flogger:api",
diff --git a/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java b/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java
index df1c7eb..a538897 100644
--- a/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java
+++ b/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java
@@ -24,9 +24,9 @@
 import com.google.common.flogger.FluentLogger;
 import com.google.common.hash.BloomFilter;
 import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.server.cache.PersistentCache;
 import com.google.gerrit.server.cache.serialize.CacheSerializer;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.inject.TypeLiteral;
 import java.io.IOException;
 import java.io.InvalidClassException;
diff --git a/java/com/google/gerrit/server/change/BatchAbandon.java b/java/com/google/gerrit/server/change/BatchAbandon.java
index 0ecfcb0..a8e2407 100644
--- a/java/com/google/gerrit/server/change/BatchAbandon.java
+++ b/java/com/google/gerrit/server/change/BatchAbandon.java
@@ -16,7 +16,6 @@
 
 import com.google.common.collect.ImmutableListMultimap;
 import com.google.common.collect.ListMultimap;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.api.changes.NotifyHandling;
 import com.google.gerrit.extensions.api.changes.RecipientType;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -29,6 +28,7 @@
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.update.BatchUpdate;
 import com.google.gerrit.server.update.UpdateException;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
diff --git a/java/com/google/gerrit/server/change/ConsistencyChecker.java b/java/com/google/gerrit/server/change/ConsistencyChecker.java
index 41757e6..0bda066 100644
--- a/java/com/google/gerrit/server/change/ConsistencyChecker.java
+++ b/java/com/google/gerrit/server/change/ConsistencyChecker.java
@@ -29,7 +29,6 @@
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.common.FooterConstants;
 import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.api.changes.FixInput;
 import com.google.gerrit.extensions.api.changes.NotifyHandling;
 import com.google.gerrit.extensions.common.ProblemInfo;
@@ -58,6 +57,7 @@
 import com.google.gerrit.server.update.RepoContext;
 import com.google.gerrit.server.update.RetryHelper;
 import com.google.gerrit.server.update.UpdateException;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
diff --git a/java/com/google/gerrit/server/change/FileContentUtil.java b/java/com/google/gerrit/server/change/FileContentUtil.java
index 00b7a88..a806f94 100644
--- a/java/com/google/gerrit/server/change/FileContentUtil.java
+++ b/java/com/google/gerrit/server/change/FileContentUtil.java
@@ -20,7 +20,6 @@
 import com.google.common.hash.Hasher;
 import com.google.common.hash.Hashing;
 import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.data.PatchScript.FileMode;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.BinaryResult;
@@ -29,6 +28,7 @@
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.mime.FileTypeRegistry;
 import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import eu.medsea.mimeutil.MimeType;
diff --git a/java/com/google/gerrit/server/config/ConfigUpdatedEvent.java b/java/com/google/gerrit/server/config/ConfigUpdatedEvent.java
index 9bd4533..66d6555 100644
--- a/java/com/google/gerrit/server/config/ConfigUpdatedEvent.java
+++ b/java/com/google/gerrit/server/config/ConfigUpdatedEvent.java
@@ -66,6 +66,10 @@
     return createUpdate(entries, UpdateResult.APPLIED);
   }
 
+  public Update reject(ConfigKey entry) {
+    return reject(Collections.singleton(entry));
+  }
+
   public Update reject(Set<ConfigKey> entries) {
     return createUpdate(entries, UpdateResult.REJECTED);
   }
diff --git a/java/com/google/gerrit/server/documentation/QueryDocumentationExecutor.java b/java/com/google/gerrit/server/documentation/QueryDocumentationExecutor.java
index c606919..66cb380 100644
--- a/java/com/google/gerrit/server/documentation/QueryDocumentationExecutor.java
+++ b/java/com/google/gerrit/server/documentation/QueryDocumentationExecutor.java
@@ -15,12 +15,12 @@
 package com.google.gerrit.server.documentation;
 
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Lists;
 import com.google.common.flogger.FluentLogger;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.zip.ZipEntry;
@@ -85,9 +85,9 @@
       // and skipped paging. Maybe add paging later.
       TopDocs results = searcher.search(query, Integer.MAX_VALUE);
       ScoreDoc[] hits = results.scoreDocs;
-      int totalHits = results.totalHits;
+      long totalHits = results.totalHits;
 
-      List<DocResult> out = Lists.newArrayListWithCapacity(totalHits);
+      List<DocResult> out = new ArrayList<>();
       for (int i = 0; i < totalHits; i++) {
         DocResult result = new DocResult();
         Document doc = searcher.doc(hits[i].doc);
diff --git a/java/com/google/gerrit/server/edit/ChangeEditModifier.java b/java/com/google/gerrit/server/edit/ChangeEditModifier.java
index a8c463d..ce359a9 100644
--- a/java/com/google/gerrit/server/edit/ChangeEditModifier.java
+++ b/java/com/google/gerrit/server/edit/ChangeEditModifier.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.server.edit;
 
 import com.google.common.collect.ImmutableList;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.MergeConflictException;
@@ -43,6 +42,7 @@
 import com.google.gerrit.server.project.InvalidChangeOperationException;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.util.CommitMessageUtil;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
diff --git a/java/com/google/gerrit/server/edit/ChangeEditUtil.java b/java/com/google/gerrit/server/edit/ChangeEditUtil.java
index 24ee881..d5add76 100644
--- a/java/com/google/gerrit/server/edit/ChangeEditUtil.java
+++ b/java/com/google/gerrit/server/edit/ChangeEditUtil.java
@@ -17,7 +17,6 @@
 import static com.google.common.base.Preconditions.checkArgument;
 
 import com.google.common.collect.ListMultimap;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.api.changes.NotifyHandling;
 import com.google.gerrit.extensions.api.changes.RecipientType;
 import com.google.gerrit.extensions.client.ChangeKind;
@@ -42,6 +41,7 @@
 import com.google.gerrit.server.update.BatchUpdateOp;
 import com.google.gerrit.server.update.RepoContext;
 import com.google.gerrit.server.update.UpdateException;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
diff --git a/java/com/google/gerrit/server/events/Event.java b/java/com/google/gerrit/server/events/Event.java
index 20fbe2f..c07987a 100644
--- a/java/com/google/gerrit/server/events/Event.java
+++ b/java/com/google/gerrit/server/events/Event.java
@@ -14,7 +14,7 @@
 
 package com.google.gerrit.server.events;
 
-import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.server.util.time.TimeUtil;
 
 public abstract class Event {
   public final String type;
diff --git a/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java b/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java
index eb62d54..b9ab6fd 100644
--- a/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java
@@ -110,14 +110,9 @@
     final MultiProgressMonitor progress;
 
     private final Collection<ReceiveCommand> commands;
-    private final ReceiveCommits receiveCommits;
 
     private Worker(Collection<ReceiveCommand> commands) {
       this.commands = commands;
-      receiveCommits =
-          factory.create(
-              projectState, user, receivePack, allRefsWatcher, extraReviewers, messageSender);
-      receiveCommits.init();
       progress = new MultiProgressMonitor(new MessageSenderOutputStream(), "Processing changes");
     }
 
@@ -173,7 +168,7 @@
     }
   }
 
-  private final ReceiveCommits.Factory factory;
+  private final ReceiveCommits receiveCommits;
   private final PermissionBackend.ForProject perm;
   private final ReceivePack receivePack;
   private final ExecutorService executor;
@@ -184,8 +179,6 @@
   private final ProjectState projectState;
   private final IdentifiedUser user;
   private final Repository repo;
-  private final MessageSender messageSender;
-  private final SetMultimap<ReviewerStateInternal, Account.Id> extraReviewers;
   private final AllRefsWatcher allRefsWatcher;
 
   @Inject
@@ -206,7 +199,6 @@
       @Assisted @Nullable MessageSender messageSender,
       @Assisted SetMultimap<ReviewerStateInternal, Account.Id> extraReviewers)
       throws PermissionBackendException {
-    this.factory = factory;
     this.executor = executor;
     this.scopePropagator = scopePropagator;
     this.receiveConfig = receiveConfig;
@@ -215,8 +207,6 @@
     this.projectState = projectState;
     this.user = user;
     this.repo = repo;
-    this.messageSender = messageSender;
-    this.extraReviewers = extraReviewers;
 
     Project.NameKey projectName = projectState.getNameKey();
     receivePack = new ReceivePack(repo);
@@ -251,6 +241,11 @@
     advHooks.add(new ReceiveCommitsAdvertiseRefsHook(queryProvider, projectName));
     advHooks.add(new HackPushNegotiateHook());
     receivePack.setAdvertiseRefsHook(AdvertiseRefsHookChain.newChain(advHooks));
+
+    receiveCommits =
+        factory.create(
+            projectState, user, receivePack, allRefsWatcher, extraReviewers, messageSender);
+    receiveCommits.init();
   }
 
   /** Determine if the user can upload commits. */
@@ -275,6 +270,11 @@
 
   @Override
   public void onPreReceive(ReceivePack rp, Collection<ReceiveCommand> commands) {
+    if (commands.stream().anyMatch(c -> c.getResult() != Result.NOT_ATTEMPTED)) {
+      // Stop processing when command was already processed by previously invoked
+      // pre-receive hooks
+      return;
+    }
     Worker w = new Worker(commands);
     try {
       w.progress.waitFor(
diff --git a/java/com/google/gerrit/server/git/receive/BUILD b/java/com/google/gerrit/server/git/receive/BUILD
index f1c604b..0724215 100644
--- a/java/com/google/gerrit/server/git/receive/BUILD
+++ b/java/com/google/gerrit/server/git/receive/BUILD
@@ -9,6 +9,7 @@
         "//java/com/google/gerrit/reviewdb:server",
         "//java/com/google/gerrit/server",
         "//java/com/google/gerrit/server/logging",
+        "//java/com/google/gerrit/server/util/time",
         "//java/com/google/gerrit/util/cli",
         "//lib:args4j",
         "//lib:guava",
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
index 4b475f9..7566b55 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
@@ -60,7 +60,6 @@
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.common.FooterConstants;
 import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.common.data.LabelTypes;
 import com.google.gerrit.extensions.api.changes.HashtagsInput;
@@ -155,6 +154,7 @@
 import com.google.gerrit.server.util.LabelVote;
 import com.google.gerrit.server.util.MagicBranch;
 import com.google.gerrit.server.util.RequestScopePropagator;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.util.cli.CmdLineParser;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -1325,6 +1325,7 @@
   static class MagicBranchInput {
     private static final Splitter COMMAS = Splitter.on(',').omitEmptyStrings();
 
+    boolean deprecatedTopicSeen;
     final ReceiveCommand cmd;
     final LabelTypes labelTypes;
     final NotesMigration notesMigration;
@@ -1482,6 +1483,7 @@
         ReceiveCommand cmd,
         LabelTypes labelTypes,
         NotesMigration notesMigration) {
+      this.deprecatedTopicSeen = false;
       this.cmd = cmd;
       this.draft = cmd.getRefName().startsWith(MagicBranch.NEW_DRAFT_CHANGE);
       this.publish = cmd.getRefName().startsWith(MagicBranch.NEW_PUBLISH_CHANGE);
@@ -1545,8 +1547,7 @@
 
       // We accept refs/for/BRANCHNAME/TOPIC. Since we don't know
       // for sure where the branch ends and the topic starts, look
-      // backward for a split that works. This behavior has not been
-      // documented and should probably be deprecated.
+      // backward for a split that works. This behavior is deprecated.
       String head = readHEAD(repo);
       int split = ref.length();
       for (; ; ) {
@@ -1562,6 +1563,7 @@
       }
       if (split < ref.length()) {
         topic = Strings.emptyToNull(ref.substring(split + 1));
+        deprecatedTopicSeen = true;
       }
       return ref.substring(0, split);
     }
@@ -1767,6 +1769,13 @@
       return;
     }
 
+    if (magicBranch.deprecatedTopicSeen) {
+      messages.add(
+          new ValidationMessage(
+              "WARNING: deprecated topic syntax. Use %topic=TOPIC instead", false));
+      logger.atInfo().log("deprecated topic push seen for project %s", project.getName());
+    }
+
     if (validateConnected(magicBranch.cmd, magicBranch.dest, tip)) {
       this.magicBranch = magicBranch;
     }
diff --git a/java/com/google/gerrit/server/group/db/GroupConfig.java b/java/com/google/gerrit/server/group/db/GroupConfig.java
index 2fae4cc..193a67f 100644
--- a/java/com/google/gerrit/server/group/db/GroupConfig.java
+++ b/java/com/google/gerrit/server/group/db/GroupConfig.java
@@ -24,7 +24,6 @@
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Streams;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
@@ -32,6 +31,7 @@
 import com.google.gerrit.server.git.meta.MetaDataUpdate;
 import com.google.gerrit.server.git.meta.VersionedMetaData;
 import com.google.gerrit.server.group.InternalGroup;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gwtorm.server.OrmDuplicateKeyException;
 import java.io.IOException;
 import java.sql.Timestamp;
diff --git a/java/com/google/gerrit/server/group/db/GroupsUpdate.java b/java/com/google/gerrit/server/group/db/GroupsUpdate.java
index 314825b..6477f31 100644
--- a/java/com/google/gerrit/server/group/db/GroupsUpdate.java
+++ b/java/com/google/gerrit/server/group/db/GroupsUpdate.java
@@ -20,7 +20,6 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.errors.NoSuchGroupException;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
@@ -42,6 +41,7 @@
 import com.google.gerrit.server.index.group.GroupIndexer;
 import com.google.gerrit.server.update.RefUpdateUtil;
 import com.google.gerrit.server.update.RetryHelper;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gwtorm.server.OrmDuplicateKeyException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
diff --git a/java/com/google/gerrit/server/index/account/AccountSchemaDefinitions.java b/java/com/google/gerrit/server/index/account/AccountSchemaDefinitions.java
index 3e702f2..6b7fe62 100644
--- a/java/com/google/gerrit/server/index/account/AccountSchemaDefinitions.java
+++ b/java/com/google/gerrit/server/index/account/AccountSchemaDefinitions.java
@@ -42,8 +42,12 @@
 
   @Deprecated static final Schema<AccountState> V7 = schema(V6, AccountField.PREFERRED_EMAIL_EXACT);
 
+  @Deprecated
   static final Schema<AccountState> V8 = schema(V7, AccountField.NAME_PART_NO_SECONDARY_EMAIL);
 
+  // Bump Lucene version requires reindexing
+  static final Schema<AccountState> V9 = schema(V8);
+
   public static final String NAME = "accounts";
   public static final AccountSchemaDefinitions INSTANCE = new AccountSchemaDefinitions();
 
diff --git a/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java b/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
index 5e7e4dd..2000cd1 100644
--- a/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
+++ b/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
@@ -96,7 +96,10 @@
   // Rename of star label 'mute' to 'reviewed' requires reindexing
   @Deprecated static final Schema<ChangeData> V48 = schema(V47);
 
-  static final Schema<ChangeData> V49 = schema(V48);
+  @Deprecated static final Schema<ChangeData> V49 = schema(V48);
+
+  // Bump Lucene version requires reindexing
+  static final Schema<ChangeData> V50 = schema(V49);
 
   public static final String NAME = "changes";
   public static final ChangeSchemaDefinitions INSTANCE = new ChangeSchemaDefinitions();
diff --git a/java/com/google/gerrit/server/index/group/GroupSchemaDefinitions.java b/java/com/google/gerrit/server/index/group/GroupSchemaDefinitions.java
index 912524f..c175434 100644
--- a/java/com/google/gerrit/server/index/group/GroupSchemaDefinitions.java
+++ b/java/com/google/gerrit/server/index/group/GroupSchemaDefinitions.java
@@ -37,7 +37,10 @@
   @Deprecated
   static final Schema<InternalGroup> V4 = schema(V3, GroupField.MEMBER, GroupField.SUBGROUP);
 
-  static final Schema<InternalGroup> V5 = schema(V4, GroupField.REF_STATE);
+  @Deprecated static final Schema<InternalGroup> V5 = schema(V4, GroupField.REF_STATE);
+
+  // Bump Lucene version requires reindexing
+  static final Schema<InternalGroup> V6 = schema(V5);
 
   public static final GroupSchemaDefinitions INSTANCE = new GroupSchemaDefinitions();
 
diff --git a/java/com/google/gerrit/server/logging/BUILD b/java/com/google/gerrit/server/logging/BUILD
index d3211f0..f983b24 100644
--- a/java/com/google/gerrit/server/logging/BUILD
+++ b/java/com/google/gerrit/server/logging/BUILD
@@ -6,7 +6,7 @@
     visibility = ["//visibility:public"],
     deps = [
         "//java/com/google/gerrit/common:annotations",
-        "//java/com/google/gerrit/common:server",
+        "//java/com/google/gerrit/server/util/time",
         "//lib:guava",
         "//lib/flogger:api",
     ],
diff --git a/java/com/google/gerrit/server/logging/RequestId.java b/java/com/google/gerrit/server/logging/RequestId.java
index b0a8ad9..ceb5da0 100644
--- a/java/com/google/gerrit/server/logging/RequestId.java
+++ b/java/com/google/gerrit/server/logging/RequestId.java
@@ -18,7 +18,7 @@
 import com.google.common.hash.Hasher;
 import com.google.common.hash.Hashing;
 import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.server.util.time.TimeUtil;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
 
diff --git a/java/com/google/gerrit/server/mail/receive/MailProcessor.java b/java/com/google/gerrit/server/mail/receive/MailProcessor.java
index 707056c..ec0c1f2 100644
--- a/java/com/google/gerrit/server/mail/receive/MailProcessor.java
+++ b/java/com/google/gerrit/server/mail/receive/MailProcessor.java
@@ -20,7 +20,6 @@
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ImmutableList;
 import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.api.changes.NotifyHandling;
 import com.google.gerrit.extensions.client.Side;
 import com.google.gerrit.extensions.registration.DynamicMap;
@@ -64,6 +63,7 @@
 import com.google.gerrit.server.update.UpdateException;
 import com.google.gerrit.server.util.ManualRequestContext;
 import com.google.gerrit.server.util.OneOffRequestContext;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
diff --git a/java/com/google/gerrit/server/mail/send/SmtpEmailSender.java b/java/com/google/gerrit/server/mail/send/SmtpEmailSender.java
index 2262b5c..8615c04 100644
--- a/java/com/google/gerrit/server/mail/send/SmtpEmailSender.java
+++ b/java/com/google/gerrit/server/mail/send/SmtpEmailSender.java
@@ -19,7 +19,6 @@
 import com.google.common.io.BaseEncoding;
 import com.google.common.primitives.Ints;
 import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.Version;
 import com.google.gerrit.common.errors.EmailException;
 import com.google.gerrit.mail.Address;
@@ -27,6 +26,7 @@
 import com.google.gerrit.server.config.ConfigUtil;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.mail.Encryption;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.inject.AbstractModule;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
diff --git a/java/com/google/gerrit/server/notedb/ChangeBundle.java b/java/com/google/gerrit/server/notedb/ChangeBundle.java
index 0ebee1a..d534ff2 100644
--- a/java/com/google/gerrit/server/notedb/ChangeBundle.java
+++ b/java/com/google/gerrit/server/notedb/ChangeBundle.java
@@ -19,11 +19,11 @@
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.collect.ImmutableList.toImmutableList;
 import static com.google.common.collect.ImmutableSortedMap.toImmutableSortedMap;
-import static com.google.gerrit.common.TimeUtil.truncateToSecond;
 import static com.google.gerrit.reviewdb.server.ReviewDbUtil.checkColumns;
 import static com.google.gerrit.reviewdb.server.ReviewDbUtil.intKeyOrdering;
 import static com.google.gerrit.server.notedb.ChangeBundle.Source.NOTE_DB;
 import static com.google.gerrit.server.notedb.ChangeBundle.Source.REVIEW_DB;
+import static com.google.gerrit.server.util.time.TimeUtil.truncateToSecond;
 import static java.util.Comparator.comparing;
 import static java.util.Comparator.naturalOrder;
 import static java.util.Comparator.nullsFirst;
diff --git a/java/com/google/gerrit/server/notedb/NoteDbChangeState.java b/java/com/google/gerrit/server/notedb/NoteDbChangeState.java
index 1c11e8b..a69128e 100644
--- a/java/com/google/gerrit/server/notedb/NoteDbChangeState.java
+++ b/java/com/google/gerrit/server/notedb/NoteDbChangeState.java
@@ -30,11 +30,11 @@
 import com.google.common.collect.Maps;
 import com.google.common.primitives.Longs;
 import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.server.ReviewDbUtil;
 import com.google.gerrit.server.git.RefCache;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gwtorm.server.OrmRuntimeException;
 import java.io.IOException;
 import java.sql.Timestamp;
diff --git a/java/com/google/gerrit/server/notedb/PrimaryStorageMigrator.java b/java/com/google/gerrit/server/notedb/PrimaryStorageMigrator.java
index 9dd3933..7b427b4 100644
--- a/java/com/google/gerrit/server/notedb/PrimaryStorageMigrator.java
+++ b/java/com/google/gerrit/server/notedb/PrimaryStorageMigrator.java
@@ -29,7 +29,6 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
@@ -53,6 +52,7 @@
 import com.google.gerrit.server.update.ChangeContext;
 import com.google.gerrit.server.update.RetryHelper;
 import com.google.gerrit.server.update.UpdateException;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gwtorm.server.AtomicUpdate;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.OrmRuntimeException;
diff --git a/java/com/google/gerrit/server/permissions/PermissionCollection.java b/java/com/google/gerrit/server/permissions/PermissionCollection.java
index 81e8d24..b419698 100644
--- a/java/com/google/gerrit/server/permissions/PermissionCollection.java
+++ b/java/com/google/gerrit/server/permissions/PermissionCollection.java
@@ -26,6 +26,10 @@
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.common.data.PermissionRule;
 import com.google.gerrit.common.data.PermissionRule.Action;
+import com.google.gerrit.metrics.Description;
+import com.google.gerrit.metrics.Description.Units;
+import com.google.gerrit.metrics.MetricMaker;
+import com.google.gerrit.metrics.Timer0;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.CurrentUser;
@@ -54,10 +58,18 @@
   @Singleton
   public static class Factory {
     private final SectionSortCache sorter;
+    // TODO(hiesel): Remove this once we got production data
+    private final Timer0 filterLatency;
 
     @Inject
-    Factory(SectionSortCache sorter) {
+    Factory(SectionSortCache sorter, MetricMaker metricMaker) {
       this.sorter = sorter;
+      this.filterLatency =
+          metricMaker.newTimer(
+              "permissions/permission_collection/filter_latency",
+              new Description("Latency for access filter computations in PermissionCollection")
+                  .setCumulative()
+                  .setUnit(Units.NANOSECONDS));
     }
 
     /**
@@ -117,41 +129,43 @@
      */
     PermissionCollection filter(
         Iterable<SectionMatcher> matcherList, String ref, CurrentUser user) {
-      if (isRE(ref)) {
-        ref = RefPattern.shortestExample(ref);
-      } else if (ref.endsWith("/*")) {
-        ref = ref.substring(0, ref.length() - 1);
+      try (Timer0.Context ignored = filterLatency.start()) {
+        if (isRE(ref)) {
+          ref = RefPattern.shortestExample(ref);
+        } else if (ref.endsWith("/*")) {
+          ref = ref.substring(0, ref.length() - 1);
+        }
+
+        // LinkedHashMap to maintain input ordering.
+        Map<AccessSection, Project.NameKey> sectionToProject = new LinkedHashMap<>();
+        boolean perUser = filterRefMatchingSections(matcherList, ref, user, sectionToProject);
+        List<AccessSection> sections = Lists.newArrayList(sectionToProject.keySet());
+
+        // Sort by ref pattern specificity. For equally specific patterns, the sections from the
+        // project closer to the current one come first.
+        sorter.sort(ref, sections);
+
+        // For block permissions, we want a different order: first, we want to go from parent to
+        // child.
+        List<Map.Entry<AccessSection, Project.NameKey>> accessDescending =
+            Lists.reverse(Lists.newArrayList(sectionToProject.entrySet()));
+
+        Map<Project.NameKey, List<AccessSection>> accessByProject =
+            accessDescending
+                .stream()
+                .collect(
+                    Collectors.groupingBy(
+                        Map.Entry::getValue,
+                        LinkedHashMap::new,
+                        mapping(Map.Entry::getKey, toList())));
+        // Within each project, sort by ref specificity.
+        for (List<AccessSection> secs : accessByProject.values()) {
+          sorter.sort(ref, secs);
+        }
+
+        return new PermissionCollection(
+            Lists.newArrayList(accessByProject.values()), sections, perUser);
       }
-
-      // LinkedHashMap to maintain input ordering.
-      Map<AccessSection, Project.NameKey> sectionToProject = new LinkedHashMap<>();
-      boolean perUser = filterRefMatchingSections(matcherList, ref, user, sectionToProject);
-      List<AccessSection> sections = Lists.newArrayList(sectionToProject.keySet());
-
-      // Sort by ref pattern specificity. For equally specific patterns, the sections from the
-      // project closer to the current one come first.
-      sorter.sort(ref, sections);
-
-      // For block permissions, we want a different order: first, we want to go from parent to
-      // child.
-      List<Map.Entry<AccessSection, Project.NameKey>> accessDescending =
-          Lists.reverse(Lists.newArrayList(sectionToProject.entrySet()));
-
-      Map<Project.NameKey, List<AccessSection>> accessByProject =
-          accessDescending
-              .stream()
-              .collect(
-                  Collectors.groupingBy(
-                      Map.Entry::getValue,
-                      LinkedHashMap::new,
-                      mapping(Map.Entry::getKey, toList())));
-      // Within each project, sort by ref specificity.
-      for (List<AccessSection> secs : accessByProject.values()) {
-        sorter.sort(ref, secs);
-      }
-
-      return new PermissionCollection(
-          Lists.newArrayList(accessByProject.values()), sections, perUser);
     }
   }
 
diff --git a/java/com/google/gerrit/server/plugins/PluginCleanerTask.java b/java/com/google/gerrit/server/plugins/PluginCleanerTask.java
index 11a4eab..6919bbc 100644
--- a/java/com/google/gerrit/server/plugins/PluginCleanerTask.java
+++ b/java/com/google/gerrit/server/plugins/PluginCleanerTask.java
@@ -15,8 +15,8 @@
 package com.google.gerrit.server.plugins;
 
 import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.server.git.WorkQueue;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import java.util.concurrent.Future;
diff --git a/java/com/google/gerrit/server/project/CreateProjectArgs.java b/java/com/google/gerrit/server/project/CreateProjectArgs.java
index e4623b2..a68bd84 100644
--- a/java/com/google/gerrit/server/project/CreateProjectArgs.java
+++ b/java/com/google/gerrit/server/project/CreateProjectArgs.java
@@ -35,6 +35,8 @@
   public InheritableBoolean newChangeForAllNotInTarget;
   public InheritableBoolean changeIdRequired;
   public InheritableBoolean rejectEmptyCommit;
+  public InheritableBoolean enableSignedPush;
+  public InheritableBoolean requireSignedPush;
   public boolean createEmptyCommit;
   public String maxObjectSizeLimit;
 
@@ -44,6 +46,8 @@
     contentMerge = InheritableBoolean.INHERIT;
     changeIdRequired = InheritableBoolean.INHERIT;
     newChangeForAllNotInTarget = InheritableBoolean.INHERIT;
+    enableSignedPush = InheritableBoolean.INHERIT;
+    requireSignedPush = InheritableBoolean.INHERIT;
     submitType = SubmitType.MERGE_IF_NECESSARY;
   }
 
diff --git a/java/com/google/gerrit/server/project/ProjectState.java b/java/com/google/gerrit/server/project/ProjectState.java
index a9b19d9..8355625 100644
--- a/java/com/google/gerrit/server/project/ProjectState.java
+++ b/java/com/google/gerrit/server/project/ProjectState.java
@@ -36,6 +36,11 @@
 import com.google.gerrit.extensions.client.SubmitType;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.index.project.ProjectData;
+import com.google.gerrit.metrics.Description;
+import com.google.gerrit.metrics.Description.Units;
+import com.google.gerrit.metrics.Field;
+import com.google.gerrit.metrics.MetricMaker;
+import com.google.gerrit.metrics.Timer1;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.BooleanProjectConfig;
 import com.google.gerrit.reviewdb.client.Branch;
@@ -92,6 +97,9 @@
   private final long globalMaxObjectSizeLimit;
   private final boolean inheritProjectMaxObjectSizeLimit;
 
+  // TODO(hiesel): Remove this once we got production data
+  private final Timer1<String> computationLatency;
+
   /** Last system time the configuration's revision was examined. */
   private volatile long lastCheckGeneration;
 
@@ -119,6 +127,7 @@
       List<CommentLinkInfo> commentLinks,
       CapabilityCollection.Factory limitsFactory,
       TransferConfig transferConfig,
+      MetricMaker metricMaker,
       @Assisted ProjectConfig config) {
     this.sitePaths = sitePaths;
     this.projectCache = projectCache;
@@ -136,6 +145,14 @@
     this.globalMaxObjectSizeLimit = transferConfig.getMaxObjectSizeLimit();
     this.inheritProjectMaxObjectSizeLimit = transferConfig.getInheritProjectMaxObjectSizeLimit();
 
+    this.computationLatency =
+        metricMaker.newTimer(
+            "permissions/project_state/computation_latency",
+            new Description("Latency for access computations in ProjectState")
+                .setCumulative()
+                .setUnit(Units.NANOSECONDS),
+            Field.ofString("method"));
+
     if (isAllProjects && !Permission.canBeOnAllProjects(AccessSection.ALL, Permission.OWNER)) {
       localOwners = Collections.emptySet();
     } else {
@@ -354,15 +371,20 @@
    * cached. Callers should try to cache this result per-request as much as possible.
    */
   public List<SectionMatcher> getAllSections() {
-    if (isAllProjects) {
-      return getLocalAccessSections();
-    }
+    try (Timer1.Context ignored = computationLatency.start("getAllSections")) {
+      if (isAllProjects) {
+        return getLocalAccessSections();
+      }
 
-    List<SectionMatcher> all = new ArrayList<>();
-    for (ProjectState s : tree()) {
-      all.addAll(s.getLocalAccessSections());
+      List<SectionMatcher> all = new ArrayList<>();
+      Iterable<ProjectState> tree = tree();
+      try (Timer1.Context ignored2 = computationLatency.start("getAllSections-parsing-only")) {
+        for (ProjectState s : tree) {
+          all.addAll(s.getLocalAccessSections());
+        }
+      }
+      return all;
     }
-    return all;
   }
 
   /**
diff --git a/java/com/google/gerrit/server/query/change/AgePredicate.java b/java/com/google/gerrit/server/query/change/AgePredicate.java
index 6310665..29f1b8a 100644
--- a/java/com/google/gerrit/server/query/change/AgePredicate.java
+++ b/java/com/google/gerrit/server/query/change/AgePredicate.java
@@ -17,10 +17,10 @@
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.concurrent.TimeUnit.SECONDS;
 
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.server.config.ConfigUtil;
 import com.google.gerrit.server.index.change.ChangeField;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 import java.sql.Timestamp;
 
diff --git a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index 5667869..3db72ef 100644
--- a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -673,6 +673,21 @@
   }
 
   @Operator
+  public Predicate<ChangeData> repo(String name) {
+    return project(name);
+  }
+
+  @Operator
+  public Predicate<ChangeData> repos(String name) {
+    return projects(name);
+  }
+
+  @Operator
+  public Predicate<ChangeData> parentrepo(String name) {
+    return parentproject(name);
+  }
+
+  @Operator
   public Predicate<ChangeData> branch(String name) {
     if (name.startsWith("^")) {
       return ref("^" + RefNames.fullName(name.substring(1)));
diff --git a/java/com/google/gerrit/server/query/change/OutputStreamQuery.java b/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
index dbbf367..1287970 100644
--- a/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
+++ b/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
@@ -18,7 +18,6 @@
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.data.LabelTypes;
 import com.google.gerrit.index.query.QueryParseException;
 import com.google.gerrit.index.query.QueryResult;
@@ -33,6 +32,7 @@
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.project.SubmitRuleEvaluator;
 import com.google.gerrit.server.project.SubmitRuleOptions;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gson.Gson;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/server/restapi/BUILD b/java/com/google/gerrit/server/restapi/BUILD
index 251c7c1..5e6dddf 100644
--- a/java/com/google/gerrit/server/restapi/BUILD
+++ b/java/com/google/gerrit/server/restapi/BUILD
@@ -18,6 +18,7 @@
         "//java/com/google/gerrit/reviewdb:server",
         "//java/com/google/gerrit/server",
         "//java/com/google/gerrit/server/ioutil",
+        "//java/com/google/gerrit/server/util/time",
         "//java/com/google/gerrit/util/cli",
         "//java/org/eclipse/jgit:server",
         "//lib:args4j",
diff --git a/java/com/google/gerrit/server/restapi/account/DeleteDraftComments.java b/java/com/google/gerrit/server/restapi/account/DeleteDraftComments.java
index 26d6cf4..108ee0e 100644
--- a/java/com/google/gerrit/server/restapi/account/DeleteDraftComments.java
+++ b/java/com/google/gerrit/server/restapi/account/DeleteDraftComments.java
@@ -21,7 +21,6 @@
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
 import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.api.accounts.DeleteDraftCommentsInput;
 import com.google.gerrit.extensions.api.accounts.DeletedDraftCommentInfo;
 import com.google.gerrit.extensions.client.ListChangesOption;
@@ -58,6 +57,7 @@
 import com.google.gerrit.server.update.BatchUpdateOp;
 import com.google.gerrit.server.update.ChangeContext;
 import com.google.gerrit.server.update.UpdateException;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
diff --git a/java/com/google/gerrit/server/restapi/change/Abandon.java b/java/com/google/gerrit/server/restapi/change/Abandon.java
index c739e54..ccce998 100644
--- a/java/com/google/gerrit/server/restapi/change/Abandon.java
+++ b/java/com/google/gerrit/server/restapi/change/Abandon.java
@@ -17,7 +17,6 @@
 import com.google.common.collect.ImmutableListMultimap;
 import com.google.common.collect.ListMultimap;
 import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.api.changes.AbandonInput;
 import com.google.gerrit.extensions.api.changes.NotifyHandling;
 import com.google.gerrit.extensions.api.changes.RecipientType;
@@ -41,6 +40,7 @@
 import com.google.gerrit.server.update.RetryHelper;
 import com.google.gerrit.server.update.RetryingRestModifyView;
 import com.google.gerrit.server.update.UpdateException;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
diff --git a/java/com/google/gerrit/server/restapi/change/CherryPickChange.java b/java/com/google/gerrit/server/restapi/change/CherryPickChange.java
index b777461..0de84053 100644
--- a/java/com/google/gerrit/server/restapi/change/CherryPickChange.java
+++ b/java/com/google/gerrit/server/restapi/change/CherryPickChange.java
@@ -17,7 +17,6 @@
 import com.google.common.base.Strings;
 import com.google.gerrit.common.FooterConstants;
 import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.api.changes.CherryPickInput;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.MergeConflictException;
@@ -60,6 +59,7 @@
 import com.google.gerrit.server.update.BatchUpdateOp;
 import com.google.gerrit.server.update.ChangeContext;
 import com.google.gerrit.server.update.UpdateException;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
diff --git a/java/com/google/gerrit/server/restapi/change/CreateChange.java b/java/com/google/gerrit/server/restapi/change/CreateChange.java
index b48e040..a99f7db 100644
--- a/java/com/google/gerrit/server/restapi/change/CreateChange.java
+++ b/java/com/google/gerrit/server/restapi/change/CreateChange.java
@@ -20,7 +20,6 @@
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Strings;
 import com.google.common.collect.Iterables;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.client.ChangeStatus;
 import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
 import com.google.gerrit.extensions.client.SubmitType;
@@ -71,6 +70,7 @@
 import com.google.gerrit.server.update.RetryHelper;
 import com.google.gerrit.server.update.RetryingRestCollectionModifyView;
 import com.google.gerrit.server.update.UpdateException;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
diff --git a/java/com/google/gerrit/server/restapi/change/CreateDraftComment.java b/java/com/google/gerrit/server/restapi/change/CreateDraftComment.java
index c22fb1e..0e93c55 100644
--- a/java/com/google/gerrit/server/restapi/change/CreateDraftComment.java
+++ b/java/com/google/gerrit/server/restapi/change/CreateDraftComment.java
@@ -17,7 +17,6 @@
 import static com.google.gerrit.server.CommentsUtil.setCommentRevId;
 
 import com.google.common.base.Strings;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.api.changes.DraftInput;
 import com.google.gerrit.extensions.common.CommentInfo;
 import com.google.gerrit.extensions.restapi.BadRequestException;
@@ -42,6 +41,7 @@
 import com.google.gerrit.server.update.RetryHelper;
 import com.google.gerrit.server.update.RetryingRestModifyView;
 import com.google.gerrit.server.update.UpdateException;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
diff --git a/java/com/google/gerrit/server/restapi/change/CreateMergePatchSet.java b/java/com/google/gerrit/server/restapi/change/CreateMergePatchSet.java
index a5698b6..178206c 100644
--- a/java/com/google/gerrit/server/restapi/change/CreateMergePatchSet.java
+++ b/java/com/google/gerrit/server/restapi/change/CreateMergePatchSet.java
@@ -17,7 +17,6 @@
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Strings;
 import com.google.common.collect.Iterables;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.api.changes.NotifyHandling;
 import com.google.gerrit.extensions.client.ListChangesOption;
 import com.google.gerrit.extensions.common.ChangeInfo;
@@ -58,6 +57,7 @@
 import com.google.gerrit.server.update.RetryHelper;
 import com.google.gerrit.server.update.RetryingRestModifyView;
 import com.google.gerrit.server.update.UpdateException;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
diff --git a/java/com/google/gerrit/server/restapi/change/DeleteAssignee.java b/java/com/google/gerrit/server/restapi/change/DeleteAssignee.java
index eb1e10e..7d68022 100644
--- a/java/com/google/gerrit/server/restapi/change/DeleteAssignee.java
+++ b/java/com/google/gerrit/server/restapi/change/DeleteAssignee.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.server.restapi.change;
 
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.common.AccountInfo;
 import com.google.gerrit.extensions.common.Input;
 import com.google.gerrit.extensions.restapi.Response;
@@ -39,6 +38,7 @@
 import com.google.gerrit.server.update.RetryHelper;
 import com.google.gerrit.server.update.RetryingRestModifyView;
 import com.google.gerrit.server.update.UpdateException;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
diff --git a/java/com/google/gerrit/server/restapi/change/DeleteChange.java b/java/com/google/gerrit/server/restapi/change/DeleteChange.java
index 4bd10ed..b89ef39 100644
--- a/java/com/google/gerrit/server/restapi/change/DeleteChange.java
+++ b/java/com/google/gerrit/server/restapi/change/DeleteChange.java
@@ -16,7 +16,6 @@
 
 import static com.google.gerrit.extensions.conditions.BooleanCondition.and;
 
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.common.Input;
 import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
 import com.google.gerrit.extensions.restapi.Response;
@@ -33,6 +32,7 @@
 import com.google.gerrit.server.update.RetryHelper;
 import com.google.gerrit.server.update.RetryingRestModifyView;
 import com.google.gerrit.server.update.UpdateException;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
diff --git a/java/com/google/gerrit/server/restapi/change/DeleteChangeMessage.java b/java/com/google/gerrit/server/restapi/change/DeleteChangeMessage.java
index 67c54c2..e2e892d 100644
--- a/java/com/google/gerrit/server/restapi/change/DeleteChangeMessage.java
+++ b/java/com/google/gerrit/server/restapi/change/DeleteChangeMessage.java
@@ -20,7 +20,6 @@
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Strings;
 import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.api.changes.DeleteChangeMessageInput;
 import com.google.gerrit.extensions.common.ChangeMessageInfo;
 import com.google.gerrit.extensions.common.Input;
@@ -44,6 +43,7 @@
 import com.google.gerrit.server.update.RetryHelper;
 import com.google.gerrit.server.update.RetryingRestModifyView;
 import com.google.gerrit.server.update.UpdateException;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
diff --git a/java/com/google/gerrit/server/restapi/change/DeleteComment.java b/java/com/google/gerrit/server/restapi/change/DeleteComment.java
index a8f39c0..2ddf359 100644
--- a/java/com/google/gerrit/server/restapi/change/DeleteComment.java
+++ b/java/com/google/gerrit/server/restapi/change/DeleteComment.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.server.restapi.change;
 
 import com.google.common.base.Strings;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.api.changes.DeleteCommentInput;
 import com.google.gerrit.extensions.common.CommentInfo;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -37,6 +36,7 @@
 import com.google.gerrit.server.update.RetryHelper;
 import com.google.gerrit.server.update.RetryingRestModifyView;
 import com.google.gerrit.server.update.UpdateException;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
diff --git a/java/com/google/gerrit/server/restapi/change/DeleteDraftComment.java b/java/com/google/gerrit/server/restapi/change/DeleteDraftComment.java
index e81f9f1..f8e3add 100644
--- a/java/com/google/gerrit/server/restapi/change/DeleteDraftComment.java
+++ b/java/com/google/gerrit/server/restapi/change/DeleteDraftComment.java
@@ -16,7 +16,6 @@
 
 import static com.google.gerrit.server.CommentsUtil.setCommentRevId;
 
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.common.CommentInfo;
 import com.google.gerrit.extensions.common.Input;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
@@ -36,6 +35,7 @@
 import com.google.gerrit.server.update.RetryHelper;
 import com.google.gerrit.server.update.RetryingRestModifyView;
 import com.google.gerrit.server.update.UpdateException;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
diff --git a/java/com/google/gerrit/server/restapi/change/DeletePrivate.java b/java/com/google/gerrit/server/restapi/change/DeletePrivate.java
index 4ff1b66..9b747e0 100644
--- a/java/com/google/gerrit/server/restapi/change/DeletePrivate.java
+++ b/java/com/google/gerrit/server/restapi/change/DeletePrivate.java
@@ -16,7 +16,6 @@
 
 import static com.google.gerrit.extensions.conditions.BooleanCondition.or;
 
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.conditions.BooleanCondition;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -31,6 +30,7 @@
 import com.google.gerrit.server.update.RetryHelper;
 import com.google.gerrit.server.update.RetryingRestModifyView;
 import com.google.gerrit.server.update.UpdateException;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
diff --git a/java/com/google/gerrit/server/restapi/change/DeleteReviewer.java b/java/com/google/gerrit/server/restapi/change/DeleteReviewer.java
index a92cf6c..245d1cd 100644
--- a/java/com/google/gerrit/server/restapi/change/DeleteReviewer.java
+++ b/java/com/google/gerrit/server/restapi/change/DeleteReviewer.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.server.restapi.change;
 
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.api.changes.DeleteReviewerInput;
 import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestApiException;
@@ -25,6 +24,7 @@
 import com.google.gerrit.server.update.RetryHelper;
 import com.google.gerrit.server.update.RetryingRestModifyView;
 import com.google.gerrit.server.update.UpdateException;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
diff --git a/java/com/google/gerrit/server/restapi/change/DeleteVote.java b/java/com/google/gerrit/server/restapi/change/DeleteVote.java
index dc44e65..78e776e 100644
--- a/java/com/google/gerrit/server/restapi/change/DeleteVote.java
+++ b/java/com/google/gerrit/server/restapi/change/DeleteVote.java
@@ -17,7 +17,6 @@
 import static com.google.common.base.Preconditions.checkNotNull;
 
 import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.data.LabelTypes;
 import com.google.gerrit.extensions.api.changes.DeleteVoteInput;
 import com.google.gerrit.extensions.api.changes.NotifyHandling;
@@ -57,6 +56,7 @@
 import com.google.gerrit.server.update.RetryingRestModifyView;
 import com.google.gerrit.server.update.UpdateException;
 import com.google.gerrit.server.util.LabelVote;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
diff --git a/java/com/google/gerrit/server/restapi/change/Move.java b/java/com/google/gerrit/server/restapi/change/Move.java
index 3833050..013d3e9 100644
--- a/java/com/google/gerrit/server/restapi/change/Move.java
+++ b/java/com/google/gerrit/server/restapi/change/Move.java
@@ -22,7 +22,6 @@
 import com.google.common.base.Strings;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.extensions.api.changes.MoveInput;
 import com.google.gerrit.extensions.common.ChangeInfo;
@@ -61,6 +60,7 @@
 import com.google.gerrit.server.update.RetryHelper;
 import com.google.gerrit.server.update.RetryingRestModifyView;
 import com.google.gerrit.server.update.UpdateException;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
diff --git a/java/com/google/gerrit/server/restapi/change/PostHashtags.java b/java/com/google/gerrit/server/restapi/change/PostHashtags.java
index f31d04e..c67fb67 100644
--- a/java/com/google/gerrit/server/restapi/change/PostHashtags.java
+++ b/java/com/google/gerrit/server/restapi/change/PostHashtags.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.server.restapi.change;
 
 import com.google.common.collect.ImmutableSortedSet;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.api.changes.HashtagsInput;
 import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestApiException;
@@ -29,6 +28,7 @@
 import com.google.gerrit.server.update.RetryHelper;
 import com.google.gerrit.server.update.RetryingRestModifyView;
 import com.google.gerrit.server.update.UpdateException;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
diff --git a/java/com/google/gerrit/server/restapi/change/PostPrivate.java b/java/com/google/gerrit/server/restapi/change/PostPrivate.java
index 5a13346..3f37bc1 100644
--- a/java/com/google/gerrit/server/restapi/change/PostPrivate.java
+++ b/java/com/google/gerrit/server/restapi/change/PostPrivate.java
@@ -17,7 +17,6 @@
 import static com.google.gerrit.extensions.conditions.BooleanCondition.and;
 import static com.google.gerrit.extensions.conditions.BooleanCondition.or;
 
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.conditions.BooleanCondition;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
@@ -35,6 +34,7 @@
 import com.google.gerrit.server.update.RetryHelper;
 import com.google.gerrit.server.update.RetryingRestModifyView;
 import com.google.gerrit.server.update.UpdateException;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
diff --git a/java/com/google/gerrit/server/restapi/change/PostReview.java b/java/com/google/gerrit/server/restapi/change/PostReview.java
index 9bbaf69..ebd23c5 100644
--- a/java/com/google/gerrit/server/restapi/change/PostReview.java
+++ b/java/com/google/gerrit/server/restapi/change/PostReview.java
@@ -37,7 +37,6 @@
 import com.google.common.hash.HashCode;
 import com.google.common.hash.Hashing;
 import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.common.data.LabelTypes;
 import com.google.gerrit.extensions.api.changes.AddReviewerInput;
@@ -120,6 +119,7 @@
 import com.google.gerrit.server.update.RetryingRestModifyView;
 import com.google.gerrit.server.update.UpdateException;
 import com.google.gerrit.server.util.LabelVote;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gson.Gson;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/server/restapi/change/PostReviewers.java b/java/com/google/gerrit/server/restapi/change/PostReviewers.java
index a9c48e3..cbaad81 100644
--- a/java/com/google/gerrit/server/restapi/change/PostReviewers.java
+++ b/java/com/google/gerrit/server/restapi/change/PostReviewers.java
@@ -24,7 +24,6 @@
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Lists;
 import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.data.GroupDescription;
 import com.google.gerrit.extensions.api.changes.AddReviewerInput;
 import com.google.gerrit.extensions.api.changes.AddReviewerResult;
@@ -73,6 +72,7 @@
 import com.google.gerrit.server.update.RetryHelper;
 import com.google.gerrit.server.update.RetryingRestCollectionModifyView;
 import com.google.gerrit.server.update.UpdateException;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
diff --git a/java/com/google/gerrit/server/restapi/change/PutAssignee.java b/java/com/google/gerrit/server/restapi/change/PutAssignee.java
index 21bd3ce..c11a858c 100644
--- a/java/com/google/gerrit/server/restapi/change/PutAssignee.java
+++ b/java/com/google/gerrit/server/restapi/change/PutAssignee.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.server.restapi.change;
 
 import com.google.common.base.Strings;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.api.changes.AddReviewerInput;
 import com.google.gerrit.extensions.api.changes.AssigneeInput;
 import com.google.gerrit.extensions.api.changes.NotifyHandling;
@@ -40,6 +39,7 @@
 import com.google.gerrit.server.update.RetryHelper;
 import com.google.gerrit.server.update.RetryingRestModifyView;
 import com.google.gerrit.server.update.UpdateException;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
diff --git a/java/com/google/gerrit/server/restapi/change/PutDescription.java b/java/com/google/gerrit/server/restapi/change/PutDescription.java
index 38fc2e2..3b5edb2 100644
--- a/java/com/google/gerrit/server/restapi/change/PutDescription.java
+++ b/java/com/google/gerrit/server/restapi/change/PutDescription.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.server.restapi.change;
 
 import com.google.common.base.Strings;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.common.DescriptionInput;
 import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestApiException;
@@ -35,6 +34,7 @@
 import com.google.gerrit.server.update.RetryHelper;
 import com.google.gerrit.server.update.RetryingRestModifyView;
 import com.google.gerrit.server.update.UpdateException;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
diff --git a/java/com/google/gerrit/server/restapi/change/PutDraftComment.java b/java/com/google/gerrit/server/restapi/change/PutDraftComment.java
index 76ef106..72358bd 100644
--- a/java/com/google/gerrit/server/restapi/change/PutDraftComment.java
+++ b/java/com/google/gerrit/server/restapi/change/PutDraftComment.java
@@ -16,7 +16,6 @@
 
 import static com.google.gerrit.server.CommentsUtil.setCommentRevId;
 
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.api.changes.DraftInput;
 import com.google.gerrit.extensions.common.CommentInfo;
 import com.google.gerrit.extensions.restapi.BadRequestException;
@@ -41,6 +40,7 @@
 import com.google.gerrit.server.update.RetryHelper;
 import com.google.gerrit.server.update.RetryingRestModifyView;
 import com.google.gerrit.server.update.UpdateException;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
diff --git a/java/com/google/gerrit/server/restapi/change/PutMessage.java b/java/com/google/gerrit/server/restapi/change/PutMessage.java
index bee0ed7..bcd0e9e 100644
--- a/java/com/google/gerrit/server/restapi/change/PutMessage.java
+++ b/java/com/google/gerrit/server/restapi/change/PutMessage.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.server.restapi.change;
 
 import com.google.gerrit.common.FooterConstants;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.api.changes.NotifyHandling;
 import com.google.gerrit.extensions.common.CommitMessageInput;
 import com.google.gerrit.extensions.restapi.AuthException;
@@ -44,6 +43,7 @@
 import com.google.gerrit.server.update.RetryingRestModifyView;
 import com.google.gerrit.server.update.UpdateException;
 import com.google.gerrit.server.util.CommitMessageUtil;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
diff --git a/java/com/google/gerrit/server/restapi/change/PutTopic.java b/java/com/google/gerrit/server/restapi/change/PutTopic.java
index 45a837a..7f56c91 100644
--- a/java/com/google/gerrit/server/restapi/change/PutTopic.java
+++ b/java/com/google/gerrit/server/restapi/change/PutTopic.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.server.restapi.change;
 
 import com.google.common.base.Strings;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.api.changes.TopicInput;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.Response;
@@ -38,6 +37,7 @@
 import com.google.gerrit.server.update.RetryHelper;
 import com.google.gerrit.server.update.RetryingRestModifyView;
 import com.google.gerrit.server.update.UpdateException;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
diff --git a/java/com/google/gerrit/server/restapi/change/Rebase.java b/java/com/google/gerrit/server/restapi/change/Rebase.java
index 0fffb58..2e2f565 100644
--- a/java/com/google/gerrit/server/restapi/change/Rebase.java
+++ b/java/com/google/gerrit/server/restapi/change/Rebase.java
@@ -17,7 +17,6 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.api.changes.RebaseInput;
 import com.google.gerrit.extensions.client.ListChangesOption;
 import com.google.gerrit.extensions.common.ChangeInfo;
@@ -49,6 +48,7 @@
 import com.google.gerrit.server.update.RetryHelper;
 import com.google.gerrit.server.update.RetryingRestModifyView;
 import com.google.gerrit.server.update.UpdateException;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
diff --git a/java/com/google/gerrit/server/restapi/change/Restore.java b/java/com/google/gerrit/server/restapi/change/Restore.java
index a29347f..d6f9e2b 100644
--- a/java/com/google/gerrit/server/restapi/change/Restore.java
+++ b/java/com/google/gerrit/server/restapi/change/Restore.java
@@ -16,7 +16,6 @@
 
 import com.google.common.base.Strings;
 import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.api.changes.RestoreInput;
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -46,6 +45,7 @@
 import com.google.gerrit.server.update.RetryHelper;
 import com.google.gerrit.server.update.RetryingRestModifyView;
 import com.google.gerrit.server.update.UpdateException;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
diff --git a/java/com/google/gerrit/server/restapi/change/Revert.java b/java/com/google/gerrit/server/restapi/change/Revert.java
index e008681..6383b29 100644
--- a/java/com/google/gerrit/server/restapi/change/Revert.java
+++ b/java/com/google/gerrit/server/restapi/change/Revert.java
@@ -20,7 +20,6 @@
 import com.google.common.base.Strings;
 import com.google.common.collect.ListMultimap;
 import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.api.changes.NotifyHandling;
 import com.google.gerrit.extensions.api.changes.RecipientType;
 import com.google.gerrit.extensions.api.changes.RevertInput;
@@ -66,6 +65,7 @@
 import com.google.gerrit.server.update.RetryHelper;
 import com.google.gerrit.server.update.RetryingRestModifyView;
 import com.google.gerrit.server.update.UpdateException;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
diff --git a/java/com/google/gerrit/server/restapi/change/SetReadyForReview.java b/java/com/google/gerrit/server/restapi/change/SetReadyForReview.java
index faf946f..7838612 100644
--- a/java/com/google/gerrit/server/restapi/change/SetReadyForReview.java
+++ b/java/com/google/gerrit/server/restapi/change/SetReadyForReview.java
@@ -17,7 +17,6 @@
 import static com.google.gerrit.extensions.conditions.BooleanCondition.and;
 import static com.google.gerrit.extensions.conditions.BooleanCondition.or;
 
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.Response;
@@ -38,6 +37,7 @@
 import com.google.gerrit.server.update.RetryHelper;
 import com.google.gerrit.server.update.RetryingRestModifyView;
 import com.google.gerrit.server.update.UpdateException;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
diff --git a/java/com/google/gerrit/server/restapi/change/SetWorkInProgress.java b/java/com/google/gerrit/server/restapi/change/SetWorkInProgress.java
index 1f9d81f..7d88aba 100644
--- a/java/com/google/gerrit/server/restapi/change/SetWorkInProgress.java
+++ b/java/com/google/gerrit/server/restapi/change/SetWorkInProgress.java
@@ -17,7 +17,6 @@
 import static com.google.gerrit.extensions.conditions.BooleanCondition.and;
 import static com.google.gerrit.extensions.conditions.BooleanCondition.or;
 
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.Response;
@@ -38,6 +37,7 @@
 import com.google.gerrit.server.update.RetryHelper;
 import com.google.gerrit.server.update.RetryingRestModifyView;
 import com.google.gerrit.server.update.UpdateException;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
diff --git a/java/com/google/gerrit/server/restapi/config/GetServerInfo.java b/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
index 66e9f90..d6071d5 100644
--- a/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
+++ b/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
@@ -157,9 +157,7 @@
     info.gerrit = getGerritInfo();
     info.noteDbEnabled = toBoolean(isNoteDbEnabled());
     info.plugin = getPluginInfo();
-    if (Files.exists(sitePaths.site_theme)) {
-      info.defaultTheme = "/static/" + SitePaths.THEME_FILENAME;
-    }
+    info.defaultTheme = getDefaultTheme();
     info.sshd = getSshdInfo();
     info.suggest = getSuggestInfo();
 
@@ -342,6 +340,22 @@
     return info;
   }
 
+  private static final String DEFAULT_THEME = "/static/" + SitePaths.THEME_FILENAME;
+
+  private String getDefaultTheme() {
+    if (config.getString("theme", null, "enableDefault") == null) {
+      // If not explicitly enabled or disabled, check for the existence of the theme file.
+      return Files.exists(sitePaths.site_theme) ? DEFAULT_THEME : null;
+    }
+    if (config.getBoolean("theme", null, "enableDefault", true)) {
+      // Return non-null theme path without checking for file existence. Even if the file doesn't
+      // exist under the site path, it may be served from a CDN (in which case it's up to the admin
+      // to also pass a proper asset path to the index Soy template).
+      return DEFAULT_THEME;
+    }
+    return null;
+  }
+
   private Map<String, String> getUrlAliasesInfo() {
     Map<String, String> urlAliases = new HashMap<>();
     for (String subsection : config.getSubsections(URL_ALIAS)) {
diff --git a/java/com/google/gerrit/server/restapi/project/CreateAccessChange.java b/java/com/google/gerrit/server/restapi/project/CreateAccessChange.java
index 33155f1..feb37c0 100644
--- a/java/com/google/gerrit/server/restapi/project/CreateAccessChange.java
+++ b/java/com/google/gerrit/server/restapi/project/CreateAccessChange.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.server.restapi.project;
 
 import com.google.common.collect.ImmutableMap;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.data.AccessSection;
 import com.google.gerrit.common.errors.InvalidNameException;
 import com.google.gerrit.extensions.api.access.ProjectAccessInput;
@@ -44,6 +43,7 @@
 import com.google.gerrit.server.project.ProjectResource;
 import com.google.gerrit.server.update.BatchUpdate;
 import com.google.gerrit.server.update.UpdateException;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
diff --git a/java/com/google/gerrit/server/restapi/project/CreateProject.java b/java/com/google/gerrit/server/restapi/project/CreateProject.java
index 271848b..030402e 100644
--- a/java/com/google/gerrit/server/restapi/project/CreateProject.java
+++ b/java/com/google/gerrit/server/restapi/project/CreateProject.java
@@ -203,6 +203,10 @@
         MoreObjects.firstNonNull(input.requireChangeId, InheritableBoolean.INHERIT);
     args.rejectEmptyCommit =
         MoreObjects.firstNonNull(input.rejectEmptyCommit, InheritableBoolean.INHERIT);
+    args.enableSignedPush =
+        MoreObjects.firstNonNull(input.enableSignedPush, InheritableBoolean.INHERIT);
+    args.requireSignedPush =
+        MoreObjects.firstNonNull(input.requireSignedPush, InheritableBoolean.INHERIT);
     try {
       args.maxObjectSizeLimit = ProjectConfig.validMaxObjectSizeLimit(input.maxObjectSizeLimit);
     } catch (ConfigInvalidException e) {
@@ -297,6 +301,8 @@
       newProject.setBooleanConfig(BooleanProjectConfig.REQUIRE_CHANGE_ID, args.changeIdRequired);
       newProject.setBooleanConfig(BooleanProjectConfig.REJECT_EMPTY_COMMIT, args.rejectEmptyCommit);
       newProject.setMaxObjectSizeLimit(args.maxObjectSizeLimit);
+      newProject.setBooleanConfig(BooleanProjectConfig.ENABLE_SIGNED_PUSH, args.enableSignedPush);
+      newProject.setBooleanConfig(BooleanProjectConfig.REQUIRE_SIGNED_PUSH, args.requireSignedPush);
       if (args.newParent != null) {
         newProject.setParentName(args.newParent);
       }
diff --git a/java/com/google/gerrit/server/restapi/project/CreateTag.java b/java/com/google/gerrit/server/restapi/project/CreateTag.java
index dd73b0b..e72deaf 100644
--- a/java/com/google/gerrit/server/restapi/project/CreateTag.java
+++ b/java/com/google/gerrit/server/restapi/project/CreateTag.java
@@ -18,7 +18,6 @@
 
 import com.google.common.base.Strings;
 import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.api.projects.TagInfo;
 import com.google.gerrit.extensions.api.projects.TagInput;
 import com.google.gerrit.extensions.restapi.AuthException;
@@ -40,6 +39,7 @@
 import com.google.gerrit.server.project.RefUtil;
 import com.google.gerrit.server.project.RefUtil.InvalidRevisionException;
 import com.google.gerrit.server.project.TagResource;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import java.io.IOException;
diff --git a/java/com/google/gerrit/server/restapi/project/Module.java b/java/com/google/gerrit/server/restapi/project/Module.java
index 0497787..8c8ab49 100644
--- a/java/com/google/gerrit/server/restapi/project/Module.java
+++ b/java/com/google/gerrit/server/restapi/project/Module.java
@@ -23,7 +23,9 @@
 import static com.google.gerrit.server.project.TagResource.TAG_KIND;
 
 import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.extensions.restapi.RestApiModule;
+import com.google.gerrit.server.config.GerritConfigListener;
 import com.google.gerrit.server.project.RefValidationHelper;
 import com.google.gerrit.server.restapi.change.CherryPickCommit;
 
@@ -41,6 +43,8 @@
     DynamicMap.mapOf(binder(), COMMIT_KIND);
     DynamicMap.mapOf(binder(), TAG_KIND);
 
+    DynamicSet.bind(binder(), GerritConfigListener.class).to(SetParent.class);
+
     create(PROJECT_KIND).to(CreateProject.class);
     put(PROJECT_KIND).to(PutProject.class);
     get(PROJECT_KIND).to(GetProject.class);
diff --git a/java/com/google/gerrit/server/restapi/project/ProjectsCollection.java b/java/com/google/gerrit/server/restapi/project/ProjectsCollection.java
index 2fc0beb..6abf102 100644
--- a/java/com/google/gerrit/server/restapi/project/ProjectsCollection.java
+++ b/java/com/google/gerrit/server/restapi/project/ProjectsCollection.java
@@ -150,23 +150,16 @@
 
     if (checkAccess) {
       // Hidden projects(permitsRead = false) should only be accessible by the project owners.
-      // READ_CONFIG is checked here because it's only allowed to project owners(ACCESS may also
+      // WRITE_CONFIG is checked here because it's only allowed to project owners (ACCESS may also
       // be allowed for other users). Allowing project owners to access here will help them to view
       // and update the config of hidden projects easily.
-      ProjectPermission permissionToCheck =
-          state.statePermitsRead() ? ProjectPermission.ACCESS : ProjectPermission.READ_CONFIG;
-      try {
-        permissionBackend.currentUser().project(nameKey).check(permissionToCheck);
-      } catch (AuthException e) {
-        return null; // Pretend like not found on access denied.
-      }
-
-      if (!state.statePermitsRead()) {
-        // If the project's state does not permit reading, we want to hide it from all callers. The
-        // only exception to that are users who are allowed to mutate the project's configuration.
-        // This enables these users to still mutate the project's state (e.g. set a HIDDEN project
-        // to ACTIVE). Individual views should still check for checkStatePermitsRead() and this
-        // should just serve as a safety net in case the individual check is forgotten.
+      if (state.statePermitsRead()) {
+        try {
+          permissionBackend.currentUser().project(nameKey).check(ProjectPermission.ACCESS);
+        } catch (AuthException e) {
+          return null;
+        }
+      } else {
         try {
           permissionBackend.currentUser().project(nameKey).check(ProjectPermission.WRITE_CONFIG);
         } catch (AuthException e) {
diff --git a/java/com/google/gerrit/server/restapi/project/SetParent.java b/java/com/google/gerrit/server/restapi/project/SetParent.java
index b38619b..d4e67b1 100644
--- a/java/com/google/gerrit/server/restapi/project/SetParent.java
+++ b/java/com/google/gerrit/server/restapi/project/SetParent.java
@@ -30,6 +30,9 @@
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.config.ConfigKey;
+import com.google.gerrit.server.config.ConfigUpdatedEvent;
+import com.google.gerrit.server.config.GerritConfigListener;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.git.meta.MetaDataUpdate;
 import com.google.gerrit.server.permissions.GlobalPermission;
@@ -43,18 +46,21 @@
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
 import org.eclipse.jgit.lib.Config;
 
 @Singleton
-public class SetParent implements RestModifyView<ProjectResource, ParentInput> {
+public class SetParent
+    implements RestModifyView<ProjectResource, ParentInput>, GerritConfigListener {
   private final ProjectCache cache;
   private final PermissionBackend permissionBackend;
   private final MetaDataUpdate.Server updateFactory;
   private final AllProjectsName allProjects;
   private final AllUsersName allUsers;
-  private final boolean allowProjectOwnersToChangeParent;
+  private volatile boolean allowProjectOwnersToChangeParent;
 
   @Inject
   SetParent(
@@ -164,4 +170,20 @@
       }
     }
   }
+
+  @Override
+  public List<ConfigUpdatedEvent.Update> configUpdated(ConfigUpdatedEvent event) {
+    ConfigKey receiveSetParent = ConfigKey.create("receive", "allowProjectOwnersToChangeParent");
+    if (!event.isValueUpdated(receiveSetParent)) {
+      return Collections.emptyList();
+    }
+    try {
+      boolean enabled =
+          event.getNewConfig().getBoolean("receive", "allowProjectOwnersToChangeParent", false);
+      this.allowProjectOwnersToChangeParent = enabled;
+      return Collections.singletonList(event.accept(receiveSetParent));
+    } catch (IllegalArgumentException iae) {
+      return Collections.singletonList(event.reject(receiveSetParent));
+    }
+  }
 }
diff --git a/java/com/google/gerrit/server/schema/BUILD b/java/com/google/gerrit/server/schema/BUILD
index 44bede9..a04def6 100644
--- a/java/com/google/gerrit/server/schema/BUILD
+++ b/java/com/google/gerrit/server/schema/BUILD
@@ -12,6 +12,7 @@
         "//java/com/google/gerrit/metrics",
         "//java/com/google/gerrit/reviewdb:server",
         "//java/com/google/gerrit/server",
+        "//java/com/google/gerrit/server/util/time",
         "//java/org/eclipse/jgit:server",
         "//lib:guava",
         "//lib:gwtorm",
diff --git a/java/com/google/gerrit/server/schema/GroupBundle.java b/java/com/google/gerrit/server/schema/GroupBundle.java
index a15cedc..26cd96a 100644
--- a/java/com/google/gerrit/server/schema/GroupBundle.java
+++ b/java/com/google/gerrit/server/schema/GroupBundle.java
@@ -31,7 +31,6 @@
 import com.google.common.collect.Multimaps;
 import com.google.common.collect.Streams;
 import com.google.common.flogger.FluentLogger;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.AccountGroupById;
@@ -44,6 +43,7 @@
 import com.google.gerrit.server.group.InternalGroup;
 import com.google.gerrit.server.group.db.AuditLogReader;
 import com.google.gerrit.server.group.db.GroupConfig;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gwtorm.jdbc.JdbcSchema;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
diff --git a/java/com/google/gerrit/server/schema/GroupRebuilder.java b/java/com/google/gerrit/server/schema/GroupRebuilder.java
index be8dcff..54cbb86 100644
--- a/java/com/google/gerrit/server/schema/GroupRebuilder.java
+++ b/java/com/google/gerrit/server/schema/GroupRebuilder.java
@@ -25,7 +25,6 @@
 import com.google.common.collect.MultimapBuilder;
 import com.google.common.collect.Sets;
 import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.AccountGroupById;
@@ -43,6 +42,7 @@
 import com.google.gerrit.server.group.db.InternalGroupUpdate;
 import com.google.gerrit.server.group.db.InternalGroupUpdate.MemberModification;
 import com.google.gerrit.server.group.db.InternalGroupUpdate.SubgroupModification;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gwtorm.server.OrmDuplicateKeyException;
 import java.io.IOException;
 import java.sql.Timestamp;
diff --git a/java/com/google/gerrit/server/submit/MergeOp.java b/java/com/google/gerrit/server/submit/MergeOp.java
index 43d5f75..c233da8 100644
--- a/java/com/google/gerrit/server/submit/MergeOp.java
+++ b/java/com/google/gerrit/server/submit/MergeOp.java
@@ -33,7 +33,6 @@
 import com.google.common.collect.SetMultimap;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.data.SubmitRecord;
 import com.google.gerrit.common.data.SubmitRequirement;
 import com.google.gerrit.common.data.SubmitTypeRecord;
@@ -79,6 +78,7 @@
 import com.google.gerrit.server.update.RetryHelper;
 import com.google.gerrit.server.update.RetryHelper.ActionType;
 import com.google.gerrit.server.update.UpdateException;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
diff --git a/java/com/google/gerrit/server/util/time/BUILD b/java/com/google/gerrit/server/util/time/BUILD
new file mode 100644
index 0000000..1d1305d
--- /dev/null
+++ b/java/com/google/gerrit/server/util/time/BUILD
@@ -0,0 +1,9 @@
+java_library(
+    name = "time",
+    srcs = glob(["**/*.java"]),
+    visibility = ["//visibility:public"],
+    deps = [
+        "//lib:guava",
+        "//lib/jgit/org.eclipse.jgit:jgit",
+    ],
+)
diff --git a/java/com/google/gerrit/common/TimeUtil.java b/java/com/google/gerrit/server/util/time/TimeUtil.java
similarity index 95%
rename from java/com/google/gerrit/common/TimeUtil.java
rename to java/com/google/gerrit/server/util/time/TimeUtil.java
index e42eb09..645dbb9 100644
--- a/java/com/google/gerrit/common/TimeUtil.java
+++ b/java/com/google/gerrit/server/util/time/TimeUtil.java
@@ -12,9 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.common;
+package com.google.gerrit.server.util.time;
 
-import com.google.common.annotations.GwtIncompatible;
 import com.google.common.annotations.VisibleForTesting;
 import java.sql.Timestamp;
 import java.time.Instant;
@@ -25,7 +24,6 @@
 import org.eclipse.jgit.util.SystemReader;
 
 /** Static utility methods for dealing with dates and times. */
-@GwtIncompatible("Unemulated Java 8 functionalities")
 public class TimeUtil {
   private static final LongSupplier SYSTEM_CURRENT_MILLIS_SUPPLIER = System::currentTimeMillis;
 
diff --git a/java/com/google/gerrit/sshd/AliasCommand.java b/java/com/google/gerrit/sshd/AliasCommand.java
index cb61651..567cf00 100644
--- a/java/com/google/gerrit/sshd/AliasCommand.java
+++ b/java/com/google/gerrit/sshd/AliasCommand.java
@@ -26,8 +26,8 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicReference;
-import org.apache.sshd.server.Command;
 import org.apache.sshd.server.Environment;
+import org.apache.sshd.server.command.Command;
 
 /** Command that executes some other command. */
 public class AliasCommand extends BaseCommand {
diff --git a/java/com/google/gerrit/sshd/AliasCommandProvider.java b/java/com/google/gerrit/sshd/AliasCommandProvider.java
index 085b6d6..58e2559 100644
--- a/java/com/google/gerrit/sshd/AliasCommandProvider.java
+++ b/java/com/google/gerrit/sshd/AliasCommandProvider.java
@@ -17,7 +17,7 @@
 import com.google.gerrit.server.permissions.PermissionBackend;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
-import org.apache.sshd.server.Command;
+import org.apache.sshd.server.command.Command;
 
 /** Resolves an alias to another command. */
 public class AliasCommandProvider implements Provider<Command> {
diff --git a/java/com/google/gerrit/sshd/BUILD b/java/com/google/gerrit/sshd/BUILD
index 47b1d89..8e1f112 100644
--- a/java/com/google/gerrit/sshd/BUILD
+++ b/java/com/google/gerrit/sshd/BUILD
@@ -18,6 +18,7 @@
         "//java/com/google/gerrit/server/logging",
         "//java/com/google/gerrit/server/restapi",
         "//java/com/google/gerrit/server/schema",
+        "//java/com/google/gerrit/server/util/time",
         "//java/com/google/gerrit/util/cli",
         "//java/org/eclipse/jgit:server",
         "//lib:args4j",
diff --git a/java/com/google/gerrit/sshd/BaseCommand.java b/java/com/google/gerrit/sshd/BaseCommand.java
index dae9016..1d19a8a 100644
--- a/java/com/google/gerrit/sshd/BaseCommand.java
+++ b/java/com/google/gerrit/sshd/BaseCommand.java
@@ -20,7 +20,6 @@
 import com.google.common.flogger.FluentLogger;
 import com.google.common.util.concurrent.Atomics;
 import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.restapi.AuthException;
@@ -36,6 +35,7 @@
 import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.project.NoSuchProjectException;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.sshd.SshScope.Context;
 import com.google.gerrit.util.cli.CmdLineParser;
 import com.google.gerrit.util.cli.EndOfOptionsHandler;
@@ -54,9 +54,9 @@
 import java.util.concurrent.ScheduledThreadPoolExecutor;
 import java.util.concurrent.atomic.AtomicReference;
 import org.apache.sshd.common.SshException;
-import org.apache.sshd.server.Command;
 import org.apache.sshd.server.Environment;
 import org.apache.sshd.server.ExitCallback;
+import org.apache.sshd.server.command.Command;
 import org.kohsuke.args4j.Argument;
 import org.kohsuke.args4j.CmdLineException;
 import org.kohsuke.args4j.Option;
diff --git a/java/com/google/gerrit/sshd/CommandFactoryProvider.java b/java/com/google/gerrit/sshd/CommandFactoryProvider.java
index 1fdf7d8..3fb2ed4 100644
--- a/java/com/google/gerrit/sshd/CommandFactoryProvider.java
+++ b/java/com/google/gerrit/sshd/CommandFactoryProvider.java
@@ -39,11 +39,11 @@
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
-import org.apache.sshd.server.Command;
-import org.apache.sshd.server.CommandFactory;
 import org.apache.sshd.server.Environment;
 import org.apache.sshd.server.ExitCallback;
 import org.apache.sshd.server.SessionAware;
+import org.apache.sshd.server.command.Command;
+import org.apache.sshd.server.command.CommandFactory;
 import org.apache.sshd.server.session.ServerSession;
 import org.eclipse.jgit.lib.Config;
 
diff --git a/java/com/google/gerrit/sshd/CommandModule.java b/java/com/google/gerrit/sshd/CommandModule.java
index 93aab0b..ac07056 100644
--- a/java/com/google/gerrit/sshd/CommandModule.java
+++ b/java/com/google/gerrit/sshd/CommandModule.java
@@ -16,7 +16,7 @@
 
 import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.inject.binder.LinkedBindingBuilder;
-import org.apache.sshd.server.Command;
+import org.apache.sshd.server.command.Command;
 
 /** Module to register commands in the SSH daemon. */
 public abstract class CommandModule extends LifecycleModule {
diff --git a/java/com/google/gerrit/sshd/CommandProvider.java b/java/com/google/gerrit/sshd/CommandProvider.java
index 61c36cb..cf2e84c 100644
--- a/java/com/google/gerrit/sshd/CommandProvider.java
+++ b/java/com/google/gerrit/sshd/CommandProvider.java
@@ -15,7 +15,7 @@
 package com.google.gerrit.sshd;
 
 import com.google.inject.Provider;
-import org.apache.sshd.server.Command;
+import org.apache.sshd.server.command.Command;
 
 final class CommandProvider {
 
diff --git a/java/com/google/gerrit/sshd/Commands.java b/java/com/google/gerrit/sshd/Commands.java
index 43d2c50..b6d3401 100644
--- a/java/com/google/gerrit/sshd/Commands.java
+++ b/java/com/google/gerrit/sshd/Commands.java
@@ -17,7 +17,7 @@
 import com.google.auto.value.AutoAnnotation;
 import com.google.inject.Key;
 import java.lang.annotation.Annotation;
-import org.apache.sshd.server.Command;
+import org.apache.sshd.server.command.Command;
 
 /** Utilities to support {@link CommandName} construction. */
 public class Commands {
diff --git a/java/com/google/gerrit/sshd/DispatchCommand.java b/java/com/google/gerrit/sshd/DispatchCommand.java
index 490dd52..4c9ca91 100644
--- a/java/com/google/gerrit/sshd/DispatchCommand.java
+++ b/java/com/google/gerrit/sshd/DispatchCommand.java
@@ -31,8 +31,8 @@
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicReference;
-import org.apache.sshd.server.Command;
 import org.apache.sshd.server.Environment;
+import org.apache.sshd.server.command.Command;
 import org.kohsuke.args4j.Argument;
 
 /** Command that dispatches to a subcommand from its command table. */
diff --git a/java/com/google/gerrit/sshd/DispatchCommandProvider.java b/java/com/google/gerrit/sshd/DispatchCommandProvider.java
index c782d2f..7ff7224 100644
--- a/java/com/google/gerrit/sshd/DispatchCommandProvider.java
+++ b/java/com/google/gerrit/sshd/DispatchCommandProvider.java
@@ -24,7 +24,7 @@
 import java.lang.annotation.Annotation;
 import java.util.List;
 import java.util.concurrent.ConcurrentMap;
-import org.apache.sshd.server.Command;
+import org.apache.sshd.server.command.Command;
 
 /** Creates DispatchCommand using commands registered by {@link CommandModule}. */
 public class DispatchCommandProvider implements Provider<DispatchCommand> {
diff --git a/java/com/google/gerrit/sshd/NoShell.java b/java/com/google/gerrit/sshd/NoShell.java
index ba0dcba..0235554 100644
--- a/java/com/google/gerrit/sshd/NoShell.java
+++ b/java/com/google/gerrit/sshd/NoShell.java
@@ -30,10 +30,10 @@
 import java.net.MalformedURLException;
 import java.net.URL;
 import org.apache.sshd.common.Factory;
-import org.apache.sshd.server.Command;
 import org.apache.sshd.server.Environment;
 import org.apache.sshd.server.ExitCallback;
 import org.apache.sshd.server.SessionAware;
+import org.apache.sshd.server.command.Command;
 import org.apache.sshd.server.session.ServerSession;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.util.SystemReader;
diff --git a/java/com/google/gerrit/sshd/PluginCommandModule.java b/java/com/google/gerrit/sshd/PluginCommandModule.java
index b0116e4..984c8a6 100644
--- a/java/com/google/gerrit/sshd/PluginCommandModule.java
+++ b/java/com/google/gerrit/sshd/PluginCommandModule.java
@@ -18,7 +18,7 @@
 import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.inject.Inject;
 import com.google.inject.binder.LinkedBindingBuilder;
-import org.apache.sshd.server.Command;
+import org.apache.sshd.server.command.Command;
 
 public abstract class PluginCommandModule extends CommandModule {
   private CommandName command;
diff --git a/java/com/google/gerrit/sshd/SingleCommandPluginModule.java b/java/com/google/gerrit/sshd/SingleCommandPluginModule.java
index 079661a..5b602f4 100644
--- a/java/com/google/gerrit/sshd/SingleCommandPluginModule.java
+++ b/java/com/google/gerrit/sshd/SingleCommandPluginModule.java
@@ -18,7 +18,7 @@
 import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.inject.Inject;
 import com.google.inject.binder.LinkedBindingBuilder;
-import org.apache.sshd.server.Command;
+import org.apache.sshd.server.command.Command;
 
 /**
  * Binds one SSH command to the plugin name itself.
diff --git a/java/com/google/gerrit/sshd/SshAutoRegisterModuleGenerator.java b/java/com/google/gerrit/sshd/SshAutoRegisterModuleGenerator.java
index 0fdde81..830dba7 100644
--- a/java/com/google/gerrit/sshd/SshAutoRegisterModuleGenerator.java
+++ b/java/com/google/gerrit/sshd/SshAutoRegisterModuleGenerator.java
@@ -28,7 +28,7 @@
 import java.lang.annotation.Annotation;
 import java.util.HashMap;
 import java.util.Map;
-import org.apache.sshd.server.Command;
+import org.apache.sshd.server.command.Command;
 
 class SshAutoRegisterModuleGenerator extends AbstractModule implements ModuleGenerator {
   private final Map<String, Class<Command>> commands = new HashMap<>();
diff --git a/java/com/google/gerrit/sshd/SshDaemon.java b/java/com/google/gerrit/sshd/SshDaemon.java
index 688c573..ef356f1 100644
--- a/java/com/google/gerrit/sshd/SshDaemon.java
+++ b/java/com/google/gerrit/sshd/SshDaemon.java
@@ -93,8 +93,6 @@
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
 import org.apache.sshd.common.util.net.SshdSocketAddress;
 import org.apache.sshd.common.util.security.SecurityUtils;
-import org.apache.sshd.server.Command;
-import org.apache.sshd.server.CommandFactory;
 import org.apache.sshd.server.ServerBuilder;
 import org.apache.sshd.server.SshServer;
 import org.apache.sshd.server.auth.UserAuth;
@@ -102,6 +100,8 @@
 import org.apache.sshd.server.auth.gss.UserAuthGSSFactory;
 import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator;
 import org.apache.sshd.server.auth.pubkey.UserAuthPublicKeyFactory;
+import org.apache.sshd.server.command.Command;
+import org.apache.sshd.server.command.CommandFactory;
 import org.apache.sshd.server.forward.ForwardingFilter;
 import org.apache.sshd.server.global.CancelTcpipForwardHandler;
 import org.apache.sshd.server.global.KeepAliveHandler;
diff --git a/java/com/google/gerrit/sshd/SshLog.java b/java/com/google/gerrit/sshd/SshLog.java
index 98c649d..0e34889 100644
--- a/java/com/google/gerrit/sshd/SshLog.java
+++ b/java/com/google/gerrit/sshd/SshLog.java
@@ -16,7 +16,6 @@
 
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.MultimapBuilder;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
@@ -29,6 +28,7 @@
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.ioutil.HexFormat;
 import com.google.gerrit.server.util.SystemLog;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.sshd.SshScope.Context;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -323,16 +323,20 @@
     if (!event.isValueUpdated(sshdRequestLog)) {
       return Collections.emptyList();
     }
-
-    boolean enabled = event.getNewConfig().getBoolean("sshd", "requestLog", true);
     boolean stateUpdated;
-    if (enabled) {
-      stateUpdated = enableLogging();
-    } else {
-      stateUpdated = disableLogging();
+    try {
+      boolean enabled = event.getNewConfig().getBoolean("sshd", "requestLog", true);
+
+      if (enabled) {
+        stateUpdated = enableLogging();
+      } else {
+        stateUpdated = disableLogging();
+      }
+      return stateUpdated
+          ? Collections.singletonList(event.accept(sshdRequestLog))
+          : Collections.emptyList();
+    } catch (IllegalArgumentException iae) {
+      return Collections.singletonList(event.reject(sshdRequestLog));
     }
-    return stateUpdated
-        ? Collections.singletonList(event.accept(sshdRequestLog))
-        : Collections.emptyList();
   }
 }
diff --git a/java/com/google/gerrit/sshd/SshModule.java b/java/com/google/gerrit/sshd/SshModule.java
index f047017..acdc958 100644
--- a/java/com/google/gerrit/sshd/SshModule.java
+++ b/java/com/google/gerrit/sshd/SshModule.java
@@ -45,9 +45,9 @@
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
-import org.apache.sshd.server.CommandFactory;
 import org.apache.sshd.server.auth.gss.GSSAuthenticator;
 import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator;
+import org.apache.sshd.server.command.CommandFactory;
 import org.eclipse.jgit.lib.Config;
 
 /** Configures standard dependencies for {@link SshDaemon}. */
diff --git a/java/com/google/gerrit/sshd/SshPluginStarterCallback.java b/java/com/google/gerrit/sshd/SshPluginStarterCallback.java
index e9a095f..6e8590c 100644
--- a/java/com/google/gerrit/sshd/SshPluginStarterCallback.java
+++ b/java/com/google/gerrit/sshd/SshPluginStarterCallback.java
@@ -24,7 +24,7 @@
 import com.google.inject.Key;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
-import org.apache.sshd.server.Command;
+import org.apache.sshd.server.command.Command;
 
 @Singleton
 class SshPluginStarterCallback implements StartPluginListener, ReloadPluginListener {
diff --git a/java/com/google/gerrit/sshd/SshScope.java b/java/com/google/gerrit/sshd/SshScope.java
index 2659831..99787d8 100644
--- a/java/com/google/gerrit/sshd/SshScope.java
+++ b/java/com/google/gerrit/sshd/SshScope.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.sshd;
 
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
@@ -23,6 +22,7 @@
 import com.google.gerrit.server.util.RequestContext;
 import com.google.gerrit.server.util.ThreadLocalRequestContext;
 import com.google.gerrit.server.util.ThreadLocalRequestScopePropagator;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gwtorm.server.SchemaFactory;
 import com.google.inject.Inject;
 import com.google.inject.Key;
diff --git a/java/com/google/gerrit/sshd/SuExec.java b/java/com/google/gerrit/sshd/SuExec.java
index 54371c1..7053a0d 100644
--- a/java/com/google/gerrit/sshd/SuExec.java
+++ b/java/com/google/gerrit/sshd/SuExec.java
@@ -34,8 +34,8 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicReference;
-import org.apache.sshd.server.Command;
 import org.apache.sshd.server.Environment;
+import org.apache.sshd.server.command.Command;
 import org.kohsuke.args4j.Argument;
 import org.kohsuke.args4j.Option;
 
diff --git a/java/com/google/gerrit/sshd/commands/CloseConnection.java b/java/com/google/gerrit/sshd/commands/CloseConnection.java
index a38461d..60a878a 100644
--- a/java/com/google/gerrit/sshd/commands/CloseConnection.java
+++ b/java/com/google/gerrit/sshd/commands/CloseConnection.java
@@ -53,7 +53,7 @@
       required = true,
       metaVar = "SESSION_ID",
       usage = "List of SSH session IDs to be closed")
-  private final List<String> sessionIds = new ArrayList<>();
+  private List<String> sessionIds = new ArrayList<>();
 
   @Option(name = "--wait", usage = "wait for connection to close before exiting")
   private boolean wait;
diff --git a/java/com/google/gerrit/sshd/commands/QueryShell.java b/java/com/google/gerrit/sshd/commands/QueryShell.java
index 9651f39..fe102ab 100644
--- a/java/com/google/gerrit/sshd/commands/QueryShell.java
+++ b/java/com/google/gerrit/sshd/commands/QueryShell.java
@@ -16,11 +16,11 @@
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.Version;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.reviewdb.server.ReviewDbUtil;
 import com.google.gerrit.server.schema.ReviewDbFactory;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gson.JsonArray;
 import com.google.gson.JsonObject;
 import com.google.gwtorm.jdbc.JdbcSchema;
diff --git a/java/com/google/gerrit/sshd/commands/ShowCaches.java b/java/com/google/gerrit/sshd/commands/ShowCaches.java
index 3c95884..e4c14d8 100644
--- a/java/com/google/gerrit/sshd/commands/ShowCaches.java
+++ b/java/com/google/gerrit/sshd/commands/ShowCaches.java
@@ -19,7 +19,6 @@
 import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
 
 import com.google.common.base.Strings;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.Version;
 import com.google.gerrit.extensions.annotations.RequiresAnyCapability;
 import com.google.gerrit.extensions.events.LifecycleListener;
@@ -38,6 +37,7 @@
 import com.google.gerrit.server.restapi.config.ListCaches;
 import com.google.gerrit.server.restapi.config.ListCaches.CacheInfo;
 import com.google.gerrit.server.restapi.config.ListCaches.CacheType;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.sshd.CommandMetaData;
 import com.google.gerrit.sshd.SshCommand;
 import com.google.gerrit.sshd.SshDaemon;
diff --git a/java/com/google/gerrit/sshd/commands/ShowConnections.java b/java/com/google/gerrit/sshd/commands/ShowConnections.java
index baadf02..0faf803 100644
--- a/java/com/google/gerrit/sshd/commands/ShowConnections.java
+++ b/java/com/google/gerrit/sshd/commands/ShowConnections.java
@@ -19,12 +19,12 @@
 import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
 
 import com.google.common.collect.ImmutableList;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.ioutil.HexFormat;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.sshd.CommandMetaData;
 import com.google.gerrit.sshd.SshCommand;
 import com.google.gerrit.sshd.SshDaemon;
diff --git a/java/com/google/gerrit/sshd/commands/ShowQueue.java b/java/com/google/gerrit/sshd/commands/ShowQueue.java
index 5d7fdbf..2a7bd6e 100644
--- a/java/com/google/gerrit/sshd/commands/ShowQueue.java
+++ b/java/com/google/gerrit/sshd/commands/ShowQueue.java
@@ -19,7 +19,6 @@
 import com.google.common.base.MoreObjects;
 import com.google.common.collect.LinkedListMultimap;
 import com.google.common.collect.ListMultimap;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.config.ConfigResource;
@@ -30,6 +29,7 @@
 import com.google.gerrit.server.permissions.PermissionBackendException;
 import com.google.gerrit.server.restapi.config.ListTasks;
 import com.google.gerrit.server.restapi.config.ListTasks.TaskInfo;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.sshd.AdminHighPriorityCommand;
 import com.google.gerrit.sshd.CommandMetaData;
 import com.google.gerrit.sshd.SshCommand;
diff --git a/java/com/google/gerrit/testing/BUILD b/java/com/google/gerrit/testing/BUILD
index 15ceb77..412a071 100644
--- a/java/com/google/gerrit/testing/BUILD
+++ b/java/com/google/gerrit/testing/BUILD
@@ -32,6 +32,7 @@
         "//java/com/google/gerrit/server/logging",
         "//java/com/google/gerrit/server/restapi",
         "//java/com/google/gerrit/server/schema",
+        "//java/com/google/gerrit/server/util/time",
         "//lib:guava",
         "//lib:gwtorm",
         "//lib:h2",
diff --git a/java/com/google/gerrit/testing/FakeAccountCache.java b/java/com/google/gerrit/testing/FakeAccountCache.java
index 224a5bf..b99a32d 100644
--- a/java/com/google/gerrit/testing/FakeAccountCache.java
+++ b/java/com/google/gerrit/testing/FakeAccountCache.java
@@ -17,12 +17,12 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
 import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.account.AccountCache;
 import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.config.AllUsersName;
 import com.google.gerrit.server.config.AllUsersNameProvider;
+import com.google.gerrit.server.util.time.TimeUtil;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Optional;
diff --git a/java/com/google/gerrit/testing/TestChanges.java b/java/com/google/gerrit/testing/TestChanges.java
index 31ef805..8e752fa 100644
--- a/java/com/google/gerrit/testing/TestChanges.java
+++ b/java/com/google/gerrit/testing/TestChanges.java
@@ -17,7 +17,6 @@
 import static com.google.common.base.MoreObjects.firstNonNull;
 
 import com.google.common.collect.Ordering;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.config.FactoryModule;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Branch;
@@ -32,6 +31,7 @@
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.notedb.ChangeUpdate;
 import com.google.gerrit.server.notedb.NotesMigration;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.inject.Injector;
 import java.util.TimeZone;
 import java.util.concurrent.atomic.AtomicInteger;
diff --git a/java/com/google/gerrit/testing/TestTimeUtil.java b/java/com/google/gerrit/testing/TestTimeUtil.java
index 1233810..9228123 100644
--- a/java/com/google/gerrit/testing/TestTimeUtil.java
+++ b/java/com/google/gerrit/testing/TestTimeUtil.java
@@ -17,7 +17,7 @@
 import static com.google.common.base.Preconditions.checkState;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 
-import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.server.util.time.TimeUtil;
 import java.sql.Timestamp;
 import java.time.Instant;
 import java.time.LocalDateTime;
@@ -51,7 +51,7 @@
   }
 
   /**
-   * Set the clock step used by {@link com.google.gerrit.common.TimeUtil}.
+   * Set the clock step used by {@link com.google.gerrit.server.util.time.TimeUtil}.
    *
    * @param clockStep amount to increment clock by on each lookup.
    * @param clockStepUnit time unit for {@code clockStep}.
diff --git a/javatests/com/google/gerrit/acceptance/BUILD b/javatests/com/google/gerrit/acceptance/BUILD
index 9246abb..32804ef 100644
--- a/javatests/com/google/gerrit/acceptance/BUILD
+++ b/javatests/com/google/gerrit/acceptance/BUILD
@@ -5,6 +5,7 @@
     srcs = glob(["**/*.java"]),
     deps = [
         "//java/com/google/gerrit/acceptance:lib",
+        "//java/com/google/gerrit/server/util/time",
         "//lib:guava",
         "//lib/jgit/org.eclipse.jgit:jgit",
         "//lib/truth",
diff --git a/javatests/com/google/gerrit/acceptance/ProjectResetterTest.java b/javatests/com/google/gerrit/acceptance/ProjectResetterTest.java
index 1844ec6..53d8ef8 100644
--- a/javatests/com/google/gerrit/acceptance/ProjectResetterTest.java
+++ b/javatests/com/google/gerrit/acceptance/ProjectResetterTest.java
@@ -18,7 +18,6 @@
 
 import com.google.common.collect.ImmutableSet;
 import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
@@ -31,6 +30,7 @@
 import com.google.gerrit.server.index.account.AccountIndexer;
 import com.google.gerrit.server.index.group.GroupIndexer;
 import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.testing.GerritBaseTests;
 import com.google.gerrit.testing.InMemoryRepositoryManager;
 import com.google.gerrit.testing.TestTimeUtil;
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
index 013d153..c0c8709 100644
--- a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
@@ -57,7 +57,6 @@
 import com.google.gerrit.acceptance.testsuite.account.AccountOperations;
 import com.google.gerrit.acceptance.testsuite.account.TestSshKeys;
 import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.data.AccessSection;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.common.data.GroupReference;
@@ -125,6 +124,7 @@
 import com.google.gerrit.server.query.account.InternalAccountQuery;
 import com.google.gerrit.server.update.RetryHelper;
 import com.google.gerrit.server.util.MagicBranch;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.server.validators.AccountActivationValidationListener;
 import com.google.gerrit.server.validators.ValidationException;
 import com.google.gerrit.testing.ConfigSuite;
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/BUILD b/javatests/com/google/gerrit/acceptance/api/accounts/BUILD
index 9bc30ba..31f3f91 100644
--- a/javatests/com/google/gerrit/acceptance/api/accounts/BUILD
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/BUILD
@@ -8,5 +8,8 @@
         "noci",
         "no_windows",
     ],
-    deps = ["//java/com/google/gerrit/mail"],
+    deps = [
+        "//java/com/google/gerrit/mail",
+        "//java/com/google/gerrit/server/util/time",
+    ],
 )
diff --git a/javatests/com/google/gerrit/acceptance/api/change/BUILD b/javatests/com/google/gerrit/acceptance/api/change/BUILD
index fd6c6d1..9279488 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/BUILD
+++ b/javatests/com/google/gerrit/acceptance/api/change/BUILD
@@ -7,4 +7,5 @@
         "api",
         "noci",
     ],
+    deps = ["//java/com/google/gerrit/server/util/time"],
 )
diff --git a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
index 88803ec..395a797 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -69,7 +69,6 @@
 import com.google.gerrit.acceptance.TestProjectInput;
 import com.google.gerrit.acceptance.testsuite.account.AccountOperations;
 import com.google.gerrit.common.FooterConstants;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.data.LabelFunction;
 import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.common.data.Permission;
@@ -146,6 +145,7 @@
 import com.google.gerrit.server.update.BatchUpdate;
 import com.google.gerrit.server.update.BatchUpdateOp;
 import com.google.gerrit.server.update.ChangeContext;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.testing.FakeEmailSender.Message;
 import com.google.gerrit.testing.TestTimeUtil;
 import com.google.inject.Inject;
diff --git a/javatests/com/google/gerrit/acceptance/api/group/BUILD b/javatests/com/google/gerrit/acceptance/api/group/BUILD
index a0b70cc..da36a02 100644
--- a/javatests/com/google/gerrit/acceptance/api/group/BUILD
+++ b/javatests/com/google/gerrit/acceptance/api/group/BUILD
@@ -8,6 +8,7 @@
         ":util",
         "//java/com/google/gerrit/server/group/db/testing",
         "//java/com/google/gerrit/server/group/testing",
+        "//java/com/google/gerrit/server/util/time",
         "//java/com/google/gerrit/truth",
         "//javatests/com/google/gerrit/acceptance/rest/account:util",
     ],
diff --git a/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java b/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java
index d6be960..ade0f3c 100644
--- a/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java
@@ -40,7 +40,6 @@
 import com.google.gerrit.acceptance.TestAccount;
 import com.google.gerrit.acceptance.testsuite.account.AccountOperations;
 import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.common.data.GroupReference;
 import com.google.gerrit.common.data.Permission;
@@ -82,6 +81,7 @@
 import com.google.gerrit.server.index.group.GroupIndexer;
 import com.google.gerrit.server.index.group.StalenessChecker;
 import com.google.gerrit.server.util.MagicBranch;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.testing.TestTimeUtil;
 import com.google.inject.Inject;
 import java.io.IOException;
diff --git a/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java b/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java
index d9d730c..9fcbaa7 100644
--- a/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java
@@ -49,6 +49,7 @@
 import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.group.SystemGroupBackend;
 import com.google.gerrit.server.index.IndexExecutor;
 import com.google.inject.Inject;
 import org.eclipse.jgit.revwalk.RevCommit;
@@ -396,6 +397,26 @@
   }
 
   @Test
+  public void nonActiveProjectCanBeMadeActiveByHostAdmin() throws Exception {
+    // ACTIVE => HIDDEN
+    ConfigInput ci1 = new ConfigInput();
+    ci1.state = ProjectState.HIDDEN;
+    gApi.projects().name(project.get()).config(ci1);
+    assertThat(gApi.projects().name(project.get()).config().state).isEqualTo(ProjectState.HIDDEN);
+
+    // Revoke OWNER permission for admin and block them from reading the project's refs
+    block(project, RefNames.REFS + "*", Permission.OWNER, SystemGroupBackend.REGISTERED_USERS);
+    block(project, RefNames.REFS + "*", Permission.READ, SystemGroupBackend.REGISTERED_USERS);
+
+    // HIDDEN => ACTIVE
+    ConfigInput ci2 = new ConfigInput();
+    ci2.state = ProjectState.ACTIVE;
+    gApi.projects().name(project.get()).config(ci2);
+    // ACTIVE is represented as null in the API
+    assertThat(gApi.projects().name(project.get()).config().state).isNull();
+  }
+
+  @Test
   public void reindexProject() throws Exception {
     createProject("child", project);
     projectIndexedCounter.clear();
diff --git a/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java b/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
index b74fc16..8891dee 100644
--- a/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
+++ b/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
@@ -231,6 +231,15 @@
   }
 
   @Test
+  @GerritConfig(name = "receive.enableSignedPush", value = "true")
+  @TestProjectInput(
+      enableSignedPush = InheritableBoolean.TRUE,
+      requireSignedPush = InheritableBoolean.TRUE)
+  public void nonSignedPushRejectedWhenSignPushRequired() throws Exception {
+    pushTo("refs/for/master").assertErrorStatus("push cert error");
+  }
+
+  @Test
   public void pushInitialCommitForRefsMetaConfigBranch() throws Exception {
     // delete refs/meta/config
     try (Repository repo = repoManager.openRepository(project);
@@ -405,6 +414,7 @@
     PushOneCommit.Result r = pushTo("refs/for/master/" + topic);
     r.assertOkStatus();
     r.assertChange(Change.Status.NEW, topic);
+    r.assertMessage("deprecated topic syntax");
 
     // specify topic as option
     r = pushTo("refs/for/master%topic=" + topic);
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java b/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
index af11149..08a76e4 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
@@ -38,7 +38,6 @@
 import com.google.gerrit.acceptance.TestAccount;
 import com.google.gerrit.acceptance.TestProjectInput;
 import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.extensions.api.changes.ChangeApi;
 import com.google.gerrit.extensions.api.changes.SubmitInput;
@@ -78,6 +77,7 @@
 import com.google.gerrit.server.update.BatchUpdate;
 import com.google.gerrit.server.update.BatchUpdateOp;
 import com.google.gerrit.server.update.ChangeContext;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.server.validators.ValidationException;
 import com.google.gerrit.testing.ConfigSuite;
 import com.google.gerrit.testing.TestTimeUtil;
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/BUILD b/javatests/com/google/gerrit/acceptance/rest/change/BUILD
index 46eb0ba..d955f1b 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/BUILD
+++ b/javatests/com/google/gerrit/acceptance/rest/change/BUILD
@@ -35,5 +35,6 @@
     deps = [
         "//java/com/google/gerrit/acceptance:lib",
         "//java/com/google/gerrit/server/restapi",
+        "//java/com/google/gerrit/server/util/time",
     ],
 )
diff --git a/javatests/com/google/gerrit/acceptance/server/change/BUILD b/javatests/com/google/gerrit/acceptance/server/change/BUILD
index 6c79618..4d1634d 100644
--- a/javatests/com/google/gerrit/acceptance/server/change/BUILD
+++ b/javatests/com/google/gerrit/acceptance/server/change/BUILD
@@ -4,4 +4,5 @@
     srcs = glob(["*IT.java"]),
     group = "server_change",
     labels = ["server"],
+    deps = ["//java/com/google/gerrit/server/util/time"],
 )
diff --git a/javatests/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java b/javatests/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java
index 29c043a..5c3a703 100644
--- a/javatests/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java
@@ -27,7 +27,6 @@
 import com.google.gerrit.acceptance.TestAccount;
 import com.google.gerrit.common.FooterConstants;
 import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.api.changes.FixInput;
 import com.google.gerrit.extensions.api.changes.NotifyHandling;
 import com.google.gerrit.extensions.client.ChangeStatus;
@@ -52,6 +51,7 @@
 import com.google.gerrit.server.update.BatchUpdateOp;
 import com.google.gerrit.server.update.ChangeContext;
 import com.google.gerrit.server.update.RepoContext;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.testing.InMemoryRepositoryManager;
 import com.google.gerrit.testing.NoteDbMode;
 import com.google.gerrit.testing.TestChanges;
diff --git a/javatests/com/google/gerrit/acceptance/server/change/GetRelatedIT.java b/javatests/com/google/gerrit/acceptance/server/change/GetRelatedIT.java
index 70ca5f5..5d3b223 100644
--- a/javatests/com/google/gerrit/acceptance/server/change/GetRelatedIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/change/GetRelatedIT.java
@@ -27,7 +27,6 @@
 import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.acceptance.RestResponse;
 import com.google.gerrit.common.RawInputUtil;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.common.CommitInfo;
 import com.google.gerrit.extensions.common.EditInfo;
 import com.google.gerrit.index.IndexConfig;
@@ -41,6 +40,7 @@
 import com.google.gerrit.server.update.BatchUpdate;
 import com.google.gerrit.server.update.BatchUpdateOp;
 import com.google.gerrit.server.update.ChangeContext;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.testing.ConfigSuite;
 import com.google.gerrit.testing.TestTimeUtil;
 import com.google.gwtorm.server.OrmException;
diff --git a/javatests/com/google/gerrit/acceptance/server/notedb/BUILD b/javatests/com/google/gerrit/acceptance/server/notedb/BUILD
index 4965402..bdb3f3b 100644
--- a/javatests/com/google/gerrit/acceptance/server/notedb/BUILD
+++ b/javatests/com/google/gerrit/acceptance/server/notedb/BUILD
@@ -10,5 +10,8 @@
     # TODO(dborowitz): Fix leaks in local disk tests so we can reduce heap size.
     # http://crbug.com/gerrit/8567
     vm_args = ["-Xmx1024m"],
-    deps = ["//java/com/google/gerrit/server/schema"],
+    deps = [
+        "//java/com/google/gerrit/server/schema",
+        "//java/com/google/gerrit/server/util/time",
+    ],
 )
diff --git a/javatests/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java b/javatests/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java
index 5f60ce3..29f1b7d 100644
--- a/javatests/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java
@@ -35,7 +35,6 @@
 import com.google.gerrit.acceptance.AcceptanceTestRequestScope;
 import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.acceptance.TestAccount;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.extensions.api.changes.DraftInput;
 import com.google.gerrit.extensions.api.changes.ReviewInput;
@@ -83,6 +82,7 @@
 import com.google.gerrit.server.update.BatchUpdateOp;
 import com.google.gerrit.server.update.ChangeContext;
 import com.google.gerrit.server.update.UpdateException;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.testing.ConfigSuite;
 import com.google.gerrit.testing.NoteDbChecker;
 import com.google.gerrit.testing.NoteDbMode;
diff --git a/javatests/com/google/gerrit/acceptance/server/notedb/NoteDbOnlyIT.java b/javatests/com/google/gerrit/acceptance/server/notedb/NoteDbOnlyIT.java
index c45ea99..8d6fecd 100644
--- a/javatests/com/google/gerrit/acceptance/server/notedb/NoteDbOnlyIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/notedb/NoteDbOnlyIT.java
@@ -24,7 +24,6 @@
 import com.google.common.collect.Streams;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.PushOneCommit;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.api.changes.ReviewInput;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.reviewdb.client.Change;
@@ -35,6 +34,7 @@
 import com.google.gerrit.server.update.ChangeContext;
 import com.google.gerrit.server.update.RepoContext;
 import com.google.gerrit.server.update.RetryHelper;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.testing.ConfigSuite;
 import com.google.inject.Inject;
 import java.io.IOException;
diff --git a/javatests/com/google/gerrit/acceptance/server/notedb/NoteDbPrimaryIT.java b/javatests/com/google/gerrit/acceptance/server/notedb/NoteDbPrimaryIT.java
index b7ce7bc..26d5461 100644
--- a/javatests/com/google/gerrit/acceptance/server/notedb/NoteDbPrimaryIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/notedb/NoteDbPrimaryIT.java
@@ -35,7 +35,6 @@
 import com.google.gerrit.acceptance.NoHttpd;
 import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.api.changes.DraftInput;
 import com.google.gerrit.extensions.api.changes.ReviewInput;
 import com.google.gerrit.extensions.api.changes.ReviewInput.CommentInput;
@@ -62,6 +61,7 @@
 import com.google.gerrit.server.notedb.PrimaryStorageMigrator;
 import com.google.gerrit.server.notedb.TestChangeRebuilderWrapper;
 import com.google.gerrit.server.update.RetryHelper;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.testing.ConfigSuite;
 import com.google.gerrit.testing.NoteDbMode;
 import com.google.gerrit.testing.TestTimeUtil;
diff --git a/javatests/com/google/gerrit/server/BUILD b/javatests/com/google/gerrit/server/BUILD
index 7be1827..8749b7a 100644
--- a/javatests/com/google/gerrit/server/BUILD
+++ b/javatests/com/google/gerrit/server/BUILD
@@ -53,6 +53,7 @@
         "//java/com/google/gerrit/server/project/testing:project-test-util",
         "//java/com/google/gerrit/server/restapi",
         "//java/com/google/gerrit/server/schema",
+        "//java/com/google/gerrit/server/util/time",
         "//java/com/google/gerrit/testing:gerrit-test-util",
         "//java/com/google/gerrit/truth",
         "//java/org/eclipse/jgit:server",
diff --git a/javatests/com/google/gerrit/server/IdentifiedUserTest.java b/javatests/com/google/gerrit/server/IdentifiedUserTest.java
index 1a76dac..da6c56d 100644
--- a/javatests/com/google/gerrit/server/IdentifiedUserTest.java
+++ b/javatests/com/google/gerrit/server/IdentifiedUserTest.java
@@ -17,7 +17,6 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.inject.Scopes.SINGLETON;
 
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.account.AccountCache;
 import com.google.gerrit.server.account.FakeRealm;
@@ -29,6 +28,7 @@
 import com.google.gerrit.server.config.DisableReverseDnsLookup;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.group.SystemGroupBackend;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.testing.ConfigSuite;
 import com.google.gerrit.testing.FakeAccountCache;
 import com.google.inject.AbstractModule;
diff --git a/javatests/com/google/gerrit/server/change/LabelNormalizerTest.java b/javatests/com/google/gerrit/server/change/LabelNormalizerTest.java
index bce00cd..5a067f1 100644
--- a/javatests/com/google/gerrit/server/change/LabelNormalizerTest.java
+++ b/javatests/com/google/gerrit/server/change/LabelNormalizerTest.java
@@ -22,7 +22,6 @@
 import static org.junit.Assert.assertEquals;
 
 import com.google.common.collect.ImmutableList;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.data.AccessSection;
 import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.lifecycle.LifecycleManager;
@@ -48,6 +47,7 @@
 import com.google.gerrit.server.schema.SchemaCreator;
 import com.google.gerrit.server.util.RequestContext;
 import com.google.gerrit.server.util.ThreadLocalRequestContext;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.testing.InMemoryDatabase;
 import com.google.gerrit.testing.InMemoryModule;
 import com.google.inject.Guice;
diff --git a/javatests/com/google/gerrit/server/git/meta/VersionedMetaDataTest.java b/javatests/com/google/gerrit/server/git/meta/VersionedMetaDataTest.java
index 56e53b9..7e12439 100644
--- a/javatests/com/google/gerrit/server/git/meta/VersionedMetaDataTest.java
+++ b/javatests/com/google/gerrit/server/git/meta/VersionedMetaDataTest.java
@@ -22,11 +22,11 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Streams;
 import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 import com.google.gerrit.server.git.meta.VersionedMetaData.BatchMetaDataUpdate;
 import com.google.gerrit.server.update.RefUpdateUtil;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.testing.TestTimeUtil;
 import java.io.IOException;
 import java.util.Arrays;
diff --git a/javatests/com/google/gerrit/server/group/db/AbstractGroupTest.java b/javatests/com/google/gerrit/server/group/db/AbstractGroupTest.java
index 943f784..9fc6da1 100644
--- a/javatests/com/google/gerrit/server/group/db/AbstractGroupTest.java
+++ b/javatests/com/google/gerrit/server/group/db/AbstractGroupTest.java
@@ -17,7 +17,6 @@
 import static com.google.gerrit.extensions.common.testing.CommitInfoSubject.assertThat;
 
 import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.data.GroupDescription;
 import com.google.gerrit.extensions.common.CommitInfo;
 import com.google.gerrit.reviewdb.client.Account;
@@ -28,6 +27,7 @@
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 import com.google.gerrit.server.git.meta.MetaDataUpdate;
 import com.google.gerrit.server.group.InternalGroup;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.testing.GerritBaseTests;
 import com.google.gerrit.testing.InMemoryRepositoryManager;
 import java.io.IOException;
diff --git a/javatests/com/google/gerrit/server/group/db/BUILD b/javatests/com/google/gerrit/server/group/db/BUILD
index eee5529..f3dd5d6 100644
--- a/javatests/com/google/gerrit/server/group/db/BUILD
+++ b/javatests/com/google/gerrit/server/group/db/BUILD
@@ -14,6 +14,7 @@
         "//java/com/google/gerrit/server",
         "//java/com/google/gerrit/server/group/db/testing",
         "//java/com/google/gerrit/server/group/testing",
+        "//java/com/google/gerrit/server/util/time",
         "//java/com/google/gerrit/testing:gerrit-test-util",
         "//java/com/google/gerrit/truth",
         "//lib:guava",
diff --git a/javatests/com/google/gerrit/server/group/db/GroupConfigTest.java b/javatests/com/google/gerrit/server/group/db/GroupConfigTest.java
index dec1d63..a5744d1 100644
--- a/javatests/com/google/gerrit/server/group/db/GroupConfigTest.java
+++ b/javatests/com/google/gerrit/server/group/db/GroupConfigTest.java
@@ -22,7 +22,6 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.data.GroupDescription;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
@@ -32,6 +31,7 @@
 import com.google.gerrit.server.git.meta.MetaDataUpdate;
 import com.google.gerrit.server.group.InternalGroup;
 import com.google.gerrit.server.group.testing.InternalGroupSubject;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.truth.OptionalSubject;
 import com.google.gwtorm.client.KeyUtil;
 import com.google.gwtorm.server.StandardKeyEncoder;
diff --git a/javatests/com/google/gerrit/server/group/db/GroupNameNotesTest.java b/javatests/com/google/gerrit/server/group/db/GroupNameNotesTest.java
index a70df6b..42d01e2 100644
--- a/javatests/com/google/gerrit/server/group/db/GroupNameNotesTest.java
+++ b/javatests/com/google/gerrit/server/group/db/GroupNameNotesTest.java
@@ -23,7 +23,6 @@
 import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
 
 import com.google.common.collect.ImmutableList;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.data.GroupReference;
 import com.google.gerrit.common.data.testing.GroupReferenceSubject;
 import com.google.gerrit.extensions.common.CommitInfo;
@@ -36,6 +35,7 @@
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 import com.google.gerrit.server.git.meta.MetaDataUpdate;
 import com.google.gerrit.server.update.RefUpdateUtil;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.testing.GitTestUtil;
 import com.google.gerrit.testing.TestTimeUtil;
 import com.google.gerrit.truth.ListSubject;
diff --git a/javatests/com/google/gerrit/server/index/account/AccountFieldTest.java b/javatests/com/google/gerrit/server/index/account/AccountFieldTest.java
index f0230d5..c69fa20 100644
--- a/javatests/com/google/gerrit/server/index/account/AccountFieldTest.java
+++ b/javatests/com/google/gerrit/server/index/account/AccountFieldTest.java
@@ -21,13 +21,13 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Streams;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.account.externalids.ExternalId;
 import com.google.gerrit.server.config.AllUsersName;
 import com.google.gerrit.server.config.AllUsersNameProvider;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.testing.GerritBaseTests;
 import java.util.List;
 import org.eclipse.jgit.lib.ObjectId;
diff --git a/javatests/com/google/gerrit/server/index/change/ChangeFieldTest.java b/javatests/com/google/gerrit/server/index/change/ChangeFieldTest.java
index 8e8a0ea..d3792b7 100644
--- a/javatests/com/google/gerrit/server/index/change/ChangeFieldTest.java
+++ b/javatests/com/google/gerrit/server/index/change/ChangeFieldTest.java
@@ -21,13 +21,13 @@
 import com.google.common.collect.HashBasedTable;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Table;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.data.SubmitRecord;
 import com.google.gerrit.common.data.SubmitRequirement;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.server.ReviewerSet;
 import com.google.gerrit.server.notedb.ReviewerStateInternal;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.testing.GerritBaseTests;
 import com.google.gerrit.testing.TestTimeUtil;
 import java.sql.Timestamp;
diff --git a/javatests/com/google/gerrit/server/mail/send/FromAddressGeneratorProviderTest.java b/javatests/com/google/gerrit/server/mail/send/FromAddressGeneratorProviderTest.java
index f99c356..75923b4 100644
--- a/javatests/com/google/gerrit/server/mail/send/FromAddressGeneratorProviderTest.java
+++ b/javatests/com/google/gerrit/server/mail/send/FromAddressGeneratorProviderTest.java
@@ -21,13 +21,13 @@
 import static org.easymock.EasyMock.replay;
 import static org.easymock.EasyMock.verify;
 
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.mail.Address;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.account.AccountCache;
 import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.config.AllUsersName;
 import com.google.gerrit.server.config.AllUsersNameProvider;
+import com.google.gerrit.server.util.time.TimeUtil;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Optional;
diff --git a/javatests/com/google/gerrit/server/notedb/AbstractChangeNotesTest.java b/javatests/com/google/gerrit/server/notedb/AbstractChangeNotesTest.java
index cc7302f..b93e9c1 100644
--- a/javatests/com/google/gerrit/server/notedb/AbstractChangeNotesTest.java
+++ b/javatests/com/google/gerrit/server/notedb/AbstractChangeNotesTest.java
@@ -18,7 +18,6 @@
 import static java.util.concurrent.TimeUnit.SECONDS;
 
 import com.google.common.collect.ImmutableList;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.data.SubmitRecord;
 import com.google.gerrit.extensions.config.FactoryModule;
 import com.google.gerrit.metrics.DisabledMetricMaker;
@@ -51,6 +50,7 @@
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.group.SystemGroupBackend;
 import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.testing.ConfigSuite;
 import com.google.gerrit.testing.FakeAccountCache;
 import com.google.gerrit.testing.GerritBaseTests;
diff --git a/javatests/com/google/gerrit/server/notedb/ChangeBundleTest.java b/javatests/com/google/gerrit/server/notedb/ChangeBundleTest.java
index 722dd08..fc2a272 100644
--- a/javatests/com/google/gerrit/server/notedb/ChangeBundleTest.java
+++ b/javatests/com/google/gerrit/server/notedb/ChangeBundleTest.java
@@ -16,18 +16,17 @@
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.gerrit.common.TimeUtil.truncateToSecond;
 import static com.google.gerrit.server.notedb.ChangeBundle.Source.NOTE_DB;
 import static com.google.gerrit.server.notedb.ChangeBundle.Source.REVIEW_DB;
 import static com.google.gerrit.server.notedb.ReviewerStateInternal.CC;
 import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER;
+import static com.google.gerrit.server.util.time.TimeUtil.truncateToSecond;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.concurrent.TimeUnit.SECONDS;
 
 import com.google.common.collect.HashBasedTable;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Table;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.ChangeMessage;
@@ -40,6 +39,7 @@
 import com.google.gerrit.reviewdb.client.RevId;
 import com.google.gerrit.server.ReviewerSet;
 import com.google.gerrit.server.notedb.rebuild.ChangeRebuilderImpl;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.testing.GerritBaseTests;
 import com.google.gerrit.testing.TestChanges;
 import com.google.gerrit.testing.TestTimeUtil;
diff --git a/javatests/com/google/gerrit/server/notedb/ChangeNotesParserTest.java b/javatests/com/google/gerrit/server/notedb/ChangeNotesParserTest.java
index 313fa07..79dcd5b 100644
--- a/javatests/com/google/gerrit/server/notedb/ChangeNotesParserTest.java
+++ b/javatests/com/google/gerrit/server/notedb/ChangeNotesParserTest.java
@@ -17,9 +17,9 @@
 import static com.google.common.truth.Truth.assertThat;
 import static org.junit.Assert.fail;
 
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.server.notedb.ChangeNotesCommit.ChangeNotesRevWalk;
+import com.google.gerrit.server.util.time.TimeUtil;
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
 import org.eclipse.jgit.junit.TestRepository;
diff --git a/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java b/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java
index 1f974e2..549f5db 100644
--- a/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java
+++ b/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java
@@ -33,7 +33,6 @@
 import com.google.common.collect.Iterables;
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Lists;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.data.SubmitRecord;
 import com.google.gerrit.mail.Address;
 import com.google.gerrit.reviewdb.client.Account;
@@ -53,6 +52,7 @@
 import com.google.gerrit.server.config.GerritServerId;
 import com.google.gerrit.server.logging.RequestId;
 import com.google.gerrit.server.notedb.ChangeNotesCommit.ChangeNotesRevWalk;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.testing.TestChanges;
 import com.google.gerrit.testing.TestTimeUtil;
 import com.google.gwtorm.server.OrmException;
diff --git a/javatests/com/google/gerrit/server/notedb/CommentJsonMigratorTest.java b/javatests/com/google/gerrit/server/notedb/CommentJsonMigratorTest.java
index 2031a14..b9027bc 100644
--- a/javatests/com/google/gerrit/server/notedb/CommentJsonMigratorTest.java
+++ b/javatests/com/google/gerrit/server/notedb/CommentJsonMigratorTest.java
@@ -23,7 +23,6 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Multimaps;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Comment;
 import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
@@ -35,6 +34,7 @@
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.config.AllUsersName;
 import com.google.gerrit.server.notedb.CommentJsonMigrator.ProjectMigrationResult;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.testing.TestChanges;
 import com.google.inject.Inject;
 import java.io.ByteArrayOutputStream;
diff --git a/javatests/com/google/gerrit/server/notedb/CommitMessageOutputTest.java b/javatests/com/google/gerrit/server/notedb/CommitMessageOutputTest.java
index 8daf67f..1b50431 100644
--- a/javatests/com/google/gerrit/server/notedb/CommitMessageOutputTest.java
+++ b/javatests/com/google/gerrit/server/notedb/CommitMessageOutputTest.java
@@ -19,12 +19,12 @@
 import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER;
 
 import com.google.common.collect.ImmutableList;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.mail.Address;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.logging.RequestId;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.testing.ConfigSuite;
 import com.google.gerrit.testing.TestChanges;
 import java.util.Date;
diff --git a/javatests/com/google/gerrit/server/notedb/NoteDbChangeStateTest.java b/javatests/com/google/gerrit/server/notedb/NoteDbChangeStateTest.java
index fe27cac..c82135e 100644
--- a/javatests/com/google/gerrit/server/notedb/NoteDbChangeStateTest.java
+++ b/javatests/com/google/gerrit/server/notedb/NoteDbChangeStateTest.java
@@ -17,11 +17,11 @@
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth8.assertThat;
-import static com.google.gerrit.common.TimeUtil.nowTs;
 import static com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage.NOTE_DB;
 import static com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage.REVIEW_DB;
 import static com.google.gerrit.server.notedb.NoteDbChangeState.applyDelta;
 import static com.google.gerrit.server.notedb.NoteDbChangeState.parse;
+import static com.google.gerrit.server.util.time.TimeUtil.nowTs;
 import static org.eclipse.jgit.lib.ObjectId.zeroId;
 
 import com.google.common.collect.ImmutableMap;
diff --git a/javatests/com/google/gerrit/server/notedb/rebuild/EventSorterTest.java b/javatests/com/google/gerrit/server/notedb/rebuild/EventSorterTest.java
index 5a1ec2b..c81a176 100644
--- a/javatests/com/google/gerrit/server/notedb/rebuild/EventSorterTest.java
+++ b/javatests/com/google/gerrit/server/notedb/rebuild/EventSorterTest.java
@@ -20,11 +20,11 @@
 
 import com.google.common.collect.Collections2;
 import com.google.common.collect.Lists;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.server.notedb.ChangeUpdate;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.testing.TestTimeUtil;
 import java.sql.Timestamp;
 import java.util.ArrayList;
diff --git a/javatests/com/google/gerrit/server/permissions/RefControlTest.java b/javatests/com/google/gerrit/server/permissions/RefControlTest.java
index 31eee9f..a3f9f93 100644
--- a/javatests/com/google/gerrit/server/permissions/RefControlTest.java
+++ b/javatests/com/google/gerrit/server/permissions/RefControlTest.java
@@ -43,6 +43,7 @@
 import com.google.gerrit.common.data.PermissionRule;
 import com.google.gerrit.common.errors.InvalidNameException;
 import com.google.gerrit.extensions.api.projects.CommentLinkInfo;
+import com.google.gerrit.metrics.MetricMaker;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -204,6 +205,7 @@
   @Inject private ThreadLocalRequestContext requestContext;
   @Inject private DefaultRefFilter.Factory refFilterFactory;
   @Inject private TransferConfig transferConfig;
+  @Inject private MetricMaker metricMaker;
 
   @Before
   public void setUp() throws Exception {
@@ -291,7 +293,7 @@
 
     Cache<SectionSortCache.EntryKey, SectionSortCache.EntryVal> c =
         CacheBuilder.newBuilder().build();
-    sectionSorter = new PermissionCollection.Factory(new SectionSortCache(c));
+    sectionSorter = new PermissionCollection.Factory(new SectionSortCache(c), metricMaker);
 
     parent = new ProjectConfig(parentKey);
     parent.load(newRepository(parentKey));
@@ -972,6 +974,7 @@
             commentLinks,
             capabilityCollectionFactory,
             transferConfig,
+            metricMaker,
             pc));
     return repo;
   }
diff --git a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index e995858..b9973e9 100644
--- a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -40,7 +40,6 @@
 import com.google.common.collect.Streams;
 import com.google.common.truth.ThrowableSubject;
 import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.extensions.api.GerritApi;
@@ -116,6 +115,7 @@
 import com.google.gerrit.server.util.OneOffRequestContext;
 import com.google.gerrit.server.util.RequestContext;
 import com.google.gerrit.server.util.ThreadLocalRequestContext;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.testing.DisabledReviewDb;
 import com.google.gerrit.testing.GerritServerTests;
 import com.google.gerrit.testing.InMemoryDatabase;
@@ -201,7 +201,7 @@
 
   protected static final String DASHBOARD_HAS_UNPUBLISHED_DRAFTS_QUERY = "has:draft";
   protected static final String DASHBOARD_ASSIGNED_QUERY =
-      "assignee:${user} (-is:wip OR " + "owner:self OR assignee:self)";
+      "assignee:${user} (-is:wip OR " + "owner:self OR assignee:self) is:open -is:ignored";
   protected static final String DASHBOARD_WORK_IN_PROGRESS_QUERY = "is:open owner:${user} is:wip";
   protected static final String DASHBOARD_OUTGOING_QUERY =
       "is:open owner:${user} -is:wip -is:ignored";
@@ -841,6 +841,43 @@
   }
 
   @Test
+  public void byRepo() throws Exception {
+    TestRepository<Repo> repo1 = createProject("repo1");
+    TestRepository<Repo> repo2 = createProject("repo2");
+    Change change1 = insert(repo1, newChange(repo1));
+    Change change2 = insert(repo2, newChange(repo2));
+
+    assertQuery("repo:foo");
+    assertQuery("repo:repo");
+    assertQuery("repo:repo1", change1);
+    assertQuery("repo:repo2", change2);
+  }
+
+  @Test
+  public void byParentRepo() throws Exception {
+    TestRepository<Repo> repo1 = createProject("repo1");
+    TestRepository<Repo> repo2 = createProject("repo2", "repo1");
+    Change change1 = insert(repo1, newChange(repo1));
+    Change change2 = insert(repo2, newChange(repo2));
+
+    assertQuery("parentrepo:repo1", change2, change1);
+    assertQuery("parentrepo:repo2", change2);
+  }
+
+  @Test
+  public void byRepoPrefix() throws Exception {
+    TestRepository<Repo> repo1 = createProject("repo1");
+    TestRepository<Repo> repo2 = createProject("repo2");
+    Change change1 = insert(repo1, newChange(repo1));
+    Change change2 = insert(repo2, newChange(repo2));
+
+    assertQuery("repos:foo");
+    assertQuery("repos:repo1", change1);
+    assertQuery("repos:repo2", change2);
+    assertQuery("repos:repo", change2, change1);
+  }
+
+  @Test
   public void byBranchAndRef() throws Exception {
     TestRepository<Repo> repo = createProject("repo");
     Change change1 = insert(repo, newChangeForBranch(repo, "master"));
@@ -2746,6 +2783,14 @@
             .create(repo);
 
     // Create changes that should not be returned by query.
+    new DashboardChangeState(user.getAccountId()).assignTo(user.getAccountId()).abandon();
+    new DashboardChangeState(user.getAccountId())
+        .assignTo(user.getAccountId())
+        .ignoreBy(user.getAccountId());
+    new DashboardChangeState(user.getAccountId())
+        .assignTo(user.getAccountId())
+        .mergeBy(user.getAccountId());
+
     assertDashboardQuery("self", DASHBOARD_ASSIGNED_QUERY, selfOpenWip, otherOpenWip);
 
     // Viewing another user's dashboard.
diff --git a/javatests/com/google/gerrit/server/query/change/BUILD b/javatests/com/google/gerrit/server/query/change/BUILD
index 09e3243..c27be68 100644
--- a/javatests/com/google/gerrit/server/query/change/BUILD
+++ b/javatests/com/google/gerrit/server/query/change/BUILD
@@ -18,6 +18,7 @@
         "//java/com/google/gerrit/server",
         "//java/com/google/gerrit/server/project/testing:project-test-util",
         "//java/com/google/gerrit/server/schema",
+        "//java/com/google/gerrit/server/util/time",
         "//java/com/google/gerrit/testing:gerrit-test-util",
         "//lib:guava",
         "//lib:gwtorm",
diff --git a/javatests/com/google/gerrit/server/rules/BUILD b/javatests/com/google/gerrit/server/rules/BUILD
index 8f4c90d..62d9a79 100644
--- a/javatests/com/google/gerrit/server/rules/BUILD
+++ b/javatests/com/google/gerrit/server/rules/BUILD
@@ -10,6 +10,7 @@
         "//java/com/google/gerrit/reviewdb:server",
         "//java/com/google/gerrit/server",
         "//java/com/google/gerrit/server/project/testing:project-test-util",
+        "//java/com/google/gerrit/server/util/time",
         "//java/com/google/gerrit/testing:gerrit-test-util",
         "//lib:guava",
         "//lib/guice",
diff --git a/javatests/com/google/gerrit/server/rules/PrologTestCase.java b/javatests/com/google/gerrit/server/rules/PrologTestCase.java
index e8eea2d..f4d8eac 100644
--- a/javatests/com/google/gerrit/server/rules/PrologTestCase.java
+++ b/javatests/com/google/gerrit/server/rules/PrologTestCase.java
@@ -18,7 +18,7 @@
 import static com.google.common.truth.Truth.assertWithMessage;
 import static java.nio.charset.StandardCharsets.UTF_8;
 
-import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.testing.GerritBaseTests;
 import com.google.inject.Guice;
 import com.google.inject.Module;
diff --git a/javatests/com/google/gerrit/server/schema/GroupBundleTest.java b/javatests/com/google/gerrit/server/schema/GroupBundleTest.java
index 43fd59a..c1de3a3 100644
--- a/javatests/com/google/gerrit/server/schema/GroupBundleTest.java
+++ b/javatests/com/google/gerrit/server/schema/GroupBundleTest.java
@@ -16,7 +16,6 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.AccountGroupById;
@@ -24,6 +23,7 @@
 import com.google.gerrit.reviewdb.client.AccountGroupMember;
 import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit;
 import com.google.gerrit.server.schema.GroupBundle.Source;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.testing.GerritBaseTests;
 import com.google.gerrit.testing.TestTimeUtil;
 import java.sql.Timestamp;
diff --git a/javatests/com/google/gerrit/server/schema/GroupRebuilderTest.java b/javatests/com/google/gerrit/server/schema/GroupRebuilderTest.java
index c76a246..bf6953a 100644
--- a/javatests/com/google/gerrit/server/schema/GroupRebuilderTest.java
+++ b/javatests/com/google/gerrit/server/schema/GroupRebuilderTest.java
@@ -22,7 +22,6 @@
 
 import com.google.common.collect.ImmutableList;
 import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.data.GroupDescription;
 import com.google.gerrit.common.data.GroupReference;
 import com.google.gerrit.extensions.common.CommitInfo;
@@ -39,6 +38,7 @@
 import com.google.gerrit.server.group.db.AuditLogReader;
 import com.google.gerrit.server.group.db.GroupNameNotes;
 import com.google.gerrit.server.update.RefUpdateUtil;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.testing.GerritBaseTests;
 import com.google.gerrit.testing.GitTestUtil;
 import com.google.gerrit.testing.InMemoryRepositoryManager;
diff --git a/javatests/com/google/gerrit/server/schema/Schema_150_to_151_Test.java b/javatests/com/google/gerrit/server/schema/Schema_150_to_151_Test.java
index 42af2ca..4d5db6d 100644
--- a/javatests/com/google/gerrit/server/schema/Schema_150_to_151_Test.java
+++ b/javatests/com/google/gerrit/server/schema/Schema_150_to_151_Test.java
@@ -17,7 +17,6 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.TruthJUnit.assume;
 
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.AccountGroup.Id;
@@ -27,6 +26,7 @@
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.Sequences;
 import com.google.gerrit.server.account.GroupUUID;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.testing.InMemoryTestEnvironment;
 import com.google.gerrit.testing.TestUpdateUI;
 import com.google.gwtorm.jdbc.JdbcSchema;
diff --git a/javatests/com/google/gerrit/server/schema/Schema_166_to_167_WithGroupsInReviewDbTest.java b/javatests/com/google/gerrit/server/schema/Schema_166_to_167_WithGroupsInReviewDbTest.java
index 75f9307..78fef39 100644
--- a/javatests/com/google/gerrit/server/schema/Schema_166_to_167_WithGroupsInReviewDbTest.java
+++ b/javatests/com/google/gerrit/server/schema/Schema_166_to_167_WithGroupsInReviewDbTest.java
@@ -23,7 +23,6 @@
 import static com.google.gerrit.truth.OptionalSubject.assertThat;
 
 import com.google.common.collect.ImmutableList;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.data.GroupReference;
 import com.google.gerrit.extensions.api.GerritApi;
 import com.google.gerrit.extensions.api.accounts.AccountInput;
@@ -62,6 +61,7 @@
 import com.google.gerrit.server.group.db.GroupsConsistencyChecker;
 import com.google.gerrit.server.group.testing.InternalGroupSubject;
 import com.google.gerrit.server.group.testing.TestGroupBackend;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.testing.InMemoryTestEnvironment;
 import com.google.gerrit.testing.TestTimeUtil;
 import com.google.gerrit.testing.TestTimeUtil.TempClockStep;
diff --git a/javatests/com/google/gerrit/server/schema/TestGroup.java b/javatests/com/google/gerrit/server/schema/TestGroup.java
index 49cf028..4627e8b 100644
--- a/javatests/com/google/gerrit/server/schema/TestGroup.java
+++ b/javatests/com/google/gerrit/server/schema/TestGroup.java
@@ -15,8 +15,8 @@
 package com.google.gerrit.server.schema;
 
 import com.google.auto.value.AutoValue;
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.server.util.time.TimeUtil;
 import java.sql.Timestamp;
 import java.util.Optional;
 import org.junit.Ignore;
diff --git a/javatests/com/google/gerrit/server/update/BUILD b/javatests/com/google/gerrit/server/update/BUILD
index 46820c7..cef3d63 100644
--- a/javatests/com/google/gerrit/server/update/BUILD
+++ b/javatests/com/google/gerrit/server/update/BUILD
@@ -31,6 +31,7 @@
         "//java/com/google/gerrit/reviewdb:server",
         "//java/com/google/gerrit/server",
         "//java/com/google/gerrit/server/schema",
+        "//java/com/google/gerrit/server/util/time",
         "//java/com/google/gerrit/testing:gerrit-test-util",
         "//lib:guava",
         "//lib:gwtorm",
diff --git a/javatests/com/google/gerrit/server/update/BatchUpdateTest.java b/javatests/com/google/gerrit/server/update/BatchUpdateTest.java
index 67672d3..70c4383 100644
--- a/javatests/com/google/gerrit/server/update/BatchUpdateTest.java
+++ b/javatests/com/google/gerrit/server/update/BatchUpdateTest.java
@@ -16,11 +16,11 @@
 
 import static org.junit.Assert.assertEquals;
 
-import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.util.time.TimeUtil;
 import com.google.gerrit.testing.InMemoryTestEnvironment;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
diff --git a/lib/mina/BUILD b/lib/mina/BUILD
index 8595bb5..6ee7e41 100644
--- a/lib/mina/BUILD
+++ b/lib/mina/BUILD
@@ -4,6 +4,7 @@
     visibility = ["//visibility:public"],
     exports = [
         ":eddsa",
+        "@sshd-mina//jar",
         "@sshd//jar",
     ],
     runtime_deps = [":core"],
diff --git a/lib/polymer_externs/BUILD b/lib/polymer_externs/BUILD
index ae8f9c0..2f1bdbd 100644
--- a/lib/polymer_externs/BUILD
+++ b/lib/polymer_externs/BUILD
@@ -18,16 +18,9 @@
 
 load("@io_bazel_rules_closure//closure:defs.bzl", "closure_js_library")
 
-genrule(
-    name = "polymer_closure_renamed",
-    srcs = ["@polymer_closure//file"],
-    outs = ["polymer_closure_renamed.js"],
-    cmd = "cp $< $@",
-)
-
 closure_js_library(
     name = "polymer_closure",
-    srcs = [":polymer_closure_renamed"],
+    srcs = ["@polymer_closure//file"],
     data = ["//lib:LICENSE-Apache2.0"],
     no_closure_library = True,
 )
diff --git a/plugins/BUILD b/plugins/BUILD
index a7622b2..137be6e 100644
--- a/plugins/BUILD
+++ b/plugins/BUILD
@@ -40,6 +40,7 @@
     "//java/com/google/gerrit/server/audit",
     "//java/com/google/gerrit/server/logging",
     "//java/com/google/gerrit/server/schema",
+    "//java/com/google/gerrit/server/util/time",
     "//java/com/google/gerrit/util/http",
     "//lib/commons:compress",
     "//lib/commons:dbcp",
diff --git a/polygerrit-ui/BUILD b/polygerrit-ui/BUILD
index fddfc57..384f835 100644
--- a/polygerrit-ui/BUILD
+++ b/polygerrit-ui/BUILD
@@ -2,6 +2,7 @@
     default_visibility = ["//visibility:public"],
 )
 
+load("@io_bazel_rules_go//go:def.bzl", "go_binary")
 load("//tools/bzl:js.bzl", "bower_component_bundle")
 load("//tools/bzl:genrule2.bzl", "genrule2")
 
@@ -52,3 +53,19 @@
     output_to_bindir = 1,
     visibility = ["//visibility:public"],
 )
+
+go_binary(
+    name = "devserver",
+    srcs = ["server.go"],
+    data = [
+        ":fonts.zip",
+        "//polygerrit-ui/app:test_components.zip",
+        "//resources/com/google/gerrit/httpd/raw",
+    ],
+    deps = [
+        "@com_github_robfig_soy//:go_default_library",
+        "@com_github_robfig_soy//soyhtml:go_default_library",
+        "@org_golang_x_tools//godoc/vfs/httpfs:go_default_library",
+        "@org_golang_x_tools//godoc/vfs/zipfs:go_default_library",
+    ],
+)
diff --git a/polygerrit-ui/README.md b/polygerrit-ui/README.md
index c119562..3c21a42 100644
--- a/polygerrit-ui/README.md
+++ b/polygerrit-ui/README.md
@@ -48,32 +48,6 @@
 Unfortunately, you can't sign in, so testing certain features will require
 you to use the "test data" technique described below.
 
-### Installing [go](https://golang.org/)
-
-This is required for running the `run-server.sh` script below.
-
-```sh
-# Debian/Ubuntu
-sudo apt-get install golang
-
-# OS X with Homebrew
-brew install go
-```
-
-All other platforms: [download from golang.org](https://golang.org/)
-
-Then add go to your path:
-
-```
-PATH=$PATH:/usr/local/go/bin
-```
-
-Install the go Soy template library:
-
-```
-go get "github.com/robfig/soy"
-```
-
 ### Running the server
 
 To test the local UI against gerrit-review.googlesource.com:
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.html b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.html
index b18e8cd..af320e2 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.html
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.html
@@ -20,6 +20,9 @@
 <link rel="import" href="../../../styles/shared-styles.html">
 <link rel="import" href="../../change-list/gr-change-list/gr-change-list.html">
 <link rel="import" href="../../core/gr-reporting/gr-reporting.html">
+<link rel="import" href="../../shared/gr-button/gr-button.html">
+<link rel="import" href="../../shared/gr-dialog/gr-dialog.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="../gr-user-header/gr-user-header.html">
 
@@ -37,18 +40,43 @@
       gr-change-list {
         width: 100%;
       }
-      .hide {
-        display: none;
-      }
       gr-user-header {
         border-bottom: 1px solid var(--border-color);
       }
+      .banner {
+        align-items: center;
+        background-color: var(--comment-background-color);
+        border-bottom: 1px solid var(--border-color);
+        display: flex;
+        justify-content: space-between;
+        padding: .25em var(--default-horizontal-margin);
+      }
+      .banner gr-button {
+        --gr-button: {
+          color: var(--primary-text-color);
+        }
+      }
+      .hide {
+        display: none;
+      }
       @media only screen and (max-width: 50em) {
         .loading {
           padding: 0 var(--default-horizontal-margin);
         }
       }
     </style>
+    <div class$="banner [[_computeBannerClass(_showDraftsBanner)]]">
+      <div>
+        You have draft comments on closed changes.
+        <a href$="[[_computeDraftsLink(_showDraftsBanner)]]" target="_blank">(view all)</a>
+      </div>
+      <div>
+        <gr-button
+            class="delete"
+            link
+            on-tap="_handleOpenDeleteDialog">Delete All</gr-button>
+      </div>
+    </div>
     <div class="loading" hidden$="[[!_loading]]">Loading...</div>
     <div hidden$="[[_loading]]" hidden>
       <gr-user-header
@@ -64,6 +92,21 @@
           on-toggle-star="_handleToggleStar"
           on-toggle-reviewed="_handleToggleReviewed"></gr-change-list>
     </div>
+    <gr-overlay id="confirmDeleteOverlay" with-backdrop>
+      <gr-dialog
+          id="confirmDeleteDialog"
+          confirm-label="Delete"
+          on-confirm="_handleConfirmDelete"
+          on-cancel="_closeConfirmDeleteOverlay">
+        <div class="header" slot="header">
+          Delete comments
+        </div>
+        <div class="main" slot="main">
+          Are you sure you want to delete all your draft comments in closed changes? This action
+          cannot be undone.
+        </div>
+      </gr-dialog>
+    </gr-overlay>
     <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
     <gr-reporting id="reporting"></gr-reporting>
   </template>
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js
index 72ef0e5..8aa7084 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js
@@ -18,67 +18,6 @@
   'use strict';
 
   const PROJECT_PLACEHOLDER_PATTERN = /\$\{project\}/g;
-  const USER_PLACEHOLDER_PATTERN = /\$\{user\}/g;
-
-  // NOTE: These queries are tested in Java. Any changes made to definitions
-  // here require corresponding changes to:
-  // javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
-  const DEFAULT_SECTIONS = [
-    {
-      // Changes with unpublished draft comments. This section is omitted when
-      // viewing other users, so we don't need to filter anything out.
-      name: 'Has draft comments',
-      query: 'has:draft',
-      selfOnly: true,
-      hideIfEmpty: true,
-      suffixForDashboard: 'limit:10',
-    },
-    {
-      // Changes that are assigned to the viewed user.
-      name: 'Assigned reviews',
-      query: 'assignee:${user} (-is:wip OR owner:self OR assignee:self)',
-      hideIfEmpty: true,
-    },
-    {
-      // WIP open changes owned by viewing user. This section is omitted when
-      // viewing other users, so we don't need to filter anything out.
-      name: 'Work in progress',
-      query: 'is:open owner:${user} is:wip',
-      selfOnly: true,
-      hideIfEmpty: true,
-    },
-    {
-      // Non-WIP open changes owned by viewed user. Filter out changes ignored
-      // by the viewing user.
-      name: 'Outgoing reviews',
-      query: 'is:open owner:${user} -is:wip -is:ignored',
-    },
-    {
-      // Non-WIP open changes not owned by the viewed user, that the viewed user
-      // is associated with (as either a reviewer or the assignee). Changes
-      // ignored by the viewing user are filtered out.
-      name: 'Incoming reviews',
-      query: 'is:open -owner:${user} -is:wip -is:ignored ' +
-          '(reviewer:${user} OR assignee:${user})',
-    },
-    {
-      // Open changes the viewed user is CCed on. Changes ignored by the viewing
-      // user are filtered out.
-      name: 'CCed on',
-      query: 'is:open -is:ignored cc:${user}',
-    },
-    {
-      name: 'Recently closed',
-      // Closed changes where viewed user is owner, reviewer, or assignee.
-      // Changes ignored by the viewing user are filtered out, and so are WIP
-      // changes not owned by the viewing user (the one instance of
-      // 'owner:self' is intentional and implements this logic).
-      query: 'is:closed -is:ignored (-is:wip OR owner:self) ' +
-          '(owner:${user} OR reviewer:${user} OR assignee:${user} ' +
-          'OR cc:${user})',
-      suffixForDashboard: '-age:4w limit:10',
-    },
-  ];
 
   Polymer({
     is: 'gr-dashboard-view',
@@ -104,10 +43,6 @@
       },
 
       _results: Array,
-      _sectionMetadata: {
-        type: Array,
-        value() { return DEFAULT_SECTIONS; },
-      },
 
       /**
        * For showing a "loading..." string during ajax requests.
@@ -116,6 +51,11 @@
         type: Boolean,
         value: true,
       },
+
+      _showDraftsBanner: {
+        type: Boolean,
+        value: false,
+      },
     },
 
     observers: [
@@ -174,16 +114,6 @@
           });
     },
 
-    _getUserDashboard(user, sections, title) {
-      sections = sections
-        .filter(section => (user === 'self' || !section.selfOnly))
-        .map(section => Object.assign({}, section, {
-          name: section.name,
-          query: section.query.replace(USER_PLACEHOLDER_PATTERN, user),
-        }));
-      return Promise.resolve({title, sections});
-    },
-
     _computeTitle(user) {
       if (!user || user === 'self') {
         return 'My Reviews';
@@ -221,13 +151,14 @@
       const {project, dashboard, title, user, sections} = this.params;
       const dashboardPromise = project ?
           this._getProjectDashboard(project, dashboard) :
-          this._getUserDashboard(
-              user || 'self',
-              sections || DEFAULT_SECTIONS,
-              title || this._computeTitle(user));
+          Promise.resolve(Gerrit.Nav.getUserDashboard(
+              user,
+              sections,
+              title || this._computeTitle(user)));
 
       return dashboardPromise.then(this._fetchDashboardChanges.bind(this))
           .then(() => {
+            this._maybeShowDraftsBanner();
             this.$.reporting.dashboardDisplayed();
           }).catch(err => {
             console.warn(err);
@@ -274,5 +205,48 @@
       this.$.restAPI.saveChangeReviewed(e.detail.change._number,
           e.detail.reviewed);
     },
+
+    /**
+     * Banner is shown if a user is on their own dashboard and they have draft
+     * comments on closed changes.
+     */
+    _maybeShowDraftsBanner() {
+      this._showDraftsBanner = false;
+      if (!(this.params.user === 'self')) { return; }
+
+      const draftSection = this._results
+          .find(section => section.query === 'has:draft');
+      if (!draftSection || !draftSection.results.length) { return; }
+
+      const closedChanges = draftSection.results
+          .filter(change => !this.changeIsOpen(change.status));
+      if (!closedChanges.length) { return; }
+
+      this._showDraftsBanner = true;
+    },
+
+    _computeBannerClass(show) {
+      return show ? '' : 'hide';
+    },
+
+    _handleOpenDeleteDialog() {
+      this.$.confirmDeleteOverlay.open();
+    },
+
+    _handleConfirmDelete() {
+      this.$.confirmDeleteDialog.disabled = true;
+      return this.$.restAPI.deleteDraftComments('-is:open').then(() => {
+        this._closeConfirmDeleteOverlay();
+        this._reload();
+      });
+    },
+
+    _closeConfirmDeleteOverlay() {
+      this.$.confirmDeleteOverlay.close();
+    },
+
+    _computeDraftsLink() {
+      return Gerrit.Nav.getUrlForSearchQuery('has:draft -is:open');
+    },
   });
 })();
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 cac2627..aebd452 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
@@ -63,6 +63,85 @@
       sandbox.restore();
     });
 
+    suite('drafts banner functionality', () => {
+      suite('_maybeShowDraftsBanner', () => {
+        test('not dashboard/self', () => {
+          element.params = {user: 'notself'};
+          element._maybeShowDraftsBanner();
+          assert.isFalse(element._showDraftsBanner);
+        });
+
+        test('no drafts at all', () => {
+          element.params = {user: 'self'};
+          element._results = [];
+          element._maybeShowDraftsBanner();
+          assert.isFalse(element._showDraftsBanner);
+        });
+
+        test('no drafts on open changes', () => {
+          element.params = {user: 'self'};
+          element._results = [{query: 'has:draft', results: [{status: '_'}]}];
+          sandbox.stub(element, 'changeIsOpen').returns(true);
+          element._maybeShowDraftsBanner();
+          assert.isFalse(element._showDraftsBanner);
+        });
+
+        test('no drafts on open changes', () => {
+          element.params = {user: 'self'};
+          element._results = [{query: 'has:draft', results: [{status: '_'}]}];
+          sandbox.stub(element, 'changeIsOpen').returns(false);
+          element._maybeShowDraftsBanner();
+          assert.isTrue(element._showDraftsBanner);
+        });
+      });
+
+      test('_showDraftsBanner', () => {
+        element._showDraftsBanner = false;
+        flushAsynchronousOperations();
+        assert.isTrue(isHidden(element.$$('.banner')));
+
+        element._showDraftsBanner = true;
+        flushAsynchronousOperations();
+        assert.isFalse(isHidden(element.$$('.banner')));
+      });
+
+      test('delete tap opens dialog', () => {
+        sandbox.stub(element, '_handleOpenDeleteDialog');
+        element._showDraftsBanner = true;
+        flushAsynchronousOperations();
+
+        MockInteractions.tap(element.$$('.banner .delete'));
+        assert.isTrue(element._handleOpenDeleteDialog.called);
+      });
+
+      test('delete comments flow', async () => {
+        sandbox.spy(element, '_handleConfirmDelete');
+        sandbox.stub(element, '_reload');
+
+        // Set up control over timing of when RPC resolves.
+        let deleteDraftCommentsPromiseResolver;
+        const deleteDraftCommentsPromise = new Promise(resolve => {
+          deleteDraftCommentsPromiseResolver = resolve;
+        });
+        sandbox.stub(element.$.restAPI, 'deleteDraftComments')
+            .returns(deleteDraftCommentsPromise);
+
+        // Open confirmation dialog and tap confirm button.
+        await element.$.confirmDeleteOverlay.open();
+        MockInteractions.tap(element.$.confirmDeleteDialog.$.confirm);
+        flushAsynchronousOperations();
+        assert.isTrue(element.$.restAPI.deleteDraftComments
+            .calledWithExactly('-is:open'));
+        assert.isTrue(element.$.confirmDeleteDialog.disabled);
+        assert.equal(element._reload.callCount, 0);
+
+        // Verify state after RPC resolves.
+        deleteDraftCommentsPromiseResolver([]);
+        await deleteDraftCommentsPromise;
+        assert.equal(element._reload.callCount, 1);
+      });
+    });
+
     test('_computeTitle', () => {
       assert.equal(element._computeTitle('self'), 'My Reviews');
       assert.equal(element._computeTitle('not self'), 'Dashboard for not self');
@@ -189,59 +268,6 @@
       });
     });
 
-    suite('_getUserDashboard', () => {
-      const sections = [
-        {name: 'section 1', query: 'query 1'},
-        {name: 'section 2', query: 'query 2 for ${user}'},
-        {name: 'section 3', query: 'self only query', selfOnly: true},
-        {name: 'section 4', query: 'query 4', suffixForDashboard: 'suffix'},
-      ];
-
-      test('dashboard for self', () => {
-        return element._getUserDashboard('self', sections, 'title')
-            .then(dashboard => {
-              assert.deepEqual(
-                  dashboard,
-                  {
-                    title: 'title',
-                    sections: [
-                      {name: 'section 1', query: 'query 1'},
-                      {name: 'section 2', query: 'query 2 for self'},
-                      {
-                        name: 'section 3',
-                        query: 'self only query',
-                        selfOnly: true,
-                      }, {
-                        name: 'section 4',
-                        query: 'query 4',
-                        suffixForDashboard: 'suffix',
-                      },
-                    ],
-                  });
-            });
-      });
-
-      test('dashboard for other user', () => {
-        return element._getUserDashboard('user', sections, 'title')
-            .then(dashboard => {
-              assert.deepEqual(
-                  dashboard,
-                  {
-                    title: 'title',
-                    sections: [
-                      {name: 'section 1', query: 'query 1'},
-                      {name: 'section 2', query: 'query 2 for user'},
-                      {
-                        name: 'section 4',
-                        query: 'query 4',
-                        suffixForDashboard: 'suffix',
-                      },
-                    ],
-                  });
-            });
-      });
-    });
-
     test('hideIfEmpty sections', () => {
       const sections = [
         {name: 'test1', query: 'test1', hideIfEmpty: true},
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 6af94a1..60479e8 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
@@ -241,6 +241,10 @@
           ];
         },
       },
+      disableEdit: {
+        type: Boolean,
+        value: false,
+      },
       _hasKnownChainState: {
         type: Boolean,
         value: false,
@@ -397,7 +401,7 @@
       '_actionsChanged(actions.*, revisionActions.*, _additionalActions.*)',
       '_changeChanged(change)',
       '_editStatusChanged(editMode, editPatchsetLoaded, ' +
-          'editBasedOnCurrentPatchSet, actions.*, change.*)',
+          'editBasedOnCurrentPatchSet, disableEdit, actions.*, change.*)',
     ],
 
     listeners: {
@@ -575,7 +579,15 @@
     },
 
     _editStatusChanged(editMode, editPatchsetLoaded,
-        editBasedOnCurrentPatchSet) {
+        editBasedOnCurrentPatchSet, disableEdit) {
+      if (disableEdit) {
+        this._deleteAndNotify('publishEdit');
+        this._deleteAndNotify('rebaseEdit');
+        this._deleteAndNotify('deleteEdit');
+        this._deleteAndNotify('stopEdit');
+        this._deleteAndNotify('edit');
+        return;
+      }
       if (editPatchsetLoaded) {
         // Only show actions that mutate an edit if an actual edit patch set
         // is loaded.
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 91b39de..89d4f1f 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
@@ -439,6 +439,20 @@
     });
 
     suite('change edits', () => {
+      test('disableEdit', () => {
+        element.set('editMode', false);
+        element.set('editPatchsetLoaded', false);
+        element.change = {status: 'NEW'};
+        element.set('disableEdit', true);
+        flushAsynchronousOperations();
+
+        assert.isNotOk(element.$$('gr-button[data-action-key="publishEdit"]'));
+        assert.isNotOk(element.$$('gr-button[data-action-key="rebaseEdit"]'));
+        assert.isNotOk(element.$$('gr-button[data-action-key="deleteEdit"]'));
+        assert.isNotOk(element.$$('gr-button[data-action-key="edit"]'));
+        assert.isNotOk(element.$$('gr-button[data-action-key="stopEdit"]'));
+      });
+
       test('shows confirm dialog for delete edit', () => {
         element.set('editMode', true);
         element.set('editPatchsetLoaded', true);
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 b056233..5b36221 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
@@ -136,43 +136,42 @@
 
     suite('label updates', () => {
       let plugin;
-      let labelChangeStub;
 
-      setup(done => {
+      setup(() => {
         Gerrit.install(p => plugin = p, '0.1',
             new URL('test/plugin.html?' + Math.random(),
                     window.location.href).toString());
         sandbox.stub(Gerrit, '_arePluginsLoaded').returns(true);
         Gerrit._setPluginsPending([]);
         element = createElement();
-
-        labelChangeStub = sandbox.stub();
-        plugin.changeMetadata().onLabelsChanged(labelChangeStub);
-        flush(done);
       });
 
       test('labels changed callback', done => {
-        assert.equal(labelChangeStub.callCount, 1);
-        assert.isTrue(labelChangeStub.calledWithExactly(labels));
-        assert.equal(labelChangeStub.args[0][0]['CI'].all.length, 2);
-        element.set(['change', 'labels'], {
-          CI: {
-            all: [
-              {value: 1, name: 'user 2', _account_id: 1},
-            ],
-            values: {
-              ' 0': 'Don\'t submit as-is',
-              '+1': 'No score',
-              '+2': 'Looks good to me',
-            },
-          },
+        let callCount = 0;
+        const labelChangeSpy = sandbox.spy(arg => {
+          callCount++;
+          if (callCount === 1) {
+            assert.deepEqual(arg, labels);
+            assert.equal(arg.CI.all.length, 2);
+            element.set(['change', 'labels'], {
+              CI: {
+                all: [
+                  {value: 1, name: 'user 2', _account_id: 1},
+                ],
+                values: {
+                  ' 0': 'Don\'t submit as-is',
+                  '+1': 'No score',
+                  '+2': 'Looks good to me',
+                },
+              },
+            });
+          } else if (callCount === 2) {
+            assert.equal(arg.CI.all.length, 1);
+            done();
+          }
         });
-        // Wait for fake rest API response.
-        flush(() => {
-          assert.equal(labelChangeStub.callCount, 2);
-          assert.equal(labelChangeStub.args[1][0]['CI'].all.length, 1);
-          done();
-        });
+
+        plugin.changeMetadata().onLabelsChanged(labelChangeSpy);
       });
     });
   });
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
index 461bfc4..8885a6f 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
@@ -394,8 +394,10 @@
           <span class="headerSubject">[[_change.subject]]</span>
         </div><!-- end headerTitle -->
         <div class="commitActions" hidden$="[[!_loggedIn]]">
-          <gr-change-actions id="actions"
+          <gr-change-actions
+              id="actions"
               change="[[_change]]"
+              disable-edit="[[disableEdit]]"
               has-parent="[[hasParent]]"
               actions="[[_change.actions]]"
               revision-actions="[[_currentRevisionActions]]"
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
index ebd72ad..4d54161 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
@@ -102,6 +102,10 @@
         type: Object,
         value() { return document.body; },
       },
+      disableEdit: {
+        type: Boolean,
+        value: false,
+      },
       _commentThreads: Array,
       /** @type {?} */
       _serverConfig: {
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 89b3548..9822860 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
@@ -112,12 +112,14 @@
         margin: 5px 4px;
         text-decoration: none;
       }
+      .invisible,
       .settingsButton,
       gr-account-dropdown {
         display: none;
       }
       :host([loading]) .accountContainer,
-      :host([logged-in]) .loginButton {
+      :host([logged-in]) .loginButton,
+      :host([logged-in]) .registerButton {
         display: none;
       }
       :host([logged-in]) .settingsButton,
@@ -135,7 +137,7 @@
         text-overflow: ellipsis;
         white-space: nowrap;
       }
-      .loginButton {
+      .loginButton, .registerButton {
         color: var(--header-text-color);
         padding: .5em 1em;
       }
@@ -194,6 +196,13 @@
             class="hideOnMobile"
             name="header-browse-source"></gr-endpoint-decorator>
         <div class="accountContainer" id="accountContainer">
+          <div class$="[[_computeIsInvisible(_registerURL)]]">
+            <a
+                class="registerButton"
+                href$="[[_registerURL]]">
+              [[_registerText]]
+            </a>
+          </div>
           <a class="loginButton" href$="[[_loginURL]]">Sign in</a>
           <a
               class="settingsButton"
diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js
index dad7b7c..738d29e 100644
--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js
@@ -62,6 +62,13 @@
     },
   ];
 
+  // Set of authentication methods that can provide custom registration page.
+  const AUTH_TYPES_WITH_REGISTER_URL = new Set([
+    'LDAP',
+    'LDAP_BIND',
+    'CUSTOM_EXTENSION',
+  ]);
+
   Polymer({
     is: 'gr-main-header',
 
@@ -102,7 +109,7 @@
       _links: {
         type: Array,
         computed: '_computeLinks(_defaultLinks, _userLinks, _adminLinks, ' +
-            '_docBaseUrl)',
+            '_topMenus, _docBaseUrl)',
       },
       _loginURL: {
         type: String,
@@ -112,6 +119,18 @@
         type: Array,
         value() { return []; },
       },
+      _topMenus: {
+        type: Array,
+        value() { return []; },
+      },
+      _registerText: {
+        type: String,
+        value: 'Sign up',
+      },
+      _registerURL: {
+        type: String,
+        value: null,
+      },
     },
 
     behaviors: [
@@ -159,7 +178,7 @@
       return '//' + window.location.host + this.getBaseUrl() + path;
     },
 
-    _computeLinks(defaultLinks, userLinks, adminLinks, docBaseUrl) {
+    _computeLinks(defaultLinks, userLinks, adminLinks, topMenus, docBaseUrl) {
       const links = defaultLinks.slice();
       if (userLinks && userLinks.length > 0) {
         links.push({
@@ -179,6 +198,12 @@
         title: 'Browse',
         links: adminLinks,
       });
+      for (const m of topMenus) {
+        links.push({
+          title: m.name,
+          links: m.items.map(this._fixCustomMenuItem),
+        });
+      }
       return links;
     },
 
@@ -203,6 +228,7 @@
       this.loading = true;
       const promises = [
         this.$.restAPI.getAccount(),
+        this.$.restAPI.getTopMenus(),
         Gerrit.awaitPluginsLoaded(),
       ];
 
@@ -211,6 +237,7 @@
         this._account = account;
         this.loggedIn = !!account;
         this.loading = false;
+        this._topMenus = result[1];
 
         return this.getAdminLinks(account,
             this.$.restAPI.getAccountCapabilities.bind(this.$.restAPI),
@@ -223,7 +250,10 @@
 
     _loadConfig() {
       this.$.restAPI.getConfig()
-          .then(config => this.getDocsBaseUrl(config, this.$.restAPI))
+          .then(config => {
+            this._retrieveRegisterURL(config);
+            return this.getDocsBaseUrl(config, this.$.restAPI);
+          })
           .then(docBaseUrl => { this._docBaseUrl = docBaseUrl; });
     },
 
@@ -232,11 +262,24 @@
 
       this.$.restAPI.getPreferences().then(prefs => {
         this._userLinks =
-            prefs.my.map(this._fixMyMenuItem).filter(this._isSupportedLink);
+            prefs.my.map(this._fixCustomMenuItem).filter(this._isSupportedLink);
       });
     },
 
-    _fixMyMenuItem(linkObj) {
+    _retrieveRegisterURL(config) {
+      if (AUTH_TYPES_WITH_REGISTER_URL.has(config.auth.auth_type)) {
+        this._registerURL = config.auth.register_url;
+        if (config.auth.register_text) {
+          this._registerText = config.auth.register_text;
+        }
+      }
+    },
+
+    _computeIsInvisible(registerURL) {
+      return registerURL ? '' : 'invisible';
+    },
+
+    _fixCustomMenuItem(linkObj) {
       // Normalize all urls to PolyGerrit style.
       if (linkObj.url.startsWith('#')) {
         linkObj.url = linkObj.url.slice(1);
@@ -251,7 +294,7 @@
       // so we'll just disable it altogether for now.
       delete linkObj.target;
 
-      // Becasue the "my menu" links may be arbitrary URLs, we don't know
+      // Because the user provided links may be arbitrary URLs, we don't know
       // whether they correspond to any client routes. Mark all such links as
       // external.
       linkObj.external = true;
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 30e8e1f..b6e64ec 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
@@ -63,6 +63,8 @@
           'none');
       assert.notEqual(getComputedStyle(element.$$('.loginButton')).display,
           'none');
+      assert.notEqual(getComputedStyle(element.$$('.registerButton')).display,
+          'none');
       assert.equal(getComputedStyle(element.$$('gr-account-dropdown')).display,
           'none');
       assert.equal(getComputedStyle(element.$$('.settingsButton')).display,
@@ -70,6 +72,8 @@
       element.loggedIn = true;
       assert.equal(getComputedStyle(element.$$('.loginButton')).display,
           'none');
+      assert.equal(getComputedStyle(element.$$('.registerButton')).display,
+          'none');
       assert.notEqual(getComputedStyle(element.$$('gr-account-dropdown'))
           .display,
           'none');
@@ -81,7 +85,7 @@
       assert.deepEqual([
         {url: 'https://awesometown.com/#hashyhash'},
         {url: 'url', target: '_blank'},
-      ].map(element._fixMyMenuItem), [
+      ].map(element._fixCustomMenuItem), [
         {url: 'https://awesometown.com/#hashyhash', external: true},
         {url: 'url', external: true},
       ]);
@@ -98,7 +102,6 @@
       ]);
     });
 
-
     test('user links', () => {
       const defaultLinks = [{
         title: 'Faves',
@@ -117,13 +120,13 @@
       }];
 
       // When no admin links are passed, it should use the default.
-      assert.deepEqual(element._computeLinks(defaultLinks, [], adminLinks),
+      assert.deepEqual(element._computeLinks(defaultLinks, [], adminLinks, []),
           defaultLinks.concat({
             title: 'Browse',
             links: adminLinks,
           }));
       assert.deepEqual(
-          element._computeLinks(defaultLinks, userLinks, adminLinks),
+          element._computeLinks(defaultLinks, userLinks, adminLinks, []),
           defaultLinks.concat([
             {
               title: 'Your',
@@ -160,5 +163,61 @@
         url: 'base/index.html',
       }]);
     });
+
+    test('top menus', () => {
+      const adminLinks = [{
+        name: 'Repos',
+        url: '/repos',
+      }];
+      const topMenus = [{
+        name: 'Plugins',
+        items: [{
+          name: 'Manage',
+          target: '_blank',
+          url: 'https://gerrit/plugins/plugin-manager/static/index.html',
+        }],
+      }];
+      assert.deepEqual(element._computeLinks([], [], adminLinks, topMenus), [{
+        title: 'Browse',
+        links: adminLinks,
+      },
+      {
+        title: 'Plugins',
+        links: [{
+          name: 'Manage',
+          external: true,
+          url: 'https://gerrit/plugins/plugin-manager/static/index.html',
+        }],
+      }]);
+    });
+
+    test('register URL', () => {
+      const config = {
+        auth: {
+          auth_type: 'LDAP',
+          register_url: 'https//gerrit.example.com/register',
+        },
+      };
+      element._retrieveRegisterURL(config);
+      assert.equal(element._registerURL, config.auth.register_url);
+      assert.equal(element._registerText, 'Sign up');
+
+      config.auth.register_text = 'Create account';
+      element._retrieveRegisterURL(config);
+      assert.equal(element._registerURL, config.auth.register_url);
+      assert.equal(element._registerText, config.auth.register_text);
+    });
+
+    test('register URL ignored for wrong auth type', () => {
+      const config = {
+        auth: {
+          auth_type: 'OPENID',
+          register_url: 'https//gerrit.example.com/register',
+        },
+      };
+      element._retrieveRegisterURL(config);
+      assert.equal(element._registerURL, null);
+      assert.equal(element._registerText, 'Sign up');
+    });
   });
 </script>
diff --git a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html
index c5e32cc..05cda9b 100644
--- a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html
+++ b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html
@@ -92,6 +92,69 @@
     const EDIT_PATCHNUM = 'edit';
     const PARENT_PATCHNUM = 'PARENT';
 
+    const USER_PLACEHOLDER_PATTERN = /\$\{user\}/g;
+
+    // NOTE: These queries are tested in Java. Any changes made to definitions
+    // here require corresponding changes to:
+    // javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+    const DEFAULT_SECTIONS = [
+      {
+        // Changes with unpublished draft comments. This section is omitted when
+        // viewing other users, so we don't need to filter anything out.
+        name: 'Has draft comments',
+        query: 'has:draft',
+        selfOnly: true,
+        hideIfEmpty: true,
+        suffixForDashboard: 'limit:10',
+      },
+      {
+        // Changes that are assigned to the viewed user.
+        name: 'Assigned reviews',
+        query: 'assignee:${user} (-is:wip OR owner:self OR assignee:self) ' +
+            'is:open -is:ignored',
+        hideIfEmpty: true,
+      },
+      {
+        // WIP open changes owned by viewing user. This section is omitted when
+        // viewing other users, so we don't need to filter anything out.
+        name: 'Work in progress',
+        query: 'is:open owner:${user} is:wip',
+        selfOnly: true,
+        hideIfEmpty: true,
+      },
+      {
+        // Non-WIP open changes owned by viewed user. Filter out changes ignored
+        // by the viewing user.
+        name: 'Outgoing reviews',
+        query: 'is:open owner:${user} -is:wip -is:ignored',
+      },
+      {
+        // Non-WIP open changes not owned by the viewed user, that the viewed user
+        // is associated with (as either a reviewer or the assignee). Changes
+        // ignored by the viewing user are filtered out.
+        name: 'Incoming reviews',
+        query: 'is:open -owner:${user} -is:wip -is:ignored ' +
+            '(reviewer:${user} OR assignee:${user})',
+      },
+      {
+        // Open changes the viewed user is CCed on. Changes ignored by the viewing
+        // user are filtered out.
+        name: 'CCed on',
+        query: 'is:open -is:ignored cc:${user}',
+      },
+      {
+        name: 'Recently closed',
+        // Closed changes where viewed user is owner, reviewer, or assignee.
+        // Changes ignored by the viewing user are filtered out, and so are WIP
+        // changes not owned by the viewing user (the one instance of
+        // 'owner:self' is intentional and implements this logic).
+        query: 'is:closed -is:ignored (-is:wip OR owner:self) ' +
+            '(owner:${user} OR reviewer:${user} OR assignee:${user} ' +
+            'OR cc:${user})',
+        suffixForDashboard: '-age:4w limit:10',
+      },
+    ];
+
     window.Gerrit.Nav = {
 
       View: {
@@ -645,6 +708,17 @@
         }
         return [].concat(this._generateWeblinks(params));
       },
+
+      getUserDashboard(user = 'self', sections = DEFAULT_SECTIONS,
+          title = '') {
+        sections = sections
+          .filter(section => (user === 'self' || !section.selfOnly))
+          .map(section => Object.assign({}, section, {
+            name: section.name,
+            query: section.query.replace(USER_PLACEHOLDER_PATTERN, user),
+          }));
+        return {title, sections};
+      },
     };
   })(window);
 </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 61d1100..2f72338 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
@@ -29,5 +29,56 @@
       assert.throw(() => Gerrit.Nav.getUrlForChange('123', undefined, 12));
       assert.throw(() => Gerrit.Nav.getUrlForDiff('123', 'x.c', undefined, 12));
     });
+
+    suite('_getUserDashboard', () => {
+      const sections = [
+        {name: 'section 1', query: 'query 1'},
+        {name: 'section 2', query: 'query 2 for ${user}'},
+        {name: 'section 3', query: 'self only query', selfOnly: true},
+        {name: 'section 4', query: 'query 4', suffixForDashboard: 'suffix'},
+      ];
+
+      test('dashboard for self', () => {
+        const dashboard =
+            Gerrit.Nav.getUserDashboard('self', sections, 'title');
+        assert.deepEqual(
+            dashboard,
+            {
+              title: 'title',
+              sections: [
+                {name: 'section 1', query: 'query 1'},
+                {name: 'section 2', query: 'query 2 for self'},
+                {
+                  name: 'section 3',
+                  query: 'self only query',
+                  selfOnly: true,
+                }, {
+                  name: 'section 4',
+                  query: 'query 4',
+                  suffixForDashboard: 'suffix',
+                },
+              ],
+            });
+      });
+
+      test('dashboard for other user', () => {
+        const dashboard =
+            Gerrit.Nav.getUserDashboard('user', sections, 'title');
+        assert.deepEqual(
+            dashboard,
+            {
+              title: 'title',
+              sections: [
+                {name: 'section 1', query: 'query 1'},
+                {name: 'section 2', query: 'query 2 for user'},
+                {
+                  name: 'section 4',
+                  query: 'query 4',
+                  suffixForDashboard: 'suffix',
+                },
+              ],
+            });
+      });
+    });
   });
 </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 f527aa3..58e3125 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.html
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.html
@@ -102,7 +102,7 @@
         class="dropdown-trigger" id="trigger"
         down-arrow="[[downArrow]]"
         on-tap="_dropdownTriggerTapHandler">
-      <content></content>
+      <slot></slot>
     </gr-button>
     <iron-dropdown id="dropdown"
         vertical-align="top"
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 c97c4c7..0b90cdf 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
@@ -2640,6 +2640,14 @@
       });
     },
 
+    getTopMenus(opt_errFn) {
+      return this._fetchJSON({
+        url: '/config/server/top-menus',
+        errFn: opt_errFn,
+        reportUrlAsIs: true,
+      });
+    },
+
     setAssignee(changeNum, assignee) {
       return this._getChangeURLAndSend({
         changeNum,
@@ -2925,5 +2933,13 @@
         reportEndpointAsIs: true,
       });
     },
+
+    deleteDraftComments(query) {
+      return this._send({
+        method: 'POST',
+        url: '/accounts/self/drafts:delete',
+        body: {query},
+      });
+    },
   });
 })();
diff --git a/polygerrit-ui/app/styles/themes/dark-theme.html b/polygerrit-ui/app/styles/themes/dark-theme.html
index 8ade9ba..6037a88 100644
--- a/polygerrit-ui/app/styles/themes/dark-theme.html
+++ b/polygerrit-ui/app/styles/themes/dark-theme.html
@@ -5,7 +5,7 @@
       --view-background-color: #212121;
       --border-color: #555555;
       --table-header-background-color: #353637;
-      --table-subheader-background-color: rgb(23, 27, 31);
+      --table-subheader-background-color: rgb(19, 20, 22);
       --header-background-color: #5487E5;
       --header-text-color: var(--primary-text-color);
       --deemphasized-text-color: #9a9a9a;
@@ -83,4 +83,4 @@
       background-color: var(--view-background-color);
     }
   </style>
-</dom-module>
\ No newline at end of file
+</dom-module>
diff --git a/polygerrit-ui/run-server.sh b/polygerrit-ui/run-server.sh
index cbe3563..64bca68 100755
--- a/polygerrit-ui/run-server.sh
+++ b/polygerrit-ui/run-server.sh
@@ -14,23 +14,7 @@
 # limitations under the License.
 
 set -eu
-
-while [[ ! -f WORKSPACE && "$PWD" != / ]]; do
-  cd ..
-done
-if [[ ! -f WORKSPACE ]]; then
-  echo "$(basename "$0"): must be run from a gerrit checkout" 1>&2
-  exit 1
-fi
-
-bazel build \
-  //polygerrit-ui/app:test_components \
-  //polygerrit-ui:fonts.zip
-
-cd polygerrit-ui/app
-rm -rf bower_components
-unzip -q ../../bazel-bin/polygerrit-ui/app/test_components.zip
-rm -rf fonts
-unzip -q ../../bazel-bin/polygerrit-ui/fonts.zip -d fonts
-cd ..
-exec go run server.go "$@"
+SCRIPTNAME=$(mktemp)
+trap "{ rm -f $SCRIPTNAME; }" EXIT
+bazel run --script_path="$SCRIPTNAME" //polygerrit-ui:devserver
+"$SCRIPTNAME" "$@"
diff --git a/polygerrit-ui/server.go b/polygerrit-ui/server.go
index f44f4d7..ba685184 100644
--- a/polygerrit-ui/server.go
+++ b/polygerrit-ui/server.go
@@ -15,6 +15,7 @@
 package main
 
 import (
+	"archive/zip"
 	"bufio"
 	"compress/gzip"
 	"encoding/json"
@@ -26,27 +27,50 @@
 	"net"
 	"net/http"
 	"net/url"
+	"os"
+	"path/filepath"
 	"regexp"
 	"strings"
 
 	"github.com/robfig/soy"
+	"github.com/robfig/soy/soyhtml"
+	"golang.org/x/tools/godoc/vfs/httpfs"
+	"golang.org/x/tools/godoc/vfs/zipfs"
 )
 
 var (
-	restHost = flag.String("host", "gerrit-review.googlesource.com", "Host to proxy requests to")
+	plugins  = flag.String("plugins", "", "comma seperated plugin paths to serve")
 	port     = flag.String("port", ":8081", "Port to serve HTTP requests on")
 	prod     = flag.Bool("prod", false, "Serve production assets")
+	restHost = flag.String("host", "gerrit-review.googlesource.com", "Host to proxy requests to")
 	scheme   = flag.String("scheme", "https", "URL scheme")
-	plugins  = flag.String("plugins", "", "comma seperated plugin paths to serve")
 
-	tofu, _ = soy.NewBundle().
-		AddTemplateFile("../resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy").
-		CompileToTofu()
+	tofu *soyhtml.Tofu
 )
 
 func main() {
 	flag.Parse()
 
+	fontsArchive, err := openDataArchive("fonts.zip")
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	componentsArchive, err := openDataArchive("app/test_components.zip")
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	tofu, err = resolveIndexTemplate()
+	if err != nil {
+		log.Fatal(err)
+	}
+
+	workspace := os.Getenv("BUILD_WORKSPACE_DIRECTORY")
+	if err := os.Chdir(filepath.Join(workspace, "polygerrit-ui")); err != nil {
+		log.Fatal(err)
+	}
+
 	http.HandleFunc("/index.html", handleIndex)
 
 	if *prod {
@@ -55,6 +79,11 @@
 		http.Handle("/", http.FileServer(http.Dir("app")))
 	}
 
+	http.Handle("/bower_components/",
+		http.FileServer(httpfs.New(zipfs.New(componentsArchive, "bower_components"))))
+	http.Handle("/fonts/",
+		http.FileServer(httpfs.New(zipfs.New(fontsArchive, "fonts"))))
+
 	http.HandleFunc("/changes/", handleRESTProxy)
 	http.HandleFunc("/accounts/", handleRESTProxy)
 	http.HandleFunc("/config/", handleRESTProxy)
@@ -71,6 +100,28 @@
 	log.Fatal(http.ListenAndServe(*port, &server{}))
 }
 
+func resolveIndexTemplate() (*soyhtml.Tofu, error) {
+	basePath, err := resourceBasePath()
+	if err != nil {
+		return nil, err
+	}
+	return soy.NewBundle().
+		AddTemplateFile(basePath + ".runfiles/gerrit/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy").
+		CompileToTofu()
+}
+
+func openDataArchive(path string) (*zip.ReadCloser, error) {
+	absBinPath, err := resourceBasePath()
+	if err != nil {
+		return nil, err
+	}
+	return zip.OpenReader(absBinPath + ".runfiles/gerrit/polygerrit-ui/" + path)
+}
+
+func resourceBasePath() (string, error) {
+	return filepath.Abs(os.Args[0])
+}
+
 func handleIndex(w http.ResponseWriter, r *http.Request) {
 	var obj = map[string]interface{}{
 		"canonicalPath":      "",
diff --git a/resources/com/google/gerrit/httpd/raw/BUILD b/resources/com/google/gerrit/httpd/raw/BUILD
new file mode 100644
index 0000000..3cd3ce8
--- /dev/null
+++ b/resources/com/google/gerrit/httpd/raw/BUILD
@@ -0,0 +1,8 @@
+filegroup(
+    name = "raw",
+    srcs = glob(
+        ["**/*"],
+        exclude = ["BUILD"],
+    ),
+    visibility = ["//visibility:public"],
+)
diff --git a/tools/bzl/junit.bzl b/tools/bzl/junit.bzl
index d711356..08d5045 100644
--- a/tools/bzl/junit.bzl
+++ b/tools/bzl/junit.bzl
@@ -64,6 +64,14 @@
     implementation = _impl,
 )
 
+POST_JDK8_OPTS = [
+    # Enforce JDK 8 compatibility on Java 9, see
+    # https://docs.oracle.com/javase/9/intl/internationalization-enhancements-jdk-9.htm#JSINT-GUID-AF5AECA7-07C1-4E7D-BC10-BC7E73DC6C7F
+    "-Djava.locale.providers=COMPAT,CLDR,SPI",
+    "--add-modules java.activation",
+    "--add-opens=jdk.management/com.sun.management.internal=ALL-UNNAMED",
+]
+
 def junit_tests(name, srcs, **kwargs):
     s_name = name + "TestSuite"
     _GenSuite(
@@ -73,13 +81,8 @@
     )
     jvm_flags = kwargs.get("jvm_flags", [])
     jvm_flags = jvm_flags + select({
-        "//:java9": [
-            # Enforce JDK 8 compatibility on Java 9, see
-            # https://docs.oracle.com/javase/9/intl/internationalization-enhancements-jdk-9.htm#JSINT-GUID-AF5AECA7-07C1-4E7D-BC10-BC7E73DC6C7F
-            "-Djava.locale.providers=COMPAT,CLDR,SPI",
-            "--add-modules java.activation",
-            "--add-opens=jdk.management/com.sun.management.internal=ALL-UNNAMED",
-        ],
+        "//:java9": POST_JDK8_OPTS,
+        "//:java10": POST_JDK8_OPTS,
         "//conditions:default": [],
     })
     native.java_test(
diff --git a/tools/eclipse/project.py b/tools/eclipse/project.py
index 64d837a..e515a3a 100755
--- a/tools/eclipse/project.py
+++ b/tools/eclipse/project.py
@@ -53,21 +53,29 @@
 opts.add_option('-b', '--batch', action='store_true',
                 dest='batch', help='Bazel batch option')
 opts.add_option('-j', '--java', action='store',
-                dest='java', help='Post Java 8 support (9|10|11|...)')
+                dest='java', help='Post Java 8 support (9)')
+opts.add_option('-e', '--edge_java', action='store',
+                dest='edge_java', help='Post Java 9 support (10|11|...)')
 args, _ = opts.parse_args()
 
 batch_option = '--batch' if args.batch else None
 custom_java = args.java
+edge_java = args.edge_java
 
 def _build_bazel_cmd(*args):
+    build = False
     cmd = ['bazel']
     if batch_option:
         cmd.append('--batch')
     for arg in args:
+        if arg == "build":
+            build = True
         cmd.append(arg)
-    if custom_java:
+    if custom_java and not edge_java:
         cmd.append('--host_java_toolchain=@bazel_tools//tools/jdk:toolchain_java%s' % custom_java)
         cmd.append('--java_toolchain=@bazel_tools//tools/jdk:toolchain_java%s' % custom_java)
+        if edge_java and build:
+            cmd.append(edge_java)
     return cmd