Merge "Remove GWT UI"
diff --git a/.mailmap b/.mailmap
index 4c71059..f2fe6ca 100644
--- a/.mailmap
+++ b/.mailmap
@@ -37,6 +37,7 @@
 Joel Dodge <dodgejoel@gmail.com>                                                            dodgejoel <dodgejoel@gmail.com>
 Johan Björk <jbjoerk@gmail.com>                                                             Johan Bjork <phb@spotify.com>
 JT Olds <hello@jtolds.com>                                                                  <jtolds@gmail.com>
+Kasper Nilsson <kaspern@google.com>                                                         <kaspern@google.com>
 Lei Sun <lei.sun01@sap.com>                                                                 LeiSun <lei.sun01@sap.com>
 Lincoln Oliveira Campos Do Nascimento <lincoln.oliveiracamposdonascimento@sonyericsson.com> lincoln <lincoln.oliveiracamposdonascimento@sonyericsson.com>
 Luca Milanesio <luca.milanesio@gmail.com>                                                   <luca@gitent-scm.com>
diff --git a/Documentation/cmd-plugin-install.txt b/Documentation/cmd-plugin-install.txt
index 5443613..ef68b40 100644
--- a/Documentation/cmd-plugin-install.txt
+++ b/Documentation/cmd-plugin-install.txt
@@ -46,29 +46,25 @@
 Install a plugin from an absolute file path on the server's host:
 
 ----
-	ssh -p 29418 localhost gerrit plugin install -n name.jar \
-	  $(pwd)/my-plugin.jar
+ssh -p 29418 localhost gerrit plugin install -n name.jar $(pwd)/my-plugin.jar
 ----
 
 Install a WebUI plugin from an absolute file path on the server's host:
 
 ----
-  ssh -p 29418 localhost gerrit plugin install -n name.js \
-    $(pwd)/my-webui-plugin.js
+ssh -p 29418 localhost gerrit plugin install -n name.js $(pwd)/my-webui-plugin.js
 ----
 
 Install a plugin from an HTTP site:
 
 ----
-	ssh -p 29418 localhost gerrit plugin install -n name.jar \
-	  http://build-server/output/our-plugin
+ssh -p 29418 localhost gerrit plugin install -n name.jar http://build-server/output/our-plugin
 ----
 
 Install a plugin from piped input:
 
 ----
-	ssh -p 29418 localhost gerrit plugin install -n name.jar \
-	  - <target/name-0.1.jar
+ssh -p 29418 localhost gerrit plugin install -n name.jar - <target/name-0.1.jar
 ----
 
 GERRIT
diff --git a/Documentation/cmd-plugin-reload.txt b/Documentation/cmd-plugin-reload.txt
index ad1e5e7..5cfb6cc 100644
--- a/Documentation/cmd-plugin-reload.txt
+++ b/Documentation/cmd-plugin-reload.txt
@@ -36,7 +36,7 @@
 Reload a plugin:
 
 ----
-	ssh -p 29418 localhost gerrit plugin reload my-plugin
+ssh -p 29418 localhost gerrit plugin reload my-plugin
 ----
 
 GERRIT
diff --git a/Documentation/cmd-plugin-remove.txt b/Documentation/cmd-plugin-remove.txt
index 805c7b4..f5fe56b 100644
--- a/Documentation/cmd-plugin-remove.txt
+++ b/Documentation/cmd-plugin-remove.txt
@@ -33,7 +33,7 @@
 Disable a plugin:
 
 ----
-	ssh -p 29418 localhost gerrit plugin remove my-plugin
+ssh -p 29418 localhost gerrit plugin remove my-plugin
 ----
 
 GERRIT
diff --git a/Documentation/cmd-query.txt b/Documentation/cmd-query.txt
index 90e5cdd..79723c5 100644
--- a/Documentation/cmd-query.txt
+++ b/Documentation/cmd-query.txt
@@ -117,18 +117,18 @@
 
 Find the 2 most recent open changes in the tools/gerrit project:
 ----
-  $ ssh -p 29418 review.example.com gerrit query --format=JSON status:open project:tools/gerrit limit:2
-  {"project":"tools/gerrit", ...}
-  {"project":"tools/gerrit", ...}
-  {"type":"stats","rowCount":2,"runningTimeMilliseconds:15}
+$ ssh -p 29418 review.example.com gerrit query --format=JSON status:open project:tools/gerrit limit:2
+{"project":"tools/gerrit", ...}
+{"project":"tools/gerrit", ...}
+{"type":"stats","rowCount":2,"runningTimeMilliseconds:15}
 ----
 
 Skip number of changes:
 ----
-  $ ssh -p 29418 review.example.com gerrit query --format=JSON --start 42 status:open project:tools/gerrit limit:2
-  {"project":"tools/gerrit", ...}
-  {"project":"tools/gerrit", ...}
-  {"type":"stats","rowCount":1,"runningTimeMilliseconds:15}
+$ ssh -p 29418 review.example.com gerrit query --format=JSON --start 42 status:open project:tools/gerrit limit:2
+{"project":"tools/gerrit", ...}
+{"project":"tools/gerrit", ...}
+{"type":"stats","rowCount":1,"runningTimeMilliseconds:15}
 ----
 
 
diff --git a/Documentation/cmd-receive-pack.txt b/Documentation/cmd-receive-pack.txt
index b62b9a9..9c6d9fa 100644
--- a/Documentation/cmd-receive-pack.txt
+++ b/Documentation/cmd-receive-pack.txt
@@ -1,7 +1,7 @@
 = git-receive-pack
 
 == NAME
-git-receive-pack - Receive what is pushed into the repository
+git-receive-pack - Receive what is pushed into the repository.
 
 == SYNOPSIS
 [verse]
@@ -43,36 +43,36 @@
 
 Send a review for a change on the master branch to charlie@example.com:
 ----
-	git push ssh://review.example.com:29418/project HEAD:refs/for/master%r=charlie@example.com
+git push ssh://review.example.com:29418/project HEAD:refs/for/master%r=charlie@example.com
 ----
 
 Send reviews, but tagging them with the topic name 'bug42':
 ----
-	git push ssh://review.example.com:29418/project HEAD:refs/for/master%r=charlie@example.com,topic=bug42
+git push ssh://review.example.com:29418/project HEAD:refs/for/master%r=charlie@example.com,topic=bug42
 ----
 
 Also CC two other parties:
 ----
-	git push ssh://review.example.com:29418/project HEAD:refs/for/master%r=charlie@example.com,cc=alice@example.com,cc=bob@example.com
+git push ssh://review.example.com:29418/project HEAD:refs/for/master%r=charlie@example.com,cc=alice@example.com,cc=bob@example.com
 ----
 
 Configure a push macro to perform the last action:
 ----
-	git config remote.charlie.url ssh://review.example.com:29418/project
-	git config remote.charlie.push HEAD:refs/for/master%r=charlie@example.com,cc=alice@example.com,cc=bob@example.com
+git config remote.charlie.url ssh://review.example.com:29418/project
+git config remote.charlie.push HEAD:refs/for/master%r=charlie@example.com,cc=alice@example.com,cc=bob@example.com
 ----
 
 afterwards `.git/config` contains the following:
 ----
 [remote "charlie"]
-  url = ssh://review.example.com:29418/project
-  push = HEAD:refs/for/master%r=charlie@example.com,cc=alice@example.com,cc=bob@example.com
+ url = ssh://review.example.com:29418/project
+ push = HEAD:refs/for/master%r=charlie@example.com,cc=alice@example.com,cc=bob@example.com
 ----
 
 and now sending a new change for review to charlie, CC'ing both
 alice and bob is much easier:
 ----
-	git push charlie
+git push charlie
 ----
 
 == SEE ALSO
diff --git a/Documentation/cmd-reload-config.txt b/Documentation/cmd-reload-config.txt
index 7a25130..6d652f5 100644
--- a/Documentation/cmd-reload-config.txt
+++ b/Documentation/cmd-reload-config.txt
@@ -33,7 +33,7 @@
 Reload the gerrit configuration:
 
 ----
-	ssh -p 29418 localhost gerrit reload-config
+ssh -p 29418 localhost gerrit reload-config
 ----
 
 GERRIT
diff --git a/Documentation/cmd-rename-group.txt b/Documentation/cmd-rename-group.txt
index a48014c..c946e88 100644
--- a/Documentation/cmd-rename-group.txt
+++ b/Documentation/cmd-rename-group.txt
@@ -32,7 +32,7 @@
 Rename the group "MyGroup" to "MyCommitters".
 
 ----
-	$ ssh -p 29418 user@review.example.com gerrit rename-group MyGroup MyCommitters
+$ ssh -p 29418 user@review.example.com gerrit rename-group MyGroup MyCommitters
 ----
 
 GERRIT
diff --git a/Documentation/cmd-review.txt b/Documentation/cmd-review.txt
index a407856..5417901 100644
--- a/Documentation/cmd-review.txt
+++ b/Documentation/cmd-review.txt
@@ -1,7 +1,7 @@
 = gerrit review
 
 == NAME
-gerrit review - Apply reviews to one or more patch sets
+gerrit review - Apply reviews to one or more patch sets.
 
 == SYNOPSIS
 [verse]
@@ -144,35 +144,35 @@
 
 Approve the change with commit c0ff33 as "Verified +1"
 ----
-	$ ssh -p 29418 review.example.com gerrit review --verified +1 c0ff33
+$ ssh -p 29418 review.example.com gerrit review --verified +1 c0ff33
 ----
 
 Vote on the project specific label "mylabel":
 ----
-	$ ssh -p 29418 review.example.com gerrit review --label mylabel=+1 c0ff33
+$ ssh -p 29418 review.example.com gerrit review --label mylabel=+1 c0ff33
 ----
 
 Append the message "Build Successful". Notice two levels of quoting is
 required, one for the local shell, and another for the argument parser
 inside the Gerrit server:
 ----
-	$ ssh -p 29418 review.example.com gerrit review -m '"Build Successful"' c0ff33
+$ ssh -p 29418 review.example.com gerrit review -m '"Build Successful"' c0ff33
 ----
 
 Mark the unmerged commits both "Verified +1" and "Code-Review +2" and
 submit them for merging:
 ----
-  $ ssh -p 29418 review.example.com gerrit review \
-    --verified +1 \
-    --code-review +2 \
-    --submit \
-    --project this/project \
-    $(git rev-list origin/master..HEAD)
+$ ssh -p 29418 review.example.com gerrit review \
+  --verified +1 \
+  --code-review +2 \
+  --submit \
+  --project this/project \
+  $(git rev-list origin/master..HEAD)
 ----
 
 Abandon an active change:
 ----
-  $ ssh -p 29418 review.example.com gerrit review --abandon c0ff33
+$ ssh -p 29418 review.example.com gerrit review --abandon c0ff33
 ----
 
 == SEE ALSO
diff --git a/Documentation/cmd-set-account.txt b/Documentation/cmd-set-account.txt
index 276306e..6808e017 100644
--- a/Documentation/cmd-set-account.txt
+++ b/Documentation/cmd-set-account.txt
@@ -110,7 +110,7 @@
 Add an email and SSH key to `watcher`'s account:
 
 ----
-    $ cat ~/.ssh/id_watcher.pub | ssh -p 29418 review.example.com gerrit set-account --add-ssh-key - --add-email mail@example.com watcher
+$ cat ~/.ssh/id_watcher.pub | ssh -p 29418 review.example.com gerrit set-account --add-ssh-key - --add-email mail@example.com watcher
 ----
 
 GERRIT
diff --git a/Documentation/cmd-set-head.txt b/Documentation/cmd-set-head.txt
index f444173..83bdf20 100644
--- a/Documentation/cmd-set-head.txt
+++ b/Documentation/cmd-set-head.txt
@@ -35,7 +35,7 @@
 Change HEAD of project `example` to `stable-2.11` branch:
 
 ----
-    $ ssh -p 29418 review.example.com gerrit set-head example --new-head stable-2.11
+$ ssh -p 29418 review.example.com gerrit set-head example --new-head stable-2.11
 ----
 
 GERRIT
diff --git a/Documentation/cmd-set-members.txt b/Documentation/cmd-set-members.txt
index 5fb2bb9..141cb33 100644
--- a/Documentation/cmd-set-members.txt
+++ b/Documentation/cmd-set-members.txt
@@ -1,7 +1,7 @@
 = gerrit set-members
 
 == NAME
-gerrit set-members - Set group members
+gerrit set-members - Set group members.
 
 == SYNOPSIS
 [verse]
@@ -59,16 +59,16 @@
 Add alice and bob, but remove eve from the groups my-committers and
 my-verifiers.
 ----
-	$ ssh -p 29418 review.example.com gerrit set-members \
-	  -a alice@example.com -a bob@example.com \
-	  -r eve@example.com my-committers my-verifiers
+$ ssh -p 29418 review.example.com gerrit set-members \
+  -a alice@example.com -a bob@example.com \
+  -r eve@example.com my-committers my-verifiers
 ----
 
 Include the group my-friends into the group my-committers, but
 exclude the included group my-testers from the group my-committers.
 ----
-	$ ssh -p 29418 review.example.com gerrit set-members \
-	  -i my-friends -e my-testers my-committers
+$ ssh -p 29418 review.example.com gerrit set-members \
+  -i my-friends -e my-testers my-committers
 ----
 
 GERRIT
diff --git a/Documentation/cmd-set-project-parent.txt b/Documentation/cmd-set-project-parent.txt
index ec5a5c6..801f15a 100644
--- a/Documentation/cmd-set-project-parent.txt
+++ b/Documentation/cmd-set-project-parent.txt
@@ -51,14 +51,14 @@
 Configure `kernel/omap` to inherit permissions from `kernel/common`:
 
 ----
-	$ ssh -p 29418 review.example.com gerrit set-project-parent --parent kernel/common kernel/omap
+$ ssh -p 29418 review.example.com gerrit set-project-parent --parent kernel/common kernel/omap
 ----
 
 Reparent all children of `myParent` to `myOtherParent`:
 
 ----
-	$ ssh -p 29418 review.example.com gerrit set-project-parent \
-	  --children-of myParent --parent myOtherParent
+$ ssh -p 29418 review.example.com gerrit set-project-parent \
+  --children-of myParent --parent myOtherParent
 ----
 
 == SEE ALSO
diff --git a/Documentation/cmd-set-project.txt b/Documentation/cmd-set-project.txt
index 7282e28..45b31ff 100644
--- a/Documentation/cmd-set-project.txt
+++ b/Documentation/cmd-set-project.txt
@@ -105,8 +105,8 @@
 and use 'merge if necessary' as merge strategy:
 
 ----
-    $ ssh -p 29418 review.example.com gerrit set-project example --submit-type MERGE_IF_NECESSARY\
-    --change-id true --content-merge false --project-state HIDDEN
+$ ssh -p 29418 review.example.com gerrit set-project example --submit-type MERGE_IF_NECESSARY \
+  --change-id true --content-merge false --project-state HIDDEN
 ----
 
 GERRIT
diff --git a/Documentation/cmd-set-reviewers.txt b/Documentation/cmd-set-reviewers.txt
index eb4335b..5e367c6 100644
--- a/Documentation/cmd-set-reviewers.txt
+++ b/Documentation/cmd-set-reviewers.txt
@@ -1,7 +1,7 @@
 = gerrit set-reviewers
 
 == NAME
-gerrit set-reviewers - Add or remove reviewers to a change
+gerrit set-reviewers - Add or remove reviewers to a change.
 
 == SYNOPSIS
 [verse]
@@ -56,32 +56,32 @@
 
 Add reviewers alice and bob, but remove eve from change Iac6b2ac2.
 ----
-	$ ssh -p 29418 review.example.com gerrit set-reviewers \
-	  -a alice@example.com -a bob@example.com \
-	  -r eve@example.com \
-	  Iac6b2ac2
+$ ssh -p 29418 review.example.com gerrit set-reviewers \
+  -a alice@example.com -a bob@example.com \
+  -r eve@example.com \
+  Iac6b2ac2
 ----
 
 Add reviewer elvis to old-style change id 1935 specifying that the change is in project "graceland"
 ----
-	$ ssh -p 29418 review.example.com gerrit set-reviewers \
-	  --project graceland \
-	  -a elvis@example.com \
-	  1935
+$ ssh -p 29418 review.example.com gerrit set-reviewers \
+  --project graceland \
+  -a elvis@example.com \
+  1935
 ----
 
 Add all project owners as reviewers to change Iac6b2ac2.
 ----
-	$ ssh -p 29418 review.example.com gerrit set-reviewers \
-	  -a "'Project Owners'" \
-	  Iac6b2ac2
+$ ssh -p 29418 review.example.com gerrit set-reviewers \
+  -a "'Project Owners'" \
+  Iac6b2ac2
 ----
 
 Add all project owners as reviewers to commit 13dff08acca571b22542ebd2e31acf4572ea0b86.
 ----
-	$ ssh -p 29418 review.example.com gerrit set-reviewers \
-	  -a "'Project Owners'" \
-	  13dff08acca571b22542ebd2e31acf4572ea0b86
+$ ssh -p 29418 review.example.com gerrit set-reviewers \
+  -a "'Project Owners'" \
+  13dff08acca571b22542ebd2e31acf4572ea0b86
 ----
 
 GERRIT
diff --git a/Documentation/cmd-show-caches.txt b/Documentation/cmd-show-caches.txt
index 215463b..050118b 100644
--- a/Documentation/cmd-show-caches.txt
+++ b/Documentation/cmd-show-caches.txt
@@ -1,7 +1,7 @@
 = gerrit show-caches
 
 == NAME
-gerrit show-caches - Display current cache statistics
+gerrit show-caches - Display current cache statistics.
 
 == SYNOPSIS
 [verse]
@@ -51,42 +51,42 @@
 == EXAMPLES
 
 ----
-  $ ssh -p 29418 review.example.com gerrit show-caches
-  Gerrit Code Review        2.9                       now   11:14:13   CEST
-                                                   uptime    6 days 20 hrs
-    Name                          |Entries              |  AvgGet |Hit Ratio|
-                                  |   Mem   Disk   Space|         |Mem  Disk|
-  --------------------------------+---------------------+---------+---------+
-    accounts                      |  4096               |   3.4ms | 99%     |
-    adv_bases                     |                     |         |         |
-    changes                       |                     |  27.1ms |  0%     |
-    groups                        |  5646               |  11.8ms | 97%     |
-    groups_bymember               |                     |         |         |
-    groups_byname                 |                     |         |         |
-    groups_bysubgroup             |   230               |   2.4ms | 62%     |
-    groups_byuuid                 |  5612               |  29.2ms | 99%     |
-    groups_external               |     1               |   1.5s  | 98%     |
-    ldap_group_existence          |                     |         |         |
-    ldap_groups                   |   650               | 680.5ms | 99%     |
-    ldap_groups_byinclude         |  1024               |         | 83%     |
-    ldap_usernames                |   390               |   3.8ms | 81%     |
-    permission_sort               | 16384               |         | 99%     |
-    plugin_resources              |                     |         |         |
-    project_list                  |     1               |   3.8s  | 99%     |
-    projects                      |  6477               |   2.9ms | 99%     |
-    sshkeys                       |  2048               |  12.5ms | 99%     |
-  D diff                          |  1299  62033 132.36m|  22.0ms | 85%  99%|
-  D diff_intraline                | 12777 218651 128.45m| 171.1ms | 31%  96%|
-  D git_tags                      |     3      6  11.85k|         |  0% 100%|
-  D web_sessions                  |  1024 151714  59.10m|         | 99%  57%|
+$ ssh -p 29418 review.example.com gerrit show-caches
+Gerrit Code Review        2.9                       now   11:14:13   CEST
+                                                 uptime    6 days 20 hrs
+  Name                          |Entries              |  AvgGet |Hit Ratio|
+                                |   Mem   Disk   Space|         |Mem  Disk|
+--------------------------------+---------------------+---------+---------+
+  accounts                      |  4096               |   3.4ms | 99%     |
+  adv_bases                     |                     |         |         |
+  changes                       |                     |  27.1ms |  0%     |
+  groups                        |  5646               |  11.8ms | 97%     |
+  groups_bymember               |                     |         |         |
+  groups_byname                 |                     |         |         |
+  groups_bysubgroup             |   230               |   2.4ms | 62%     |
+  groups_byuuid                 |  5612               |  29.2ms | 99%     |
+  groups_external               |     1               |   1.5s  | 98%     |
+  ldap_group_existence          |                     |         |         |
+  ldap_groups                   |   650               | 680.5ms | 99%     |
+  ldap_groups_byinclude         |  1024               |         | 83%     |
+  ldap_usernames                |   390               |   3.8ms | 81%     |
+  permission_sort               | 16384               |         | 99%     |
+  plugin_resources              |                     |         |         |
+  project_list                  |     1               |   3.8s  | 99%     |
+  projects                      |  6477               |   2.9ms | 99%     |
+  sshkeys                       |  2048               |  12.5ms | 99%     |
+D diff                          |  1299  62033 132.36m|  22.0ms | 85%  99%|
+D diff_intraline                | 12777 218651 128.45m| 171.1ms | 31%  96%|
+D git_tags                      |     3      6  11.85k|         |  0% 100%|
+D web_sessions                  |  1024 151714  59.10m|         | 99%  57%|
 
-  SSH:    385  users, oldest session started    6 days 20 hrs ago
-  Tasks:   10  total =    6 running +      0 ready +    4 sleeping
-  Mem:  14.94g total =   3.04g used +  11.89g free +  10.00m buffers
-        28.44g max
-           107 open files
+SSH:    385  users, oldest session started    6 days 20 hrs ago
+Tasks:   10  total =    6 running +      0 ready +    4 sleeping
+Mem:  14.94g total =   3.04g used +  11.89g free +  10.00m buffers
+      28.44g max
+         107 open files
 
-  Threads: 4 CPUs available, 371 threads
+Threads: 4 CPUs available, 371 threads
 ----
 
 == SEE ALSO
diff --git a/Documentation/cmd-show-connections.txt b/Documentation/cmd-show-connections.txt
index 2f70e3c..c5274e1 100644
--- a/Documentation/cmd-show-connections.txt
+++ b/Documentation/cmd-show-connections.txt
@@ -1,7 +1,7 @@
 = gerrit show-connections
 
 == NAME
-gerrit show-connections - Display active client SSH connections
+gerrit show-connections - Display active client SSH connections.
 
 == SYNOPSIS
 [verse]
@@ -65,20 +65,20 @@
 
 With reverse DNS lookup (default):
 ----
-	$ ssh -p 29418 review.example.com gerrit show-connections
-	Session     Start     Idle   User            Remote Host
-	--------------------------------------------------------------
-	3abf31e6 20:09:02 00:00:00  jdoe            jdoe-desktop.example.com
-	--
+$ ssh -p 29418 review.example.com gerrit show-connections
+Session     Start     Idle   User            Remote Host
+--------------------------------------------------------------
+3abf31e6 20:09:02 00:00:00  jdoe            jdoe-desktop.example.com
+--
 ----
 
 Without reverse DNS lookup:
 ----
-	$ ssh -p 29418 review.example.com gerrit show-connections -n
-	Session     Start     Idle   User            Remote Host
-	--------------------------------------------------------------
-	3abf31e6 20:09:02 00:00:00  a/1001240       10.0.0.1
-	--
+$ ssh -p 29418 review.example.com gerrit show-connections -n
+Session     Start     Idle   User            Remote Host
+--------------------------------------------------------------
+3abf31e6 20:09:02 00:00:00  a/1001240       10.0.0.1
+--
 ----
 
 GERRIT
diff --git a/Documentation/cmd-show-queue.txt b/Documentation/cmd-show-queue.txt
index 141f7e2..005ffe0 100644
--- a/Documentation/cmd-show-queue.txt
+++ b/Documentation/cmd-show-queue.txt
@@ -1,7 +1,8 @@
 = gerrit show-queue
 
 == NAME
-gerrit show-queue - Display the background work queues, including replication and indexing
+gerrit show-queue - Display the background work queues, including replication
+and indexing.
 
 == SYNOPSIS
 [verse]
@@ -75,13 +76,13 @@
 and `dst2`:
 
 ----
-	$ ssh -p 29418 review.example.com gerrit show-queue
-	Task     State                 Command
-	------------------------------------------------------------------------------
-	7aae09b2 14:31:15.435          mirror dst1:/home/git/tools/gerrit.git
-	9ad09d27 14:31:25.434          mirror dst2:/var/cache/tools/gerrit.git
-	------------------------------------------------------------------------------
-	  2 tasks
+$ ssh -p 29418 review.example.com gerrit show-queue
+Task     State                 Command
+------------------------------------------------------------------------------
+7aae09b2 14:31:15.435          mirror dst1:/home/git/tools/gerrit.git
+9ad09d27 14:31:25.434          mirror dst2:/var/cache/tools/gerrit.git
+------------------------------------------------------------------------------
+2 tasks
 ----
 
 GERRIT
diff --git a/Documentation/cmd-stream-events.txt b/Documentation/cmd-stream-events.txt
index 37af110..08b661d 100644
--- a/Documentation/cmd-stream-events.txt
+++ b/Documentation/cmd-stream-events.txt
@@ -1,6 +1,6 @@
 = gerrit stream-events
 == NAME
-gerrit stream-events - Monitor events occurring in real time
+gerrit stream-events - Monitor events occurring in real time.
 
 == SYNOPSIS
 [verse]
@@ -37,16 +37,16 @@
 == EXAMPLES
 
 ----
-  $ ssh -p 29418 review.example.com gerrit stream-events
-  {"type":"comment-added",change:{"project":"tools/gerrit", ...}, ...}
-  {"type":"comment-added",change:{"project":"tools/gerrit", ...}, ...}
+$ ssh -p 29418 review.example.com gerrit stream-events
+{"type":"comment-added",change:{"project":"tools/gerrit", ...}, ...}
+{"type":"comment-added",change:{"project":"tools/gerrit", ...}, ...}
 ----
 
 Only subscribe to specific event types:
 
 ----
-  $ ssh -p 29418 review.example.com gerrit stream-events \
-      -s patchset-created -s ref-replicated
+$ ssh -p 29418 review.example.com gerrit stream-events \
+   -s patchset-created -s ref-replicated
 ----
 
 == SCHEMA
diff --git a/Documentation/cmd-suexec.txt b/Documentation/cmd-suexec.txt
index 16338ba..9808edc 100644
--- a/Documentation/cmd-suexec.txt
+++ b/Documentation/cmd-suexec.txt
@@ -1,7 +1,7 @@
 = suexec
 
 == NAME
-suexec - Execute a command as any registered user account
+suexec - Execute a command as any registered user account.
 
 == SYNOPSIS
 [verse]
@@ -49,13 +49,13 @@
 
 Approve the change with commit c0ff33 as "Verified +1" as user bob@example.com
 ----
-  $ sudo -u gerrit ssh -p 29418 \
-    -i site_path/etc/ssh_host_rsa_key \
-    "Gerrit Code Review@localhost" \
-    suexec \
-    --as bob@example.com \
-    -- \
-    gerrit approve --verified +1 c0ff33
+$ sudo -u gerrit ssh -p 29418 \
+  -i site_path/etc/ssh_host_rsa_key \
+  "Gerrit Code Review@localhost" \
+  suexec \
+  --as bob@example.com \
+  -- \
+  gerrit approve --verified +1 c0ff33
 ----
 
 GERRIT
diff --git a/Documentation/cmd-test-submit-rule.txt b/Documentation/cmd-test-submit-rule.txt
index b8c4380..33cf2ea 100644
--- a/Documentation/cmd-test-submit-rule.txt
+++ b/Documentation/cmd-test-submit-rule.txt
@@ -29,29 +29,29 @@
 
 Test submit_rule from stdin and return the results as JSON.
 ----
- cat rules.pl | ssh -p 29418 review.example.com gerrit test-submit rule -s I78f2c6673db24e4e92ed32f604c960dc952437d9
- [
-   {
-     "status": "NOT_READY",
-     "reject": {
-       "Any-Label-Name": {}
-     }
-   }
- ]
+cat rules.pl | ssh -p 29418 review.example.com gerrit test-submit rule -s I78f2c6673db24e4e92ed32f604c960dc952437d9
+[
+  {
+    "status": "NOT_READY",
+    "reject": {
+      "Any-Label-Name": {}
+    }
+  }
+]
 ----
 
 Test the active submit_rule from the refs/meta/config branch, ignoring filters in the project parents.
 ----
- $ ssh -p 29418 review.example.com gerrit test-submit rule I78f2c6673db24e4e92ed32f604c960dc952437d9 --no-filters
- [
-   {
-     "status": "NOT_READY",
-     "need": {
-       "Code-Review": {}
-       "Verified": {}
-     }
-   }
- ]
+$ ssh -p 29418 review.example.com gerrit test-submit rule I78f2c6673db24e4e92ed32f604c960dc952437d9 --no-filters
+[
+  {
+    "status": "NOT_READY",
+    "need": {
+      "Code-Review": {}
+      "Verified": {}
+    }
+  }
+]
 ----
 
 == SCRIPTING
diff --git a/Documentation/cmd-test-submit-type.txt b/Documentation/cmd-test-submit-type.txt
index 508684f..48e5e75 100644
--- a/Documentation/cmd-test-submit-type.txt
+++ b/Documentation/cmd-test-submit-type.txt
@@ -29,14 +29,14 @@
 
 Test submit_type from stdin and return the submit type.
 ----
- cat rules.pl | ssh -p 29418 review.example.com gerrit test-submit type -s I78f2c6673db24e4e92ed32f604c960dc952437d9
- "MERGE_IF_NECESSARY"
+cat rules.pl | ssh -p 29418 review.example.com gerrit test-submit type -s I78f2c6673db24e4e92ed32f604c960dc952437d9
+"MERGE_IF_NECESSARY"
 ----
 
 Test the active submit_type from the refs/meta/config branch, ignoring filters in the project parents.
 ----
- $ ssh -p 29418 review.example.com gerrit test-submit type I78f2c6673db24e4e92ed32f604c960dc952437d9 --no-filters
- "MERGE_IF_NECESSARY"
+$ ssh -p 29418 review.example.com gerrit test-submit type I78f2c6673db24e4e92ed32f604c960dc952437d9 --no-filters
+"MERGE_IF_NECESSARY"
 ----
 
 == SCRIPTING
diff --git a/Documentation/cmd-version.txt b/Documentation/cmd-version.txt
index 85b0491..cdfc779 100644
--- a/Documentation/cmd-version.txt
+++ b/Documentation/cmd-version.txt
@@ -1,7 +1,7 @@
 = gerrit version
 
 == NAME
-gerrit version - Show the version of the currently executing Gerrit server
+gerrit version - Show the version of the currently executing Gerrit server.
 
 == SYNOPSIS
 [verse]
@@ -34,8 +34,8 @@
 == EXAMPLES
 
 ----
-	$ ssh -p 29418 review.example.com gerrit version
-	gerrit version 2.4.2
+$ ssh -p 29418 review.example.com gerrit version
+gerrit version 2.4.2
 ----
 
 GERRIT
diff --git a/WORKSPACE b/WORKSPACE
index 261e53e..50714f1 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -672,10 +672,10 @@
 
 maven_jar(
     name = "blame-cache",
-    artifact = "com/google/gitiles:blame-cache:0.2-6",
+    artifact = "com/google/gitiles:blame-cache:0.2-7",
     attach_source = False,
     repository = GERRIT,
-    sha1 = "64827f1bc2cbdbb6515f1d29ce115db94c03bb6a",
+    sha1 = "8170f33b8b1db6f55e41d7069fa050a4d102a62b",
 )
 
 # Keep this version of Soy synchronized with the version used in Gitiles.
diff --git a/java/com/google/gerrit/server/project/ContributorAgreementsChecker.java b/java/com/google/gerrit/server/project/ContributorAgreementsChecker.java
index 4ed1c0c..8912e31 100644
--- a/java/com/google/gerrit/server/project/ContributorAgreementsChecker.java
+++ b/java/com/google/gerrit/server/project/ContributorAgreementsChecker.java
@@ -15,7 +15,7 @@
 package com.google.gerrit.server.project;
 
 import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkNotNull;
+import static java.util.Objects.requireNonNull;
 
 import com.google.gerrit.common.data.ContributorAgreement;
 import com.google.gerrit.common.data.PermissionRule;
@@ -135,7 +135,7 @@
   }
 
   private boolean projectMatchesAnyPattern(String projectName, List<String> regexes) {
-    checkNotNull(regexes);
+    requireNonNull(regexes);
     checkArgument(!regexes.isEmpty());
     for (String patternString : regexes) {
       Pattern pattern;
diff --git a/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html b/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html
index d1fdf2f..af982cf 100644
--- a/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html
+++ b/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html
@@ -165,6 +165,7 @@
     PREV_FILE: 'PREV_FILE',
     NEXT_FILE_WITH_COMMENTS: 'NEXT_FILE_WITH_COMMENTS',
     PREV_FILE_WITH_COMMENTS: 'PREV_FILE_WITH_COMMENTS',
+    NEXT_UNREVIEWED_FILE: 'NEXT_UNREVIEWED_FILE',
     CURSOR_NEXT_FILE: 'CURSOR_NEXT_FILE',
     CURSOR_PREV_FILE: 'CURSOR_PREV_FILE',
     OPEN_FILE: 'OPEN_FILE',
@@ -255,6 +256,8 @@
       'Mark/unmark file as reviewed');
   _describe(Shortcut.TOGGLE_DIFF_MODE, ShortcutSection.DIFFS,
       'Toggle unified/side-by-side diff');
+  _describe(Shortcut.NEXT_UNREVIEWED_FILE, ShortcutSection.DIFFS,
+      'Mark file as reviewed and go to next unreviewed file');
 
   _describe(Shortcut.NEXT_FILE, ShortcutSection.NAVIGATION, 'Select next file');
   _describe(Shortcut.PREV_FILE, ShortcutSection.NAVIGATION,
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.js b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.js
index 5a463be..4073798 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.js
+++ b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.js
@@ -128,8 +128,17 @@
           });
     },
 
+    _refreshGroupsList() {
+      this.$.restAPI.invalidateGroupsCache(this._filter,
+          this._groupsPerPage, this._offset);
+      return this._getGroups(this._filter, this._groupsPerPage,
+          this._offset);
+    },
+
     _handleCreateGroup() {
-      this.$.createNewModal.handleCreateGroup();
+      this.$.createNewModal.handleCreateGroup().then(() => {
+        this._refreshGroupsList();
+      });
     },
 
     _handleCloseCreate() {
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.js b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.js
index 4b82e57..116f084 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.js
@@ -129,8 +129,17 @@
           });
     },
 
+    _refreshReposList() {
+      this.$.restAPI.invalidateReposCache(this._filter,
+          this._reposPerPage, this._offset);
+      return this._getRepos(this._filter, this._reposPerPage,
+          this._offset);
+    },
+
     _handleCreateRepo() {
-      this.$.createNewModal.handleCreateRepo();
+      this.$.createNewModal.handleCreateRepo().then(() => {
+        this._refreshReposList();
+      });
     },
 
     _handleCloseCreate() {
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
index 6509bb1..bec08a8 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
@@ -27,6 +27,7 @@
 <link rel="import" href="../../shared/gr-account-link/gr-account-link.html">
 <link rel="import" href="../../shared/gr-date-formatter/gr-date-formatter.html">
 <link rel="import" href="../../shared/gr-editable-label/gr-editable-label.html">
+<link rel="import" href="../../shared/gr-icons/gr-icons.html">
 <link rel="import" href="../../shared/gr-limited-text/gr-limited-text.html">
 <link rel="import" href="../../shared/gr-linked-chip/gr-linked-chip.html">
 <link rel="import" href="../../shared/gr-tooltip-content/gr-tooltip-content.html">
@@ -114,6 +115,19 @@
       #parentNotCurrentMessage {
         display: none;
       }
+      .icon {
+        margin: -.25em 0;
+      }
+      .icon.help,
+      .icon.notTrusted {
+        color: #FFA62F;
+      }
+      .icon.invalid {
+        color: var(--vote-text-color-disliked);
+      }
+      .icon.trusted {
+        color: var(--vote-text-color-recommended);
+      }
       .parentList.notCurrent.nonMerge #parentNotCurrentMessage {
         --arrow-color: #ffa62f;
         display: inline-block;
@@ -137,13 +151,40 @@
         <span class="title">Owner</span>
         <span class="value">
           <gr-account-link account="[[change.owner]]"></gr-account-link>
+          <template is="dom-if" if="[[_pushCertificateValidation]]">
+            <gr-tooltip-content
+                has-tooltip
+                title$="[[_pushCertificateValidation.message]]">
+              <iron-icon
+                  class$="icon [[_pushCertificateValidation.class]]"
+                  icon="[[_pushCertificateValidation.icon]]">
+              </iron-icon>
+            </gr-tooltip-content>
+          </template>
         </span>
       </section>
-      <section class$="[[_computeShowUploaderHide(change)]]">
+      <section class$="[[_computeShowRoleClass(change, _CHANGE_ROLE.UPLOADER)]]">
         <span class="title">Uploader</span>
         <span class="value">
           <gr-account-link
-              account="[[_computeShowUploader(change)]]"></gr-account-link>
+              account="[[_getNonOwnerRole(change, _CHANGE_ROLE.UPLOADER)]]"
+              ></gr-account-link>
+        </span>
+      </section>
+      <section class$="[[_computeShowRoleClass(change, _CHANGE_ROLE.AUTHOR)]]">
+        <span class="title">Author</span>
+        <span class="value">
+          <gr-account-link
+              account="[[_getNonOwnerRole(change, _CHANGE_ROLE.AUTHOR)]]"
+              ></gr-account-link>
+        </span>
+      </section>
+      <section class$="[[_computeShowRoleClass(change, _CHANGE_ROLE.COMMITTER)]]">
+        <span class="title">Committer</span>
+        <span class="value">
+          <gr-account-link
+              account="[[_getNonOwnerRole(change, _CHANGE_ROLE.COMMITTER)]]"
+              ></gr-account-link>
         </span>
       </section>
       <section class="assignee">
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
index 8d1546b..d3fc7e0 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
@@ -17,6 +17,17 @@
 (function() {
   'use strict';
 
+  const Defs = {};
+
+  /**
+   * @typedef {{
+   *    message: string,
+   *    icon: string,
+   *    class: string,
+   *  }}
+   */
+  Defs.PushCertificateValidation;
+
   const HASHTAG_ADD_MESSAGE = 'Add Hashtag';
 
   const SubmitTypeLabel = {
@@ -30,6 +41,24 @@
 
   const NOT_CURRENT_MESSAGE = 'Not current - rebase possible';
 
+  /**
+   * @enum {string}
+   */
+  const CertificateStatus = {
+    /**
+     * This certificate status is bad.
+     */
+    BAD: 'BAD',
+    /**
+     * This certificate status is OK.
+     */
+    OK: 'OK',
+    /**
+     * This certificate status is TRUSTED.
+     */
+    TRUSTED: 'TRUSTED',
+  };
+
   Polymer({
     is: 'gr-change-metadata',
 
@@ -76,6 +105,13 @@
         type: Boolean,
         computed: '_computeShowReviewersByState(serverConfig)',
       },
+      /**
+       * @type {Defs.PushCertificateValidation}
+       */
+      _pushCertificateValidation: {
+        type: Object,
+        computed: '_computePushCertificateValidation(serverConfig, change)',
+      },
       _showRequirements: {
         type: Boolean,
         computed: '_computeShowRequirements(change)',
@@ -97,6 +133,18 @@
         type: Array,
         computed: '_computeParents(change)',
       },
+
+      /** @type {?} */
+      _CHANGE_ROLE: {
+        type: Object,
+        readOnly: true,
+        value: {
+          OWNER: 'owner',
+          UPLOADER: 'uploader',
+          AUTHOR: 'author',
+          COMMITTER: 'committer',
+        },
+      },
     },
 
     behaviors: [
@@ -248,6 +296,59 @@
       return hasRequirements || hasLabels || !!change.work_in_progress;
     },
 
+    /**
+     * @return {?Defs.PushCertificateValidation} object representing data for
+     *     the push validation.
+     */
+    _computePushCertificateValidation(serverConfig, change) {
+      if (!serverConfig || !serverConfig.receive ||
+          !serverConfig.receive.enable_signed_push) {
+        return null;
+      }
+      const rev = change.revisions[change.current_revision];
+      if (!rev.push_certificate || !rev.push_certificate.key) {
+        return {
+          class: 'help',
+          icon: 'gr-icons:help',
+          message: 'This patch set was created without a push certificate',
+        };
+      }
+
+      const key = rev.push_certificate.key;
+      switch (key.status) {
+        case CertificateStatus.BAD:
+          return {
+            class: 'invalid',
+            icon: 'gr-icons:close',
+            message: this._problems('Push certificate is invalid', key),
+          };
+        case CertificateStatus.OK:
+          return {
+            class: 'notTrusted',
+            icon: 'gr-icons:info',
+            message: this._problems(
+                'Push certificate is valid, but key is not trusted', key),
+          };
+        case CertificateStatus.TRUSTED:
+          return {
+            class: 'trusted',
+            icon: 'gr-icons:check',
+            message: this._problems(
+                'Push certificate is valid and key is trusted', key),
+          };
+        default:
+          throw new Error(`unknown certificate status: ${key.status}`);
+      }
+    },
+
+    _problems(msg, key) {
+      if (!key || !key.problems || key.problems.length === 0) {
+        return msg;
+      }
+
+      return [msg + ':'].concat(key.problems).join('\n');
+    },
+
     _computeProjectURL(project) {
       return Gerrit.Nav.getUrlForProjectChanges(project);
     },
@@ -299,24 +400,45 @@
       return !!change.work_in_progress;
     },
 
-    _computeShowUploaderHide(change) {
-      return this._computeShowUploader(change) ? '' : 'hideDisplay';
+    _computeShowRoleClass(change, role) {
+      return this._getNonOwnerRole(change, role) ? '' : 'hideDisplay';
     },
 
-    _computeShowUploader(change) {
+    /**
+     * Get the user with the specified role on the change. Returns null if the
+     * user with that role is the same as the owner.
+     * @param {!Object} change
+     * @param {string} role One of the values from _CHANGE_ROLE
+     * @return {Object|null} either an accound or null.
+     */
+    _getNonOwnerRole(change, role) {
       if (!change.current_revision ||
           !change.revisions[change.current_revision]) {
         return null;
       }
 
       const rev = change.revisions[change.current_revision];
+      if (!rev) { return null; }
 
-      if (!rev || !rev.uploader ||
-        change.owner._account_id === rev.uploader._account_id) {
-        return null;
+      if (role === this._CHANGE_ROLE.UPLOADER &&
+          rev.uploader &&
+          change.owner._account_id !== rev.uploader._account_id) {
+        return rev.uploader;
       }
 
-      return rev.uploader;
+      if (role === this._CHANGE_ROLE.AUTHOR &&
+          rev.commit && rev.commit.author &&
+          change.owner.email !== rev.commit.author.email) {
+        return rev.commit.author;
+      }
+
+      if (role === this._CHANGE_ROLE.COMMITTER &&
+          rev.commit && rev.commit.committer &&
+          change.owner.email !== rev.commit.committer.email) {
+        return rev.commit.committer;
+      }
+
+      return null;
     },
 
     _computeParents(change) {
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
index af25d91..c5a569e 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
@@ -185,7 +185,138 @@
       assert.equal(element._computeWebLinks(element.commitInfo).length, 1);
     });
 
-    test('_computeShowUploader test for uploader', () => {
+    suite('_getNonOwnerRole', () => {
+      let change;
+
+      setup(() => {
+        change = {
+          owner: {
+            email: 'abc@def',
+            _account_id: 1019328,
+          },
+          revisions: {
+            rev1: {
+              _number: 1,
+              uploader: {
+                email: 'ghi@def',
+                _account_id: 1011123,
+              },
+              commit: {
+                author: {email: 'jkl@def'},
+                committer: {email: 'ghi@def'},
+              },
+            },
+          },
+          current_revision: 'rev1',
+        };
+      });
+
+      suite('role=uploader', () => {
+        test('_getNonOwnerRole for uploader', () => {
+          assert.deepEqual(
+              element._getNonOwnerRole(change, element._CHANGE_ROLE.UPLOADER),
+              {email: 'ghi@def', _account_id: 1011123});
+        });
+
+        test('_getNonOwnerRole that it does not return uploader', () => {
+          // Set the uploader email to be the same as the owner.
+          change.revisions.rev1.uploader._account_id = 1019328;
+          assert.isNull(element._getNonOwnerRole(change,
+              element._CHANGE_ROLE.UPLOADER));
+        });
+
+        test('_getNonOwnerRole null for uploader with no current rev', () => {
+          delete change.current_revision;
+          assert.isNull(element._getNonOwnerRole(change,
+              element._CHANGE_ROLE.UPLOADER));
+        });
+
+        test('_computeShowRoleClass show uploader', () => {
+          assert.equal(element._computeShowRoleClass(
+              change, element._CHANGE_ROLE.UPLOADER), '');
+        });
+
+        test('_computeShowRoleClass hide uploader', () => {
+          // Set the uploader email to be the same as the owner.
+          change.revisions.rev1.uploader._account_id = 1019328;
+          assert.equal(element._computeShowRoleClass(change,
+              element._CHANGE_ROLE.UPLOADER), 'hideDisplay');
+        });
+      });
+
+      suite('role=committer', () => {
+        test('_getNonOwnerRole for committer', () => {
+          assert.deepEqual(
+              element._getNonOwnerRole(change, element._CHANGE_ROLE.COMMITTER),
+              {email: 'ghi@def'});
+        });
+
+        test('_getNonOwnerRole that it does not return committer', () => {
+          // Set the committer email to be the same as the owner.
+          change.revisions.rev1.commit.committer.email = 'abc@def';
+          assert.isNull(element._getNonOwnerRole(change,
+              element._CHANGE_ROLE.COMMITTER));
+        });
+
+        test('_getNonOwnerRole null for committer with no current rev', () => {
+          delete change.current_revision;
+          assert.isNull(element._getNonOwnerRole(change,
+              element._CHANGE_ROLE.COMMITTER));
+        });
+
+        test('_getNonOwnerRole null for committer with no commit', () => {
+          delete change.revisions.rev1.commit;
+          assert.isNull(element._getNonOwnerRole(change,
+              element._CHANGE_ROLE.COMMITTER));
+        });
+
+        test('_getNonOwnerRole null for committer with no committer', () => {
+          delete change.revisions.rev1.commit.committer;
+          assert.isNull(element._getNonOwnerRole(change,
+              element._CHANGE_ROLE.COMMITTER));
+        });
+      });
+
+      suite('role=author', () => {
+        test('_getNonOwnerRole for author', () => {
+          assert.deepEqual(
+              element._getNonOwnerRole(change, element._CHANGE_ROLE.AUTHOR),
+              {email: 'jkl@def'});
+        });
+
+        test('_getNonOwnerRole that it does not return author', () => {
+          // Set the author email to be the same as the owner.
+          change.revisions.rev1.commit.author.email = 'abc@def';
+          assert.isNull(element._getNonOwnerRole(change,
+              element._CHANGE_ROLE.AUTHOR));
+        });
+
+        test('_getNonOwnerRole null for author with no current rev', () => {
+          delete change.current_revision;
+          assert.isNull(element._getNonOwnerRole(change,
+              element._CHANGE_ROLE.AUTHOR));
+        });
+
+        test('_getNonOwnerRole null for author with no commit', () => {
+          delete change.revisions.rev1.commit;
+          assert.isNull(element._getNonOwnerRole(change,
+              element._CHANGE_ROLE.AUTHOR));
+        });
+
+        test('_getNonOwnerRole null for author with no author', () => {
+          delete change.revisions.rev1.commit.author;
+          assert.isNull(element._getNonOwnerRole(change,
+              element._CHANGE_ROLE.AUTHOR));
+        });
+      });
+    });
+
+    test('Push Certificate Validation test BAD', () => {
+      const serverConfig = {
+        receive: {
+          enable_signed_push: true,
+        },
+      };
       const change = {
         change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
         owner: {
@@ -194,8 +325,13 @@
         revisions: {
           rev1: {
             _number: 1,
-            uploader: {
-              _account_id: 1011123,
+            push_certificate: {
+              key: {
+                status: 'BAD',
+                problems: [
+                  'No public keys found for key ID E5E20E52',
+                ],
+              },
             },
           },
         },
@@ -204,54 +340,21 @@
         labels: {},
         mergeable: true,
       };
-      assert.deepEqual(element._computeShowUploader(change),
-          {_account_id: 1011123});
+      const result =
+          element._computePushCertificateValidation(serverConfig, change);
+      assert.equal(result.message,
+          'Push certificate is invalid:\n' +
+          'No public keys found for key ID E5E20E52');
+      assert.equal(result.icon, 'gr-icons:close');
+      assert.equal(result.class, 'invalid');
     });
 
-    test('_computeShowUploader test that it does not return uploader', () => {
-      const change = {
-        change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
-        owner: {
-          _account_id: 1011123,
+    test('Push Certificate Validation test TRUSTED', () => {
+      const serverConfig = {
+        receive: {
+          enable_signed_push: true,
         },
-        revisions: {
-          rev1: {
-            _number: 1,
-            uploader: {
-              _account_id: 1011123,
-            },
-          },
-        },
-        current_revision: 'rev1',
-        status: 'NEW',
-        labels: {},
-        mergeable: true,
       };
-      assert.isNotOk(element._computeShowUploader(change));
-    });
-
-    test('no current_revision makes _computeShowUploader return null', () => {
-      const change = {
-        change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
-        owner: {
-          _account_id: 1011123,
-        },
-        revisions: {
-          rev1: {
-            _number: 1,
-            uploader: {
-              _account_id: 1011123,
-            },
-          },
-        },
-        status: 'NEW',
-        labels: {},
-        mergeable: true,
-      };
-      assert.isNotOk(element._computeShowUploader(change));
-    });
-
-    test('_computeShowUploaderHide test for string which equals true', () => {
       const change = {
         change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
         owner: {
@@ -260,8 +363,10 @@
         revisions: {
           rev1: {
             _number: 1,
-            uploader: {
-              _account_id: 1011123,
+            push_certificate: {
+              key: {
+                status: 'TRUSTED',
+              },
             },
           },
         },
@@ -270,21 +375,28 @@
         labels: {},
         mergeable: true,
       };
-      assert.equal(element._computeShowUploaderHide(change), '');
+      const result =
+          element._computePushCertificateValidation(serverConfig, change);
+      assert.equal(result.message,
+          'Push certificate is valid and key is trusted');
+      assert.equal(result.icon, 'gr-icons:check');
+      assert.equal(result.class, 'trusted');
     });
 
-    test('_computeShowUploaderHide test for hideDisplay', () => {
+    test('Push Certificate Validation is missing test', () => {
+      const serverConfig = {
+        receive: {
+          enable_signed_push: true,
+        },
+      };
       const change = {
         change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
         owner: {
-          _account_id: 1011123,
+          _account_id: 1019328,
         },
         revisions: {
           rev1: {
             _number: 1,
-            uploader: {
-              _account_id: 1011123,
-            },
           },
         },
         current_revision: 'rev1',
@@ -292,8 +404,12 @@
         labels: {},
         mergeable: true,
       };
-      assert.equal(
-          element._computeShowUploaderHide(change), 'hideDisplay');
+      const result =
+          element._computePushCertificateValidation(serverConfig, change);
+      assert.equal(result.message,
+          'This patch set was created without a push certificate');
+      assert.equal(result.icon, 'gr-icons:help');
+      assert.equal(result.class, 'help');
     });
 
     test('_computeParents', () => {
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
index f9745b8..a88142e 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
@@ -599,6 +599,7 @@
       };
       element._change = {
         change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
+        owner: {email: 'abc@def'},
         revisions: {
           rev2: {_number: 2, commit: {parents: []}},
           rev1: {_number: 1, commit: {parents: []}},
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
index 65d681d..9f9f026 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
@@ -43,7 +43,7 @@
   };
 
   const ButtonTooltips = {
-    SAVE: 'Save reply but do not send',
+    SAVE: 'Save reply but do not send notification',
     START_REVIEW: 'Mark as ready for review and send reply',
     SEND: 'Send reply',
   };
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router.js b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
index f4f36b3..8ae30ad 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.js
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
@@ -324,14 +324,10 @@
       if (!weblinks || !weblinks.length) return [];
       return weblinks.filter(weblink => !this._isDirectCommit(weblink)).map(
           ({name, url}) => {
-            if (url.startsWith('https:') || url.startsWith('http:')) {
-              return {name, url};
-            } else {
-              return {
-                name,
-                url: `../../${url}`,
-              };
+            if (!url.startsWith('https:') && !url.startsWith('http:')) {
+              url = this.getBaseUrl() + (url.startsWith('/') ? '' : '/') + url;
             }
+            return {name, url};
           });
     },
 
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
index b9eaa18..584fb35 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
@@ -44,6 +44,23 @@
 
     teardown(() => { sandbox.restore(); });
 
+    test('_getChangeWeblinks', () => {
+      sandbox.stub(element, '_isDirectCommit').returns(false);
+      sandbox.stub(element, 'getBaseUrl').returns('base');
+      const link = {name: 'test', url: 'test/url'};
+      const mapLinksToConfig = weblink => ({options: {weblinks: [weblink]}});
+      assert.deepEqual(element._getChangeWeblinks(mapLinksToConfig(link))[0],
+          {name: 'test', url: 'base/test/url'});
+
+      link.url = '/' + link.url;
+      assert.deepEqual(element._getChangeWeblinks(mapLinksToConfig(link))[0],
+          {name: 'test', url: 'base/test/url'});
+
+      link.url = 'https:/' + link.url;
+      assert.deepEqual(element._getChangeWeblinks(mapLinksToConfig(link))[0],
+          {name: 'test', url: 'https://test/url'});
+    });
+
     test('_getHashFromCanonicalPath', () => {
       let url = '/foo/bar';
       let hash = element._getHashFromCanonicalPath(url);
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-binary.js b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-binary.js
index a9242be..d2731a2 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-binary.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-binary.js
@@ -20,9 +20,9 @@
   // Prevent redefinition.
   if (window.GrDiffBuilderBinary) { return; }
 
-  function GrDiffBuilderBinary(diff, patchRange, commentThreadEls, prefs,
+  function GrDiffBuilderBinary(diff, commentThreadEls, prefs,
       outputEl) {
-    GrDiffBuilder.call(this, diff, patchRange, commentThreadEls, prefs,
+    GrDiffBuilder.call(this, diff, commentThreadEls, prefs,
         outputEl);
   }
 
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-image.js b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-image.js
index c52a504..f05f4f0 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-image.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-image.js
@@ -22,9 +22,9 @@
 
   const IMAGE_MIME_PATTERN = /^image\/(bmp|gif|jpeg|jpg|png|tiff|webp)$/;
 
-  function GrDiffBuilderImage(diff, patchRange, commentThreadEls, prefs,
+  function GrDiffBuilderImage(diff, commentThreadEls, prefs,
       outputEl, baseImage, revisionImage) {
-    GrDiffBuilderSideBySide.call(this, diff, patchRange, commentThreadEls,
+    GrDiffBuilderSideBySide.call(this, diff, commentThreadEls,
         prefs, outputEl, []);
     this._baseImage = baseImage;
     this._revisionImage = revisionImage;
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-side-by-side.js b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-side-by-side.js
index da085c2..81cbabb 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-side-by-side.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-side-by-side.js
@@ -20,9 +20,9 @@
   // Prevent redefinition.
   if (window.GrDiffBuilderSideBySide) { return; }
 
-  function GrDiffBuilderSideBySide(diff, patchRange, commentThreadEls,
+  function GrDiffBuilderSideBySide(diff, commentThreadEls,
       prefs, outputEl, layers) {
-    GrDiffBuilder.call(this, diff, patchRange, commentThreadEls, prefs,
+    GrDiffBuilder.call(this, diff, commentThreadEls, prefs,
         outputEl, layers);
   }
   GrDiffBuilderSideBySide.prototype = Object.create(GrDiffBuilder.prototype);
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.js b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.js
index 0657ee4..2dcdee4 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.js
@@ -20,9 +20,9 @@
   // Prevent redefinition.
   if (window.GrDiffBuilderUnified) { return; }
 
-  function GrDiffBuilderUnified(diff, patchRange, commentThreadEls, prefs,
+  function GrDiffBuilderUnified(diff, commentThreadEls, prefs,
       outputEl, layers) {
-    GrDiffBuilder.call(this, diff, patchRange, commentThreadEls, prefs,
+    GrDiffBuilder.call(this, diff, commentThreadEls, prefs,
         outputEl, layers);
   }
   GrDiffBuilderUnified.prototype = Object.create(GrDiffBuilder.prototype);
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html
index e77eb57..420a14f 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html
@@ -169,8 +169,7 @@
           // Stop the processor and syntax layer (if they're running).
           this.cancel();
 
-          this._builder = this._getDiffBuilder(
-              this.diff, comments.meta.patchRange, prefs);
+          this._builder = this._getDiffBuilder(this.diff, prefs);
 
           this.$.processor.context = prefs.context;
           this.$.processor.keyLocations = this._getKeyLocations(comments,
@@ -294,7 +293,7 @@
           throw Error(`Invalid preference value: ${pref}`);
         },
 
-        _getDiffBuilder(diff, patchRange, prefs) {
+        _getDiffBuilder(diff, prefs) {
           if (isNaN(prefs.tab_size) || prefs.tab_size <= 0) {
             this._handlePreferenceError('tab size');
             return;
@@ -307,19 +306,19 @@
 
           let builder = null;
           if (this.isImageDiff) {
-            builder = new GrDiffBuilderImage(diff, patchRange,
+            builder = new GrDiffBuilderImage(diff,
               this._commentThreadElements, prefs, this.diffElement,
               this.baseImage, this.revisionImage);
           } else if (diff.binary) {
             // If the diff is binary, but not an image.
-            return new GrDiffBuilderBinary(diff, patchRange,
+            return new GrDiffBuilderBinary(diff,
                 this._commentThreadElements, prefs, this.diffElement);
           } else if (this.viewMode === DiffViewMode.SIDE_BY_SIDE) {
-            builder = new GrDiffBuilderSideBySide(diff, patchRange,
+            builder = new GrDiffBuilderSideBySide(diff,
                 this._commentThreadElements, prefs, this.diffElement,
                 this._layers);
           } else if (this.viewMode === DiffViewMode.UNIFIED) {
-            builder = new GrDiffBuilderUnified(diff, patchRange,
+            builder = new GrDiffBuilderUnified(diff,
                 this._commentThreadElements, prefs, this.diffElement,
                 this._layers);
           }
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js
index 6ea48ac..d428f68 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js
@@ -96,10 +96,9 @@
    */
   const REGEX_TAB_OR_SURROGATE_PAIR = /\t|[\uD800-\uDBFF][\uDC00-\uDFFF]/;
 
-  function GrDiffBuilder(diff, patchRange, commentThreadEls, prefs,
+  function GrDiffBuilder(diff, commentThreadEls, prefs,
       outputEl, layers) {
     this._diff = diff;
-    this._patchRange = patchRange;
     this._prefs = prefs;
     this._outputEl = outputEl;
     this.groups = [];
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html
index c277f34..fd74d55 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder_test.html
@@ -75,8 +75,7 @@
         show_tabs: true,
         tab_size: 4,
       };
-      builder = new GrDiffBuilder(
-          {content: []}, {left: [], right: []}, [], prefs);
+      builder = new GrDiffBuilder({content: []}, [], prefs);
     });
 
     teardown(() => { sandbox.restore(); });
@@ -329,9 +328,7 @@
       r5.setAttribute('comment-side', 'right');
       r5.setAttribute('line-num', 5);
 
-      builder = new GrDiffBuilder(
-          {content: []}, {basePatchNum: 'PARENT', patchNum: '3'}, [l3, l5, r5],
-          prefs);
+      builder = new GrDiffBuilder({content: []}, [l3, l5, r5], prefs);
 
       function checkThreadGroupProps(threadGroupEl,
           expectedThreadEls) {
@@ -357,8 +354,6 @@
           builder._commentThreadGroupForLine(line, GrDiffBuilder.Side.LEFT);
       checkThreadGroupProps(threadGroupEl, [l5]);
 
-      builder._patchRange.basePatchNum = '1';
-
       threadGroupEl = builder._commentThreadGroupForLine(line);
       checkThreadGroupProps(threadGroupEl, [l5, r5]);
 
@@ -370,8 +365,6 @@
           builder._commentThreadGroupForLine(line, GrDiffBuilder.Side.RIGHT);
       checkThreadGroupProps(threadGroupEl, [r5]);
 
-      builder._patchRange.basePatchNum = 'PARENT';
-
       line = new GrDiffLine(GrDiffLine.Type.REMOVE);
       line.beforeNumber = 5;
       line.afterNumber = 5;
@@ -389,7 +382,7 @@
     test('_handlePreferenceError called with invalid preference', () => {
       sandbox.stub(element, '_handlePreferenceError');
       const prefs = {tab_size: 0};
-      element._getDiffBuilder(element.diff, undefined, prefs);
+      element._getDiffBuilder(element.diff, prefs);
       assert.isTrue(element._handlePreferenceError.lastCall
           .calledWithExactly('tab size'));
     });
@@ -947,8 +940,7 @@
         outputEl = element.queryEffectiveChildren('#diffTable');
         comments = {left: [], right: [], meta: {patchRange: undefined}};
         sandbox.stub(element, '_getDiffBuilder', () => {
-          const builder = new GrDiffBuilder(
-              {content}, undefined, [], prefs, outputEl);
+          const builder = new GrDiffBuilder({content}, [], prefs, outputEl);
           sandbox.stub(builder, 'addColumns');
           builder.buildSectionElement = function(group) {
             const section = document.createElement('stub');
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
index 59c5b1f..fd54af4 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
@@ -169,6 +169,10 @@
         type: Object,
         computed: '_getRevisionInfo(_change)',
       },
+      _reviewedFiles: {
+        type: Object,
+        value: () => new Set(),
+      },
     },
 
     behaviors: [
@@ -215,6 +219,7 @@
         [this.Shortcut.TOGGLE_DIFF_MODE]: '_handleToggleDiffMode',
         [this.Shortcut.TOGGLE_FILE_REVIEWED]: '_handleToggleFileReviewed',
         [this.Shortcut.EXPAND_ALL_DIFF_CONTEXT]: '_handleExpandAllDiffContext',
+        [this.Shortcut.NEXT_UNREVIEWED_FILE]: '_handleNextUnreviewedFile',
 
         // Final two are actually handled by gr-diff-comment-thread.
         [this.Shortcut.EXPAND_ALL_COMMENT_THREADS]: null,
@@ -564,10 +569,18 @@
       return {path: fileList[idx]};
     },
 
+    _getReviewedFiles(changeNum, patchNum) {
+      return this.$.restAPI.getReviewedFiles(changeNum, patchNum)
+          .then(files => {
+            this._reviewedFiles = new Set(files);
+            return this._reviewedFiles;
+          });
+    },
+
     _getReviewedStatus(editMode, changeNum, patchNum, path) {
       if (editMode) { return Promise.resolve(false); }
-      return this.$.restAPI.getReviewedFiles(changeNum, patchNum)
-          .then(files => files.includes(path));
+      return this._getReviewedFiles(changeNum, patchNum)
+          .then(files => files.has(path));
     },
 
     _paramsChanged(value) {
@@ -1025,5 +1038,15 @@
     _computeDiffPrefsDisabled(disableDiffPrefs, loggedIn) {
       return disableDiffPrefs || !loggedIn;
     },
+
+    _handleNextUnreviewedFile(e) {
+      this._setReviewed(true);
+      // Ensure that the currently viewed file always appears in unreviewedFiles
+      // so we resolve the right "next" file.
+      const unreviewedFiles = this._fileList
+          .filter(file =>
+          (file === this._path || !this._reviewedFiles.has(file)));
+      this._navToFile(this._path, unreviewedFiles, 1);
+    },
   });
 })();
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
index 3a5ca51..0274330 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
@@ -67,6 +67,7 @@
     kb.bindShortcut(kb.Shortcut.EXPAND_ALL_DIFF_CONTEXT, 'shift+x');
     kb.bindShortcut(kb.Shortcut.EXPAND_ALL_COMMENT_THREADS, 'e');
     kb.bindShortcut(kb.Shortcut.COLLAPSE_ALL_COMMENT_THREADS, 'shift+e');
+    kb.bindShortcut(kb.Shortcut.NEXT_UNREVIEWED_FILE, 'shift+m');
 
     let element;
     let sandbox;
@@ -1127,5 +1128,22 @@
       assert.isTrue(setStub.calledOnce);
       assert.isTrue(setStub.calledWith(101, 'test-project'));
     });
+
+    test('shift+m navigates to next unreviewed file', () => {
+      element._fileList = ['file1', 'file2', 'file3'];
+      element._reviewedFiles = new Set(['file1', 'file2']);
+      element._path = 'file1';
+      const reviewedStub = sandbox.stub(element, '_setReviewed');
+      const navStub = sandbox.stub(element, '_navToFile');
+      MockInteractions.pressAndReleaseKeyOn(element, 77, 'shift', 'm');
+      flushAsynchronousOperations();
+
+      assert.isTrue(reviewedStub.lastCall.args[0]);
+      assert.deepEqual(navStub.lastCall.args, [
+        'file1',
+        ['file1', 'file3'],
+        1,
+      ]);
+    });
   });
 </script>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
index 4befd2f..62284ad 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
@@ -302,8 +302,7 @@
 
         const mock = document.createElement('mock-diff-response');
         element.$.diffBuilder._builder = element.$.diffBuilder._getDiffBuilder(
-            mock.diffResponse, {left: [], right: []},
-            {tab_size: 2, line_length: 80});
+            mock.diffResponse, {tab_size: 2, line_length: 80});
 
         // No thread groups.
         assert.isNotOk(element._getThreadGroupForLine(contentEl));
diff --git a/polygerrit-ui/app/elements/gr-app.js b/polygerrit-ui/app/elements/gr-app.js
index e878f7c..f2f06d1 100644
--- a/polygerrit-ui/app/elements/gr-app.js
+++ b/polygerrit-ui/app/elements/gr-app.js
@@ -275,6 +275,8 @@
       this.bindShortcut(
           this.Shortcut.TOGGLE_FILE_REVIEWED, 'r');
       this.bindShortcut(
+          this.Shortcut.NEXT_UNREVIEWED_FILE, 'shift+m');
+      this.bindShortcut(
           this.Shortcut.TOGGLE_ALL_INLINE_DIFFS, 'shift+i:keyup');
       this.bindShortcut(
           this.Shortcut.TOGGLE_INLINE_DIFF, 'i:keyup');
diff --git a/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.html b/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.html
index 4f80475..4ea8cc7 100644
--- a/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.html
+++ b/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.html
@@ -48,6 +48,10 @@
       <g id="publishEdit"><path d="M5 4v2h14V4H5zm0 10h4v6h6v-6h4l-7-7-7 7z"/></g>
       <!-- This SVG is a copy from iron-icons https://github.com/PolymerElements/iron-icons/blob/master/editor-icons.html -->
       <g id="delete"><path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"/></g>
+      <!-- This SVG is a copy from iron-icons https://github.com/PolymerElements/iron-icons/blob/master/iron-icons.js -->
+      <g id="help"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 17h-2v-2h2v2zm2.07-7.75l-.9.92C13.45 12.9 13 13.5 13 15h-2v-.5c0-1.1.45-2.1 1.17-2.83l1.24-1.26c.37-.36.59-.86.59-1.41 0-1.1-.9-2-2-2s-2 .9-2 2H8c0-2.21 1.79-4 4-4s4 1.79 4 4c0 .88-.36 1.68-.93 2.25z"></path></g>
+      <!-- This SVG is a copy from iron-icons https://github.com/PolymerElements/iron-icons/blob/master/iron-icons.js -->
+      <g id="info"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"></path></g>
       <!-- This SVG is a copy from material.io https://material.io/icons/#ic_hourglass_full-->
       <g id="hourglass"><path d="M6 2v6h.01L6 8.01 10 12l-4 4 .01.01H6V22h12v-5.99h-.01L18 16l-4-4 4-3.99-.01-.01H18V2H6z"/><path d="M0 0h24v24H0V0z" fill="none"/></g>
       <!-- This is a custom PolyGerrit SVG -->
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 2a1ad9e..d9b0cbf 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
@@ -169,6 +169,16 @@
     delete(key) {
       this._cache().delete(key);
     }
+
+    invalidatePrefix(prefix) {
+      const newMap = new Map();
+      for (const [key, value] of this._cache().entries()) {
+        if (!key.startsWith(prefix)) {
+          newMap.set(key, value);
+        }
+      }
+      this._data.set(window.CANONICAL_PATH, newMap);
+    }
   }
 
   Polymer({
@@ -1207,6 +1217,20 @@
       return this._sharedFetchPromises[req.url];
     },
 
+    /**
+     * @param {string} prefix
+     */
+    _invalidateSharedFetchPromisesPrefix(prefix) {
+      const newObject = {};
+      Object.entries(this._sharedFetchPromises).forEach(([key, value]) => {
+        if (!key.startsWith(prefix)) {
+          newObject[key] = value;
+        }
+      });
+      this._sharedFetchPromises = newObject;
+      this._cache.invalidatePrefix(prefix);
+    },
+
     _isNarrowScreen() {
       return window.innerWidth < MAX_UNIFIED_DEFAULT_WINDOW_WIDTH_PX;
     },
@@ -1297,21 +1321,27 @@
      * @param {function()=} opt_cancelCondition
      */
     getChangeDetail(changeNum, opt_errFn, opt_cancelCondition) {
-      const options = this.listChangesOptionsToHex(
-          this.ListChangesOption.ALL_COMMITS,
-          this.ListChangesOption.ALL_REVISIONS,
-          this.ListChangesOption.CHANGE_ACTIONS,
-          this.ListChangesOption.CURRENT_ACTIONS,
-          this.ListChangesOption.DETAILED_LABELS,
-          this.ListChangesOption.DOWNLOAD_COMMANDS,
-          this.ListChangesOption.MESSAGES,
-          this.ListChangesOption.SUBMITTABLE,
-          this.ListChangesOption.WEB_LINKS,
-          this.ListChangesOption.SKIP_MERGEABLE
-      );
-      return this._getChangeDetail(
-          changeNum, options, opt_errFn, opt_cancelCondition)
-          .then(GrReviewerUpdatesParser.parse);
+      const options = [
+        this.ListChangesOption.ALL_COMMITS,
+        this.ListChangesOption.ALL_REVISIONS,
+        this.ListChangesOption.CHANGE_ACTIONS,
+        this.ListChangesOption.CURRENT_ACTIONS,
+        this.ListChangesOption.DETAILED_LABELS,
+        this.ListChangesOption.DOWNLOAD_COMMANDS,
+        this.ListChangesOption.MESSAGES,
+        this.ListChangesOption.SUBMITTABLE,
+        this.ListChangesOption.WEB_LINKS,
+        this.ListChangesOption.SKIP_MERGEABLE,
+      ];
+      return this.getConfig(false).then(config => {
+        if (config.receive && config.receive.enable_signed_push) {
+          options.push(this.ListChangesOption.PUSH_CERTIFICATES);
+        }
+        const optionsHex = this.listChangesOptionsToHex(...options);
+        return this._getChangeDetail(
+            changeNum, optionsHex, opt_errFn, opt_cancelCondition)
+            .then(GrReviewerUpdatesParser.parse);
+      });
     },
 
     /**
@@ -1527,25 +1557,20 @@
      * @param {string} filter
      * @param {number} groupsPerPage
      * @param {number=} opt_offset
-     * @return {!Promise<?Object>}
      */
-    getGroups(filter, groupsPerPage, opt_offset) {
+    _getGroupsUrl(filter, groupsPerPage, opt_offset) {
       const offset = opt_offset || 0;
 
-      return this._fetchSharedCacheURL({
-        url: `/groups/?n=${groupsPerPage + 1}&S=${offset}` +
-            this._computeFilter(filter),
-        anonymizedUrl: '/groups/?*',
-      });
+      return `/groups/?n=${groupsPerPage + 1}&S=${offset}` +
+        this._computeFilter(filter);
     },
 
     /**
      * @param {string} filter
      * @param {number} reposPerPage
      * @param {number=} opt_offset
-     * @return {!Promise<?Object>}
      */
-    getRepos(filter, reposPerPage, opt_offset) {
+    _getReposUrl(filter, reposPerPage, opt_offset) {
       const defaultFilter = 'state:active OR state:read-only';
       const namePartDelimiters = /[@.\-\s\/_]/g;
       const offset = opt_offset || 0;
@@ -1572,11 +1597,46 @@
       filter = filter.trim();
       const encodedFilter = encodeURIComponent(filter);
 
+      return `/projects/?n=${reposPerPage + 1}&S=${offset}` +
+        `&query=${encodedFilter}`;
+    },
+
+    invalidateGroupsCache() {
+      this._invalidateSharedFetchPromisesPrefix('/groups/?');
+    },
+
+    invalidateReposCache(filter, reposPerPage, opt_offset) {
+      this._invalidateSharedFetchPromisesPrefix('/projects/?');
+    },
+
+    /**
+     * @param {string} filter
+     * @param {number} groupsPerPage
+     * @param {number=} opt_offset
+     * @return {!Promise<?Object>}
+     */
+    getGroups(filter, groupsPerPage, opt_offset) {
+      const url = this._getGroupsUrl(filter, groupsPerPage, opt_offset);
+
+      return this._fetchSharedCacheURL({
+        url,
+        anonymizedUrl: '/groups/?*',
+      });
+    },
+
+    /**
+     * @param {string} filter
+     * @param {number} reposPerPage
+     * @param {number=} opt_offset
+     * @return {!Promise<?Object>}
+     */
+    getRepos(filter, reposPerPage, opt_offset) {
+      const url = this._getReposUrl(filter, reposPerPage, opt_offset);
+
       // TODO(kaspern): Rename rest api from /projects/ to /repos/ once backend
       // supports it.
       return this._fetchSharedCacheURL({
-        url: `/projects/?n=${reposPerPage + 1}&S=${offset}` +
-            `&query=${encodedFilter}`,
+        url,
         anonymizedUrl: '/projects/?*',
       });
     },
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
index eaac5ef..667f24c 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
@@ -96,6 +96,18 @@
       });
     });
 
+    test('cache invalidation', () => {
+      element._cache.set('/foo/bar', 1);
+      element._cache.set('/bar', 2);
+      element._sharedFetchPromises['/foo/bar'] = 3;
+      element._sharedFetchPromises['/bar'] = 4;
+      element._invalidateSharedFetchPromisesPrefix('/foo/');
+      assert.isFalse(element._cache.has('/foo/bar'));
+      assert.isTrue(element._cache.has('/bar'));
+      assert.isUndefined(element._sharedFetchPromises['/foo/bar']);
+      assert.strictEqual(4, element._sharedFetchPromises['/bar']);
+    });
+
     test('params are properly encoded', () => {
       let url = element._urlWithParams('/path/', {
         sp: 'hola',
@@ -722,15 +734,6 @@
       assert.deepEqual(element._send.lastCall.args[0].body, {token: 'foo'});
     });
 
-    test('GrReviewerUpdatesParser.parse is used', () => {
-      sandbox.stub(GrReviewerUpdatesParser, 'parse').returns(
-          Promise.resolve('foo'));
-      return element.getChangeDetail(42).then(result => {
-        assert.isTrue(GrReviewerUpdatesParser.parse.calledOnce);
-        assert.equal(result, 'foo');
-      });
-    });
-
     test('setAccountStatus', () => {
       sandbox.stub(element, '_send').returns(Promise.resolve('OOO'));
       element._cache.set('/accounts/self/detail', {});
@@ -935,6 +938,31 @@
       });
     });
 
+    test('normal use', () => {
+      const defaultQuery = 'state%3Aactive%20OR%20state%3Aread-only';
+
+      assert.equal(element._getReposUrl('test', 25),
+          '/projects/?n=26&S=0&query=test');
+
+      assert.equal(element._getReposUrl(null, 25),
+          `/projects/?n=26&S=0&query=${defaultQuery}`);
+
+      assert.equal(element._getReposUrl('test', 25, 25),
+          '/projects/?n=26&S=25&query=test');
+    });
+
+    test('invalidateReposCache', () => {
+      const url = '/projects/?n=26&S=0&query=test';
+
+      element._cache.set(url, {});
+
+      element.invalidateReposCache('test', 25);
+
+      assert.isUndefined(element._sharedFetchPromises[url]);
+
+      assert.isFalse(element._cache.has(url));
+    });
+
     suite('getRepos', () => {
       const defaultQuery = 'state%3Aactive%20OR%20state%3Aread-only';
 
@@ -999,11 +1027,57 @@
       });
     });
 
-    test('getGroups filter regex', () => {
-      sandbox.stub(element, '_fetchSharedCacheURL');
-      element.getGroups('^test.*', 25);
-      assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
-          '/groups/?n=26&S=0&r=%5Etest.*');
+    test('_getGroupsUrl normal use', () => {
+      assert.equal(element._getGroupsUrl('test', 25),
+          '/groups/?n=26&S=0&m=test');
+
+      assert.equal(element._getGroupsUrl(null, 25),
+          '/groups/?n=26&S=0');
+
+      assert.equal(element._getGroupsUrl('test', 25, 25),
+          '/groups/?n=26&S=25&m=test');
+    });
+
+    test('invalidateGroupsCache', () => {
+      const url = '/groups/?n=26&S=0&m=test';
+
+      element._cache.set(url, {});
+
+      element.invalidateGroupsCache('test', 25);
+
+      assert.isUndefined(element._sharedFetchPromises[url]);
+
+      assert.isFalse(element._cache.has(url));
+    });
+
+    suite('getGroups', () => {
+      setup(() => {
+        sandbox.stub(element, '_fetchSharedCacheURL');
+      });
+
+      test('normal use', () => {
+        element.getGroups('test', 25);
+        assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+            '/groups/?n=26&S=0&m=test');
+
+        element.getGroups(null, 25);
+        assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+            '/groups/?n=26&S=0');
+
+        element.getGroups('test', 25, 25);
+        assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+            '/groups/?n=26&S=25&m=test');
+      });
+
+      test('regex', () => {
+        element.getGroups('^test.*', 25);
+        assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+            '/groups/?n=26&S=0&r=%5Etest.*');
+
+        element.getGroups('^test.*', 25, 25);
+        assert.equal(element._fetchSharedCacheURL.lastCall.args[0].url,
+            '/groups/?n=26&S=25&r=%5Etest.*');
+      });
     });
 
     test('gerrit auth is used', () => {
@@ -1031,7 +1105,49 @@
       });
     });
 
-    suite('_getChangeDetail', () => {
+    suite('getChangeDetail', () => {
+      suite('change detail options', () => {
+        let toHexStub;
+
+        setup(() => {
+          toHexStub = sandbox.stub(element, 'listChangesOptionsToHex',
+              options => 'deadbeef');
+          sandbox.stub(element, '_getChangeDetail',
+              async (changeNum, options) => ({changeNum, options}));
+        });
+
+        test('signed pushes disabled', async () => {
+          const {PUSH_CERTIFICATES} = element.ListChangesOption;
+          sandbox.stub(element, 'getConfig', async () => ({}));
+          const {changeNum, options} = await element.getChangeDetail(123);
+          assert.strictEqual(123, changeNum);
+          assert.strictEqual('deadbeef', options);
+          assert.isTrue(toHexStub.calledOnce);
+          assert.isFalse(toHexStub.lastCall.args.includes(PUSH_CERTIFICATES));
+        });
+
+        test('signed pushes enabled', async () => {
+          const {PUSH_CERTIFICATES} = element.ListChangesOption;
+          sandbox.stub(element, 'getConfig', async () => {
+            return {receive: {enable_signed_push: true}};
+          });
+          const {changeNum, options} = await element.getChangeDetail(123);
+          assert.strictEqual(123, changeNum);
+          assert.strictEqual('deadbeef', options);
+          assert.isTrue(toHexStub.calledOnce);
+          assert.isTrue(toHexStub.lastCall.args.includes(PUSH_CERTIFICATES));
+        });
+      });
+
+      test('GrReviewerUpdatesParser.parse is used', () => {
+        sandbox.stub(GrReviewerUpdatesParser, 'parse').returns(
+            Promise.resolve('foo'));
+        return element.getChangeDetail(42).then(result => {
+          assert.isTrue(GrReviewerUpdatesParser.parse.calledOnce);
+          assert.equal(result, 'foo');
+        });
+      });
+
       test('_getChangeDetail passes params to ETags decorator', () => {
         const changeNum = 4321;
         element._projectLookup[changeNum] = 'test';