Merge "Apply Prolog reduction limit even to bootstrapped machine"
diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt
index d333347..77a58fa4 100644
--- a/Documentation/access-control.txt
+++ b/Documentation/access-control.txt
@@ -7,10 +7,10 @@
 
 To view/edit the access controls for a specific project, first
 navigate to the projects page: for example,
-https://gerrit-review.googlesource.com/admin/repos/ . Then click on
+https://gerrit-review.googlesource.com/admin/repos/[role=external,window=_blank]. Then click on
 the individual project, and then click Access. This will bring you
 to a url that looks like
-https://gerrit-review.googlesource.com/admin/repos/gerrit,access
+https://gerrit-review.googlesource.com/admin/repos/gerrit,access[role=external,window=_blank]
 
 [[system_groups]]
 == System Groups
@@ -218,7 +218,7 @@
 `^refs/heads/[a-z]{1,8}` matches all lower case branch names
 between 1 and 8 characters long.  Within a regular expression `.`
 is a wildcard matching any character, but may be escaped as `\.`.
-The link:http://www.brics.dk/automaton/[dk.brics.automaton library]
+The link:http://www.brics.dk/automaton/[dk.brics.automaton library,role=external,window=_blank]
 is used for evaluation of regular expression access control
 rules. See the library documentation for details on this
 particular regular expression flavor. One quirk is that the
@@ -979,7 +979,7 @@
 to build and then leave a verdict somehow.
 
 As an example, the popular
-link:https://wiki.jenkins-ci.org/display/JENKINS/Gerrit+Trigger[gerrit-trigger plugin]
+link:https://wiki.jenkins-ci.org/display/JENKINS/Gerrit+Trigger[gerrit-trigger plugin,role=external,window=_blank]
 for Jenkins/Hudson can set labels at:
 
 * The start of a build
diff --git a/Documentation/backup.txt b/Documentation/backup.txt
index cd247af..624ffcb 100644
--- a/Documentation/backup.txt
+++ b/Documentation/backup.txt
@@ -47,7 +47,7 @@
 +
 If you have chosen to use _Elastic Search_ for indexing,
 refer to its
-link:https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-snapshots.html[backup documentation].
+link:https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-snapshots.html[backup documentation,role=external,window=_blank].
 
 [#optional-backup-cache]
 Caches::
@@ -70,7 +70,7 @@
 * public and private SSH host keys
 +
 You may consider to use the
-link:https://gerrit.googlesource.com/plugins/secure-config/[secure-config plugin]
+link:https://gerrit.googlesource.com/plugins/secure-config/[secure-config plugin,role=external,window=_blank]
 to encrypt these secrets.
 
 [#optional-backup-plugin-data]
@@ -144,7 +144,7 @@
 Make the server read-only before taking the backup. This means read-access
 is still available during backup, because only write operations have to be
 stopped to ensure consistency. This can be implemented using the
-link:https://gerrit.googlesource.com/plugins/readonly/[_readonly_] plugin.
+link:https://gerrit.googlesource.com/plugins/readonly/[_readonly_,role=external,window=_blank] plugin.
 
 [#cons-backup-replicate]
 === Replicate data for backup
@@ -156,9 +156,9 @@
 Replicate all git repositories to another file system using
 `git clone --mirror`,
 or the
-link:https://gerrit.googlesource.com/plugins/replication[replication plugin]
+link:https://gerrit.googlesource.com/plugins/replication[replication plugin,role=external,window=_blank]
 or the
-link:https://gerrit.googlesource.com/plugins/pull-replication[pull-replication plugin].
+link:https://gerrit.googlesource.com/plugins/pull-replication[pull-replication plugin,role=external,window=_blank].
 Best you use a filesystem supporting snapshots to create a backup archive
 of such a replica.
 
@@ -173,7 +173,7 @@
 Do not skip backing up the replica, the replica alone IS NOT a backup.
 Imagine someone deleted a project by mistake and this deletion got replicated.
 Replication of repository deletions can be switched off using the
-link:https://gerrit.googlesource.com/plugins/replication/+/refs/heads/master/src/main/resources/Documentation/config.md[server option]
+link:https://gerrit.googlesource.com/plugins/replication/+/refs/heads/master/src/main/resources/Documentation/config.md[server option,role=external,window=_blank]
 `remote.NAME.replicateProjectDeletions`.
 
 If you are using Gerrit replica to offload read traffic you can use one of these
@@ -195,13 +195,13 @@
 Filesystems supporting copy on write snapshots::
 +
 Use a file system supporting copy-on-write snapshots like
-link:https://btrfs.wiki.kernel.org/index.php/SysadminGuide#Snapshots[btrfs]
+link:https://btrfs.wiki.kernel.org/index.php/SysadminGuide#Snapshots[btrfs,role=external,window=_blank]
 or
-https://wiki.debian.org/ZFS#Snapshots[zfs].
+https://wiki.debian.org/ZFS#Snapshots[zfs,role=external,window=_blank].
 
 
 Other filesystems supporting snapshots::
-https://wiki.archlinux.org/index.php/LVM#Snapshots[lvm] or nfs.
+https://wiki.archlinux.org/index.php/LVM#Snapshots[lvm,role=external,window=_blank] or nfs.
 +
 Create a snapshot and then archive the snapshot to another storage.
 +
@@ -257,7 +257,7 @@
 [#backup-dr-multi-site]
 === Multi-site setup
 
-Use the https://gerrit.googlesource.com/plugins/multi-site[multi-site plugin]
+Use the https://gerrit.googlesource.com/plugins/multi-site[multi-site plugin,role=external,window=_blank]
 to install Gerrit with multiple sites installed in different datacenters
 across different regions. This ensures that in case of a severe problem with
 one of the sites, the other sites can still serve your repositories.
diff --git a/Documentation/cmd-hook-commit-msg.txt b/Documentation/cmd-hook-commit-msg.txt
index 49d5c17..8d81768 100644
--- a/Documentation/cmd-hook-commit-msg.txt
+++ b/Documentation/cmd-hook-commit-msg.txt
@@ -86,8 +86,8 @@
 
 
 * link:user-changeid.html[Change-Id Lines]
-* link:http://www.kernel.org/pub/software/scm/git/docs/git-commit.html[git-commit(1)]
-* link:http://www.kernel.org/pub/software/scm/git/docs/githooks.html[githooks(5)]
+* link:http://www.kernel.org/pub/software/scm/git/docs/git-commit.html[git-commit(1),role=external,window=_blank]
+* link:http://www.kernel.org/pub/software/scm/git/docs/githooks.html[githooks(5),role=external,window=_blank]
 
 == IMPLEMENTATION
 
diff --git a/Documentation/concept-refs-for-namespace.txt b/Documentation/concept-refs-for-namespace.txt
index c8776ae..ee0be39 100644
--- a/Documentation/concept-refs-for-namespace.txt
+++ b/Documentation/concept-refs-for-namespace.txt
@@ -1,7 +1,7 @@
 = The refs/for namespace
 
 When pushing a new or updated commit to Gerrit, you push that commit using a
-link:https://www.kernel.org/pub/software/scm/git/docs/gitglossary.html#def_ref[reference],
+link:https://www.kernel.org/pub/software/scm/git/docs/gitglossary.html#def_ref[reference,role=external,window=_blank],
 in the `refs/for` namespace. This reference must also define
 the target branch, such as `refs/for/[BRANCH_NAME]`.
 
diff --git a/Documentation/config-accounts.txt b/Documentation/config-accounts.txt
index 45aa42a..cdca0a4 100644
--- a/Documentation/config-accounts.txt
+++ b/Documentation/config-accounts.txt
@@ -143,7 +143,7 @@
 
 In addition it contains an
 link:https://en.wikibooks.org/wiki/OpenSSH/Client_Configuration_Files#.7E.2F.ssh.2Fauthorized_keys[
-authorized_keys] file with the link:#ssh-keys[SSH keys] of the account.
+authorized_keys,role=external,window=_blank] file with the link:#ssh-keys[SSH keys] of the account.
 
 [[account-properties]]
 === Account Properties
@@ -256,7 +256,7 @@
 SSH keys are stored in the user branch in an `authorized_keys` file,
 which is the
 link:https://en.wikibooks.org/wiki/OpenSSH/Client_Configuration_Files#.7E.2F.ssh.2Fauthorized_keys[
-standard OpenSSH file format] for storing SSH keys:
+standard OpenSSH file format,role=external,window=_blank] for storing SSH keys:
 
 ----
 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQCgug5VyMXQGnem2H1KVC4/HcRcD4zzBqSuJBRWVonSSoz3RoAZ7bWXCVVGwchtXwUURD689wFYdiPecOrWOUgeeyRq754YWRhU+W28vf8IZixgjCmiBhaL2gt3wff6pP+NXJpTSA4aeWE5DfNK5tZlxlSxqkKOS8JRSUeNQov5Tw== john.doe@example.com
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index d126c96..bb6cf89 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -174,7 +174,7 @@
 +
 The default setting.  Gerrit uses any valid OpenID
 provider chosen by the end-user.  For more information see
-http://openid.net/[openid.net].
+http://openid.net/[openid.net,role=external,window=_blank].
 +
 * `OpenID_SSO`
 +
@@ -286,7 +286,7 @@
 +
 Patterns may be either a
 link:http://download.oracle.com/javase/6/docs/api/java/util/regex/Pattern.html[standard
-Java regular expression (java.util.regex)] (start with `^` and
+Java regular expression (java.util.regex),role=external,window=_blank] (start with `^` and
 end with `$`) or be a simple prefix (any other string).
 +
 By default, the list contains two values, `http://` and `https://`,
@@ -304,7 +304,7 @@
 +
 Patterns may be either a
 link:http://download.oracle.com/javase/6/docs/api/java/util/regex/Pattern.html[standard
-Java regular expression (java.util.regex)] (start with `^` and
+Java regular expression (java.util.regex),role=external,window=_blank] (start with `^` and
 end with `$`) or be a simple prefix (any other string).
 +
 By default, the list contains two values, `http://` and `https://`,
@@ -695,7 +695,7 @@
 +
 Technically the H2 cache size is configured using the CACHE_SIZE parameter in
 the H2 JDBC connection URL, as described
-link:http://www.h2database.com/html/features.html#cache_settings[here]
+link:http://www.h2database.com/html/features.html#cache_settings[here,role=external,window=_blank]
 +
 Default is unset, using up to half of the available memory.
 +
@@ -708,7 +708,7 @@
 If set to true, enable H2 autoserver mode for the H2-backed persistent cache
 databases.
 +
-See link:http://www.h2database.com/html/features.html#auto_mixed_mode[here]
+See link:http://www.h2database.com/html/features.html#auto_mixed_mode[here,role=external,window=_blank]
 for detail.
 +
 Default is false.
@@ -1712,7 +1712,7 @@
 +
 As explained in this
 link:http://codicesoftware.blogspot.com/2011/09/merge-recursive-strategy.html[
-blog], the recursive merge produces better results if the two commits
+blog,role=external,window=_blank], the recursive merge produces better results if the two commits
 that are merged have more than one common predecessor.
 +
 Default is true.
@@ -2245,9 +2245,9 @@
 branch names. Some web servers, such as Tomcat, reject this hexadecimal
 encoding in the URL.
 +
-Some alternative gitweb services, such as link:http://gitblit.com[Gitblit],
+Some alternative gitweb services, such as link:http://gitblit.com[Gitblit,role=external,window=_blank],
 allow using an alternative path separator character. In Gitblit, this can be
-configured through the property link:http://gitblit.com/properties.html[web.forwardSlashCharacter].
+configured through the property link:http://gitblit.com/properties.html[web.forwardSlashCharacter,role=external,window=_blank].
 In Gerrit, the alternative path separator can be configured correspondingly
 using the property `gitweb.pathSeparator`.
 +
@@ -2397,7 +2397,7 @@
 Like `http://`, but additional header parsing features are
 enabled to honor `X-Forwarded-For`, `X-Forwarded-Host` and
 `X-Forwarded-Server`.  These headers are typically set by Apache's
-link:https://httpd.apache.org/docs/2.4/mod/mod_proxy.html#x-headers[mod_proxy].
+link:https://httpd.apache.org/docs/2.4/mod/mod_proxy.html#x-headers[mod_proxy,role=external,window=_blank].
 +
 [NOTE]
 --
@@ -2748,7 +2748,7 @@
 +
 * `ELASTICSEARCH` look into link:#elasticsearch[Elasticsearch section]
 +
-An link:https://www.elastic.co/products/elasticsearch[Elasticsearch] index is
+An link:https://www.elastic.co/products/elasticsearch[Elasticsearch,role=external,window=_blank] index is
 used. Refer to the link:#elasticsearch[Elasticsearch section] for further
 configuration details.
 
@@ -2912,7 +2912,7 @@
 Determines the amount of RAM that may be used for buffering added documents
 and deletions before they are flushed to the index.  See the
 link:http://lucene.apache.org/core/4_6_0/core/org/apache/lucene/index/LiveIndexWriterConfig.html#setRAMBufferSizeMB(double)[
-Lucene documentation] for further details.
+Lucene documentation,role=external,window=_blank] for further details.
 +
 Defaults to 16M.
 
@@ -2922,7 +2922,7 @@
 in-memory documents are flushed to the index. Large values generally
 give faster indexing.  See the
 link:http://lucene.apache.org/core/4_6_0/core/org/apache/lucene/index/LiveIndexWriterConfig.html#setMaxBufferedDocs(int)[
-Lucene documentation] for further details.
+Lucene documentation,role=external,window=_blank] for further details.
 +
 Defaults to -1, meaning no maximum is set and the writer will flush
 according to RAM usage.
@@ -2955,7 +2955,7 @@
 completed.  Note that Lucene will only run the smallest maxThreadCount merges
 at a time. See the
 link:https://lucene.apache.org/core/5_5_0/core/org/apache/lucene/index/ConcurrentMergeScheduler.html#setDefaultMaxMergesAndThreads(boolean)[
-Lucene documentation] for further details.
+Lucene documentation,role=external,window=_blank] for further details.
 +
 Defaults to -1 for (auto detection).
 
@@ -2965,13 +2965,13 @@
 Determines the max number of simultaneous Lucene merge threads that should be running at
 once. This must be less than or equal to maxMergeCount. See the
 link:https://lucene.apache.org/core/5_5_0/core/org/apache/lucene/index/ConcurrentMergeScheduler.html#setDefaultMaxMergesAndThreads(boolean)[
-Lucene documentation] for further details.
+Lucene documentation,role=external,window=_blank] for further details.
 +
 For further details on Lucene index configuration (auto detection) which
 affects maxThreadCount and maxMergeCount settings.
 See the
 link:https://lucene.apache.org/core/5_5_0/core/org/apache/lucene/index/ConcurrentMergeScheduler.html#AUTO_DETECT_MERGES_AND_THREADS[
-Lucene documentation]
+Lucene documentation,role=external,window=_blank]
 +
 Defaults to -1 for (auto detection).
 
@@ -2982,7 +2982,7 @@
 on is used to adaptively rate limit writes bytes/sec to the minimal rate necessary
 so merges do not fall behind. See the
 link:https://lucene.apache.org/core/5_5_0/core/org/apache/lucene/index/ConcurrentMergeScheduler.html#enableAutoIOThrottle()[
-Lucene documentation] for further details.
+Lucene documentation,role=external,window=_blank] for further details.
 +
 Defaults to true (throttling enabled).
 
@@ -3012,7 +3012,7 @@
 
 WARNING: Support for Elasticsearch is still experimental and is not recommended
 for production use. For compatibility information, please refer to the
-link:https://www.gerritcodereview.com/elasticsearch.html[project homepage].
+link:https://www.gerritcodereview.com/elasticsearch.html[project homepage,role=external,window=_blank].
 
 When using Elasticsearch version 5.6, the open and closed changes are
 indexed in a single index, separated into types `open_changes` and `closed_changes`
@@ -3047,7 +3047,7 @@
 +
 Sets the number of shards to use per index. Refer to the
 link:https://www.elastic.co/guide/en/elasticsearch/reference/current/getting-started-concepts.html#getting-started-shards-and-replicas[
-Elasticsearch documentation] for details.
+Elasticsearch documentation,role=external,window=_blank] for details.
 +
 Defaults to 5 for Elasticsearch versions 5 and 6, and to 1 starting with Elasticsearch 7.
 
@@ -3055,7 +3055,7 @@
 +
 Sets the number of replicas to use per index. Refer to the
 link:https://www.elastic.co/guide/en/elasticsearch/reference/current/getting-started-concepts.html#getting-started-shards-and-replicas[
-Elasticsearch documentation] for details.
+Elasticsearch documentation,role=external,window=_blank] for details.
 +
 Defaults to 1.
 
@@ -3066,12 +3066,12 @@
 
 For further information about Elasticsearch security, please refer to the documentation:
 
-* link:https://www.elastic.co/guide/en/x-pack/5.6/security-getting-started.html[Elasticsearch 5.6]
-* link:https://www.elastic.co/guide/en/x-pack/6.2/security-getting-started.html[Elasticsearch 6.2]
-* link:https://www.elastic.co/guide/en/elastic-stack-overview/6.3/security-getting-started.html[Elasticsearch 6.3]
-* link:https://www.elastic.co/guide/en/elastic-stack-overview/6.4/security-getting-started.html[Elasticsearch 6.4]
-* link:https://www.elastic.co/guide/en/elastic-stack-overview/6.5/security-getting-started.html[Elasticsearch 6.5]
-* link:https://www.elastic.co/guide/en/elastic-stack-overview/6.6/security-getting-started.html[Elasticsearch 6.6]
+* link:https://www.elastic.co/guide/en/x-pack/5.6/security-getting-started.html[Elasticsearch 5.6,role=external,window=_blank]
+* link:https://www.elastic.co/guide/en/x-pack/6.2/security-getting-started.html[Elasticsearch 6.2,role=external,window=_blank]
+* link:https://www.elastic.co/guide/en/elastic-stack-overview/6.3/security-getting-started.html[Elasticsearch 6.3,role=external,window=_blank]
+* link:https://www.elastic.co/guide/en/elastic-stack-overview/6.4/security-getting-started.html[Elasticsearch 6.4,role=external,window=_blank]
+* link:https://www.elastic.co/guide/en/elastic-stack-overview/6.5/security-getting-started.html[Elasticsearch 6.5,role=external,window=_blank]
+* link:https://www.elastic.co/guide/en/elastic-stack-overview/6.6/security-getting-started.html[Elasticsearch 6.6,role=external,window=_blank]
 
 [[elasticsearch.username]]elasticsearch.username::
 +
@@ -3122,8 +3122,8 @@
 the parameters introduced here.  Suitable defaults for most
 parameters are automatically guessed based on the type of server
 detected during startup.  The guessed defaults support
-link:http://www.ietf.org/rfc/rfc2307.txt[RFC 2307], Active
-Directory and link:https://www.freeipa.org[FreeIPA].
+link:http://www.ietf.org/rfc/rfc2307.txt[RFC 2307,role=external,window=_blank], Active
+Directory and link:https://www.freeipa.org[FreeIPA,role=external,window=_blank].
 
 ----
 [ldap]
@@ -3477,8 +3477,8 @@
 garbage collected), the connection is returned to the pool for future use.
 +
 For details, see link:http://docs.oracle.com/javase/tutorial/jndi/ldap/pool.html[
-LDAP connection management (Pool)] and link:http://docs.oracle.com/javase/tutorial/jndi/ldap/config.html[
-LDAP connection management (Configuration)]
+LDAP connection management (Pool),role=external,window=_blank] and link:http://docs.oracle.com/javase/tutorial/jndi/ldap/config.html[
+LDAP connection management (Configuration),role=external,window=_blank]
 +
 By default, false.
 
@@ -3497,7 +3497,7 @@
 ldap.useConnectionPooling] configuration property to `true`, the connection pool
 can be configured using JVM system properties as explained in the
 link:http://docs.oracle.com/javase/7/docs/technotes/guides/jndi/jndi-ldap.html#POOL[
-Java SE Documentation].
+Java SE Documentation,role=external,window=_blank].
 
 For standalone Gerrit (running with the embedded Jetty), JVM system properties
 are specified in the link:#container[container section]:
@@ -3515,7 +3515,7 @@
 +
 The name of a plugin which serves the
 link:https://github.com/github/git-lfs/blob/master/docs/api/v1/http-v1-batch.md[
-LFS protocol] on the `<project-name>/info/lfs/objects/batch` endpoint. When
+LFS protocol,role=external,window=_blank] on the `<project-name>/info/lfs/objects/batch` endpoint. When
 not configured Gerrit will respond with `501 Not Implemented` on LFS protocol
 requests.
 +
@@ -3926,7 +3926,7 @@
 +
 Trust signatures can be added to a key using the `tsign` command to
 link:https://www.gnupg.org/documentation/manuals/gnupg/OpenPGP-Key-Management.html[
-`gpg --edit-key`], after which the signed key should be re-uploaded.
+`gpg --edit-key`,role=external,window=_blank], after which the signed key should be re-uploaded.
 +
 If no keys are specified, web-of-trust checks are disabled. This is the
 default behavior.
@@ -4379,7 +4379,7 @@
 [[sendemail.allowTLD]]sendemail.allowTLD::
 +
 List of custom TLDs to allow sending emails to in addition to those specified
-in the link:http://data.iana.org/TLD/[IANA list].
+in the link:http://data.iana.org/TLD/[IANA list,role=external,window=_blank].
 +
 Defaults to an empty list, meaning no additional TLDs are allowed.
 
@@ -4889,7 +4889,7 @@
 [[trackingid.name.match]]trackingid.<name>.match::
 +
 A link:http://download.oracle.com/javase/6/docs/api/java/util/regex/Pattern.html[standard
-Java regular expression (java.util.regex)] used to match the
+Java regular expression (java.util.regex),role=external,window=_blank] used to match the
 external tracking id part of the footer line. The match can
 result in several entries in the DB.  If grouping is used in the
 regex the first group will be interpreted as the tracking id.
diff --git a/Documentation/config-gitweb.txt b/Documentation/config-gitweb.txt
index d49acfee..fe23583 100644
--- a/Documentation/config-gitweb.txt
+++ b/Documentation/config-gitweb.txt
@@ -167,21 +167,21 @@
 Instructions are available for installing the gitweb module distributed with
 MsysGit:
 
-link:https://github.com/msysgit/msysgit/wiki/GitWeb[GitWeb]
+link:https://github.com/msysgit/msysgit/wiki/GitWeb[GitWeb,role=external,window=_blank]
 
 If you don't have Apache installed, you can download the appropriate build for
 Windows from link:http://www.apachelounge.com/download[apachelounge.org].
 
 After you have installed Apache, you will want to create a link:http://httpd.apache.org/docs/2.0/platform/windows.html#winsvc[new service user
-account] to use with Apache.
+account,role=external,window=_blank] to use with Apache.
 
 If you're still having difficulty setting up permissions, you may find this
 tech note useful for configuring Apache Service to run under another account.
-You must grant the new account link:http://technet.microsoft.com/en-us/library/cc794944(WS.10).aspx["run as service"] permission:
+You must grant the new account link:http://technet.microsoft.com/en-us/library/cc794944(WS.10).aspx["run as service",role=external,window=_blank] permission:
 
 The gitweb version in msysgit is missing several important and required
 perl modules, including CGI.pm. The perl included with the msysgit distro 1.7.8
-is broken.. The link:http://groups.google.com/group/msysgit/browse_thread/thread/ba3501f1f0ed95af[unicore folder is missing along with utf8_heavy.pl and CGI.pm]. You can
+is broken.. The link:http://groups.google.com/group/msysgit/browse_thread/thread/ba3501f1f0ed95af[unicore folder is missing along with utf8_heavy.pl and CGI.pm,role=external,window=_blank]. You can
 verify by checking for perl modules. From an msys console, execute the
 following to check:
 
@@ -202,7 +202,7 @@
 If you're missing CGI.pm, you'll have to deploy the module to the msys
 environment: You will have to retrieve them from the 5.8.8 distro on :
 
-http://strawberryperl.com/releases.html
+http://strawberryperl.com/releases.html[role=external,window=_blank]
 
 File: strawberry-perl-5.8.8.3.zip
 
@@ -272,7 +272,7 @@
 === SEE ALSO
 
 * link:config-gerrit.html#gitweb[Section gitweb]
-* link:http://git.zx2c4.com/cgit/about/[cgit]
+* link:http://git.zx2c4.com/cgit/about/[cgit,role=external,window=_blank]
 
 GERRIT
 ------
diff --git a/Documentation/config-hooks.txt b/Documentation/config-hooks.txt
index 835ec11..f44638b 100644
--- a/Documentation/config-hooks.txt
+++ b/Documentation/config-hooks.txt
@@ -3,7 +3,7 @@
 Gerrit does not run any of the standard git hooks in the repositories
 it works with, but it does have its own hook mechanism included via
 the link:https://gerrit-review.googlesource.com/admin/repos/plugins/hooks[
-hooks plugin].
+hooks plugin,role=external,window=_blank].
 
 GERRIT
 ------
diff --git a/Documentation/config-mail.txt b/Documentation/config-mail.txt
index 7d46e26..e524065 100644
--- a/Documentation/config-mail.txt
+++ b/Documentation/config-mail.txt
@@ -1,6 +1,6 @@
 = Gerrit Code Review - Mail Templates
 
-Gerrit uses link:https://developers.google.com/closure/templates/[Closure Templates]
+Gerrit uses link:https://developers.google.com/closure/templates/[Closure Templates,role=external,window=_blank]
 (Soy) for the bulk of the standard mails it sends out.
 There are builtin default templates which are used if they are not overridden.
 These defaults are also provided as examples so that administrators may copy
diff --git a/Documentation/config-plugins.txt b/Documentation/config-plugins.txt
index af00d1c..818d701 100644
--- a/Documentation/config-plugins.txt
+++ b/Documentation/config-plugins.txt
@@ -25,10 +25,10 @@
 
 If you want to share your plugin under the link:licenses.html#Apache2_0[
 Apache License 2.0] you can host your plugin development on the
-link:https://gerrit-review.googlesource.com[gerrit-review] Gerrit
+link:https://gerrit-review.googlesource.com[gerrit-review,role=external,window=_blank] Gerrit
 Server. You can request the creation of a new Project by email
 to the link:https://groups.google.com/forum/#!forum/repo-discuss[Gerrit
-mailing list]. You would be assigned as project owner of the new plugin
+mailing list,role=external,window=_blank]. You would be assigned as project owner of the new plugin
 project so that you can submit changes on your own. It is the
 responsibility of the project owner to maintain the plugin, e.g. to
 make sure that it works with new Gerrit versions and to create stable
@@ -51,7 +51,7 @@
 CodeMirror plugin for polygerrit.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/codemirror-editor[
-Project] |
+Project,role=external,window=_blank] |
 
 [[commit-message-length-validator]]
 === commit-message-length-validator
@@ -61,11 +61,11 @@
 lengths are exceeded.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/commit-message-length-validator[
-Project] |
+Project,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/commit-message-length-validator/+doc/master/src/main/resources/Documentation/about.md[
-Documentation] |
+Documentation,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/commit-message-length-validator/+doc/master/src/main/resources/Documentation/config.md[
-Configuration]
+Configuration,role=external,window=_blank]
 
 [[delete-project]]
 === delete-project
@@ -73,11 +73,11 @@
 Provides the ability to delete a project.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/delete-project[
-Project] |
+Project,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/delete-project/+doc/master/src/main/resources/Documentation/about.md[
-Documentation] |
+Documentation,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/delete-project/+doc/master/src/main/resources/Documentation/config.md[
-Configuration]
+Configuration,role=external,window=_blank]
 
 [[download-commands]]
 === download-commands
@@ -86,11 +86,11 @@
 download schemes (for downloading via different network protocols).
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/download-commands[
-Project] |
+Project,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/download-commands/+doc/master/src/main/resources/Documentation/about.md[
-Documentation] |
+Documentation,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/download-commands/+doc/master/src/main/resources/Documentation/config.md[
-Configuration]
+Configuration,role=external,window=_blank]
 
 [[gitiles]]
 === gitiles
@@ -98,7 +98,7 @@
 Plugin running Gitiles alongside a Gerrit server.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/gitiles[
-Project]
+Project,role=external,window=_blank]
 
 [[hooks]]
 === hooks
@@ -106,11 +106,11 @@
 This plugin runs server-side hooks on events.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/hooks[
-Project] |
+Project,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/hooks/+doc/master/src/main/resources/Documentation/about.md[
-Documentation] |
+Documentation,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/hooks/+doc/master/src/main/resources/Documentation/config.md[
-Configuration]
+Configuration,role=external,window=_blank]
 
 [[plugin-manager]]
 === plugin-manager
@@ -120,11 +120,11 @@
 this can be changed per plugin configuration.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/plugin-manager[
-Project]
+Project,role=external,window=_blank]
 link:https://gerrit.googlesource.com/plugins/plugin-manager/+doc/master/src/main/resources/Documentation/about.md[
-Documentation]
+Documentation,role=external,window=_blank]
 link:https://gerrit.googlesource.com/plugins/plugin-manager/+doc/master/src/main/resources/Documentation/config.md[
-Configuration]
+Configuration,role=external,window=_blank]
 
 [[replication]]
 === replication
@@ -135,11 +135,11 @@
 backups, or a load-balanced public mirror farm.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/replication[
-Project] |
+Project,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/replication/+doc/master/src/main/resources/Documentation/about.md[
-Documentation] |
+Documentation,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/replication/+doc/master/src/main/resources/Documentation/config.md[
-Configuration]
+Configuration,role=external,window=_blank]
 
 [[reviewnotes]]
 === reviewnotes
@@ -148,9 +148,9 @@
 branch.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/reviewnotes[
-Project] |
+Project,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/reviewnotes/+doc/master/src/main/resources/Documentation/about.md[
-Documentation]
+Documentation,role=external,window=_blank]
 
 [[singleusergroup]]
 === singleusergroup
@@ -165,11 +165,11 @@
 This plugin allows to propagate Gerrit events to remote http endpoints.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/webhooks[
-Project] |
+Project,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/webhooks/+doc/master/src/main/resources/Documentation/about.md[
-Documentation] |
+Documentation,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/webhooks/+doc/master/src/main/resources/Documentation/config.md[
-Configuration]
+Configuration,role=external,window=_blank]
 
 [[other-plugins]]
 == Other Plugins
@@ -183,12 +183,12 @@
 there is one public service that offers the download of pre-built
 plugin jars:
 
-* link:https://gerrit-ci.gerritforge.com[CI Server from GerritForge]
+* link:https://gerrit-ci.gerritforge.com[CI Server from GerritForge,role=external,window=_blank]
 
 The following list gives an overview of available plugins, but the
 list may not be complete. You may discover more plugins on
 link:https://gerrit-review.googlesource.com/admin/repos/?filter=plugins%252F[
-gerrit-review].
+gerrit-review,role=external,window=_blank].
 
 Note that the documentation and configuration links in the list below are
 to the plugins' master branch. Please refer to the appropriate branch for
@@ -205,9 +205,9 @@
 project/account.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/admin-console[
-Project] |
+Project,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/admin-console/+doc/master/src/main/resources/Documentation/about.md[
-Documentation]
+Documentation,role=external,window=_blank]
 
 [[analytics]]
 === analytics
@@ -218,8 +218,8 @@
 archived and processed with popular BigData transformation tools such
 Apache Spark or published and visualized in dashboards.
 
-link:https://gerrit-review.googlesource.com/admin/repos/plugins/analytics[Project] |
-link:https://gerrit.googlesource.com/plugins/analytics/+doc/master/README.md[Documentation]
+link:https://gerrit-review.googlesource.com/admin/repos/plugins/analytics[Project,role=external,window=_blank] |
+link:https://gerrit.googlesource.com/plugins/analytics/+doc/master/README.md[Documentation,role=external,window=_blank]
 
 [[avatars-external]]
 === avatars-external
@@ -228,11 +228,11 @@
 from.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/avatars-external[
-Project] |
+Project,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/avatars-external/+doc/master/src/main/resources/Documentation/about.md[
-Documentation] |
+Documentation,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/avatars-external/+doc/master/src/main/resources/Documentation/config.md[
-Configuration]
+Configuration,role=external,window=_blank]
 
 [[avatars-gravatar]]
 === avatars-gravatar
@@ -240,7 +240,7 @@
 Plugin to display user icons from Gravatar.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/avatars-gravatar[
-Project]
+Project,role=external,window=_blank]
 
 [[branch-network]]
 === branch-network
@@ -250,11 +250,11 @@
 "project link" in a gitweb configuration.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/branch-network[
-Project] |
+Project,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/branch-network/+doc/master/src/main/resources/Documentation/about.md[
-Documentation] |
+Documentation,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/branch-network/+doc/master/src/main/resources/Documentation/config.md[
-Configuration]
+Configuration,role=external,window=_blank]
 
 [[changemessage]]
 === changemessage
@@ -262,11 +262,11 @@
 This plugin allows to display a static info message on the change screen.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/changemessage[
-Project] |
+Project,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/changemessage/+doc/master/src/main/resources/Documentation/about.md[
-Plugin Documentation] |
+Plugin Documentation,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/changemessage/+doc/master/src/main/resources/Documentation/config.md[
-Configuration]
+Configuration,role=external,window=_blank]
 
 [[checks]]
 === checks
@@ -275,9 +275,9 @@
 CI systems with Gerrit.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/checks[
-Project] |
+Project,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/checks/+doc/master/README.md[
-Plugin Documentation]
+Plugin Documentation,role=external,window=_blank]
 
 [[egit]]
 === egit
@@ -289,9 +289,9 @@
 downloading a Gerrit change from within EGit.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/egit[
-Project] |
+Project,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/egit/+doc/master/src/main/resources/Documentation/about.md[
-Documentation]
+Documentation,role=external,window=_blank]
 
 [[emoticons]]
 === emoticons
@@ -299,11 +299,11 @@
 This plugin allows users to see emoticons in comments as images.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/emoticons[
-Project] |
+Project,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/emoticons/+doc/master/src/main/resources/Documentation/about.md[
-Documentation] |
+Documentation,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/emoticons/+doc/master/src/main/resources/Documentation/config.md[
-Configuration]
+Configuration,role=external,window=_blank]
 
 [[find-owners]]
 === find-owners
@@ -312,9 +312,9 @@
 (2) Prolog predicates to make sure that a CL is submittable
 only with owner Code-Review +1 votes.
 
-link:https://gerrit-review.googlesource.com/admin/repos/plugins/find-owners[Project] |
-link:https://gerrit.googlesource.com/plugins/find-owners/+doc/master/src/main/resources/Documentation/about.md[Documentation] |
-link:https://gerrit.googlesource.com/plugins/find-owners/+doc/master/src/main/resources/Documentation/config.md[Configuration]
+link:https://gerrit-review.googlesource.com/admin/repos/plugins/find-owners[Project,role=external,window=_blank] |
+link:https://gerrit.googlesource.com/plugins/find-owners/+doc/master/src/main/resources/Documentation/about.md[Documentation,role=external,window=_blank] |
+link:https://gerrit.googlesource.com/plugins/find-owners/+doc/master/src/main/resources/Documentation/config.md[Configuration,role=external,window=_blank]
 
 [[gitblit]]
 === gitblit
@@ -322,7 +322,7 @@
 GitBlit code-viewer plugin with SSO and Security Access Control.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/gitblit[
-Project]
+Project,role=external,window=_blank]
 
 [[github]]
 === github
@@ -330,7 +330,7 @@
 Plugin to integrate with GitHub: replication, pull-request to Change-Sets
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/github[
-Project]
+Project,role=external,window=_blank]
 
 [[healthcheck]]
 === healthcheck
@@ -346,11 +346,11 @@
 Gerrit internal metrics and can be published to dashboards.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/healthcheck[
-Project] |
+Project,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/healthcheck/+doc/master/src/main/resources/Documentation/about.md[
-Documentation] |
+Documentation,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/healthcheck/+doc/master/src/main/resources/Documentation/config.md[
-Configuration]
+Configuration,role=external,window=_blank]
 
 [[imagare]]
 === imagare
@@ -358,11 +358,11 @@
 The imagare plugin allows Gerrit users to upload and share images.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/imagare[
-Project] |
+Project,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/imagare/+doc/master/src/main/resources/Documentation/about.md[
-Documentation] |
+Documentation,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/imagare/+doc/master/src/main/resources/Documentation/config.md[
-Configuration]
+Configuration,role=external,window=_blank]
 
 [[importer]]
 === importer
@@ -386,9 +386,9 @@
 plugin it can be used to rename a project.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/importer[
-Project] |
+Project,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/importer/+doc/master/src/main/resources/Documentation/about.md[
-Documentation]
+Documentation,role=external,window=_blank]
 
 [[its-plugins]]
 === Issue Tracker System Plugins
@@ -402,11 +402,11 @@
 framework for the ITS plugins which is packaged within each ITS plugin.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/its-base[
-its-base Project] |
+its-base Project,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/its-base/+doc/master/src/main/resources/Documentation/about.md[
-its-base Documentation] |
+its-base Documentation,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/its-base/+doc/master/src/main/resources/Documentation/config.md[
-its-base Configuration]
+its-base Configuration,role=external,window=_blank]
 
 [[its-bugzilla]]
 ==== its-bugzilla
@@ -414,9 +414,9 @@
 Plugin to integrate with Bugzilla.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/its-bugzilla[
-Project] |
+Project,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/its-bugzilla/+doc/master/src/main/resources/Documentation/about.md[
-Documentation]
+Documentation,role=external,window=_blank]
 
 [[its-jira]]
 ==== its-jira
@@ -424,9 +424,9 @@
 Plugin to integrate with Jira.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/its-jira[
-Project] |
+Project,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/its-jira/+doc/master/src/main/resources/Documentation/config.md[
-Configuration]
+Configuration,role=external,window=_blank]
 
 [[its-phabricator]]
 ==== its-phabricator
@@ -434,9 +434,9 @@
 Plugin to integrate with Phabricator.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/its-phabricator[
-Project] |
+Project,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/its-phabricator/+doc/master/src/main/resources/Documentation/config.md[
-Configuration]
+Configuration,role=external,window=_blank]
 
 [[its-rtc]]
 ==== its-rtc
@@ -444,9 +444,9 @@
 Plugin to integrate with IBM Rational Team Concert (RTC).
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/its-rtc[
-Project] |
+Project,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/its-rtc/+doc/master/src/main/resources/Documentation/config.md[
-Configuration]
+Configuration,role=external,window=_blank]
 
 [[its-storyboard]]
 ==== its-storyboard
@@ -454,9 +454,9 @@
 Plugin to integrate with Storyboard task tracking system.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/its-storyboard[
-Project] |
+Project,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/its-storyboard/+doc/master/src/main/resources/Documentation/about.md[
-Documentation]
+Documentation,role=external,window=_blank]
 
 [[javamelody]]
 === javamelody
@@ -467,11 +467,11 @@
 instrumentation data from Gerrit.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/javamelody[
-Project] |
+Project,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/javamelody/+doc/master/src/main/resources/Documentation/about.md[
-Documentation] |
+Documentation,role=external,window=_blank] |
 https://gerrit.googlesource.com/plugins/javamelody/+doc/master/src/main/resources/Documentation/config.md[
-Configuration]
+Configuration,role=external,window=_blank]
 
 [[labelui]]
 === labelui
@@ -482,9 +482,9 @@
 screen).
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/labelui[
-Project] |
+Project,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/labelui/+doc/master/src/main/resources/Documentation/about.md[
-Documentation]
+Documentation,role=external,window=_blank]
 
 [[menuextender]]
 === menuextender
@@ -493,11 +493,11 @@
 additional menu entries from the WebUI.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/menuextender[
-Project] |
+Project,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/menuextender/+doc/master/src/main/resources/Documentation/about.md[
-Documentation] |
+Documentation,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/menuextender/+doc/master/src/main/resources/Documentation/config.md[
-Configuration]
+Configuration,role=external,window=_blank]
 
 [[metrics-reporter-elasticsearch]]
 === metrics-reporter-elasticsearch
@@ -505,7 +505,7 @@
 This plugin reports Gerrit metrics to Elasticsearch.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/metrics-reporter-elasticsearch[
-Project].
+Project,role=external,window=_blank].
 
 [[metrics-reporter-graphite]]
 === metrics-reporter-graphite
@@ -513,7 +513,7 @@
 This plugin reports Gerrit metrics to Graphite.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/metrics-reporter-graphite[
-Project].
+Project,role=external,window=_blank].
 
 [[metrics-reporter-jmx]]
 === metrics-reporter-jmx
@@ -521,7 +521,7 @@
 This plugin reports Gerrit metrics to JMX.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/metrics-reporter-jmx[
-Project].
+Project,role=external,window=_blank].
 
 [[metrics-reporter-prometheus]]
 === metrics-reporter-prometheus
@@ -529,7 +529,7 @@
 This plugin exposes Gerrit metrics for consumption by Prometheus.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/metrics-reporter-prometheus[
-Project].
+Project,role=external,window=_blank].
 
 [[motd]]
 === motd
@@ -541,11 +541,11 @@
 discarded otherwise.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/motd[
-Project] |
+Project,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/motd/+doc/master/src/main/resources/Documentation/about.md[
-Documentation] |
+Documentation,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/motd/+doc/master/src/main/resources/Documentation/config.md[
-Configuration]
+Configuration,role=external,window=_blank]
 
 [[oauth-authentication-provider]]
 === OAuth authentication provider
@@ -563,16 +563,16 @@
 * Keycloak
 * Office365
 
-link:https://gerrit-review.googlesource.com/admin/repos/plugins/oauth[Project] |
-link:https://gerrit.googlesource.com/plugins/oauth/+doc/master/src/main/resources/Documentation/config.md[Configuration]
+link:https://gerrit-review.googlesource.com/admin/repos/plugins/oauth[Project,role=external,window=_blank] |
+link:https://gerrit.googlesource.com/plugins/oauth/+doc/master/src/main/resources/Documentation/config.md[Configuration,role=external,window=_blank]
 
 [[owners]]
 === owners
 This plugin provides a Prolog predicate `add_owner_approval/3` that
 appends `label('Owner-Approval', need(_))` to a provided list.
 
-link:https://gerrit-review.googlesource.com/admin/repos/plugins/owners[Project] |
-link:https://gerrit.googlesource.com/plugins/owners/+doc/master/README.md[Documentation]
+link:https://gerrit-review.googlesource.com/admin/repos/plugins/owners[Project,role=external,window=_blank] |
+link:https://gerrit.googlesource.com/plugins/owners/+doc/master/README.md[Documentation,role=external,window=_blank]
 
 [[project-download-commands]]
 === project-download-commands
@@ -584,11 +584,11 @@
 inherited download command or remove it by assigning no value to it.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/project-download-commands[
-Project] |
+Project,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/project-download-commands/+doc/master/src/main/resources/Documentation/about.md[
-Documentation] |
+Documentation,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/project-download-commands/+doc/master/src/main/resources/Documentation/config.md[
-Configuration]
+Configuration,role=external,window=_blank]
 
 [[quota]]
 === quota
@@ -600,21 +600,21 @@
 can use this plugin to define quotas on project namespaces.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/quota[
-Project] |
+Project,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/quota/+doc/master/src/main/resources/Documentation/about.md[
-Documentation]
+Documentation,role=external,window=_blank]
 link:https://gerrit.googlesource.com/plugins/quota/+doc/master/src/main/resources/Documentation/config.md[
-Configuration]
+Configuration,role=external,window=_blank]
 
 [[rabbitmq]]
 === rabbitmq
 
 A plugin that publishes Gerrit events to a
-link:https://www.rabbitmq.com/[RabbitMQ] exchange.
+link:https://www.rabbitmq.com/[RabbitMQ,role=external,window=_blank] exchange.
 
-link:https://gerrit-review.googlesource.com/admin/repos/plugins/rabbitmq[Project]
+link:https://gerrit-review.googlesource.com/admin/repos/plugins/rabbitmq[Project,role=external,window=_blank]
 link:https://gerrit.googlesource.com/plugins/rabbitmq/+/master/src/main/resources/Documentation/config.md[
-Configuration]
+Configuration,role=external,window=_blank]
 
 [[readonly]]
 === readonly
@@ -623,11 +623,11 @@
 blocking HTTP PUT/POST/DELETE requests, and disabling SSH commands.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/readonly[
-Project] |
+Project,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/readonly/+doc/master/src/main/resources/Documentation/about.md[
-Documentation] |
+Documentation,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/readonly/+doc/master/src/main/resources/Documentation/config.md[
-Configuration]
+Configuration,role=external,window=_blank]
 
 [[ref-protection]]
 === ref-protection
@@ -638,9 +638,9 @@
 `refs/backups/` namespace.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/ref-protection[
-Project] |
+Project,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/ref-protection/+doc/master/src/main/resources/Documentation/about.md[
-Documentation]
+Documentation,role=external,window=_blank]
 
 [[reparent]]
 === reparent
@@ -648,11 +648,11 @@
 A plugin that provides project reparenting as a self-service for project owners.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/reparent[
-Project] |
+Project,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/reparent/+doc/master/src/main/resources/Documentation/about.md[
-Documentation] |
+Documentation,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/reparent/+doc/master/src/main/resources/Documentation/config.md[
-Configuration]
+Configuration,role=external,window=_blank]
 
 [[review-strategy]]
 === review-strategy
@@ -660,9 +660,9 @@
 This plugin allows users to configure different review strategies.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/review-strategy[
-Project] |
+Project,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/review-strategy/+doc/master/src/main/resources/Documentation/about.md[
-Documentation]
+Documentation,role=external,window=_blank]
 
 [[reviewers]]
 === reviewers
@@ -670,11 +670,11 @@
 A plugin that allows adding default reviewers to a change.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/reviewers[
-Project] |
+Project,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/reviewers/+doc/master/src/main/resources/Documentation/about.md[
-Documentation] |
+Documentation,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/reviewers/+doc/master/src/main/resources/Documentation/config.md[
-Configuration]
+Configuration,role=external,window=_blank]
 
 [[reviewers-by-blame]]
 === reviewers-by-blame
@@ -686,11 +686,11 @@
 change.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/reviewers-by-blame[
-Project] |
+Project,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/reviewers-by-blame/+doc/master/src/main/resources/Documentation/about.md[
-Documentation] |
+Documentation,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/reviewers-by-blame/+doc/master/src/main/resources/Documentation/config.md[
-Configuration]
+Configuration,role=external,window=_blank]
 
 [[groovy-provider]]
 === scripting/groovy-provider
@@ -698,16 +698,16 @@
 This plugin provides a Groovy runtime environment for Gerrit plugins in Groovy.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/scripting/groovy-provider[
-Project] |
+Project,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/scripting/groovy-provider/+doc/master/src/main/resources/Documentation/about.md[
-Documentation]
+Documentation,role=external,window=_blank]
 
 [[saml-authentication-provider]]
 === SAML2 authentication provider
 
 This plugin enables Gerrit to use SAML2 protocol for authentication.
 
-link:https://gerrit-review.googlesource.com/admin/repos/plugins/saml[Project]
+link:https://gerrit-review.googlesource.com/admin/repos/plugins/saml[Project,role=external,window=_blank]
 
 [[scala-provider]]
 === scripting/scala-provider
@@ -715,9 +715,9 @@
 This plugin provides a Scala runtime environment for Gerrit plugins in Scala.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/scripting/scala-provider[
-Project] |
+Project,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/scripting/scala-provider/+doc/master/src/main/resources/Documentation/about.md[
-Documentation]
+Documentation,role=external,window=_blank]
 
 [[scripts]]
 === scripts
@@ -728,8 +728,8 @@
 Groovy and Scala scripts require the installation of the corresponding
 scripting/*-provider plugin in order to be loaded into Gerrit.
 
-link:https://gerrit-review.googlesource.com/admin/repos/plugins/scripts[Project]
-link:https://gerrit.googlesource.com/plugins/scripts/+doc/master/README.md[Documentation]
+link:https://gerrit-review.googlesource.com/admin/repos/plugins/scripts[Project,role=external,window=_blank]
+link:https://gerrit.googlesource.com/plugins/scripts/+doc/master/README.md[Documentation,role=external,window=_blank]
 
 [[server-config]]
 === server-config
@@ -741,7 +741,7 @@
 get.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/server-config[
-Project]
+Project,role=external,window=_blank]
 
 [[serviceuser]]
 === serviceuser
@@ -754,11 +754,11 @@
 WebUI and it cannot push commits or tags.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/serviceuser[
-Project] |
+Project,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/serviceuser/+doc/master/src/main/resources/Documentation/about.md[
-Documentation] |
+Documentation,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/serviceuser/+doc/master/src/main/resources/Documentation/config.md[
-Configuration]
+Configuration,role=external,window=_blank]
 
 [[uploadvalidator]]
 === uploadvalidator
@@ -770,11 +770,11 @@
 settings are rejected by Gerrit.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/uploadvalidator[
-Project] |
+Project,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/uploadvalidator/+doc/master/src/main/resources/Documentation/about.md[
-Documentation] |
+Documentation,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/uploadvalidator/+doc/master/src/main/resources/Documentation/config.md[
-Configuration]
+Configuration,role=external,window=_blank]
 
 [[verify-status]]
 === verify-status
@@ -784,11 +784,11 @@
 or in a completely separate datastore.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/verify-status[
-Project] |
+Project,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/verify-status/+doc/master/src/main/resources/Documentation/about.md[
-Documentation] |
+Documentation,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/verify-status/+doc/master/src/main/resources/Documentation/database.md[
-Configuration]
+Configuration,role=external,window=_blank]
 
 [[websession-flatfile]]
 === websession-flatfile
@@ -799,11 +799,11 @@
 Gerrit installations.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/websession-flatfile[
-Project] |
+Project,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/websession-flatfile/+doc/master/src/main/resources/Documentation/about.md[
-Documentation] |
+Documentation,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/websession-flatfile/+doc/master/src/main/resources/Documentation/config.md[
-Configuration]
+Configuration,role=external,window=_blank]
 
 [[x-docs]]
 === x-docs
@@ -811,11 +811,11 @@
 This plugin serves project documentation as HTML pages.
 
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/x-docs[
-Project] |
+Project,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/x-docs/+doc/master/src/main/resources/Documentation/about.md[
-Documentation] |
+Documentation,role=external,window=_blank] |
 link:https://gerrit.googlesource.com/plugins/x-docs/+doc/master/src/main/resources/Documentation/config.md[
-Configuration]
+Configuration,role=external,window=_blank]
 
 
 GERRIT
diff --git a/Documentation/config-sso.txt b/Documentation/config-sso.txt
index 6f3a32d..7a68689 100644
--- a/Documentation/config-sso.txt
+++ b/Documentation/config-sso.txt
@@ -17,7 +17,7 @@
 As this is the default setting there is nothing required from the
 site administrator to make use of the OpenID authentication services.
 
-* http://openid.net/[openid.net]
+* http://openid.net/[openid.net,,role=external,window=_blank]
 
 If Jetty is being used, you may need to increase the header
 buffer size parameter, due to very long header lines.
@@ -34,7 +34,7 @@
 `auth.trustedOpenID` list in `gerrit.config`.  Patterns may be
 either a
 link:http://download.oracle.com/javase/6/docs/api/java/util/regex/Pattern.html[standard
-Java regular expression (java.util.regex)] (must start with `^`
+Java regular expression (java.util.regex),,role=external,window=_blank] (must start with `^`
 and end with `$`) or be a simple prefix (any other string).
 
 Out of the box Gerrit is configured to trust two patterns, which
@@ -65,12 +65,12 @@
 subsequent attempts to link that account with the existing account will fail.
 In cases where this happens, the administrator will need to manually merge the
 accounts.  See link:https://gerrit.googlesource.com/homepage/+/md-pages/docs/SqlMergeUserAccounts.md[
-Merging Gerrit User Accounts] on the Gerrit Wiki for details.
+Merging Gerrit User Accounts,,role=external,window=_blank] on the Gerrit Wiki for details.
 
 Linking another identity is also useful for users whose primary OpenID provider
 shuts down. For example Google
 link:https://developers.google.com/+/api/auth-migration[shut down their OpenID
-service on 20th April 2015]. Users who failed to add an alternative identity with
+service on 20th April 2015,,role=external,window=_blank]. Users who failed to add an alternative identity with
 another OpenID provider before that date will end up with their account only having
 a disabled Google identity. After creating a separate account with an alternative
 provider, they will need to ask the administrator to merge the accounts using the
diff --git a/Documentation/dev-bazel.txt b/Documentation/dev-bazel.txt
index 436b918..6a046ee1 100644
--- a/Documentation/dev-bazel.txt
+++ b/Documentation/dev-bazel.txt
@@ -8,10 +8,10 @@
 * A Linux or macOS system (Windows is not supported at this time)
 * A JDK for Java 8|9|10|11|...
 * Python 2 or 3
-* link:https://github.com/nodesource/distributions/blob/master/README.md[Node.js (including npm)]
+* link:https://github.com/nodesource/distributions/blob/master/README.md[Node.js (including npm),role=external,window=_blank]
 * Bower (`sudo npm install -g bower`)
-* link:https://docs.bazel.build/versions/master/install.html[Bazel] directly
-or through link:https://github.com/bazelbuild/bazelisk[Bazelisk]
+* link:https://docs.bazel.build/versions/master/install.html[Bazel,role=external,window=_blank] directly
+or through link:https://github.com/bazelbuild/bazelisk[Bazelisk,role=external,window=_blank]
 * Maven
 * zip, unzip
 * gcc
@@ -36,7 +36,7 @@
 ==== Java 13 support
 
 Java 13 (and newer) is supported through vanilla java toolchain
-link:https://docs.bazel.build/versions/master/toolchains.html[Bazel option].
+link:https://docs.bazel.build/versions/master/toolchains.html[Bazel option,role=external,window=_blank].
 To build Gerrit with Java 13 and newer, specify vanilla java toolchain and
 provide the path to JDK home:
 
@@ -96,7 +96,7 @@
 ```
 
 === Node.js and npm packages
-See link:https://gerrit.googlesource.com/gerrit/+/master/polygerrit-ui/README.md#installing-node_js-and-npm-packages[Installing Node.js and npm packages].
+See link:https://gerrit.googlesource.com/gerrit/+/master/polygerrit-ui/README.md#installing-node_js-and-npm-packages[Installing Node.js and npm packages,role=external,window=_blank].
 
 [[build]]
 == Building on the Command Line
@@ -212,7 +212,7 @@
 
 === IntelliJ
 
-The Gerrit build works with Bazel's link:https://ij.bazel.build[IntelliJ plugin].
+The Gerrit build works with Bazel's link:https://ij.bazel.build[IntelliJ plugin,role=external,window=_blank].
 Please follow the instructions on <<dev-intellij#,IntelliJ Setup>>.
 
 === Eclipse
@@ -232,7 +232,7 @@
 If an updated classpath is needed, the Eclipse project can be
 refreshed and missing dependency JARs can be downloaded by running
 `project.py` again. For IntelliJ, you need to click the `Sync Project
-with BUILD Files` button of link:https://ij.bazel.build[Bazel plugin].
+with BUILD Files` button of link:https://ij.bazel.build[Bazel plugin,role=external,window=_blank].
 
 [[documentation]]
 === Documentation
@@ -342,11 +342,11 @@
 
 Successfully running the Elasticsearch tests requires Docker, and
 may require setting the local
-link:https://www.elastic.co/guide/en/elasticsearch/reference/current/vm-max-map-count.html[virtual memory].
+link:https://www.elastic.co/guide/en/elasticsearch/reference/current/vm-max-map-count.html[virtual memory,role=external,window=_blank].
 
 If Docker is not available, the Elasticsearch tests will be skipped.
 Note that Bazel currently does not show
-link:https://github.com/bazelbuild/bazel/issues/3476[the skipped tests].
+link:https://github.com/bazelbuild/bazel/issues/3476[the skipped tests,role=external,window=_blank].
 
 == Dependencies
 
@@ -459,7 +459,7 @@
 * ~/.gerritcodereview/bazel-cache/cas
 
 Currently none of these caches have a maximum size limit. See
-link:https://github.com/bazelbuild/bazel/issues/5139[this bazel issue] for
+link:https://github.com/bazelbuild/bazel/issues/5139[this bazel issue,role=external,window=_blank] for
 details. Users should watch the cache sizes and clean them manually if
 necessary.
 
@@ -470,7 +470,7 @@
 "binaries". We don't attempt to resolve and download NPM dependencies at build
 time, but instead use pre-built bundles of the NPM binary along with all its
 dependencies. Some packages on
-link:https://docs.npmjs.com/misc/registry[registry.npmjs.org] come with their
+link:https://docs.npmjs.com/misc/registry[registry.npmjs.org,role=external,window=_blank] come with their
 dependencies bundled, but this is the exception rather than the rule. More
 commonly, to add a new binary to this list, you will need to bundle the binary
 yourself.
@@ -498,7 +498,7 @@
 storage bucket if the licenses allow us to do so. As long as all of the listed
 license are allowed by
 link:https://opensource.google.com/docs/thirdparty/licenses/[Google's
-standards]. Any `by_exception_only`, commercial, prohibited, or unlisted
+standards,role=external,window=_blank]. Any `by_exception_only`, commercial, prohibited, or unlisted
 licenses are not allowed; otherwise, it is ok to distribute the source. If in
 doubt, contact a maintainer who is a Googler.
 
@@ -523,7 +523,7 @@
 
 Any project maintainer can upload this file to the
 link:https://console.cloud.google.com/storage/browser/gerrit-maven/npm-packages[storage
-bucket].
+bucket,role=external,window=_blank].
 
 Finally, add the new binary to the build process:
 ----
diff --git a/Documentation/dev-cla.txt b/Documentation/dev-cla.txt
index 267351f..9997178 100644
--- a/Documentation/dev-cla.txt
+++ b/Documentation/dev-cla.txt
@@ -6,17 +6,17 @@
 following:
 
 . Click 'Sign In' at the top right corner of
-  https://gerrit-review.googlesource.com/
+  https://gerrit-review.googlesource.com/[role=external,window=_blank]
 . Sign In with your Google account
 . After signing in, go to the
-  link:https://gerrit-review.googlesource.com/#/settings/agreements[Agreements]
+  link:https://gerrit-review.googlesource.com/#/settings/agreements[Agreements,role=external,window=_blank]
   tab on the settings page
 . Click on 'New Contributor Agreement' and follow the instructions
 
 For reference, the actual agreements are linked below:
 
-* link:https://cla.developers.google.com/about/google-individual[Individual Agreement]
-* link:https://cla.developers.google.com/about/google-corporate[Corporate Agreement]
+* link:https://cla.developers.google.com/about/google-individual[Individual Agreement,role=external,window=_blank]
+* link:https://cla.developers.google.com/about/google-corporate[Corporate Agreement,role=external,window=_blank]
 
 GERRIT
 ------
diff --git a/Documentation/dev-community.txt b/Documentation/dev-community.txt
index a497064..ed9eead 100644
--- a/Documentation/dev-community.txt
+++ b/Documentation/dev-community.txt
@@ -1,19 +1,19 @@
 = Gerrit Community
 
 Gerrit is developed as a
-link:https://gerrit-review.googlesource.com/[self-hosting open source project]
+link:https://gerrit-review.googlesource.com/[self-hosting open source project,role=external,window=_blank]
 and very much welcomes contributions from anyone with a
 link:dev-cla.html[contributor's agreement] on file with the project.
 
 [[project-information]]
 == Project Information
 
-* link:https://www.gerritcodereview.com/[Project Homepage]
-* link:https://www.gerritcodereview.com/codeofconduct.html[Code of Conduct]
-* link:https://www.gerritcodereview.com/releases-readme.html[Release Versions]
-* link:https://gerrit.googlesource.com/gerrit[Source]
-* link:https://bugs.chromium.org/p/gerrit/issues/list[Issue Tracking]
-* link:https://gerrit-review.googlesource.com/q/status:open+project:gerrit[Change Review]
+* link:https://www.gerritcodereview.com/[Project Homepage,role=external,window=_blank]
+* link:https://www.gerritcodereview.com/codeofconduct.html[Code of Conduct,role=external,window=_blank]
+* link:https://www.gerritcodereview.com/releases-readme.html[Release Versions,role=external,window=_blank]
+* link:https://gerrit.googlesource.com/gerrit[Source,role=external,window=_blank]
+* link:https://bugs.chromium.org/p/gerrit/issues/list[Issue Tracking,role=external,window=_blank]
+* link:https://gerrit-review.googlesource.com/q/status:open+project:gerrit[Change Review,role=external,window=_blank]
 * link:dev-design.html[System Design]
 * Processes
 ** link:dev-processes.html#project-governance[Project Governance / Engineering Steering Committee]
diff --git a/Documentation/dev-contributing.txt b/Documentation/dev-contributing.txt
index 0bac643..5e9156b 100644
--- a/Documentation/dev-contributing.txt
+++ b/Documentation/dev-contributing.txt
@@ -33,7 +33,7 @@
 design-driven contribution process instead.
 
 If you are in doubt which process is right for you, consult the
-link:https://groups.google.com/d/forum/repo-discuss[repo-discuss]
+link:https://groups.google.com/d/forum/repo-discuss[repo-discuss,role=external,window=_blank]
 mailing list.
 
 These contribution processes apply to everyone who contributes code to
@@ -83,11 +83,11 @@
 be reviewed before they will get submitted to the code base.  To
 start your contribution, please make a git commit and upload it
 for review to the link:https://gerrit-review.googlesource.com/[
-gerrit-review.googlesource.com] Gerrit server.  To help speed up the
+gerrit-review.googlesource.com,role=external,window=_blank] Gerrit server.  To help speed up the
 review of your change, review these link:dev-crafting-changes.html[
 guidelines] before submitting your change.  You can view the pending
 Gerrit contributions and their statuses
-link:https://gerrit-review.googlesource.com/#/q/status:open+project:gerrit[here].
+link:https://gerrit-review.googlesource.com/#/q/status:open+project:gerrit[here,role=external,window=_blank].
 
 Depending on the size of that list it might take a while for
 your change to get reviewed.  Naturally there are fewer
diff --git a/Documentation/dev-core-plugins.txt b/Documentation/dev-core-plugins.txt
index 11027ef..1b49097 100644
--- a/Documentation/dev-core-plugins.txt
+++ b/Documentation/dev-core-plugins.txt
@@ -9,7 +9,7 @@
 
 To make working with core plugins easy, they are linked as
 link:https://gerrit.googlesource.com/gerrit/+/refs/heads/master/.gitmodules[Git
-submodules] in the `gerrit` repository. E.g. this means they can be easily
+submodules,role=external,window=_blank] in the `gerrit` repository. E.g. this means they can be easily
 link:dev-readme.html#clone[cloned] together with Gerrit.
 
 All core plugins are developed and maintained by the
@@ -35,12 +35,12 @@
 1. License:
 +
 The plugin code is available under the
-link:http://www.apache.org/licenses/LICENSE-2.0[Apache License Version 2.0].
+link:http://www.apache.org/licenses/LICENSE-2.0[Apache License Version 2.0,role=external,window=_blank].
 
 2. Hosting:
 +
 The plugin development is hosted on the
-link:https://gerrit-review.googlesource.com[gerrit-review] Gerrit Server.
+link:https://gerrit-review.googlesource.com[gerrit-review,role=external,window=_blank] Gerrit Server.
 
 3. Scope:
 +
diff --git a/Documentation/dev-crafting-changes.txt b/Documentation/dev-crafting-changes.txt
index eb9dee4..d691280 100644
--- a/Documentation/dev-crafting-changes.txt
+++ b/Documentation/dev-crafting-changes.txt
@@ -86,10 +86,10 @@
 The HTTPS access requires proper username and password; this can be obtained
 by clicking the 'Obtain Password' link on the
 link:https://gerrit-review.googlesource.com/#/settings/http-password[HTTP
-Password tab of the user settings page].
+Password tab of the user settings page,role=external,window=_blank].
 
 Alternately, you may use the
-link:https://pypi.org/project/git-review/[git-review] tool to submit changes
+link:https://pypi.org/project/git-review/[git-review,role=external,window=_blank] tool to submit changes
 to Gerrit. If you do, it will set up the Change-Id hook and `gerrit` remote
 for you. You will still need to do the HTTP access step.
 
@@ -109,12 +109,12 @@
 
 Gerrit generally follows the
 link:https://google.github.io/styleguide/javaguide.html[Google Java Style
-Guide].
+Guide,role=external,window=_blank].
 
 To format Java source code, Gerrit uses the
-link:https://github.com/google/google-java-format[`google-java-format`]
+link:https://github.com/google/google-java-format[`google-java-format`,role=external,window=_blank]
 tool (version 1.7), and to format Bazel BUILD, WORKSPACE and .bzl files the
-link:https://github.com/bazelbuild/buildtools/tree/master/buildifier[`buildifier`]
+link:https://github.com/bazelbuild/buildtools/tree/master/buildifier[`buildifier`,role=external,window=_blank]
 tool (version 0.29.0).
 These tools automatically apply format according to the style guides; this
 streamlines code review by reducing the need for time-consuming, tedious,
diff --git a/Documentation/dev-design-docs.txt b/Documentation/dev-design-docs.txt
index 5e3f7a9..ca5ff62 100644
--- a/Documentation/dev-design-docs.txt
+++ b/Documentation/dev-design-docs.txt
@@ -72,7 +72,7 @@
 
 To propose a new design, upload a change to the
 link:https://gerrit-review.googlesource.com/admin/repos/homepage[
-homepage] repository that adds a new folder under `pages/design-docs/`
+homepage,role=external,window=_blank] repository that adds a new folder under `pages/design-docs/`
 which contains at least an `index.md` and a `uses-cases.md` file (see
 link:#structure[design doc structure] above).
 
@@ -87,7 +87,7 @@
 
 Only very few maintainers actively watch out for uploaded design docs.
 To raise awareness you may want to send a notification to the
-link:https://groups.google.com/d/forum/repo-discuss[repo-discuss]
+link:https://groups.google.com/d/forum/repo-discuss[repo-discuss,role=external,window=_blank]
 mailing list about your uploaded design doc. But the discussion should
 not take place on the mailing list, comments should be made by reviewing
 the change in Gerrit.
@@ -122,7 +122,7 @@
 
 . Go to the
   link:https://gerrit-review.googlesource.com/settings/#Notifications[
-  notification settings]
+  notification settings,role=external,window=_blank]
 . Add a project watch for the `homepage` repository with the following
   query: `dir:pages/design-docs`
 
diff --git a/Documentation/dev-design.txt b/Documentation/dev-design.txt
index fd53cac..8ad400b 100644
--- a/Documentation/dev-design.txt
+++ b/Documentation/dev-design.txt
@@ -68,10 +68,10 @@
 Since Gerrit 3.x link:note-db.html[NoteDb] replaced the SQL database
 and all metadata is now stored in Git.
 
-* link:http://video.google.com/videoplay?docid=-8502904076440714866[Mondrian Code Review On The Web]
-* link:https://github.com/rietveld-codereview/rietveld[Rietveld - Code Review for Subversion]
-* link:http://eagain.net/gitweb/?p=gitosis.git;a=blob;f=README.rst;hb=HEAD[Gitosis README]
-* link:http://source.android.com/[Android Open Source Project]
+* link:http://video.google.com/videoplay?docid=-8502904076440714866[Mondrian Code Review On The Web,role=external,window=_blank]
+* link:https://github.com/rietveld-codereview/rietveld[Rietveld - Code Review for Subversion,role=external,window=_blank]
+* link:http://eagain.net/gitweb/?p=gitosis.git;a=blob;f=README.rst;hb=HEAD[Gitosis README,role=external,window=_blank]
+* link:http://source.android.com/[Android Open Source Project,role=external,window=_blank]
 
 
 == Overview
@@ -167,8 +167,8 @@
 requires that the OpenID provider selected by a user must be
 online and operating in order to authenticate that user.
 
-* link:http://www.kernel.org/pub/software/scm/git/docs/gitrepository-layout.html[Git Repository Format]
-* link:http://openid.net/developers/specs/[OpenID Specifications]
+* link:http://www.kernel.org/pub/software/scm/git/docs/gitrepository-layout.html[Git Repository Format,role=external,window=_blank]
+* link:http://openid.net/developers/specs/[OpenID Specifications,role=external,window=_blank]
 
 *1  Although an effort is underway to eliminate the use of the
 database altogether, and to store all the metadata directly in
diff --git a/Documentation/dev-e2e-tests.txt b/Documentation/dev-e2e-tests.txt
index 7329a43..d13d29c 100644
--- a/Documentation/dev-e2e-tests.txt
+++ b/Documentation/dev-e2e-tests.txt
@@ -8,9 +8,9 @@
 == What is Gatling?
 
 Gatling is a load testing tool which provides out of the box support for the HTTP protocol. Documentation on how to write an
-HTTP load test can be found link:https://gatling.io/docs/current/http/http_protocol/[`here`].
+HTTP load test can be found link:https://gatling.io/docs/current/http/http_protocol/[`here`,role=external,window=_blank].
 
-However, in the scenario we are proposing, we are leveraging the link:https://github.com/GerritForge/gatling-git[`Gatling Git extension`]
+However, in the scenario we are proposing, we are leveraging the link:https://github.com/GerritForge/gatling-git[`Gatling Git extension`,role=external,window=_blank]
 to run tests at Git protocol level.
 
 Gatling is written in Scala, but the abstraction provided by the Gatling DSL makes the scenarios implementation easy even without any Scala knowledge.
@@ -32,7 +32,7 @@
 ==== Setup
 
 If you are running SSH commands the private keys of the users used for testing need to go in `/tmp/ssh-keys`.
-The keys need to be generated this way (JSch won't validate them [otherwise](https://stackoverflow.com/questions/53134212/invalid-privatekey-when-using-jsch):
+The keys need to be generated this way (JSch won't validate them [otherwise,role=external,window=_blank](https://stackoverflow.com/questions/53134212/invalid-privatekey-when-using-jsch):
 
 ----
 ssh-keygen -m PEM -t rsa -C "test@mail.com" -f /tmp/ssh-keys/id_rsa
diff --git a/Documentation/dev-eclipse.txt b/Documentation/dev-eclipse.txt
index dfe6aa5..2e4f553 100644
--- a/Documentation/dev-eclipse.txt
+++ b/Documentation/dev-eclipse.txt
@@ -8,7 +8,7 @@
 [[setup]]
 == Project Setup
 
-In your Eclipse installation's link:https://wiki.eclipse.org/Eclipse.ini[`eclipse.ini`] file,
+In your Eclipse installation's link:https://wiki.eclipse.org/Eclipse.ini[`eclipse.ini`,role=external,window=_blank] file,
 add the following line in the `vmargs` section:
 
 ----
@@ -60,13 +60,13 @@
 == Code Formatter Settings
 
 To format source code, Gerrit uses the
-link:https://github.com/google/google-java-format[`google-java-format`]
+link:https://github.com/google/google-java-format[`google-java-format`,role=external,window=_blank]
 tool (version 1.7), which automatically formats code to follow the
 style guide. See link:dev-crafting-changes.html#style[Code Style] for the
 instruction how to set up command line tool that uses this formatter.
 The Eclipse plugin is provided that allows to format with the same
 formatter from within the Eclipse IDE. See
-link:https://github.com/google/google-java-format#eclipse[Eclipse plugin]
+link:https://github.com/google/google-java-format#eclipse[Eclipse plugin,role=external,window=_blank]
 for details how to install it. It's important to use the same plugin version
 as the `google-java-format` script.
 
diff --git a/Documentation/dev-inspector.txt b/Documentation/dev-inspector.txt
index 39736d7..31ad9fa 100644
--- a/Documentation/dev-inspector.txt
+++ b/Documentation/dev-inspector.txt
@@ -33,7 +33,7 @@
 
 Gerrit Inspector requires Jython library ('jython.jar') to be installed
 in the '$site_path/lib' directory. Jython, a Python interpreter for
-the Java Virtual Machine, can be obtained from the http://www.jython.org/
+the Java Virtual Machine, can be obtained from the http://www.jython.org/[role=external,window=_blank]
 website. Only 'jython.jar' file is needed, installation of Jython libraries
 is optional. Gerrit Inspector has been tested with Jython 2.5.2 but
 might work an earlier version.
@@ -87,7 +87,7 @@
 
 For more information on using Jython, especially with regards to its limitations
 in interfacing to the Java Virtual Machine, please refer to the
-http://www.jython.org/[Jython documentation].
+http://www.jython.org/[Jython documentation,role=external,window=_blank].
 
 After successful initialization it is possible to examine components of
 Java packages, classes and live instances.
diff --git a/Documentation/dev-intellij.txt b/Documentation/dev-intellij.txt
index 81790db..5399928 100644
--- a/Documentation/dev-intellij.txt
+++ b/Documentation/dev-intellij.txt
@@ -14,7 +14,7 @@
 === IntelliJ version and Bazel plugin
 
 Before downloading IntelliJ, look at the
-link:https://plugins.jetbrains.com/plugin/8609-bazel/versions[JetBrains plugin repository page of the Bazel plugin]
+link:https://plugins.jetbrains.com/plugin/8609-bazel/versions[JetBrains plugin repository page of the Bazel plugin,role=external,window=_blank]
 to see what version of the IntelliJ IDEA it is actually compatible with.
 
 Also note that the version of the Bazel plugin used in turn may or may not be
@@ -30,7 +30,7 @@
 === Installation of IntelliJ IDEA
 
 Please refer to the
-link:https://www.jetbrains.com/help/idea/installation-guide.html[installation guide provided by Jetbrains]
+link:https://www.jetbrains.com/help/idea/installation-guide.html[installation guide provided by Jetbrains,role=external,window=_blank]
 to install it on your platform. Make sure to install a version compatible with
 the Bazel plugin as mentioned above.
 
@@ -47,7 +47,7 @@
 . Search for the plugin `Bazel` (by Google).
 +
 TIP: In case the Bazel plugin is not listed, or if it shows an outdated version,
-verify the compatibility between the Bazel plugin and IntelliJ IDEA on link:https://plugins.jetbrains.com/plugin/8609-bazel/versions[the JetBrains plugin page].
+verify the compatibility between the Bazel plugin and IntelliJ IDEA on link:https://plugins.jetbrains.com/plugin/8609-bazel/versions[the JetBrains plugin page,role=external,window=_blank].
 . Install it.
 . Restart IntelliJ IDEA.
 
@@ -117,7 +117,7 @@
 
 . Download
 https://raw.githubusercontent.com/google/styleguide/gh-pages/intellij-java-google-style.xml[
-intellij-java-google-style.xml].
+intellij-java-google-style.xml,role=external,window=_blank].
 . Go to *File -> Settings -> Editor -> Code Style*.
 . Click on the wrench icon with the tooltip _Show Scheme Actions_.
 . Click on *Import Scheme*.
@@ -196,7 +196,7 @@
 use the instructions of <<dev-readme#run_daemon,Running the Daemon>> in
 combination with <<remote-debug,Debugging a remote Gerrit server>>.
 
-(link:https://bugs.chromium.org/p/gerrit/issues/detail?id=11360[Issue 11360])
+(link:https://bugs.chromium.org/p/gerrit/issues/detail?id=11360[Issue 11360,role=external,window=_blank])
 ====
 
 Copy `$(gerrit_source_code)/tools/intellij/gerrit_daemon.xml` to
diff --git a/Documentation/dev-plugins-lifecycle.txt b/Documentation/dev-plugins-lifecycle.txt
index b552472..fb9e4a0 100644
--- a/Documentation/dev-plugins-lifecycle.txt
+++ b/Documentation/dev-plugins-lifecycle.txt
@@ -1,7 +1,7 @@
 = Plugin Lifecycle
 
 Most of the plugins are hosted on the same instance as the
-link:https://gerrit-review.googlesource.com[Gerrit project itself] to make them
+link:https://gerrit-review.googlesource.com[Gerrit project itself,role=external,window=_blank] to make them
 more discoverable and have more chances to be reviewed by the whole community.
 
 [[hosting_lifecycle]]
@@ -12,7 +12,7 @@
 - Ideation and Discussion:
 +
 The idea of creating a new plugin is posted and discussed on the
-link:https://groups.google.com/d/forum/repo-discuss[repo-discuss] mailing list.
+link:https://groups.google.com/d/forum/repo-discuss[repo-discuss,role=external,window=_blank] mailing list.
 +
 Also see section link#ideation_discussion[Ideation and discussion] below.
 
@@ -27,14 +27,14 @@
 +
 The author proposes to release the plugin under the
 link:https://www.apache.org/licenses/LICENSE-2.0.html[Apache 2.0 OpenSource
-license] and requests the plugin to be hosted on
-link:https://gerrit-review.googlesource.com[the Gerrit project site]. The
+license,role=external,window=_blank] and requests the plugin to be hosted on
+link:https://gerrit-review.googlesource.com[the Gerrit project site,role=external,window=_blank]. The
 proposal must be   accepted by at least one Gerrit maintainer. In case of
 disagreement between maintainers, the issue can be escalated to the
 link:dev-processes.html#steering-committee[Engineering Steering Committee]. If
 the plugin is accepted, the Gerrit maintainer creates the project under the
 plugins path on link:https://gerrit-review.googlesource.com[the Gerrit project
-site].
+site,role=external,window=_blank].
 +
 Also see section link#plugin_proposal[Plugin Proposal] below.
 
@@ -42,7 +42,7 @@
 +
 To make the consumption of the plugin easy and to notice plugin breakages early
 the plugin author should setup build jobs on
-link:https://gerrit-ci.gerritforge.com[the GerritForge CI] that build the
+link:https://gerrit-ci.gerritforge.com[the GerritForge CI,role=external,window=_blank] that build the
 plugin for each Gerrit version that it supports.
 +
 Also see section link#build[Build] below.
@@ -58,7 +58,7 @@
 - Release:
 +
 The author releases the plugin by creating a Git tag and announcing the plugin
-on the link:https://groups.google.com/d/forum/repo-discuss[repo-discuss]
+on the link:https://groups.google.com/d/forum/repo-discuss[repo-discuss,role=external,window=_blank]
 mailing list.
 +
 Also see section link#plugin_release[Plugin release] below.
@@ -84,7 +84,7 @@
 contribution of ideas and suggestions by the whole community.
 
 The ideator of the plugin starts with an RFC (Request For Comments) post on the
-link:https://groups.google.com/d/forum/repo-discuss[repo-discuss] mailing list
+link:https://groups.google.com/d/forum/repo-discuss[repo-discuss,role=external,window=_blank] mailing list
 with a description of the main reasons for starting a new plugin.
 
 Example of a post:
@@ -138,9 +138,9 @@
 
 The author decides that the plugin prototype makes sense as a general purpose
 plugin and decides to release the code with the same
-link:https://www.apache.org/licenses/LICENSE-2.0.html[Apache 2.0 license]
+link:https://www.apache.org/licenses/LICENSE-2.0.html[Apache 2.0 license,role=external,window=_blank]
 as the Gerrit Code Review project and have it hosted on
-link:https://gerrit-review.googlesource.com[the Gerrit project site].
+link:https://gerrit-review.googlesource.com[the Gerrit project site,role=external,window=_blank].
 
 The plugin author formalizes the proposal with a follow-up of the initial RFC
 post and asks for public opinion on it.
@@ -167,7 +167,7 @@
 - The plugin's project request is widely appreciated and formally accepted by
   at least one Gerrit maintainer who creates the repository as child project of
   'Public-Projects' on link:https://gerrit-review.googlesource.com[the Gerrit
-  project site], creates an associated plugin owners group with "Owner"
+  project site,role=external,window=_blank], creates an associated plugin owners group with "Owner"
   permissions for the plugin and adds the plugin's author as member of it.
 - The plugin's project is widely appreciated; however, another existing plugin
   already partially covers the same use-case and thus it would make more sense
@@ -177,15 +177,15 @@
 - The plugin's project is found useful; however, it is too specific to the
   author's use-case and would not make sense outside of it. The plugin remains
   in a public repository, widely accessible and OpenSource, but not hosted on
-  link:https://gerrit-review.googlesource.com[the Gerrit project site].
+  link:https://gerrit-review.googlesource.com[the Gerrit project site,role=external,window=_blank].
 
 [[build]]
 == Build
 
 The plugin's maintainer creates a job on the
-link:https://gerrit-ci.gerritforge.com[GerritForge CI] by creating a new YAML
+link:https://gerrit-ci.gerritforge.com[GerritForge CI,role=external,window=_blank] by creating a new YAML
 definition in the link:https://gerrit.googlesource.com/gerrit-ci-scripts[Gerrit
-CI Scripts] repository.
+CI Scripts,role=external,window=_blank] repository.
 
 Example of a YAML CI job for plugins:
 
@@ -203,7 +203,7 @@
 
 The plugin follows the same lifecycle as Gerrit Code Review and needs to be
 kept up-to-date with the current active branches, according to the
-link:https://www.gerritcodereview.com/#support[current support policy].
+link:https://www.gerritcodereview.com/#support[current support policy,role=external,window=_blank].
 During the development, the plugin's maintainer can reward contributors
 requesting to be more involved and making them maintainers of his plugin,
 adding them to the list of the project owners.
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index b7b807f..5910a7c 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -36,7 +36,7 @@
 
 To get started with the development of a plugin, take a look at
 the samples in the
-link:https://gerrit.googlesource.com/plugins/examples[examples plugin project].
+link:https://gerrit.googlesource.com/plugins/examples[examples plugin project,role=external,window=_blank].
 
 This is a project that demonstrates the various features of the
 plugin API. It can be taken as an example to develop an own plugin.
@@ -367,7 +367,7 @@
 Plugins are loaded from a single JAR file. If a plugin needs
 additional libraries, it must include those dependencies within
 its own JAR. Plugins built using Maven may be able to use the
-link:http://maven.apache.org/plugins/maven-shade-plugin/[shade plugin]
+link:http://maven.apache.org/plugins/maven-shade-plugin/[shade plugin,role=external,window=_blank]
 to package additional dependencies. Relocating (or renaming) classes
 should not be necessary due to the ClassLoader isolation.
 
@@ -2189,7 +2189,7 @@
 
 Gerrit provides an extension point that enables development of
 link:https://github.com/github/git-lfs/blob/master/docs/api/v1/http-v1-batch.md[
-LFS (Large File Storage)] storage plugins. Gerrit core exposes the default LFS
+LFS (Large File Storage),role=external,window=_blank] storage plugins. Gerrit core exposes the default LFS
 protocol endpoint `<project-name>/info/lfs/objects/batch` and forwards the requests
 to the configured link:config-gerrit.html#lfs[lfs.plugin] plugin which implements
 the LFS protocol. By exposing the default LFS endpoint, the git-lfs client can be
@@ -2247,16 +2247,16 @@
 To send Gerrit's metrics data to an external reporting backend, a plugin can
 get a `MetricRegistry` injected and register an instance of a class that
 implements the `Reporter` interface from link:http://metrics.dropwizard.io/[
-DropWizard Metrics].
+DropWizard Metrics,role=external,window=_blank].
 
 Metric reporting plugin implementations are provided for
-link:https://gerrit.googlesource.com/plugins/metrics-reporter-jmx/[JMX],
-link:https://gerrit.googlesource.com/plugins/metrics-reporter-elasticsearch/[Elastic Search],
-and link:https://gerrit.googlesource.com/plugins/metrics-reporter-graphite/[Graphite].
+link:https://gerrit.googlesource.com/plugins/metrics-reporter-jmx/[JMX,role=external,window=_blank],
+link:https://gerrit.googlesource.com/plugins/metrics-reporter-elasticsearch/[Elastic Search,role=external,window=_blank],
+and link:https://gerrit.googlesource.com/plugins/metrics-reporter-graphite/[Graphite,role=external,window=_blank].
 
 There is also a working example of reporting metrics to the console in the
 link:https://gerrit.googlesource.com/plugins/cookbook-plugin/+/master/src/main/java/com/googlesource/gerrit/plugins/cookbook/ConsoleMetricReporter.java[
-cookbook plugin].
+cookbook plugin,role=external,window=_blank].
 
 === Providing own metrics
 
@@ -2293,7 +2293,7 @@
 
 See the replication metrics in the
 link:https://gerrit.googlesource.com/plugins/replication/+/master/src/main/java/com/googlesource/gerrit/plugins/replication/ReplicationMetrics.java[
-replication plugin] for an example of usage.
+replication plugin,role=external,window=_blank] for an example of usage.
 
 [[account-patch-review-store]]
 == AccountPatchReviewStore
@@ -2338,7 +2338,7 @@
 attribute.
 
 Documentation may be written in the Markdown flavor
-link:https://github.com/vsch/flexmark-java[flexmark-java]
+link:https://github.com/vsch/flexmark-java[flexmark-java,role=external,window=_blank]
 if the file name ends with `.md`. Gerrit will automatically convert
 Markdown to HTML if accessed with extension `.html`.
 
diff --git a/Documentation/dev-processes.txt b/Documentation/dev-processes.txt
index 6472f2a..423f484 100644
--- a/Documentation/dev-processes.txt
+++ b/Documentation/dev-processes.txt
@@ -30,7 +30,7 @@
   period of 1 year (see link:#steering-committee-election[below])
 
 Refer to the project homepage for the link:https://www.gerritcodereview.com/members.html#engineering-steering-committee[
-list of current committee members].
+list of current committee members,role=external,window=_blank].
 
 The steering committee should act in the interest of the Gerrit project
 and the whole Gerrit community.
@@ -84,7 +84,7 @@
 [[versioning]]
 == Semantic versioning
 
-Gerrit follows a light link:https://semver.org/[semantic versioning scheme] MAJOR.MINOR[.PATCH[.HOTFIX]]
+Gerrit follows a light link:https://semver.org/[semantic versioning scheme,role=external,window=_blank] MAJOR.MINOR[.PATCH[.HOTFIX]]
 format:
 
   * MAJOR is incremented when there are substantial incompatible changes and/or
@@ -182,7 +182,7 @@
 
 To report a security vulnerability file a
 link:https://bugs.chromium.org/p/gerrit/issues/entry?template=Security+Issue[
-security issue] in the Gerrit issue tracker. The visibility of issues that are
+security issue,role=external,window=_blank] in the Gerrit issue tracker. The visibility of issues that are
 created with the `Security Issue` template is automatically restricted to
 Gerrit maintainers and a few long-term contributors. This means as a reporter
 you may not be able to see the issue once it is created. Security issues are
@@ -222,7 +222,7 @@
 address the security vulnerability immediately (either by upgrading to a fixed
 release or applying the mitigation). The information about the security
 vulnerability is disclosed via the
-link:https://groups.google.com/d/forum/repo-discuss[repo-discuss] mailing list.
+link:https://groups.google.com/d/forum/repo-discuss[repo-discuss,role=external,window=_blank] mailing list.
 
 [[handle-security-issue]]
 === Handling of the Security Vulnerability
@@ -253,7 +253,7 @@
 +
 Instead security fixes should be implemented and reviewed in the non-public
 link:https://gerrit-review.googlesource.com/admin/repos/gerrit-security-fixes[
-gerrit-security-fixes] repository which is only accessible by Gerrit
+gerrit-security-fixes,role=external,window=_blank] repository which is only accessible by Gerrit
 maintainers and Gerrit community members that work on security fixes.
 +
 The change that fixes the security vulnerability should contain an integration
@@ -284,7 +284,7 @@
 Once all releases are ready and tested and the announcement is prepared, the
 releases should be all published at the same time. Immediately after that, the
 announcement should be sent out to the
-link:https://groups.google.com/d/forum/repo-discuss[repo-discuss] mailing list.
+link:https://groups.google.com/d/forum/repo-discuss[repo-discuss,role=external,window=_blank] mailing list.
 +
 This ends the embargo and any issue that discusses the security vulnerability
 should be made public.
@@ -293,7 +293,7 @@
 +
 The ESC should discuss if there are any learnings from the security
 vulnerability and define action items to follow up in the
-link:https://bugs.chromium.org/p/gerrit[issue tracker].
+link:https://bugs.chromium.org/p/gerrit[issue tracker,role=external,window=_blank].
 
 [[core-plugins]]
 == Core Plugins
diff --git a/Documentation/dev-readme.txt b/Documentation/dev-readme.txt
index 34b409c..03ea81d 100644
--- a/Documentation/dev-readme.txt
+++ b/Documentation/dev-readme.txt
@@ -1,6 +1,6 @@
 = Gerrit Code Review: Developer Setup
 
-To build a developer instance, you'll need link:https://bazel.build/[Bazel] to
+To build a developer instance, you'll need link:https://bazel.build/[Bazel,role=external,window=_blank] to
 compile the code.
 
 == Git Setup
@@ -31,7 +31,7 @@
 
 CAUTION: If you store Eclipse or IntelliJ project files in the Gerrit source
 directories, do *_not_* run `git clean -fdx`. Doing so may remove untracked files and damage your project. For more information, see
-link:https://git-scm.com/docs/git-clean[git-clean].
+link:https://git-scm.com/docs/git-clean[git-clean,role=external,window=_blank].
 
 Run the following:
 
diff --git a/Documentation/dev-release-deploy-config.txt b/Documentation/dev-release-deploy-config.txt
index 98a3df5..db8165d 100644
--- a/Documentation/dev-release-deploy-config.txt
+++ b/Documentation/dev-release-deploy-config.txt
@@ -11,7 +11,7 @@
 be done:
 
 * Create an account on
-link:https://issues.sonatype.org/secure/Signup!default.jspa[Sonatype's Jira].
+link:https://issues.sonatype.org/secure/Signup!default.jspa[Sonatype's Jira,role=external,window=_blank].
 +
 Sonatype is the company that runs Maven Central and you need a Sonatype
 account to be able to upload artifacts to Maven Central.
@@ -30,7 +30,7 @@
 repository on Maven Central:
 +
 Ask for this permission by adding a comment on the
-link:https://issues.sonatype.org/browse/OSSRH-7392[OSSRH-7392] Jira
+link:https://issues.sonatype.org/browse/OSSRH-7392[OSSRH-7392,role=external,window=_blank] Jira
 ticket at Sonatype.
 +
 The request needs to be approved by someone who already has this
@@ -43,7 +43,7 @@
 +
 Generate and publish a PGP key as described in
 link:http://central.sonatype.org/pages/working-with-pgp-signatures.html[
-Working with PGP Signatures]. In addition to the keyserver mentioned
+Working with PGP Signatures,role=external,window=_blank]. In addition to the keyserver mentioned
 there it is recommended to also publish the key to the
 link:https://keyserver.ubuntu.com/[Ubuntu key server].
 +
@@ -51,7 +51,7 @@
 while until it is visible to the Sonatype server.
 +
 Add an entry for the public key in the
-link:https://gerrit.googlesource.com/homepage/+/md-pages/releases/public-keys.md[key list]
+link:https://gerrit.googlesource.com/homepage/+/md-pages/releases/public-keys.md[key list,role=external,window=_blank]
 on the homepage.
 +
 The PGP passphrase can be put in `~/.m2/settings.xml`:
@@ -80,7 +80,7 @@
 
 Gerrit Subproject Artifacts are stored on
 link:https://developers.google.com/storage/[Google Cloud Storage].
-Via the link:https://console.developers.google.com/project/164060093628[Developers Console] the
+Via the link:https://console.developers.google.com/project/164060093628[Developers Console,role=external,window=_blank] the
 Gerrit maintainers have access to the `Gerrit Code Review` project.
 This projects host several buckets for storing Gerrit artifacts:
 
@@ -96,7 +96,7 @@
 To upload artifacts to a bucket the user must authenticate with a
 username and password. The username and password need to be retrieved
 from the link:https://console.cloud.google.com/storage/settings?project=api-project-164060093628[
-Storage Setting in the Google Cloud Platform Console]:
+Storage Setting in the Google Cloud Platform Console,role=external,window=_blank]:
 
 Select the `Interoperability` tab, and if no keys are listed under
 `Interoperable storage access keys`, select 'Create a new key'.
diff --git a/Documentation/dev-release-jgit.txt b/Documentation/dev-release-jgit.txt
index 1a8b501..23a82fe 100644
--- a/Documentation/dev-release-jgit.txt
+++ b/Documentation/dev-release-jgit.txt
@@ -2,7 +2,7 @@
 
 This step is only necessary if we need to create an unofficial JGit
 snapshot release and publish it to the
-link:https://developers.google.com/storage/[Google Cloud Storage].
+link:https://developers.google.com/storage/[Google Cloud Storage,role=external,window=_blank].
 
 [[prepare-environment]]
 == Prepare the Maven Environment
diff --git a/Documentation/dev-release.txt b/Documentation/dev-release.txt
index 9e1744c..e62ff43 100644
--- a/Documentation/dev-release.txt
+++ b/Documentation/dev-release.txt
@@ -158,14 +158,14 @@
 ** SNAPSHOT versions are directly uploaded into the Sonatype snapshots
 repository and no further action is needed:
 +
-https://oss.sonatype.org/content/repositories/snapshots/com/google/gerrit/
+https://oss.sonatype.org/content/repositories/snapshots/com/google/gerrit/[role=external,window=_blank]
 
 ** Release versions are uploaded into a staging repository in the
 link:https://oss.sonatype.org/[Sonatype Nexus Server].
 
 * Verify the staging repository
 
-** Go to the link:https://oss.sonatype.org/[Sonatype Nexus Server] and
+** Go to the link:https://oss.sonatype.org/[Sonatype Nexus Server,role=external,window=_blank] and
 sign in with your Sonatype credentials.
 
 ** Click on 'Build Promotion' in the left navigation bar under
@@ -188,7 +188,7 @@
 ** Test closed staging repository
 +
 Once a repository is closed you can find the URL to it in the `Summary`
-section, e.g. https://oss.sonatype.org/content/repositories/comgooglegerrit-1029
+section, e.g. https://oss.sonatype.org/content/repositories/comgooglegerrit-1029[role=external,window=_blank]
 +
 Use this URL for further testing of the artifacts in this repository,
 e.g. to try building a plugin against the plugin API in this repository
@@ -207,7 +207,7 @@
 +
 How to release a staging repository is described in the
 link:https://docs.sonatype.org/display/Repository/Sonatype+OSS+Maven+Repository+Usage+Guide#SonatypeOSSMavenRepositoryUsageGuide-8.a.2.ReleasingaStagingRepository[
-Sonatype OSS Maven Repository Usage Guide].
+Sonatype OSS Maven Repository Usage Guide,role=external,window=_blank].
 +
 [WARNING]
 Releasing artifacts to Maven Central cannot be undone!
@@ -217,17 +217,17 @@
 click on `Release`.
 
 ** The released artifacts are available in
-https://oss.sonatype.org/content/repositories/releases/com/google/gerrit/
+https://oss.sonatype.org/content/repositories/releases/com/google/gerrit/[role=external,window=_blank]
 
 ** It may take up to 2 hours until the artifacts appear on Maven
 Central:
 +
-http://central.maven.org/maven2/com/google/gerrit/
+http://central.maven.org/maven2/com/google/gerrit/[role=external,window=_blank]
 
 * [optional]: View download statistics
 
 ** Sign in to the
-link:https://oss.sonatype.org/[Sonatype Nexus Server].
+link:https://oss.sonatype.org/[Sonatype Nexus Server,role=external,window=_blank].
 
 ** Click on 'Views/Repositories' in the left navigation bar under
 'Central Statistics'.
@@ -239,7 +239,7 @@
 ==== Publish the Gerrit WAR to the Google Cloud Storage
 
 * go to the link:https://console.cloud.google.com/storage/browser/gerrit-releases/?project=api-project-164060093628[
-gerrit-releases bucket in the Google cloud storage console]
+gerrit-releases bucket in the Google cloud storage console,role=external,window=_blank]
 * make sure you are signed in with your Gmail account
 * manually upload the Gerrit WAR file by using the `Upload` button
 
@@ -248,7 +248,7 @@
 
 * Create the stable branch `stable-$version` in the `gerrit` project via the
 link:https://gerrit-review.googlesource.com/admin/repos/gerrit,branches[
-Gerrit Web UI] or by push.
+Gerrit Web UI,role=external,window=_blank] or by push.
 
 * Push the commits done on `stable-$version` to `refs/for/stable-$version` and
 get them merged.
@@ -282,7 +282,7 @@
 * Upload the files manually via web browser to the appropriate folder
 in the
 link:https://console.cloud.google.com/storage/browser/gerrit-documentation/?project=api-project-164060093628[
-gerrit-documentation] storage bucket.
+gerrit-documentation,role=external,window=_blank] storage bucket.
 
 [[finalize-release-notes]]
 === Finalize the Release Notes
@@ -298,7 +298,7 @@
 ==== Update homepage links
 
 Upload a change on the link:https://gerrit-review.googlesource.com/admin/repos/homepage[
-homepage project] to change the version numbers to the new version.
+homepage project,role=external,window=_blank] to change the version numbers to the new version.
 
 [[update-issues]]
 ==== Update the Issues
@@ -364,7 +364,7 @@
 
 Bazlets is used by gerrit plugins to simplify build process. To allow the
 new released version to be used by gerrit plugins,
-link:https://gerrit.googlesource.com/bazlets/+/master/gerrit_api.bzl#8[gerrit_api.bzl]
+link:https://gerrit.googlesource.com/bazlets/+/master/gerrit_api.bzl#8[gerrit_api.bzl,role=external,window=_blank]
 must reference the new version. Upload a change to bazlets repository with
 api version upgrade.
 
diff --git a/Documentation/dev-roles.txt b/Documentation/dev-roles.txt
index f457667..d3811b9 100644
--- a/Documentation/dev-roles.txt
+++ b/Documentation/dev-roles.txt
@@ -14,19 +14,19 @@
 There are many possibilities to support the project, e.g.:
 
 * get involved in discussions on the
-  link:https://groups.google.com/d/forum/repo-discuss[repo-discuss]
+  link:https://groups.google.com/d/forum/repo-discuss[repo-discuss,role=external,window=_blank]
   mailing list (post your questions, provide feedback, share your
   experiences, help other users)
 * attend community events like user summits (see
   link:https://calendar.google.com/calendar?cid=Z29vZ2xlLmNvbV91YmIxcGxhNmlqNzg1b3FianI2MWg0dmRpc0Bncm91cC5jYWxlbmRhci5nb29nbGUuY29t[
-  community calendar])
-* report link:https://bugs.chromium.org/p/gerrit/issues/list[issues]
+  community calendar,role=external,window=_blank])
+* report link:https://bugs.chromium.org/p/gerrit/issues/list[issues,role=external,window=_blank]
   and help to clarify existing issues
 * provide feedback on
   link:https://www.gerritcodereview.com/releases-readme.html[new
-  releases and release candidates]
+  releases and release candidates,role=external,window=_blank]
 * review
-  link:https://gerrit-review.googlesource.com/q/status:open[changes]
+  link:https://gerrit-review.googlesource.com/q/status:open[changes,role=external,window=_blank]
   and help to verify that they work as advertised, comment if you like
   or dislike a feature
 * serve as contact person for a proprietary Gerrit installation and
@@ -35,7 +35,7 @@
 Supporters can:
 
 * post on the
-  link:https://groups.google.com/d/forum/repo-discuss[repo-discuss]
+  link:https://groups.google.com/d/forum/repo-discuss[repo-discuss,role=external,window=_blank]
   mailing list (Please note that the `repo-discuss` mailing list is
   managed to prevent spam posts. This means posts from new participants
   must be approved manually before they appear on the mailing list.
@@ -43,7 +43,7 @@
   participate in mailing list discussions frequently are approved
   automatically)
 * comment on
-  link:https://gerrit-review.googlesource.com/q/status:open[changes]
+  link:https://gerrit-review.googlesource.com/q/status:open[changes,role=external,window=_blank]
   and vote from `-1` to `+1` on the `Code-Review` label (these votes
   are important to understand the interest in a change and to address
   concerns early, however link:#maintainer[maintainers] can
@@ -53,7 +53,7 @@
   permissions to vote on the `Verified` label are granted by request,
   see below)
 * file issues in the link:https://bugs.chromium.org/p/gerrit/issues/list[
-  issue tracker] and comment on existing issues
+  issue tracker,role=external,window=_blank] and comment on existing issues
 * support the
   link:dev-processes.html#design-driven-contribution-process[
   design-driven contribution process] by reviewing incoming
@@ -62,7 +62,7 @@
 
 Supporters who want to engage further can get additional privileges
 on request (ask for it on the
-link:https://groups.google.com/d/forum/repo-discuss[repo-discuss]
+link:https://groups.google.com/d/forum/repo-discuss[repo-discuss,role=external,window=_blank]
 mailing list):
 
 * become member of the `gerrit-verifiers` group, which allows to:
@@ -71,10 +71,10 @@
 ** edit topics on all open changes
 ** abandon changes
 * approve posts to the
-  link:https://groups.google.com/d/forum/repo-discuss[repo-discuss]
+  link:https://groups.google.com/d/forum/repo-discuss[repo-discuss,role=external,window=_blank]
   mailing list
 * administrate issues in the
-  link:https://bugs.chromium.org/p/gerrit/issues/list[issue tracker]
+  link:https://bugs.chromium.org/p/gerrit/issues/list[issue tracker,role=external,window=_blank]
 
 Supporters can become link:#contributor[contributors] by signing a
 contributor license agreement and contributing code to the Gerrit
@@ -87,7 +87,7 @@
 agreement] and who has link:dev-contributing.html[contributed] at least
 one change to any project on
 link:https://gerrit-review.googlesource.com/[
-gerrit-review.googlesource.com] is a contributor.
+gerrit-review.googlesource.com,role=external,window=_blank] is a contributor.
 
 Contributions can be:
 
@@ -123,10 +123,10 @@
 
 Contributors may also be invited to join the Gerrit hackathons which
 happen regularly (e.g. twice a year). Hackathons are announced on the
-link:https://groups.google.com/d/forum/repo-discuss[repo-discuss]
+link:https://groups.google.com/d/forum/repo-discuss[repo-discuss,role=external,window=_blank]
 mailing list (also see
 link:https://calendar.google.com/calendar?cid=Z29vZ2xlLmNvbV91YmIxcGxhNmlqNzg1b3FianI2MWg0dmRpc0Bncm91cC5jYWxlbmRhci5nb29nbGUuY29t[
-community calendar]).
+community calendar,role=external,window=_blank]).
 
 Outstanding contributors that are actively engaged in the community, in
 activities outlined above, may be nominated as link:#maintainer[
@@ -138,7 +138,7 @@
 Maintainers are the gatekeepers of the project and are in charge of
 approving and submitting changes. Refer to the project homepage for
 the link:https://www.gerritcodereview.com/members.html#maintainers[
-list of current maintainers].
+list of current maintainers,role=external,window=_blank].
 
 Maintainers should only approve changes that:
 
@@ -185,20 +185,20 @@
   link:dev-processes.html#project-governance[Project Governance]
 * nominate new maintainers and vote on nominations (see below)
 * administrate the link:https://groups.google.com/d/forum/repo-discuss[
-  mailing list], the
-  link:https://bugs.chromium.org/p/gerrit/issues/list[issue tracker]
-  and the link:https://www.gerritcodereview.com/[homepage]
+  mailing list,role=external,window=_blank], the
+  link:https://bugs.chromium.org/p/gerrit/issues/list[issue tracker,role=external,window=_blank]
+  and the link:https://www.gerritcodereview.com/[homepage,role=external,window=_blank]
 * gain permissions to do Gerrit releases and publish release artifacts
 * create new projects and groups on
   link:https://gerrit-review.googlesource.com/[
-  gerrit-review.googlesource.com]
+  gerrit-review.googlesource.com,role=external,window=_blank]
 * administrate the Gerrit projects on
   link:https://gerrit-review.googlesource.com/[
-  gerrit-review.googlesource.com] (e.g. edit ACLs, update project
+  gerrit-review.googlesource.com,role=external,window=_blank] (e.g. edit ACLs, update project
   configuration)
 * create events in the
   link:https://calendar.google.com/calendar?cid=Z29vZ2xlLmNvbV91YmIxcGxhNmlqNzg1b3FianI2MWg0dmRpc0Bncm91cC5jYWxlbmRhci5nb29nbGUuY29t[
-  community calendar]
+  community calendar,role=external,window=_blank]
 * discuss with other maintainers on the private maintainers mailing
   list and Slack channel
 
@@ -243,7 +243,7 @@
 Members of the steering committee are expected to act in the interest
 of the Gerrit project and the whole Gerrit community. Refer to the project
 homepage for the link:https://www.gerritcodereview.com/members.html#engineering-steering-committee[
-list of current committee members].
+list of current committee members,role=external,window=_blank].
 
 For those that are familiar with scrum, the steering committee member
 role is similar to the role of an agile product owner.
@@ -254,7 +254,7 @@
 requests in a timely manner.
 
 Community members may submit new items under the
-link:https://bugs.chromium.org/p/gerrit/issues/list?q=component:ESC[ESC component]
+link:https://bugs.chromium.org/p/gerrit/issues/list?q=component:ESC[ESC component,role=external,window=_blank]
 in the issue tracker, or add that component to existing items, to raise them to
 the attention of ESC members.
 
@@ -301,7 +301,7 @@
 Community managers should act as stakeholders for the Gerrit community
 and focus on the health of the community. Refer to the project homepage
 for the link:https://www.gerritcodereview.com/members.html#community-managers[
-list of current community managers].
+list of current community managers,role=external,window=_blank].
 
 Tasks:
 
@@ -315,7 +315,7 @@
 * serve as contact person for community issues
 
 Community members may submit new items under the
-link:https://bugs.chromium.org/p/gerrit/issues/list?q=component:Community[Community component]
+link:https://bugs.chromium.org/p/gerrit/issues/list?q=component:Community[Community component,role=external,window=_blank]
 backlog, for community managers to refine. Only public topics should be
 issued through that backlog.
 
diff --git a/Documentation/dev-starter-projects.txt b/Documentation/dev-starter-projects.txt
index ae40ea6..ffc816b 100644
--- a/Documentation/dev-starter-projects.txt
+++ b/Documentation/dev-starter-projects.txt
@@ -1,10 +1,10 @@
 = Gerrit Code Review - Starter Projects
 
 We have created a
-link:https://bugs.chromium.org/p/gerrit/issues/list?can=2&q=label%3AStarterProject[StarterProject]
+link:https://bugs.chromium.org/p/gerrit/issues/list?can=2&q=label%3AStarterProject[StarterProject,role=external,window=_blank]
 category in the issue tracker and try to assign easy hack projects to it. If in
 doubt, do not hesitate to ask on the developer
-link:https://groups.google.com/forum/#!forum/repo-discuss[mailing list].
+link:https://groups.google.com/forum/#!forum/repo-discuss[mailing list,role=external,window=_blank].
 
 GERRIT
 ------
diff --git a/Documentation/error-change-closed.txt b/Documentation/error-change-closed.txt
index a239ef1..387cc46 100644
--- a/Documentation/error-change-closed.txt
+++ b/Documentation/error-change-closed.txt
@@ -15,7 +15,7 @@
 new change. To do this you have to remove the Change-Id from the
 commit message as explained link:error-push-fails-due-to-commit-message.html[here] and ideally generate a new Change-Id
 using the link:cmd-hook-commit-msg.html[commit hook] or EGit. Before pushing again it is also
-recommended to do a link:http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html[git rebase] to base your commit on the submitted
+recommended to do a link:http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html[git rebase,role=external,window=_blank] to base your commit on the submitted
 change. Pushing again should now create a new change in Gerrit.
 
 If the change for which you wanted to upload a new patch set was
diff --git a/Documentation/error-changeid-above-footer.txt b/Documentation/error-changeid-above-footer.txt
index abc0186..b3eb30e 100644
--- a/Documentation/error-changeid-above-footer.txt
+++ b/Documentation/error-changeid-above-footer.txt
@@ -8,7 +8,7 @@
 of a commit message. For details, see link:user-changeid.html[Change-Id Lines].
 
 You can see the commit messages for existing commits in the history
-by doing a link:http://www.kernel.org/pub/software/scm/git/docs/git-log.html[git log].
+by doing a link:http://www.kernel.org/pub/software/scm/git/docs/git-log.html[git log,role=external,window=_blank].
 
 
 == Change-Id is contained in the commit message but not in the last paragraph
diff --git a/Documentation/error-contains-banned-commit.txt b/Documentation/error-contains-banned-commit.txt
index 13a0eaa..cd69b2a1 100644
--- a/Documentation/error-contains-banned-commit.txt
+++ b/Documentation/error-contains-banned-commit.txt
@@ -11,7 +11,7 @@
 error message "contains banned commit ...".
 
 If you have commits that you want to push that are based on a banned
-commit you may want to link:http://www.kernel.org/pub/software/scm/git/docs/git-cherry-pick.html[cherry-pick] them onto a clean base and push
+commit you may want to link:http://www.kernel.org/pub/software/scm/git/docs/git-cherry-pick.html[cherry-pick,role=external,window=_blank] them onto a clean base and push
 them again.
 
 
diff --git a/Documentation/error-has-duplicates.txt b/Documentation/error-has-duplicates.txt
index a520f5d..7168aae 100644
--- a/Documentation/error-has-duplicates.txt
+++ b/Documentation/error-has-duplicates.txt
@@ -10,7 +10,7 @@
 
 Since this error should never occur in practice, you should inform
 your Gerrit administrator if you hit this problem and/or
-link:https://bugs.chromium.org/p/gerrit/issues/list[open a Gerrit issue].
+link:https://bugs.chromium.org/p/gerrit/issues/list[open a Gerrit issue,role=external,window=_blank].
 
 In any case to not be blocked with your work, you can simply create a
 new Change-Id for your commit and then push it as new change to
diff --git a/Documentation/error-invalid-author.txt b/Documentation/error-invalid-author.txt
index 5808d4f..d2b7f83 100644
--- a/Documentation/error-invalid-author.txt
+++ b/Documentation/error-invalid-author.txt
@@ -121,7 +121,7 @@
 ----
 
 For further details about git rebase please check the
-link:http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html[Git documentation].
+link:http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html[Git documentation,role=external,window=_blank].
 
 
 == Missing privileges to push commits of other users
diff --git a/Documentation/error-invalid-changeid-line.txt b/Documentation/error-invalid-changeid-line.txt
index 9d3d2fc..63323bf 100644
--- a/Documentation/error-invalid-changeid-line.txt
+++ b/Documentation/error-invalid-changeid-line.txt
@@ -4,7 +4,7 @@
 message footer contains an invalid Change-Id line.
 
 You can see the commit messages for existing commits in the history
-by doing a link:http://www.kernel.org/pub/software/scm/git/docs/git-log.html[git log].
+by doing a link:http://www.kernel.org/pub/software/scm/git/docs/git-log.html[git log,role=external,window=_blank].
 
 If it was the intention to rework a change and to push a new patch
 set, find the change in the Gerrit Web UI, copy its Change-Id line and
diff --git a/Documentation/error-invalid-committer.txt b/Documentation/error-invalid-committer.txt
index a669010..ca35d6a 100644
--- a/Documentation/error-invalid-committer.txt
+++ b/Documentation/error-invalid-committer.txt
@@ -86,7 +86,7 @@
 commits and then confirming all the commit messages). Just picking
 all the changes will not work as in this case the committer is not
 rewritten. For further details about git rebase please check the
-link:http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html[Git documentation].
+link:http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html[Git documentation,role=external,window=_blank].
 
 
 == Missing privileges to push commits that were committed by other users
diff --git a/Documentation/error-missing-changeid.txt b/Documentation/error-missing-changeid.txt
index 27bfea5..866fb8c 100644
--- a/Documentation/error-missing-changeid.txt
+++ b/Documentation/error-missing-changeid.txt
@@ -6,7 +6,7 @@
 a Change-Id.
 
 You can see the commit messages for existing commits in the history
-by doing a link:http://www.kernel.org/pub/software/scm/git/docs/git-log.html[git log].
+by doing a link:http://www.kernel.org/pub/software/scm/git/docs/git-log.html[git log,role=external,window=_blank].
 
 To avoid this error you should use the link:cmd-hook-commit-msg.html[commit hook] or EGit to
 automatically create and insert a unique Change-Id into the commit
diff --git a/Documentation/error-missing-subject.txt b/Documentation/error-missing-subject.txt
index 6ef37a4..0903ead 100644
--- a/Documentation/error-missing-subject.txt
+++ b/Documentation/error-missing-subject.txt
@@ -9,7 +9,7 @@
 message.
 
 You can see the commit messages for existing commits in the history
-by doing a link:http://www.kernel.org/pub/software/scm/git/docs/git-log.html[git log].
+by doing a link:http://www.kernel.org/pub/software/scm/git/docs/git-log.html[git log,role=external,window=_blank].
 
 == Change-Id is the only line in the commit message
 
diff --git a/Documentation/error-multiple-changeid-lines.txt b/Documentation/error-multiple-changeid-lines.txt
index 31567f4..6564949 100644
--- a/Documentation/error-multiple-changeid-lines.txt
+++ b/Documentation/error-multiple-changeid-lines.txt
@@ -4,7 +4,7 @@
 message footer of the pushed commit contains several Change-Id lines.
 
 You can see the commit messages for existing commits in the history
-by doing a link:http://www.kernel.org/pub/software/scm/git/docs/git-log.html[git log].
+by doing a link:http://www.kernel.org/pub/software/scm/git/docs/git-log.html[git log,role=external,window=_blank].
 
 If it was the intention to rework a change and to push a new patch
 set, find the change in the Gerrit Web UI, copy its Change-Id line and
diff --git a/Documentation/error-no-new-changes.txt b/Documentation/error-no-new-changes.txt
index 17422ad..24f8dc0 100644
--- a/Documentation/error-no-new-changes.txt
+++ b/Documentation/error-no-new-changes.txt
@@ -38,7 +38,7 @@
   in the Gerrit Web UI will not find any change)
 
 If you need to re-push a commit you may rewrite this commit by
-link:http://www.kernel.org/pub/software/scm/git/docs/git-commit.html[amending] it or doing an interactive link:http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html[git rebase]. By rewriting the
+link:http://www.kernel.org/pub/software/scm/git/docs/git-commit.html[amending,role=external,window=_blank] it or doing an interactive link:http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html[git rebase,role=external,window=_blank]. By rewriting the
 commit you actually create a new commit (with a new commit ID in
 project scope) which can then be pushed to Gerrit.
 
diff --git a/Documentation/error-non-fast-forward.txt b/Documentation/error-non-fast-forward.txt
index 923132e..0cbda6b 100644
--- a/Documentation/error-non-fast-forward.txt
+++ b/Documentation/error-non-fast-forward.txt
@@ -28,8 +28,8 @@
 bypassing code review, your push will be rejected with the error
 message 'non-fast forward'. To solve the problem you have to either
 
-. link:http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html[rebase] your commit on the new tip of the remote branch or
-. link:http://www.kernel.org/pub/software/scm/git/docs/git-merge.html[merge] your commit with the new tip of the remote branch.
+. link:http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html[rebase,role=external,window=_blank] your commit on the new tip of the remote branch or
+. link:http://www.kernel.org/pub/software/scm/git/docs/git-merge.html[merge,role=external,window=_blank] your commit with the new tip of the remote branch.
 
 Afterwards the push should be successful.
 
@@ -46,7 +46,7 @@
 Although it is considered bad practice, it is possible to allow
 non-fast forward updates with Git. For this the remote Git repository
 has to be configured to not deny non-fast forward updates (set the
-link:http://www.kernel.org/pub/software/scm/git/docs/git-config.html[Git configuration] parameter 'receive.denyNonFastForwards' to
+link:http://www.kernel.org/pub/software/scm/git/docs/git-config.html[Git configuration,role=external,window=_blank] parameter 'receive.denyNonFastForwards' to
 'false'). Then it is possible to push a non-fast forward update by
 using the '--force' option.
 
diff --git a/Documentation/error-not-allowed-to-upload-merges.txt b/Documentation/error-not-allowed-to-upload-merges.txt
index d025bd0..ca44a09 100644
--- a/Documentation/error-not-allowed-to-upload-merges.txt
+++ b/Documentation/error-not-allowed-to-upload-merges.txt
@@ -12,7 +12,7 @@
 If one of your changes could not be merged in Gerrit due to conflicts
 and you created the merge commit to resolve the conflicts, you might
 want to revert the merge and instead of this do a
-link:http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html[rebase].
+link:http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html[rebase,role=external,window=_blank].
 
 
 GERRIT
diff --git a/Documentation/error-permission-denied.txt b/Documentation/error-permission-denied.txt
index 879273d..7eb9428c 100644
--- a/Documentation/error-permission-denied.txt
+++ b/Documentation/error-permission-denied.txt
@@ -3,8 +3,8 @@
 With this error message an SSH command to Gerrit is rejected if the
 SSH authentication is not successful.
 
-The link:http://en.wikipedia.org/wiki/Secure_Shell[SSH] protocol can use
-link:http://en.wikipedia.org/wiki/Public-key_cryptography[Public-key Cryptography]
+The link:http://en.wikipedia.org/wiki/Secure_Shell[SSH,role=external,window=_blank] protocol can use
+link:http://en.wikipedia.org/wiki/Public-key_cryptography[Public-key Cryptography,role=external,window=_blank]
 for authentication.
 In general configurations, Gerrit will authenticate you by the public keys
 known to you. Optionally, it can be configured by the administrator to allow
diff --git a/Documentation/error-push-fails-due-to-commit-message.txt b/Documentation/error-push-fails-due-to-commit-message.txt
index f6e5c1f..ba941e5 100644
--- a/Documentation/error-push-fails-due-to-commit-message.txt
+++ b/Documentation/error-push-fails-due-to-commit-message.txt
@@ -6,7 +6,7 @@
 
 If the commit message of the last commit needs to be fixed you can
 simply amend the last commit (please find a detailed description in
-the link:http://www.kernel.org/pub/software/scm/git/docs/git-commit.html[Git documentation]):
+the link:http://www.kernel.org/pub/software/scm/git/docs/git-commit.html[Git documentation,role=external,window=_blank]):
 
 ----
   $ git commit --amend
@@ -17,7 +17,7 @@
 rebase for the affected commits. While doing the interactive rebase
 you can e.g. choose 'reword' for those commits for which you want to
 fix the commit messages. For a detailed description of git rebase
-please check the link:http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html[Git documentation].
+please check the link:http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html[Git documentation,role=external,window=_blank].
 
 Please use interactive git rebase with care as it rewrites existing
 commits. Generally you should never rewrite commits that have already
diff --git a/Documentation/error-same-change-id-in-multiple-changes.txt b/Documentation/error-same-change-id-in-multiple-changes.txt
index b6aad69..25e58c8 100644
--- a/Documentation/error-same-change-id-in-multiple-changes.txt
+++ b/Documentation/error-same-change-id-in-multiple-changes.txt
@@ -64,7 +64,7 @@
 the example above where the last two commits have the same Change-Id,
 this means an interactive rebase for the last two commits should be
 done. For further details about the git rebase command please check
-the link:http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html[Git documentation for rebase].
+the link:http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html[Git documentation for rebase,role=external,window=_blank].
 
 ----
   $ git rebase -i HEAD~2
@@ -100,7 +100,7 @@
 by using a link:cmd-hook-commit-msg.html[commit hook] or by using EGit) or the Change-Id could be
 removed (not recommended since then amending this commit to create
 subsequent patch sets is more error prone). To change the Change-Id
-of an existing commit do an interactive link:http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html[git rebase] and fix the
+of an existing commit do an interactive link:http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html[git rebase,role=external,window=_blank] and fix the
 affected commit messages.
 
 
diff --git a/Documentation/error-upload-denied.txt b/Documentation/error-upload-denied.txt
index 30c5f2d..5e5a409 100644
--- a/Documentation/error-upload-denied.txt
+++ b/Documentation/error-upload-denied.txt
@@ -9,7 +9,7 @@
 . contact one of the project owners and request upload permissions
   for the project (access right
   link:access-control.html#category_push['Push'])
-. export your commit as a patch using the link:http://www.kernel.org/pub/software/scm/git/docs/git-format-patch.html[git format-patch] command
+. export your commit as a patch using the link:http://www.kernel.org/pub/software/scm/git/docs/git-format-patch.html[git format-patch,role=external,window=_blank] command
   and provide the patch file to one of the project owners
 
 
diff --git a/Documentation/index.txt b/Documentation/index.txt
index 63671cd..09cb21f 100644
--- a/Documentation/index.txt
+++ b/Documentation/index.txt
@@ -16,7 +16,7 @@
 == Guides
 . link:intro-user.html[User Guide]
 . link:intro-project-owner.html[Project Owner Guide]
-. link:https://source.android.com/source/developing[Default Android Workflow] (external)
+. link:https://source.android.com/source/developing[Default Android Workflow,role=external,window=_blank] (external)
 
 == Tutorials
 . Web
@@ -43,7 +43,7 @@
 . link:access-control.html[Access Controls]
 . Multi-project management
 .. link:user-submodules.html[Submodules]
-.. link:https://source.android.com/source/using-repo.html[Repo] (external)
+.. link:https://source.android.com/source/using-repo.html[Repo,role=external,window=_blank] (external)
 . Prolog rules
 .. link:prolog-cookbook.html[Prolog Cookbook]
 .. link:prolog-change-facts.html[Prolog Facts for Gerrit Changes]
@@ -84,11 +84,11 @@
 
 == Resources
 * link:licenses.html[Licenses and Notices]
-* link:https://www.gerritcodereview.com/[Homepage]
-* link:https://gerrit-releases.storage.googleapis.com/index.html[Downloads]
-* link:https://bugs.chromium.org/p/gerrit/issues/list[Issue Tracking]
-* link:https://gerrit.googlesource.com/gerrit[Source Code]
-* link:https://www.gerritcodereview.com/about.md[A History of Gerrit Code Review]
+* link:https://www.gerritcodereview.com/[Homepage,role=external,window=_blank]
+* link:https://gerrit-releases.storage.googleapis.com/index.html[Downloads,role=external,window=_blank]
+* link:https://bugs.chromium.org/p/gerrit/issues/list[Issue Tracking,role=external,window=_blank]
+* link:https://gerrit.googlesource.com/gerrit[Source Code,role=external,window=_blank]
+* link:https://www.gerritcodereview.com/about.md[A History of Gerrit Code Review,role=external,window=_blank]
 
 GERRIT
 ------
diff --git a/Documentation/install-j2ee.txt b/Documentation/install-j2ee.txt
index 48751b7..1f5f895 100644
--- a/Documentation/install-j2ee.txt
+++ b/Documentation/install-j2ee.txt
@@ -47,7 +47,7 @@
 Download and unzip a release version of Jetty.  From here on we
 call the unpacked directory `$JETTY_HOME`.
 
-* link:http://www.eclipse.org/jetty/downloads.php[Jetty Downloads]
+* link:http://www.eclipse.org/jetty/downloads.php[Jetty Downloads,role=external,window=_blank]
 
 If this is a fresh installation of Jetty, move into the installation
 directory and do some cleanup to remove the sample webapps:
@@ -99,7 +99,7 @@
 
 Excerpt from the
 link:https://tomcat.apache.org/tomcat-7.0-doc/config/systemprops.html[
-documentation]:
+documentation,role=external,window=_blank]:
 
 ----
 Property org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH:
diff --git a/Documentation/install.txt b/Documentation/install.txt
index fe32029..12b4e32 100644
--- a/Documentation/install.txt
+++ b/Documentation/install.txt
@@ -5,7 +5,7 @@
 
 To run the Gerrit service, the following requirement must be met on the host:
 
-* JRE, version 1.8 http://www.oracle.com/technetwork/java/javase/downloads/index.html[Download]
+* JRE, version 1.8 http://www.oracle.com/technetwork/java/javase/downloads/index.html[Download,role=external,window=_blank]
 +
 Gerrit is not yet compatible with Java 9 or newer at this time.
 
@@ -22,8 +22,8 @@
 
 . Download the unlimited strength JCE policy files.
 +
-- link:http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html[JDK7 JCE policy files]
-- link:http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html[JDK8 JCE policy files]
+- link:http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html[JDK7 JCE policy files,role=external,window=_blank]
+- link:http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html[JDK8 JCE policy files,role=external,window=_blank]
 . Uncompress and extract the downloaded file.
 +
 The downloaded file  contains the following files:
@@ -47,7 +47,7 @@
 
 Current and past binary releases of Gerrit can be obtained from
 the link:https://gerrit-releases.storage.googleapis.com/index.html[
-Gerrit Releases site].
+Gerrit Releases site,role=external,window=_blank].
 
 Download any current `*.war` package. The war will be referred to as
 `gerrit.war` from this point forward, so you may find it easier to
@@ -173,7 +173,7 @@
 
 The `ssh-keygen` command must be available during the init phase to
 generate SSH host keys. If you have
-link:https://git-for-windows.github.io/[Git for Windows] installed,
+link:https://git-for-windows.github.io/[Git for Windows,role=external,window=_blank] installed,
 start Command Prompt and temporary add directory with ssh-keygen to the
 PATH environment variable just before running init command:
 
@@ -197,7 +197,7 @@
 
 To install Gerrit as Windows Service use the
 link:http://commons.apache.org/proper/commons-daemon/procrun.html[Apache
-Commons Daemon Procrun].
+Commons Daemon Procrun,role=external,window=_blank].
 
 Sample install command:
 
@@ -235,7 +235,7 @@
 Gerrit's internal SSH daemon.  See the `git-daemon` documentation
 for details on how to configure this if anonymous access is desired.
 
-* http://www.kernel.org/pub/software/scm/git/docs/git-daemon.html[man git-daemon]
+* http://www.kernel.org/pub/software/scm/git/docs/git-daemon.html[man git-daemon,role=external,window=_blank]
 
 
 [[plugins]]
@@ -246,7 +246,7 @@
 
 == External Documentation Links
 
-* http://www.kernel.org/pub/software/scm/git/docs/git-daemon.html[git-daemon]
+* http://www.kernel.org/pub/software/scm/git/docs/git-daemon.html[git-daemon,role=external,window=_blank]
 
 
 [[backup]]
diff --git a/Documentation/intro-gerrit-walkthrough-github.txt b/Documentation/intro-gerrit-walkthrough-github.txt
index 39a779d..cee97b1 100644
--- a/Documentation/intro-gerrit-walkthrough-github.txt
+++ b/Documentation/intro-gerrit-walkthrough-github.txt
@@ -25,7 +25,7 @@
 doing the same with GitHub:
 
 * You need the add a commit-msg hook script when you clone a repo for the first
-time using a snippet you can find e.g. https://gerrit-review.googlesource.com/admin/repos/gerrit[here];
+time using a snippet you can find e.g. https://gerrit-review.googlesource.com/admin/repos/gerrit[here,role=external,window=_blank];
 * Your review will be on a single commit instead of a branch. You use
 `git commit --amend` to modify a code change.
 * Instead of using the Web UI to create a pull request, you use
@@ -54,11 +54,11 @@
 and GitHub, you can simply use the `git clone` command.
 
 For Gerrit, there is an additional step before you can start making changes. For
-reasons we explain below, you’ll have to add a https://gerrit-review.googlesource.com/Documentation/user-changeid.html[commit-msg hook] script. This will
+reasons we explain below, you’ll have to add a https://gerrit-review.googlesource.com/Documentation/user-changeid.html[commit-msg hook,role=external,window=_blank] script. This will
 append the Gerrit Change-Id to every commit message such that Gerrit can track
 commits through the review process. To make this process a little easier in
 Gerrit, you can find a command snippet for cloning and adding the commit-msg
-hook on the repository page (e.g. https://gerrit-review.googlesource.com/admin/repos/gerrit[here]).
+hook on the repository page (e.g. https://gerrit-review.googlesource.com/admin/repos/gerrit[here,role=external,window=_blank]).
 
 [[create-change]]
 == 2. Making a Change
@@ -141,7 +141,7 @@
 (“Changes” -> “Open”). This view is more similar to what you see on Github, when
 you navigate to the Pull Requests tab of the project/repository you are working
 on. Note, however, that a single Gerrit instance can host multiple projects
-(also referred to as repositories; a list can be found, for example, https://gerrit-review.googlesource.com/admin/repos[here]). Your
+(also referred to as repositories; a list can be found, for example, https://gerrit-review.googlesource.com/admin/repos[here,role=external,window=_blank]). Your
 dashboard and other lists of changes will show all changes across the
 projects/repositories by default.
 
@@ -191,7 +191,7 @@
 on. To do that, you might have to checkout the respective commit first if it is
 not at the tip of your local branch, for example if you stacked multiple changes
 on top of each other. Another common use case is to not have a local branch but
-to work in the so-called https://www.git-tower.com/learn/git/faq/detached-head-when-checkout-commit["detached HEAD"] mode. In that case you can use the
+to work in the so-called https://www.git-tower.com/learn/git/faq/detached-head-when-checkout-commit["detached HEAD",role=external,window=_blank] mode. In that case you can use the
 “Download” button on the files tab to copy a command that fetches and checks out
 the commit underlying your change. Make sure to select the latest patchset,
 though!
diff --git a/Documentation/intro-gerrit-walkthrough.txt b/Documentation/intro-gerrit-walkthrough.txt
index a631b47..15781c2 100644
--- a/Documentation/intro-gerrit-walkthrough.txt
+++ b/Documentation/intro-gerrit-walkthrough.txt
@@ -130,7 +130,7 @@
 while the *Verified* check is done by an automated build server, through a
 mechanism such as the
 link:https://wiki.jenkins-ci.org/display/JENKINS/Gerrit+Trigger[Gerrit Trigger
-Jenkins Plugin].
+Jenkins Plugin,role=external,window=_blank].
 
 IMPORTANT: The Code-Review and Verified checks require different permissions
 in Gerrit. This requirement allows teams to separate these tasks. For example,
@@ -252,7 +252,7 @@
 can add custom checks or even remove the Verified check entirely.
 
 Verification is typically an automated process using the
-link:https://wiki.jenkins-ci.org/display/JENKINS/Gerrit+Trigger[Gerrit Trigger Jenkins Plugin]
+link:https://wiki.jenkins-ci.org/display/JENKINS/Gerrit+Trigger[Gerrit Trigger Jenkins Plugin,role=external,window=_blank]
 or a similar mechanism. However, there are still times when a change requires
 manual verification, or a reviewer needs to check how or if a change works.
 To accommodate these and other similar circumstances, Gerrit exposes each change
diff --git a/Documentation/intro-project-owner.txt b/Documentation/intro-project-owner.txt
index 1f98291..e465376 100644
--- a/Documentation/intro-project-owner.txt
+++ b/Documentation/intro-project-owner.txt
@@ -181,7 +181,7 @@
 to be prefixed with `ldap/`.
 
 If the link:https://gerrit-review.googlesource.com/admin/repos/plugins/singleusergroup[
-singleusergroup] plugin is installed you can also directly assign
+singleusergroup,role=external,window=_blank] plugin is installed you can also directly assign
 access rights to users, by prefixing the username with `user/` or the
 user's account ID by `userid/`.
 
@@ -374,10 +374,10 @@
 systems. The most commonly used are:
 
 - link:https://wiki.jenkins-ci.org/display/JENKINS/Gerrit+Trigger[
-  Gerrit Trigger] plugin for link:http://jenkins-ci.org/[Jenkins]
+  Gerrit Trigger,role=external,window=_blank] plugin for link:http://jenkins-ci.org/[Jenkins,role=external,window=_blank]
 
 - link:http://www.mediawiki.org/wiki/Continuous_integration/Zuul[
-  Zuul] for link:http://jenkins-ci.org/[Jenkins]
+  Zuul,role=external,window=_blank] for link:http://jenkins-ci.org/[Jenkins,role=external,window=_blank]
 
 For the integration with the continuous integration system you must
 have a service user that is able to access Gerrit. To create a service
@@ -387,7 +387,7 @@
 a Gerrit administrator to create the service user.
 
 If the link:https://gerrit-review.googlesource.com/admin/repos/plugins/serviceuser[
-serviceuser] plugin is installed you can also create new service users
+serviceuser,role=external,window=_blank] plugin is installed you can also create new service users
 in the Gerrit Web UI under `People` > `Create Service User`. For this
 the `Create Service User` global capability must be assigned.
 
@@ -407,7 +407,7 @@
 
 Gerrit provides an
 link:https://gerrit-review.googlesource.com/Documentation/config-validation.html#new-commit-validation[
-extension point to do validation of new commits]. A Gerrit plugin
+extension point to do validation of new commits,role=external,window=_blank]. A Gerrit plugin
 implementing this extension point can perform validation checks when
 new commits are pushed to Gerrit. The plugin can either provide a
 message to the client or reject the commit and cause the push to fail.
@@ -415,13 +415,13 @@
 There are some plugins available that provide commit validation:
 
 - link:https://gerrit-review.googlesource.com/admin/repos/plugins/uploadvalidator[
-  uploadvalidator]:
+  uploadvalidator,role=external,window=_blank]:
 +
 The `uploadvalidator` plugin allows project owners to configure blocked
 file extensions, required footers and a maximum allowed path length.
 
 - link:https://gerrit-review.googlesource.com/admin/repos/plugins/commit-message-length-validator[
-  commit-message-length-validator]
+  commit-message-length-validator,role=external,window=_blank]
 +
 The `commit-message-length-validator` core plugin validates that commit
 messages conform to line length limits.
@@ -501,9 +501,9 @@
 - Issue Tracker System Plugins
 +
 There are Gerrit plugins for a tight integration with
-link:https://gerrit-review.googlesource.com//admin/repos/plugins/its-jira[Jira],
-link:https://gerrit-review.googlesource.com/admin/repos/plugins/its-bugzilla[Bugzilla] and
-link:https://gerrit-review.googlesource.com/admin/repos/plugins/its-rtc[IBM Rational Team Concert].
+link:https://gerrit-review.googlesource.com//admin/repos/plugins/its-jira[Jira,role=external,window=_blank],
+link:https://gerrit-review.googlesource.com/admin/repos/plugins/its-bugzilla[Bugzilla,role=external,window=_blank] and
+link:https://gerrit-review.googlesource.com/admin/repos/plugins/its-rtc[IBM Rational Team Concert,role=external,window=_blank].
 If installed, these plugins can e.g. be used to automatically add links
 to Gerrit changes to the issues in the issue tracker system or to
 automatically close an issue if the corresponding change is merged.
@@ -551,15 +551,15 @@
 Gerrit will then notify this person by email about the review request.
 
 With the link:https://gerrit-review.googlesource.com/admin/repos/plugins/reviewers[
-reviewers] plugin it is possible to configure default reviewers who
+reviewers,role=external,window=_blank] plugin it is possible to configure default reviewers who
 will be automatically added to each change. The default reviewers can
 be configured in the Gerrit Web UI under `Projects` > `List` >
 <your project> > `General` in the `reviewers Plugin` section.
 
 The link:https://gerrit-review.googlesource.com/admin/repos/plugins/reviewers-by-blame[
-reviewers-by-blame] plugin can automatically add reviewers to changes
+reviewers-by-blame,role=external,window=_blank] plugin can automatically add reviewers to changes
 based on the link:https://www.kernel.org/pub/software/scm/git/docs/git-blame.html[
-git blame] computation on the changed files. This means that the plugin
+git blame,role=external,window=_blank] computation on the changed files. This means that the plugin
 will add those users as reviewer that authored most of the lines
 touched by the change, since these users should be familiar with the
 code and can most likely review the change. How many reviewers the
@@ -578,7 +578,7 @@
 plugins:
 
 - link:https://gerrit-review.googlesource.com/admin/repos/plugins/download-commands[
-  download-commands] plugin:
+  download-commands,role=external,window=_blank] plugin:
 +
 The `download-commands` plugin provides the default download commands
 (`Checkout`, `Cherry Pick`, `Format Patch` and `Pull`).
@@ -587,14 +587,14 @@
 the change screen.
 
 - link:https://gerrit-review.googlesource.com/admin/repos/plugins/egit[
-  egit] plugin:
+  egit,role=external,window=_blank] plugin:
 +
 The `egit` plugin provides the change ref as a download command, which is
 needed for downloading a change from within
 link:https://www.eclipse.org/egit/[EGit].
 
 - link:https://gerrit-review.googlesource.com/admin/repos/plugins/project-download-commands[
-  project-download-commands] plugin:
+  project-download-commands,role=external,window=_blank] plugin:
 +
 The `project-download-commands` plugin enables project owners to
 configure project-specific download commands. For example, a
@@ -678,14 +678,14 @@
 contains this history. If your existing codebase is in another VCS you
 must migrate it to Git first. For Subversion you can use the
 link:http://git-scm.com/book/en/Git-and-Other-Systems-Git-and-Subversion[
-git svn] command as described in the
+git svn,role=external,window=_blank] command as described in the
 link:http://git-scm.com/book/en/Git-and-Other-Systems-Migrating-to-Git#Subversion[
-Subversion migration guide]. An importer for Perforce is available in
+Subversion migration guide,role=external,window=_blank]. An importer for Perforce is available in
 the `contrib` section of the Git source code; how to use
-link:http://git-scm.com/docs/git-p4[git p4] to do the import from
+link:http://git-scm.com/docs/git-p4[git p4,role=external,window=_blank] to do the import from
 Perforce is described in the
 link:http://git-scm.com/book/en/Git-and-Other-Systems-Migrating-to-Git#Perforce[
-Perforce migration guide].
+Perforce migration guide,role=external,window=_blank].
 
 To import an existing history into a Gerrit project you bypass code
 review and push it directly to `refs/heads/<branch>`. For this you must
@@ -699,7 +699,7 @@
 link:access-control.html#category_forge_committer[Forge Committer]
 access right globally. In this case you must use the
 link:https://www.kernel.org/pub/software/scm/git/docs/git-filter-branch.html[
-git filter-branch] command to rewrite the committer information for all
+git filter-branch,role=external,window=_blank] command to rewrite the committer information for all
 commits (the author information that records who was writing the code
 stays intact; signed tags will lose their signature):
 
@@ -741,7 +741,7 @@
 Gerrit core does not support the deletion of projects.
 
 If the link:https://gerrit-review.googlesource.com/admin/repos/plugins/delete-project[
-delete-project] plugin is installed, projects can be deleted from the
+delete-project,role=external,window=_blank] plugin is installed, projects can be deleted from the
 Gerrit Web UI under `Projects` > `List` > <project> > `General` by
 clicking on the `Delete` command under `Project Commands`. The `Delete`
 command is only available if you have the `Delete Projects` global
@@ -769,7 +769,7 @@
 history (changes, review comments) is lost.
 
 Alternatively, you can use the
-link:https://gerrit.googlesource.com/plugins/importer/[importer] plugin
+link:https://gerrit.googlesource.com/plugins/importer/[importer,role=external,window=_blank] plugin
 to copy the project _including the review history_, and then
 link:#project-deletion[delete the old project].
 
diff --git a/Documentation/intro-quick.txt b/Documentation/intro-quick.txt
index 11d5052..a8ebdc6 100644
--- a/Documentation/intro-quick.txt
+++ b/Documentation/intro-quick.txt
@@ -1,7 +1,7 @@
 = Gerrit Code Review Product Overview
 
 Gerrit Code Review is a web-based code review tool built on
-https://git-scm.com/[Git version control].
+https://git-scm.com/[Git version control,role=external,window=_blank].
 
 == What is Gerrit Code Review?
 
@@ -45,7 +45,7 @@
 
 . link:intro-user.html[User Guide]
 . link:intro-project-owner.html[Project Owner Guide]
-. link:https://source.android.com/source/life-of-a-patch[Default Android Workflow] (external)
+. link:https://source.android.com/source/life-of-a-patch[Default Android Workflow,role=external,window=_blank] (external)
 
 GERRIT
 ------
diff --git a/Documentation/intro-rockstar.txt b/Documentation/intro-rockstar.txt
index 0b67950..025e024 100644
--- a/Documentation/intro-rockstar.txt
+++ b/Documentation/intro-rockstar.txt
@@ -42,7 +42,7 @@
 
 * git cherry-pick
 
-* link:https://www.kernel.org/pub/software/scm/git/docs/git-bisect-lk2009.html[git bisect]
+* link:https://www.kernel.org/pub/software/scm/git/docs/git-bisect-lk2009.html[git bisect,role=external,window=_blank]
 
 
 [[amending]]
@@ -59,8 +59,8 @@
 
 At least two well-known open source projects insist on these practices:
 
-* link:http://git-scm.com/[Git]
-* link:http://www.kernel.org/category/about.html[Linux Kernel]
+* link:http://git-scm.com/[Git,role=external,window=_blank]
+* link:http://www.kernel.org/category/about.html[Linux Kernel,role=external,window=_blank]
 
 However, contributors to these projects don’t refine and polish their changes
 in private until they’re perfect. Instead, polishing code is part of a review
diff --git a/Documentation/intro-user.txt b/Documentation/intro-user.txt
index 16929ae..28b5c09 100644
--- a/Documentation/intro-user.txt
+++ b/Documentation/intro-user.txt
@@ -4,7 +4,7 @@
 explains the standard Gerrit workflows and how a user can adapt Gerrit
 to personal preferences.
 
-It is expected that readers know about link:http://git-scm.com/[Git]
+It is expected that readers know about link:http://git-scm.com/[Git,role=external,window=_blank]
 and that they are familiar with basic git commands and workflows.
 
 [[gerrit]]
@@ -21,20 +21,20 @@
 
 Gerrit uses the git protocol. This means in order to work with Gerrit
 you do *not* need to install any Gerrit client, but having a regular
-git client, such as the link:http://git-scm.com/[git command line] or
-link:http://eclipse.org/egit/[EGit] in Eclipse, is sufficient.
+git client, such as the link:http://git-scm.com/[git command line,role=external,window=_blank] or
+link:http://eclipse.org/egit/[EGit,role=external,window=_blank] in Eclipse, is sufficient.
 
 Still there are some client-side tools for Gerrit, which can be used
 optionally:
 
-* link:http://eclipse.org/mylyn/[Mylyn Gerrit Connector]: Gerrit
+* link:http://eclipse.org/mylyn/[Mylyn Gerrit Connector,role=external,window=_blank]: Gerrit
   integration with Mylyn
 * link:https://github.com/uwolfer/gerrit-intellij-plugin[Gerrit
-  IntelliJ Plugin]: Gerrit integration with the
-  link:http://www.jetbrains.com/idea/[IntelliJ Platform]
+  IntelliJ Plugin,role=external,window=_blank]: Gerrit integration with the
+  link:http://www.jetbrains.com/idea/[IntelliJ Platform,role=external,window=_blank]
 * link:https://play.google.com/store/apps/details?id=com.jbirdvegas.mgerrit[
-  mGerrit]: Android client for Gerrit
-* link:https://github.com/stackforge/gertty[Gertty]: Console-based
+  mGerrit,role=external,window=_blank]: Android client for Gerrit
+* link:https://github.com/stackforge/gertty[Gertty,role=external,window=_blank]: Console-based
   interface for Gerrit
 
 [[clone]]
@@ -209,7 +209,7 @@
 Instead of manually installing the `commit-msg` hook for each git
 repository, you can copy it into the
 link:http://git-scm.com/docs/git-init#_template_directory[git template
-directory]. Then it is automatically copied to every newly cloned
+directory,role=external,window=_blank]. Then it is automatically copied to every newly cloned
 repository.
 
 [[review-change]]
diff --git a/Documentation/linux-quickstart.txt b/Documentation/linux-quickstart.txt
index 0d8848e..17a0777 100644
--- a/Documentation/linux-quickstart.txt
+++ b/Documentation/linux-quickstart.txt
@@ -29,7 +29,7 @@
 . Download the desired Gerrit archive.
 
 To view previous archives, see
-link:https://gerrit-releases.storage.googleapis.com/index.html[Gerrit Code Review: Releases]. The steps below install Gerrit 3.0.3:
+link:https://gerrit-releases.storage.googleapis.com/index.html[Gerrit Code Review: Releases,role=external,window=_blank]. The steps below install Gerrit 3.0.3:
 
 ....
 wget https://gerrit-releases.storage.googleapis.com/gerrit-3.0.3.war
diff --git a/Documentation/note-db.txt b/Documentation/note-db.txt
index 8725cee..94e1c5d 100644
--- a/Documentation/note-db.txt
+++ b/Documentation/note-db.txt
@@ -36,7 +36,7 @@
   the upgrade process by running `gerrit.war init`
 - Account, group and change metadata on the servers behind `googlesource.com` is fully
   migrated to NoteDb. In other words, if you use
-  link:https://gerrit-review.googlesource.com/[gerrit-review], you're already
+  link:https://gerrit-review.googlesource.com/[gerrit-review,role=external,window=_blank], you're already
   using NoteDb.
 - NoteDb is the only database format supported by Gerrit 3.0. The change data
   migration tools are only included in Gerrit 2.15 and 2.16; they are not
@@ -197,5 +197,5 @@
 
 In case of rollback from NoteDB to ReviewDB, all the meta refs and the
 sequence ref need to be removed.
-The [remove-notedb-refs.sh](https://gerrit.googlesource.com/gerrit/+/refs/heads/master/contrib/remove-notedb-refs.sh)
+The [remove-notedb-refs.sh,role=external,window=_blank](https://gerrit.googlesource.com/gerrit/+/refs/heads/master/contrib/remove-notedb-refs.sh)
 script has been written to automate this process.
diff --git a/Documentation/pg-plugin-dev.txt b/Documentation/pg-plugin-dev.txt
index d901851..fee3be9 100644
--- a/Documentation/pg-plugin-dev.txt
+++ b/Documentation/pg-plugin-dev.txt
@@ -2,7 +2,7 @@
 
 CAUTION: Work in progress. Hard hat area. Please
 link:https://bugs.chromium.org/p/gerrit/issues/entry?template=PolyGerrit%20plugins[send
-feedback] if something's not right.
+feedback,role=external,window=_blank] if something's not right.
 
 For migrating existing GWT UI plugins, please check out the
 link:pg-plugin-migration.html#migration[migration guide].
@@ -11,7 +11,7 @@
 == Plugin loading and initialization
 
 link:js-api.html#_entry_point[Entry point] for the plugin and the loading method
-is based on link:http://w3c.github.io/webcomponents/spec/imports/[HTML Imports]
+is based on link:http://w3c.github.io/webcomponents/spec/imports/[HTML Imports,role=external,window=_blank]
 spec.
 
 * The plugin provides pluginname.html, and can be a standalone file or a static
@@ -103,7 +103,7 @@
 
 A plugin may provide Polymer's
 https://www.polymer-project.org/2.0/docs/devguide/style-shadow-dom#style-modules[style
-modules] to style individual endpoints using
+modules,role=external,window=_blank] to style individual endpoints using
 `plugin.registerStyleModule(endpointName, moduleName)`. A style must be defined
 as a standalone `<dom-module>` defined in the same .html file.
 
@@ -152,7 +152,7 @@
 
 Alternative for
 link:https://www.polymer-project.org/1.0/docs/devguide/data-binding[Polymer data
-binding] for plugins that don't use Polymer. Can be used to bind element
+binding,role=external,window=_blank] for plugins that don't use Polymer. Can be used to bind element
 attribute changes to callbacks.
 
 See `samples/bind-parameters.html` for examples on both Polymer data bindings
diff --git a/Documentation/pg-plugin-migration.txt b/Documentation/pg-plugin-migration.txt
index 3ddceed..1b1861d 100644
--- a/Documentation/pg-plugin-migration.txt
+++ b/Documentation/pg-plugin-migration.txt
@@ -2,7 +2,7 @@
 
 CAUTION: Work in progress. Hard hat area. Please
 link:https://bugs.chromium.org/p/gerrit/issues/entry?template=PolyGerrit%20plugins[send
-feedback] if something's not right.
+feedback,role=external,window=_blank] if something's not right.
 
 [[migration]]
 == Incremental migration of existing GWT UI plugins
diff --git a/Documentation/pg-plugin-styling.txt b/Documentation/pg-plugin-styling.txt
index 2453bad..0d5e1d1 100644
--- a/Documentation/pg-plugin-styling.txt
+++ b/Documentation/pg-plugin-styling.txt
@@ -4,17 +4,17 @@
 
 Plugins may provide
 link:https://www.polymer-project.org/2.0/docs/devguide/style-shadow-dom#style-modules[Polymer
-style modules] for UI CSS-based customization.
+style modules,role=external,window=_blank] for UI CSS-based customization.
 
 PolyGerrit UI implements number of styling endpoints, which apply CSS mixins
-link:https://tabatkins.github.io/specs/css-apply-rule/[using @apply] to its
+link:https://tabatkins.github.io/specs/css-apply-rule/[using @apply,role=external,window=_blank] to its
 direct contents.
 
 NOTE: Only items (i.e. CSS properties and mixin targets) documented here are
 guaranteed to work in the long term, since they are covered by integration
 tests. + When there is a need to add new property or endpoint, please
 link:https://bugs.chromium.org/p/gerrit/issues/entry?template=PolyGerrit%20Issue[file
-a bug] stating your use case to track and maintain for future releases.
+a bug,role=external,window=_blank] stating your use case to track and maintain for future releases.
 
 Plugins should be html-based and imported following PolyGerrit's
 link:pg-plugin-dev.html#loading[dev guide].
@@ -64,7 +64,7 @@
 
 Following CSS properties have
 link:https://gerrit.googlesource.com/gerrit/+/master/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata-it_test.html[long-term
-support via integration test]:
+support via integration test,role=external,window=_blank]:
 
 * `display`
 +
diff --git a/Documentation/pgm-MigrateAccountPatchReviewDb.txt b/Documentation/pgm-MigrateAccountPatchReviewDb.txt
index c8ab193..597e337 100644
--- a/Documentation/pgm-MigrateAccountPatchReviewDb.txt
+++ b/Documentation/pgm-MigrateAccountPatchReviewDb.txt
@@ -30,7 +30,7 @@
 [NOTE]
 When using MySQL, the file_name column length in the account_patch_reviews table will be shortened
 from the standard 4096 characters down to 255 characters. This is due to a
-link:https://dev.mysql.com/doc/refman/5.7/en/innodb-restrictions.html[MySQL limitation]
+link:https://dev.mysql.com/doc/refman/5.7/en/innodb-restrictions.html[MySQL limitation,role=external,window=_blank]
 on the max size of 767 bytes for each column in an index.
 
 == OPTIONS
diff --git a/Documentation/prolog-cookbook.txt b/Documentation/prolog-cookbook.txt
index f291920..b2fc526 100644
--- a/Documentation/prolog-cookbook.txt
+++ b/Documentation/prolog-cookbook.txt
@@ -23,10 +23,10 @@
 link:config-gerrit.html#_a_id_rules_a_section_rules[rules section])
 
 link:https://groups.google.com/d/topic/repo-discuss/wJxTGhlHZMM/discussion[This
-discussion thread] explains why Prolog was chosen for the purpose of writing
+discussion thread,role=external,window=_blank] explains why Prolog was chosen for the purpose of writing
 project specific submit rules.
 link:http://gerrit-documentation.googlecode.com/svn/ReleaseNotes/ReleaseNotes-2.2.2.html[Gerrit
-2.2.2 ReleaseNotes] introduces Prolog support in Gerrit.
+2.2.2 ReleaseNotes,role=external,window=_blank] introduces Prolog support in Gerrit.
 
 [[SubmitType]]
 == Submit Type
@@ -58,14 +58,14 @@
 
 == Prolog Language
 This document is not a complete Prolog tutorial.
-link:http://en.wikipedia.org/wiki/Prolog[This Wikipedia page on Prolog] is a
+link:http://en.wikipedia.org/wiki/Prolog[This Wikipedia page on Prolog,role=external,window=_blank] is a
 good starting point for learning the Prolog language. This document will only
 explain some elements of Prolog that are necessary to understand the provided
 examples.
 
 == Prolog in Gerrit
-Gerrit uses its own link:https://gerrit.googlesource.com/prolog-cafe/[fork] of the
-original link:http://kaminari.istc.kobe-u.ac.jp/PrologCafe/[prolog-cafe]
+Gerrit uses its own link:https://gerrit.googlesource.com/prolog-cafe/[fork,role=external,window=_blank] of the
+original link:http://kaminari.istc.kobe-u.ac.jp/PrologCafe/[prolog-cafe,role=external,window=_blank]
 project. Gerrit embeds the prolog-cafe library and can interpret Prolog programs
 at runtime.
 
@@ -75,7 +75,7 @@
 Prolog interpreter shell.
 
 For batch or unit tests, see the examples in Gerrit source directory
-link:https://gerrit.googlesource.com/gerrit/+/refs/heads/master/prologtests/examples/[prologtests/examples].
+link:https://gerrit.googlesource.com/gerrit/+/refs/heads/master/prologtests/examples/[prologtests/examples,role=external,window=_blank].
 
 [NOTE]
 The interactive shell is just a prolog shell, it does not load
@@ -84,7 +84,7 @@
 
 == SWI-Prolog
 Instead of using the link:pgm-prolog-shell.html[prolog-shell] program one can
-also use the link:http://www.swi-prolog.org/[SWI-Prolog] environment. It
+also use the link:http://www.swi-prolog.org/[SWI-Prolog,role=external,window=_blank] environment. It
 provides a better shell interface and a graphical source-level debugger.
 
 [[RulesFile]]
diff --git a/Documentation/quota.txt b/Documentation/quota.txt
index a647e33..b2e6380 100644
--- a/Documentation/quota.txt
+++ b/Documentation/quota.txt
@@ -3,7 +3,7 @@
 Gerrit does not provide out of the box quota enforcement. However, it does
 support an extension mechanism for plugins to hook into to provide this
 functionality. The most prominent plugin is the
-link:https://gerrit.googlesource.com/plugins/quota/[Quota Plugin].
+link:https://gerrit.googlesource.com/plugins/quota/[Quota Plugin,role=external,window=_blank].
 
 This documentation is intended to be read by plugin developers. It contains all
 quota requests implemented in Gerrit-core as well as the metadata that they have
diff --git a/Documentation/rest-api-accounts.txt b/Documentation/rest-api-accounts.txt
index 85cdace..fed5c0b 100644
--- a/Documentation/rest-api-accounts.txt
+++ b/Documentation/rest-api-accounts.txt
@@ -2661,7 +2661,7 @@
 |`id`         |Not set in map context|The 8-char hex GPG key ID.
 |`fingerprint`|Not set for deleted keys|The 40-char (plus spaces) hex GPG key fingerprint.
 |`user_ids`   |Not set for deleted keys|
-link:https://tools.ietf.org/html/rfc4880#section-5.11[OpenPGP User IDs]
+link:https://tools.ietf.org/html/rfc4880#section-5.11[OpenPGP User IDs,role=external,window=_blank]
 associated with the public key.
 |`key`        |Not set for deleted keys|ASCII armored public key material.
 |`status`     |Not set for deleted keys|
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 0fc733a..084f6af 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -2356,7 +2356,7 @@
 A message can be specified in the request body inside a
 link:#private-input[PrivateInput] entity. Historically, this method allowed
 a body in the DELETE, but that behavior is
-link:https://www.gerritcodereview.com/releases/2.16.md[deprecated].
+link:https://www.gerritcodereview.com/releases/2.16.md[deprecated,role=external,window=_blank].
 In this case, use a POST request instead:
 
 .Request
@@ -3342,7 +3342,7 @@
 Options can be provided in the request body as a
 link:#delete-reviewer-input[DeleteReviewerInput] entity.
 Historically, this method allowed a body in the DELETE, but that behavior is
-link:https://www.gerritcodereview.com/releases/2.16.md[deprecated].
+link:https://www.gerritcodereview.com/releases/2.16.md[deprecated,role=external,window=_blank].
 In this case, use a POST request instead:
 
 .Request
@@ -3422,7 +3422,7 @@
 Options can be provided in the request body as a
 link:#delete-vote-input[DeleteVoteInput] entity.
 Historically, this method allowed a body in the DELETE, but that behavior is
-link:https://www.gerritcodereview.com/releases/2.16.md[deprecated].
+link:https://www.gerritcodereview.com/releases/2.16.md[deprecated,role=external,window=_blank].
 In this case, use a POST request instead:
 
 .Request
@@ -4795,7 +4795,7 @@
 Deletion reason can be provided in the request body as a
 link:#delete-comment-input[DeleteCommentInput] entity.
 Historically, this method allowed a body in the DELETE, but that behavior is
-link:https://www.gerritcodereview.com/releases/2.16.md[deprecated].
+link:https://www.gerritcodereview.com/releases/2.16.md[deprecated,role=external,window=_blank].
 In this case, use a POST request instead:
 
 .Request
@@ -5615,7 +5615,7 @@
 Options can be provided in the request body as a
 link:#delete-vote-input[DeleteVoteInput] entity.
 Historically, this method allowed a body in the DELETE, but that behavior is
-link:https://www.gerritcodereview.com/releases/2.16.md[deprecated].
+link:https://www.gerritcodereview.com/releases/2.16.md[deprecated,role=external,window=_blank].
 In this case, use a POST request instead:
 
 .Request
@@ -6017,6 +6017,10 @@
 The numeric Change-Id of the change that this change reverts.
 |`submission_id`      |optional|
 ID of the submission of this change. Only set if the status is `MERGED`.
+This ID is equal to the numeric ID of the change that triggered the submission.
+If the change that triggered the submission also has a topic, it will be
+"<id>-<topic>" of the change that triggered the submission.
+The callers must not rely on the format of the submission ID.
 |==================================
 
 [[change-input]]
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index c373b96..5a2ec47 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -610,7 +610,7 @@
 A commit message can be provided in the request body as a
 link:#project-description-input[ProjectDescriptionInput] entity.
 Historically, this method allowed a body in the DELETE, but that behavior is
-link:https://www.gerritcodereview.com/releases/2.16.md[deprecated].
+link:https://www.gerritcodereview.com/releases/2.16.md[deprecated,role=external,window=_blank].
 In this case, use link:#set-project-description[PUT] instead.
 
 .Request
diff --git a/Documentation/rest-api.txt b/Documentation/rest-api.txt
index a8ab353..899e291 100644
--- a/Documentation/rest-api.txt
+++ b/Documentation/rest-api.txt
@@ -128,7 +128,7 @@
 === Response Codes
 The Gerrit REST endpoints use HTTP status codes as described
 in the link:http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html[
-HTTP specification].
+HTTP specification,role=external,window=_blank].
 
 In most cases, the response body of an error response will be a
 plaintext, human-readable error message.
diff --git a/Documentation/user-inline-edit.txt b/Documentation/user-inline-edit.txt
index 1f5e195..b872fc9 100644
--- a/Documentation/user-inline-edit.txt
+++ b/Documentation/user-inline-edit.txt
@@ -16,7 +16,7 @@
 
 To create a change in the Gerrit web interface:
 
-. From the link:http://gerrit-review.googlesource.com[Gerrit Code Review]
+. From the link:http://gerrit-review.googlesource.com[Gerrit Code Review,role=external,window=_blank]
   dashboard, select Browse > Repositories.
 
 . Under Repository Name, click the name of the repository you want to work
diff --git a/Documentation/user-notify.txt b/Documentation/user-notify.txt
index 3c922ed..5346b2e 100644
--- a/Documentation/user-notify.txt
+++ b/Documentation/user-notify.txt
@@ -125,6 +125,7 @@
 should be sent to the emails named in this section. Within a Git-style
 configuration file double quotes around complex operator values may
 need to be escaped, e.g. `filter = branch:\"^(maint|stable)-.*\"`.
+Single quotes are illegal and must be omitted.
 
 When sending email to a bare email address in a notify block, Gerrit
 Code Review ignores read access controls and assumes the administrator
diff --git a/Documentation/user-request-tracing.txt b/Documentation/user-request-tracing.txt
index b26f4c1..356d0c076 100644
--- a/Documentation/user-request-tracing.txt
+++ b/Documentation/user-request-tracing.txt
@@ -75,7 +75,7 @@
 [[auto-retry-succeeded]]
 If an auto-retry succeeds you may consider filing this as
 link:https://bugs.chromium.org/p/gerrit/issues/entry?template=GoogleSource+Issue[
-Gerrit issue] so that the Gerrit developers can fix this and treat this
+Gerrit issue,role=external,window=_blank] so that the Gerrit developers can fix this and treat this
 exception as recoverable.
 
 The trace IDs for auto-retries are generated and start with
diff --git a/Documentation/user-review-ui.txt b/Documentation/user-review-ui.txt
index de17c00..f1a9fa8 100644
--- a/Documentation/user-review-ui.txt
+++ b/Documentation/user-review-ui.txt
@@ -432,7 +432,7 @@
 The available download commands depend on the installed Gerrit plugins.
 The most popular plugin for download commands, the
 link:https://gerrit-review.googlesource.com/admin/repos/plugins/download-commands[
-download-commands] plugin, provides commands to checkout, pull and
+download-commands,role=external,window=_blank] plugin, provides commands to checkout, pull and
 cherry-pick a patch set.
 
 Each command has a copy-to-clipboard icon that allows the command to be
@@ -943,7 +943,7 @@
 contain a match and one navigates to it.
 
 For additional possibilities to search please check the
-link:http://www.vim.org/docs.php[Vim documentation]. There are other
+link:http://www.vim.org/docs.php[Vim documentation,role=external,window=_blank]. There are other
 useful ways to search, e.g. while the cursor is on a word, pressing `*`
 or `#` searches for the next or previous occurrence of the word.
 
@@ -962,7 +962,7 @@
 - `gg` / `G` moves to cursor to the start / end of the file
 - `Ctrl-D` / `Ctrl-U` scrolls downwards / upwards
 
-Please check the link:http://www.vim.org/docs.php[Vim documentation]
+Please check the link:http://www.vim.org/docs.php[Vim documentation,role=external,window=_blank]
 for further information.
 
 [[diff-preferences]]
diff --git a/Documentation/user-search.txt b/Documentation/user-search.txt
index bde9508..cab95c7 100644
--- a/Documentation/user-search.txt
+++ b/Documentation/user-search.txt
@@ -161,7 +161,7 @@
 Changes occurring in 'PROJECT'. If 'PROJECT' starts with `^` it
 matches project names by regular expression.  The
 link:http://www.brics.dk/automaton/[dk.brics.automaton
-library] is used for evaluation of such patterns.
+library,role=external,window=_blank] is used for evaluation of such patterns.
 
 [[projects]]
 projects:'PREFIX'::
@@ -180,7 +180,7 @@
 Changes occurring in 'REPOSITORY'. If 'REPOSITORY' starts with `^` it
 matches repository names by regular expression.  The
 link:http://www.brics.dk/automaton/[dk.brics.automaton
-library] is used for evaluation of such patterns.
+library,role=external,window=_blank] is used for evaluation of such patterns.
 
 [[repositories]]
 repositories:'PREFIX', repos:'PREFIX'::
@@ -203,7 +203,7 @@
 If 'BRANCH' starts with `^` it matches branch names by regular
 expression patterns.  The
 link:http://www.brics.dk/automaton/[dk.brics.automaton
-library] is used for evaluation of such patterns.
+library,role=external,window=_blank] is used for evaluation of such patterns.
 
 [[intopic]]
 intopic:'TOPIC'::
@@ -213,7 +213,7 @@
 If 'TOPIC' starts with `^` it matches topic names by regular
 expression patterns.  The
 link:http://www.brics.dk/automaton/[dk.brics.automaton
-library] is used for evaluation of such patterns.
+library,role=external,window=_blank] is used for evaluation of such patterns.
 
 [[topic]]
 topic:'TOPIC'::
@@ -238,7 +238,7 @@
 If 'REF' starts with `^` it matches reference names by regular
 expression patterns.  The
 link:http://www.brics.dk/automaton/[dk.brics.automaton
-library] is used for evaluation of such patterns.
+library,role=external,window=_blank] is used for evaluation of such patterns.
 
 [[tr,bug]]
 tr:'ID', bug:'ID'::
@@ -273,7 +273,7 @@
 Matches any change touching file at 'PATH'. By default exact path
 matching is used, but regular expressions can be enabled by starting
 with `^`.  For example, to match all XML files use `file:^.*\.xml$`.
-The link:http://www.brics.dk/automaton/[dk.brics.automaton library]
+The link:http://www.brics.dk/automaton/[dk.brics.automaton library,role=external,window=_blank]
 is used for the evaluation of such patterns.
 +
 The `^` required at the beginning of the regular expression not only
@@ -332,7 +332,7 @@
 +
 If 'DIR' starts with `^` it matches directories and directory segments by
 regular expression. The link:http://www.brics.dk/automaton/[dk.brics.automaton
-library] is used for evaluation of such patterns.
+library,role=external,window=_blank] is used for evaluation of such patterns.
 
 [[footer-operator]]
 footer:'FOOTER'::
diff --git a/Documentation/user-signedoffby.txt b/Documentation/user-signedoffby.txt
index 507f4f2..c52c18c 100644
--- a/Documentation/user-signedoffby.txt
+++ b/Documentation/user-signedoffby.txt
@@ -1,7 +1,7 @@
 = Gerrit Code Review - Signed-off-by Lines
 
 [NOTE]
-This document was literally taken from link:http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=blob;f=Documentation/SubmittingPatches;hb=4e8a2372f9255a1464ef488ed925455f53fbdaa1[linux-2.6 Documentation/SubmittingPatches]
+This document was literally taken from link:http://git.kernel.org/?p=linux/kernel/git/torvalds/linux-2.6.git;a=blob;f=Documentation/SubmittingPatches;hb=4e8a2372f9255a1464ef488ed925455f53fbdaa1[linux-2.6 Documentation/SubmittingPatches,role=external,window=_blank]
 and is covered by the GPLv2.
 
 [[Signed-off-by]]
diff --git a/Documentation/user-upload.txt b/Documentation/user-upload.txt
index 6cf5587..4eb59640 100644
--- a/Documentation/user-upload.txt
+++ b/Documentation/user-upload.txt
@@ -618,7 +618,7 @@
 
 repo is a multiple repository management tool, most commonly
 used by the Android Open Source Project.  For more details, see
-link:http://source.android.com/source/using-repo.html[using repo].
+link:http://source.android.com/source/using-repo.html[using repo,role=external,window=_blank].
 
 [[repo_create]]
 === Create Changes
diff --git a/java/com/google/gerrit/entities/SubmissionId.java b/java/com/google/gerrit/entities/SubmissionId.java
new file mode 100644
index 0000000..eb03a5a
--- /dev/null
+++ b/java/com/google/gerrit/entities/SubmissionId.java
@@ -0,0 +1,34 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.entities;
+
+import org.eclipse.jgit.annotations.Nullable;
+
+public class SubmissionId {
+  private final String submissionId;
+
+  public SubmissionId(Change.Id changeId, @Nullable String topic) {
+    submissionId = topic != null ? String.format("%s-%s", changeId, topic) : changeId.toString();
+  }
+
+  public SubmissionId(Change change) {
+    this(change.getId(), change.getTopic());
+  }
+
+  @Override
+  public String toString() {
+    return submissionId;
+  }
+}
diff --git a/java/com/google/gerrit/pgm/Daemon.java b/java/com/google/gerrit/pgm/Daemon.java
index 568fb60..da8411f 100644
--- a/java/com/google/gerrit/pgm/Daemon.java
+++ b/java/com/google/gerrit/pgm/Daemon.java
@@ -538,14 +538,14 @@
 
   private Injector createWebInjector() {
     final List<Module> modules = new ArrayList<>();
-    if (sshd) {
-      modules.add(new ProjectQoSFilter.Module());
-    }
     modules.add(RequestContextFilter.module());
     modules.add(RequestMetricsFilter.module());
     modules.add(H2CacheBasedWebSession.module());
     modules.add(sysInjector.getInstance(GerritAuthModule.class));
     modules.add(sysInjector.getInstance(GitOverHttpModule.class));
+    if (sshd) {
+      modules.add(new ProjectQoSFilter.Module());
+    }
     modules.add(AllRequestFilter.module());
     modules.add(sysInjector.getInstance(WebModule.class));
     modules.add(sysInjector.getInstance(RequireSslFilter.Module.class));
diff --git a/java/com/google/gerrit/prettify/BUILD b/java/com/google/gerrit/prettify/BUILD
index 7c1241a..0a15fda 100644
--- a/java/com/google/gerrit/prettify/BUILD
+++ b/java/com/google/gerrit/prettify/BUILD
@@ -7,5 +7,7 @@
     deps = [
         "//lib:guava",
         "//lib:jgit",
+        "//lib/auto:auto-value",
+        "//lib/auto:auto-value-annotations",
     ],
 )
diff --git a/java/com/google/gerrit/prettify/common/SparseFileContent.java b/java/com/google/gerrit/prettify/common/SparseFileContent.java
index 348f9b2..1249b65 100644
--- a/java/com/google/gerrit/prettify/common/SparseFileContent.java
+++ b/java/com/google/gerrit/prettify/common/SparseFileContent.java
@@ -14,160 +14,175 @@
 
 package com.google.gerrit.prettify.common;
 
-import java.util.ArrayList;
-import java.util.List;
+import com.google.auto.value.AutoValue;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
 
-public class SparseFileContent {
-  protected List<Range> ranges;
-  protected int size;
+/**
+ * A class to store subset of a file's lines in a memory efficient way. Internally, it stores lines
+ * as a list of ranges. Each range represents continuous set of lines and has information about line
+ * numbers in original file (zero-based).
+ *
+ * <p>{@link SparseFileContent.Accessor} must be used to work with the stored content.
+ */
+@AutoValue
+public abstract class SparseFileContent {
+  abstract ImmutableList<Range> getRanges();
 
-  private transient int currentRangeIdx;
+  public abstract int getSize();
 
-  public SparseFileContent() {
-    ranges = new ArrayList<>();
+  public static SparseFileContent create(ImmutableList<Range> ranges, int size) {
+    return new AutoValue_SparseFileContent(ranges, size);
   }
 
-  public int size() {
-    return size;
+  @VisibleForTesting
+  public int getRangesCount() {
+    return getRanges().size();
   }
 
-  public void setSize(int s) {
-    size = s;
+  public Accessor createAccessor() {
+    return new Accessor(this);
   }
 
-  public String get(int idx) {
-    final String line = getLine(idx);
-    if (line == null) {
-      throw new ArrayIndexOutOfBoundsException(idx);
-    }
-    return line;
-  }
+  /**
+   * Provide a methods to work with the content of a {@link SparseFileContent}.
+   *
+   * <p>The class hides internal representation of a {@link SparseFileContent} and provides
+   * convenient way for accessing a content.
+   */
+  public static class Accessor {
+    private final SparseFileContent content;
+    private int currentRangeIdx;
 
-  public boolean contains(int idx) {
-    return getLine(idx) != null;
-  }
-
-  public int first() {
-    return ranges.isEmpty() ? size() : ranges.get(0).base;
-  }
-
-  public int next(int idx) {
-    // Most requests are sequential in nature, fetching the next
-    // line from the current range, or the immediate next range.
-    //
-    int high = ranges.size();
-    if (currentRangeIdx < high) {
-      Range cur = ranges.get(currentRangeIdx);
-      if (cur.contains(idx + 1)) {
-        return idx + 1;
-      }
-
-      if (++currentRangeIdx < high) {
-        // Its not plus one, its the base of the next range.
-        //
-        return ranges.get(currentRangeIdx).base;
-      }
+    private Accessor(SparseFileContent content) {
+      this.content = content;
     }
 
-    // Binary search for the current value, since we know its a sorted list.
-    //
-    int low = 0;
-    do {
-      final int mid = (low + high) / 2;
-      final Range cur = ranges.get(mid);
+    public String get(int idx) {
+      final String line = getLine(idx);
+      if (line == null) {
+        throw new ArrayIndexOutOfBoundsException(idx);
+      }
+      return line;
+    }
 
-      if (cur.contains(idx)) {
+    public int getSize() {
+      return content.getSize();
+    }
+
+    public boolean contains(int idx) {
+      return getLine(idx) != null;
+    }
+
+    public int first() {
+      return content.getRanges().isEmpty() ? getSize() : content.getRanges().get(0).getBase();
+    }
+
+    public int next(int idx) {
+      // Most requests are sequential in nature, fetching the next
+      // line from the current range, or the immediate next range.
+      //
+      ImmutableList<Range> ranges = content.getRanges();
+      int high = ranges.size();
+      if (currentRangeIdx < high) {
+        Range cur = ranges.get(currentRangeIdx);
         if (cur.contains(idx + 1)) {
-          // Trivial plus one case above failed due to wrong currentRangeIdx.
-          // Reset the cache so we don't miss in the future.
-          //
-          currentRangeIdx = mid;
           return idx + 1;
         }
 
-        if (mid + 1 < ranges.size()) {
-          // Its the base of the next range.
-          currentRangeIdx = mid + 1;
-          return ranges.get(currentRangeIdx).base;
-        }
-
-        // No more lines in the file.
-        //
-        return size();
-      }
-
-      if (idx < cur.base) {
-        high = mid;
-      } else {
-        low = mid + 1;
-      }
-    } while (low < high);
-
-    return size();
-  }
-
-  private String getLine(int idx) {
-    // Most requests are sequential in nature, fetching the next
-    // line from the current range, or the next range.
-    //
-    int high = ranges.size();
-    if (currentRangeIdx < high) {
-      Range cur = ranges.get(currentRangeIdx);
-      if (cur.contains(idx)) {
-        return cur.get(idx);
-      }
-
-      if (++currentRangeIdx < high) {
-        final Range next = ranges.get(currentRangeIdx);
-        if (next.contains(idx)) {
-          return next.get(idx);
+        if (++currentRangeIdx < high) {
+          // Its not plus one, its the base of the next range.
+          //
+          return ranges.get(currentRangeIdx).getBase();
         }
       }
+
+      // Binary search for the current value, since we know its a sorted list.
+      //
+      int low = 0;
+      do {
+        final int mid = (low + high) / 2;
+        final Range cur = ranges.get(mid);
+
+        if (cur.contains(idx)) {
+          if (cur.contains(idx + 1)) {
+            // Trivial plus one case above failed due to wrong currentRangeIdx.
+            // Reset the cache so we don't miss in the future.
+            //
+            currentRangeIdx = mid;
+            return idx + 1;
+          }
+
+          if (mid + 1 < ranges.size()) {
+            // Its the base of the next range.
+            currentRangeIdx = mid + 1;
+            return ranges.get(currentRangeIdx).getBase();
+          }
+
+          // No more lines in the file.
+          //
+          return getSize();
+        }
+
+        if (idx < cur.getBase()) {
+          high = mid;
+        } else {
+          low = mid + 1;
+        }
+      } while (low < high);
+
+      return getSize();
     }
 
-    // Binary search for the range, since we know its a sorted list.
-    //
-    if (ranges.isEmpty()) {
+    private String getLine(int idx) {
+      // Most requests are sequential in nature, fetching the next
+      // line from the current range, or the next range.
+      //
+      ImmutableList<Range> ranges = content.getRanges();
+      int high = ranges.size();
+      if (currentRangeIdx < high) {
+        Range cur = ranges.get(currentRangeIdx);
+        if (cur.contains(idx)) {
+          return cur.get(idx);
+        }
+
+        if (++currentRangeIdx < high) {
+          final Range next = ranges.get(currentRangeIdx);
+          if (next.contains(idx)) {
+            return next.get(idx);
+          }
+        }
+      }
+
+      // Binary search for the range, since we know its a sorted list.
+      //
+      if (ranges.isEmpty()) {
+        return null;
+      }
+
+      int low = 0;
+      do {
+        final int mid = (low + high) / 2;
+        final Range cur = ranges.get(mid);
+        if (cur.contains(idx)) {
+          currentRangeIdx = mid;
+          return cur.get(idx);
+        }
+        if (idx < cur.getBase()) {
+          high = mid;
+        } else {
+          low = mid + 1;
+        }
+      } while (low < high);
       return null;
     }
-
-    int low = 0;
-    do {
-      final int mid = (low + high) / 2;
-      final Range cur = ranges.get(mid);
-      if (cur.contains(idx)) {
-        currentRangeIdx = mid;
-        return cur.get(idx);
-      }
-      if (idx < cur.base) {
-        high = mid;
-      } else {
-        low = mid + 1;
-      }
-    } while (low < high);
-    return null;
-  }
-
-  public void addLine(int i, String content) {
-    final Range r;
-    if (!ranges.isEmpty() && i == last().end()) {
-      r = last();
-    } else {
-      r = new Range(i);
-      ranges.add(r);
-    }
-    r.lines.add(content);
-  }
-
-  private Range last() {
-    return ranges.get(ranges.size() - 1);
   }
 
   @Override
-  public String toString() {
+  public final String toString() {
     final StringBuilder b = new StringBuilder();
     b.append("SparseFileContent[\n");
-    for (Range r : ranges) {
+    for (Range r : getRanges()) {
       b.append("  ");
       b.append(r.toString());
       b.append('\n');
@@ -176,33 +191,32 @@
     return b.toString();
   }
 
-  static class Range {
-    protected int base;
-    protected List<String> lines;
-
-    private Range(int b) {
-      base = b;
-      lines = new ArrayList<>();
+  @AutoValue
+  abstract static class Range {
+    static Range create(int base, ImmutableList<String> lines) {
+      return new AutoValue_SparseFileContent_Range(base, lines);
     }
 
-    protected Range() {}
+    abstract int getBase();
+
+    abstract ImmutableList<String> getLines();
 
     private String get(int i) {
-      return lines.get(i - base);
+      return getLines().get(i - getBase());
     }
 
     private int end() {
-      return base + lines.size();
+      return getBase() + getLines().size();
     }
 
     private boolean contains(int i) {
-      return base <= i && i < end();
+      return getBase() <= i && i < end();
     }
 
     @Override
-    public String toString() {
+    public final String toString() {
       // Usage of [ and ) is intentional to denote inclusive/exclusive range
-      return "Range[" + base + "," + end() + ")";
+      return "Range[" + getBase() + "," + end() + ")";
     }
   }
 }
diff --git a/java/com/google/gerrit/prettify/common/SparseFileContentBuilder.java b/java/com/google/gerrit/prettify/common/SparseFileContentBuilder.java
new file mode 100644
index 0000000..04fb5d1
--- /dev/null
+++ b/java/com/google/gerrit/prettify/common/SparseFileContentBuilder.java
@@ -0,0 +1,90 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.prettify.common;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.prettify.common.SparseFileContent.Range;
+
+/**
+ * A builder for creating immutable {@link SparseFileContent}. Lines can be only be added in
+ * sequential (increased) order
+ */
+public class SparseFileContentBuilder {
+  private final ImmutableList.Builder<Range> ranges;
+  private final int size;
+  private int lastRangeBase;
+  private int lastRangeEnd;
+  private ImmutableList.Builder<String> lastRangeLines;
+
+  public SparseFileContentBuilder(int size) {
+    ranges = new ImmutableList.Builder<>();
+    startNextRange(0);
+    this.size = size;
+  }
+
+  public void addLine(int lineNumber, String content) {
+    if (lineNumber < 0) {
+      throw new IllegalArgumentException("Line number must be non-negative");
+    }
+    //    if (lineNumber >= size) {
+    //     The following 4 tests are failed if you uncomment this condition:
+    //
+    //
+    // diffOfFileWithMultilineRebaseHunkRemovingNewlineAtEndOfFileAndWithCommentReturnsFileContents
+    //
+    // diffOfFileWithMultilineRebaseHunkAddingNewlineAtEndOfFileAndWithCommentReturnsFileContents
+    //
+    //
+    // diffOfFileWithMultilineRebaseHunkRemovingNewlineAtEndOfFileAndWithCommentReturnsFileContents
+    //
+    // diffOfFileWithMultilineRebaseHunkAddingNewlineAtEndOfFileAndWithCommentReturnsFileContents
+    //     Tests are failed because there are some bug with diff calculation.
+    //     The condition must be uncommented after all these bugs are fixed.
+    //     Also don't forget to remove ignore from for SparseFileContentBuilder
+    //      throw new IllegalArgumentException(String.format("The zero-based line number %d is after
+    // the end of file. The file size is %d line(s).", lineNumber, size));
+    //    }
+    if (lineNumber < lastRangeEnd) {
+      throw new IllegalArgumentException(
+          String.format(
+              "Invalid line number %d. You are trying to add a line before an already added line"
+                  + " %d",
+              lineNumber, lastRangeEnd));
+    }
+    if (lineNumber > lastRangeEnd) {
+      finishLastRange();
+      startNextRange(lineNumber);
+    }
+    lastRangeLines.add(content);
+    lastRangeEnd++;
+  }
+
+  private void startNextRange(int base) {
+    lastRangeLines = new ImmutableList.Builder<>();
+    lastRangeBase = lastRangeEnd = base;
+  }
+
+  private void finishLastRange() {
+    if (lastRangeEnd > lastRangeBase) {
+      ranges.add(Range.create(lastRangeBase, lastRangeLines.build()));
+      lastRangeLines = null;
+    }
+  }
+
+  public SparseFileContent build() {
+    finishLastRange();
+    return SparseFileContent.create(ranges.build(), size);
+  }
+}
diff --git a/java/com/google/gerrit/prettify/common/testing/BUILD b/java/com/google/gerrit/prettify/common/testing/BUILD
new file mode 100644
index 0000000..5057fdb
--- /dev/null
+++ b/java/com/google/gerrit/prettify/common/testing/BUILD
@@ -0,0 +1,14 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
+package(default_testonly = True)
+
+java_library(
+    name = "testing",
+    srcs = glob(["*.java"]),
+    visibility = ["//visibility:public"],
+    deps = [
+        "//java/com/google/gerrit/prettify:server",
+        "//lib:guava",
+        "//lib/truth",
+    ],
+)
diff --git a/java/com/google/gerrit/prettify/common/testing/SparseFileContentSubject.java b/java/com/google/gerrit/prettify/common/testing/SparseFileContentSubject.java
new file mode 100644
index 0000000..c1fe1ec
--- /dev/null
+++ b/java/com/google/gerrit/prettify/common/testing/SparseFileContentSubject.java
@@ -0,0 +1,65 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.prettify.common.testing;
+
+import static com.google.common.truth.Truth.assertAbout;
+
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.IntegerSubject;
+import com.google.common.truth.MapSubject;
+import com.google.common.truth.Subject;
+import com.google.gerrit.prettify.common.SparseFileContent;
+import java.util.HashMap;
+import java.util.Map;
+
+public class SparseFileContentSubject extends Subject {
+  public static SparseFileContentSubject assertThat(SparseFileContent sparseFileContent) {
+    return assertAbout(sparseFileContent()).that(sparseFileContent);
+  }
+
+  private final SparseFileContent sparseFileContent;
+
+  private SparseFileContentSubject(FailureMetadata metadata, SparseFileContent actual) {
+    super(metadata, actual);
+    this.sparseFileContent = actual;
+  }
+
+  private static Subject.Factory<SparseFileContentSubject, SparseFileContent> sparseFileContent() {
+    return SparseFileContentSubject::new;
+  }
+
+  public IntegerSubject getSize() {
+    isNotNull();
+    return check("size()").that(sparseFileContent.getSize());
+  }
+
+  public IntegerSubject getRangesCount() {
+    isNotNull();
+    return check("rangesCount()").that(sparseFileContent.getRangesCount());
+  }
+
+  public MapSubject lines() {
+    isNotNull();
+    Map<Integer, String> lines = new HashMap<>();
+    SparseFileContent.Accessor accessor = sparseFileContent.createAccessor();
+    int size = accessor.getSize();
+    int idx = accessor.first();
+    while (idx < size) {
+      lines.put(idx, accessor.get(idx));
+      idx = accessor.next(idx);
+    }
+    return check("lines()").that(lines);
+  }
+}
diff --git a/java/com/google/gerrit/server/ExceptionHook.java b/java/com/google/gerrit/server/ExceptionHook.java
index a2bade0..019a715 100644
--- a/java/com/google/gerrit/server/ExceptionHook.java
+++ b/java/com/google/gerrit/server/ExceptionHook.java
@@ -34,6 +34,12 @@
    * <p>Only affects operations that are executed with {@link
    * com.google.gerrit.server.update.RetryHelper}.
    *
+   * <p>Should return {@code true} only for exceptions that are caused by temporary issues where a
+   * retry of the operation has a chance to succeed.
+   *
+   * <p>If {@code false} is returned the operation is still retried once to capture a trace, unless
+   * {@link #skipRetryWithTrace(Throwable)} skips the auto-retry.
+   *
    * @param throwable throwable that was thrown while executing the operation
    * @return whether the operation should be retried
    */
@@ -42,6 +48,34 @@
   }
 
   /**
+   * Whether auto-retrying of an operation with tracing should be skipped for the given throwable.
+   *
+   * <p>Only affects operations that are executed with {@link
+   * com.google.gerrit.server.update.RetryHelper}.
+   *
+   * <p>This method is only called for exceptions for which the operation should not be retried
+   * ({@link #shouldRetry(Throwable)} returned {@code false}).
+   *
+   * <p>By default this method returns {@code false}, so that by default traces for unexpected
+   * exceptions are captured, which allows to investigate them.
+   *
+   * <p>Implementors may use this method to skip retry with tracing for exceptions that occur due to
+   * known causes that are permanent and where a trace is not needed for the investigation. For
+   * example, if an operation fails because persisted data is corrupt, it makes no sense to retry
+   * the operation with a trace, because the trace will not help with fixing the corrupt data.
+   *
+   * <p>This method is only invoked if retry with tracing is enabled on the server ({@code
+   * retry.retryWithTraceOnFailure} in {@code gerrit.config} is set to {@code true}).
+   *
+   * @param throwable throwable that was thrown while executing the operation
+   * @return whether auto-retrying of an operation with tracing should be skipped for the given
+   *     throwable
+   */
+  default boolean skipRetryWithTrace(Throwable throwable) {
+    return false;
+  }
+
+  /**
    * Formats the cause of an exception for use in metrics.
    *
    * <p>This method allows implementors to group exceptions that have the same cause into one metric
diff --git a/java/com/google/gerrit/server/change/ChangeInserter.java b/java/com/google/gerrit/server/change/ChangeInserter.java
index 4263373..d1c27d5 100644
--- a/java/com/google/gerrit/server/change/ChangeInserter.java
+++ b/java/com/google/gerrit/server/change/ChangeInserter.java
@@ -37,6 +37,7 @@
 import com.google.gerrit.entities.PatchSet;
 import com.google.gerrit.entities.PatchSetApproval;
 import com.google.gerrit.entities.PatchSetInfo;
+import com.google.gerrit.entities.SubmissionId;
 import com.google.gerrit.extensions.api.changes.NotifyHandling;
 import com.google.gerrit.extensions.client.ReviewerState;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -55,7 +56,6 @@
 import com.google.gerrit.server.git.GroupCollector;
 import com.google.gerrit.server.git.validators.CommitValidationException;
 import com.google.gerrit.server.git.validators.CommitValidators;
-import com.google.gerrit.server.logging.RequestId;
 import com.google.gerrit.server.mail.send.CreateChangeSender;
 import com.google.gerrit.server.notedb.ChangeUpdate;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
@@ -396,7 +396,7 @@
      * instead of setting the status directly?
      */
     if (change.getStatus() == Change.Status.MERGED) {
-      update.fixStatusToMerged(new RequestId(ctx.getChange().getId().toString()));
+      update.fixStatusToMerged(new SubmissionId(change));
     } else {
       update.setStatus(change.getStatus());
     }
diff --git a/java/com/google/gerrit/server/change/ConsistencyChecker.java b/java/com/google/gerrit/server/change/ConsistencyChecker.java
index 19db5ee..513f5ca 100644
--- a/java/com/google/gerrit/server/change/ConsistencyChecker.java
+++ b/java/com/google/gerrit/server/change/ConsistencyChecker.java
@@ -33,6 +33,7 @@
 import com.google.gerrit.entities.Change;
 import com.google.gerrit.entities.PatchSet;
 import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.SubmissionId;
 import com.google.gerrit.exceptions.StorageException;
 import com.google.gerrit.extensions.api.changes.FixInput;
 import com.google.gerrit.extensions.common.ProblemInfo;
@@ -44,7 +45,6 @@
 import com.google.gerrit.server.PatchSetUtil;
 import com.google.gerrit.server.account.Accounts;
 import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.logging.RequestId;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.notedb.PatchSetState;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
@@ -578,7 +578,7 @@
     public boolean updateChange(ChangeContext ctx) {
       ctx.getChange().setStatus(Change.Status.MERGED);
       ctx.getUpdate(ctx.getChange().currentPatchSetId())
-          .fixStatusToMerged(new RequestId(ctx.getChange().getId().toString()));
+          .fixStatusToMerged(new SubmissionId(ctx.getChange()));
       p.status = Status.FIXED;
       p.outcome = "Marked change as merged";
       return true;
diff --git a/java/com/google/gerrit/server/diff/DiffInfoCreator.java b/java/com/google/gerrit/server/diff/DiffInfoCreator.java
new file mode 100644
index 0000000..c29ffc8
--- /dev/null
+++ b/java/com/google/gerrit/server/diff/DiffInfoCreator.java
@@ -0,0 +1,299 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.diff;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.data.PatchScript;
+import com.google.gerrit.common.data.PatchScript.DisplayMethod;
+import com.google.gerrit.common.data.PatchScript.PatchScriptFileInfo;
+import com.google.gerrit.entities.Patch;
+import com.google.gerrit.extensions.common.ChangeType;
+import com.google.gerrit.extensions.common.DiffInfo;
+import com.google.gerrit.extensions.common.DiffInfo.ContentEntry;
+import com.google.gerrit.extensions.common.DiffInfo.FileMeta;
+import com.google.gerrit.extensions.common.DiffInfo.IntraLineStatus;
+import com.google.gerrit.extensions.common.DiffWebLinkInfo;
+import com.google.gerrit.extensions.common.WebLinkInfo;
+import com.google.gerrit.jgit.diff.ReplaceEdit;
+import com.google.gerrit.prettify.common.SparseFileContent;
+import com.google.gerrit.server.change.FileContentUtil;
+import com.google.gerrit.server.project.ProjectState;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import org.eclipse.jgit.diff.Edit;
+
+/** Creates and fills a new {@link DiffInfo} object based on diff between files. */
+public class DiffInfoCreator {
+  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+  private static final ImmutableMap<Patch.ChangeType, ChangeType> CHANGE_TYPE =
+      Maps.immutableEnumMap(
+          new ImmutableMap.Builder<Patch.ChangeType, ChangeType>()
+              .put(Patch.ChangeType.ADDED, ChangeType.ADDED)
+              .put(Patch.ChangeType.MODIFIED, ChangeType.MODIFIED)
+              .put(Patch.ChangeType.DELETED, ChangeType.DELETED)
+              .put(Patch.ChangeType.RENAMED, ChangeType.RENAMED)
+              .put(Patch.ChangeType.COPIED, ChangeType.COPIED)
+              .put(Patch.ChangeType.REWRITE, ChangeType.REWRITE)
+              .build());
+
+  private final DiffWebLinksProvider webLinksProvider;
+  private final boolean intraline;
+  private final ProjectState state;
+
+  public DiffInfoCreator(
+      ProjectState state, DiffWebLinksProvider webLinksProvider, boolean intraline) {
+    this.webLinksProvider = webLinksProvider;
+    this.state = state;
+    this.intraline = intraline;
+  }
+
+  /* Returns the {@link DiffInfo} to display for end-users */
+  public DiffInfo create(PatchScript ps, DiffSide sideA, DiffSide sideB) {
+    DiffInfo result = new DiffInfo();
+
+    ImmutableList<DiffWebLinkInfo> links = webLinksProvider.getDiffLinks();
+    result.webLinks = links.isEmpty() ? null : links;
+
+    if (ps.isBinary()) {
+      result.binary = true;
+    }
+    result.metaA = createFileMeta(sideA).orElse(null);
+    result.metaB = createFileMeta(sideB).orElse(null);
+
+    if (intraline) {
+      if (ps.hasIntralineTimeout()) {
+        result.intralineStatus = IntraLineStatus.TIMEOUT;
+      } else if (ps.hasIntralineFailure()) {
+        result.intralineStatus = IntraLineStatus.FAILURE;
+      } else {
+        result.intralineStatus = IntraLineStatus.OK;
+      }
+      logger.atFine().log("intralineStatus = %s", result.intralineStatus);
+    }
+
+    result.changeType = CHANGE_TYPE.get(ps.getChangeType());
+    logger.atFine().log("changeType = %s", result.changeType);
+    if (result.changeType == null) {
+      throw new IllegalStateException("unknown change type: " + ps.getChangeType());
+    }
+
+    if (ps.getPatchHeader().size() > 0) {
+      result.diffHeader = ps.getPatchHeader();
+    }
+    result.content = calculateDiffContentEntries(ps);
+    return result;
+  }
+
+  private static List<ContentEntry> calculateDiffContentEntries(PatchScript ps) {
+    ContentCollector contentCollector = new ContentCollector(ps);
+    Set<Edit> editsDueToRebase = ps.getEditsDueToRebase();
+    for (Edit edit : ps.getEdits()) {
+      logger.atFine().log("next edit = %s", edit);
+
+      if (edit.getType() == Edit.Type.EMPTY) {
+        logger.atFine().log("skip empty edit");
+        continue;
+      }
+      contentCollector.addCommon(edit.getBeginA());
+
+      checkState(
+          contentCollector.nextA == edit.getBeginA(),
+          "nextA = %s; want %s",
+          contentCollector.nextA,
+          edit.getBeginA());
+      checkState(
+          contentCollector.nextB == edit.getBeginB(),
+          "nextB = %s; want %s",
+          contentCollector.nextB,
+          edit.getBeginB());
+      switch (edit.getType()) {
+        case DELETE:
+        case INSERT:
+        case REPLACE:
+          List<Edit> internalEdit =
+              edit instanceof ReplaceEdit ? ((ReplaceEdit) edit).getInternalEdits() : null;
+          boolean dueToRebase = editsDueToRebase.contains(edit);
+          contentCollector.addDiff(edit.getEndA(), edit.getEndB(), internalEdit, dueToRebase);
+          break;
+        case EMPTY:
+        default:
+          throw new IllegalStateException();
+      }
+    }
+    contentCollector.addCommon(ps.getA().getSize());
+
+    return contentCollector.lines;
+  }
+
+  private Optional<FileMeta> createFileMeta(DiffSide side) {
+    PatchScriptFileInfo fileInfo = side.fileInfo();
+    if (fileInfo.displayMethod == DisplayMethod.NONE) {
+      return Optional.empty();
+    }
+    FileMeta result = new FileMeta();
+    result.name = side.fileName();
+    result.contentType =
+        FileContentUtil.resolveContentType(
+            state, side.fileName(), fileInfo.mode, fileInfo.mimeType);
+    result.lines = fileInfo.content.getSize();
+    ImmutableList<WebLinkInfo> links = webLinksProvider.getFileWebLinks(side.type());
+    result.webLinks = links.isEmpty() ? null : links;
+    result.commitId = fileInfo.commitId;
+    return Optional.of(result);
+  }
+
+  private static class ContentCollector {
+
+    private final List<ContentEntry> lines;
+    private final SparseFileContent.Accessor fileA;
+    private final SparseFileContent.Accessor fileB;
+    private final boolean ignoreWS;
+
+    private int nextA;
+    private int nextB;
+
+    ContentCollector(PatchScript ps) {
+      lines = Lists.newArrayListWithExpectedSize(ps.getEdits().size() + 2);
+      fileA = ps.getA().createAccessor();
+      fileB = ps.getB().createAccessor();
+      ignoreWS = ps.isIgnoreWhitespace();
+    }
+
+    void addCommon(int end) {
+      logger.atFine().log("addCommon: end = %d", end);
+
+      end = Math.min(end, fileA.getSize());
+      logger.atFine().log("end = %d", end);
+
+      if (nextA >= end) {
+        logger.atFine().log("nextA >= end: nextA = %d, end = %d", nextA, end);
+        return;
+      }
+
+      while (nextA < end) {
+        logger.atFine().log("nextA < end: nextA = %d, end = %d", nextA, end);
+
+        if (!fileA.contains(nextA)) {
+          logger.atFine().log("fileA does not contain nextA: nextA = %d", nextA);
+
+          int endRegion = Math.min(end, nextA == 0 ? fileA.first() : fileA.next(nextA - 1));
+          int len = endRegion - nextA;
+          entry().skip = len;
+          nextA = endRegion;
+          nextB += len;
+
+          logger.atFine().log("setting: nextA = %d, nextB = %d", nextA, nextB);
+          continue;
+        }
+
+        ContentEntry e = null;
+        for (int i = nextA; i == nextA && i < end; i = fileA.next(i), nextA++, nextB++) {
+          if (ignoreWS && fileB.contains(nextB)) {
+            if (e == null || e.common == null) {
+              logger.atFine().log("create new common entry: nextA = %d, nextB = %d", nextA, nextB);
+              e = entry();
+              e.a = Lists.newArrayListWithCapacity(end - nextA);
+              e.b = Lists.newArrayListWithCapacity(end - nextA);
+              e.common = true;
+            }
+            e.a.add(fileA.get(nextA));
+            e.b.add(fileB.get(nextB));
+          } else {
+            if (e == null || e.common != null) {
+              logger.atFine().log(
+                  "create new non-common entry: nextA = %d, nextB = %d", nextA, nextB);
+              e = entry();
+              e.ab = Lists.newArrayListWithCapacity(end - nextA);
+            }
+            e.ab.add(fileA.get(nextA));
+          }
+        }
+      }
+    }
+
+    void addDiff(int endA, int endB, List<Edit> internalEdit, boolean dueToRebase) {
+      logger.atFine().log(
+          "addDiff: endA = %d, endB = %d, numberOfInternalEdits = %d, dueToRebase = %s",
+          endA, endB, internalEdit != null ? internalEdit.size() : 0, dueToRebase);
+
+      int lenA = endA - nextA;
+      int lenB = endB - nextB;
+      logger.atFine().log("lenA = %d, lenB = %d", lenA, lenB);
+      checkState(lenA > 0 || lenB > 0);
+
+      logger.atFine().log("create non-common entry");
+      ContentEntry e = entry();
+      if (lenA > 0) {
+        logger.atFine().log("lenA > 0: lenA = %d", lenA);
+        e.a = Lists.newArrayListWithCapacity(lenA);
+        for (; nextA < endA; nextA++) {
+          e.a.add(fileA.get(nextA));
+        }
+      }
+      if (lenB > 0) {
+        logger.atFine().log("lenB > 0: lenB = %d", lenB);
+        e.b = Lists.newArrayListWithCapacity(lenB);
+        for (; nextB < endB; nextB++) {
+          e.b.add(fileB.get(nextB));
+        }
+      }
+      if (internalEdit != null && !internalEdit.isEmpty()) {
+        logger.atFine().log("processing internal edits");
+
+        e.editA = Lists.newArrayListWithCapacity(internalEdit.size() * 2);
+        e.editB = Lists.newArrayListWithCapacity(internalEdit.size() * 2);
+        int lastA = 0;
+        int lastB = 0;
+        for (Edit edit : internalEdit) {
+          logger.atFine().log("internal edit = %s", edit);
+
+          if (edit.getBeginA() != edit.getEndA()) {
+            logger.atFine().log(
+                "edit.getBeginA() != edit.getEndA(): edit.getBeginA() = %d, edit.getEndA() = %d",
+                edit.getBeginA(), edit.getEndA());
+            e.editA.add(
+                ImmutableList.of(edit.getBeginA() - lastA, edit.getEndA() - edit.getBeginA()));
+            lastA = edit.getEndA();
+            logger.atFine().log("lastA = %d", lastA);
+          }
+          if (edit.getBeginB() != edit.getEndB()) {
+            logger.atFine().log(
+                "edit.getBeginB() != edit.getEndB(): edit.getBeginB() = %d, edit.getEndB() = %d",
+                edit.getBeginB(), edit.getEndB());
+            e.editB.add(
+                ImmutableList.of(edit.getBeginB() - lastB, edit.getEndB() - edit.getBeginB()));
+            lastB = edit.getEndB();
+            logger.atFine().log("lastB = %d", lastB);
+          }
+        }
+      }
+      e.dueToRebase = dueToRebase ? true : null;
+    }
+
+    private ContentEntry entry() {
+      ContentEntry e = new ContentEntry();
+      lines.add(e);
+      return e;
+    }
+  }
+}
diff --git a/java/com/google/gerrit/server/diff/DiffSide.java b/java/com/google/gerrit/server/diff/DiffSide.java
new file mode 100644
index 0000000..28c7810
--- /dev/null
+++ b/java/com/google/gerrit/server/diff/DiffSide.java
@@ -0,0 +1,37 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.diff;
+
+import com.google.auto.value.AutoValue;
+import com.google.gerrit.common.data.PatchScript.PatchScriptFileInfo;
+
+/** Contains settings for one of two sides in diff view. Each diff view has exactly 2 sides. */
+@AutoValue
+public abstract class DiffSide {
+  public enum Type {
+    SIDE_A,
+    SIDE_B
+  }
+
+  public static DiffSide create(PatchScriptFileInfo fileInfo, String fileName, Type type) {
+    return new AutoValue_DiffSide(fileInfo, fileName, type);
+  }
+
+  public abstract PatchScriptFileInfo fileInfo();
+
+  public abstract String fileName();
+
+  public abstract Type type();
+}
diff --git a/java/com/google/gerrit/server/diff/DiffWebLinksProvider.java b/java/com/google/gerrit/server/diff/DiffWebLinksProvider.java
new file mode 100644
index 0000000..0f71b17
--- /dev/null
+++ b/java/com/google/gerrit/server/diff/DiffWebLinksProvider.java
@@ -0,0 +1,29 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.diff;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.extensions.common.DiffWebLinkInfo;
+import com.google.gerrit.extensions.common.WebLinkInfo;
+
+/** Provider for different types of links which can be displayed in a diff view. */
+public interface DiffWebLinksProvider {
+
+  /** Returns links associated with the diff view */
+  ImmutableList<DiffWebLinkInfo> getDiffLinks();
+
+  /** Returns links associated with the diff side */
+  ImmutableList<WebLinkInfo> getFileWebLinks(DiffSide.Type fileInfoType);
+}
diff --git a/java/com/google/gerrit/server/git/MergedByPushOp.java b/java/com/google/gerrit/server/git/MergedByPushOp.java
index 9aebebf..01d5380 100644
--- a/java/com/google/gerrit/server/git/MergedByPushOp.java
+++ b/java/com/google/gerrit/server/git/MergedByPushOp.java
@@ -22,11 +22,11 @@
 import com.google.gerrit.entities.LabelId;
 import com.google.gerrit.entities.PatchSet;
 import com.google.gerrit.entities.PatchSetInfo;
+import com.google.gerrit.entities.SubmissionId;
 import com.google.gerrit.server.ChangeMessagesUtil;
 import com.google.gerrit.server.PatchSetUtil;
 import com.google.gerrit.server.config.SendEmailExecutor;
 import com.google.gerrit.server.extensions.events.ChangeMerged;
-import com.google.gerrit.server.logging.RequestId;
 import com.google.gerrit.server.mail.send.MergedSender;
 import com.google.gerrit.server.notedb.ChangeUpdate;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
@@ -52,7 +52,7 @@
     MergedByPushOp create(
         RequestScopePropagator requestScopePropagator,
         PatchSet.Id psId,
-        @Assisted RequestId submissionId,
+        @Assisted SubmissionId submissionId,
         @Assisted("refName") String refName,
         @Assisted("mergeResultRevId") String mergeResultRevId);
   }
@@ -66,7 +66,7 @@
   private final ChangeMerged changeMerged;
 
   private final PatchSet.Id psId;
-  private final RequestId submissionId;
+  private final SubmissionId submissionId;
   private final String refName;
   private final String mergeResultRevId;
 
@@ -86,7 +86,7 @@
       ChangeMerged changeMerged,
       @Assisted RequestScopePropagator requestScopePropagator,
       @Assisted PatchSet.Id psId,
-      @Assisted RequestId submissionId,
+      @Assisted SubmissionId submissionId,
       @Assisted("refName") String refName,
       @Assisted("mergeResultRevId") String mergeResultRevId) {
     this.patchSetInfoFactory = patchSetInfoFactory;
@@ -137,7 +137,7 @@
     }
     change.setCurrentPatchSet(info);
     change.setStatus(Change.Status.MERGED);
-    change.setSubmissionId(submissionId.toStringForStorage());
+    change.setSubmissionId(submissionId.toString());
     // we cannot reconstruct the submit records for when this change was
     // submitted, this is why we must fix the status and other details.
     update.fixStatusToMerged(submissionId);
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
index cec9e4e..5155925 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
@@ -74,6 +74,7 @@
 import com.google.gerrit.entities.PatchSetInfo;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.entities.SubmissionId;
 import com.google.gerrit.exceptions.StorageException;
 import com.google.gerrit.extensions.api.changes.HashtagsInput;
 import com.google.gerrit.extensions.api.changes.NotifyHandling;
@@ -3023,7 +3024,8 @@
                     info,
                     groups,
                     magicBranch,
-                    receivePack.getPushCertificate())
+                    receivePack.getPushCertificate(),
+                    notes.getChange())
                 .setRequestScopePropagator(requestScopePropagator);
         bu.addOp(notes.getChangeId(), replaceOp);
         if (progress != null) {
@@ -3281,7 +3283,7 @@
 
                 int existingPatchSets = 0;
                 int newPatchSets = 0;
-                RequestId submissionId = null;
+                SubmissionId submissionId = null;
                 COMMIT:
                 for (RevCommit c; (c = rw.next()) != null; ) {
                   rw.parseBody(c);
@@ -3290,10 +3292,10 @@
                       receivePackRefCache.tipsFromObjectId(c.copy(), RefNames.REFS_CHANGES)) {
                     PatchSet.Id psId = PatchSet.Id.fromRef(ref.getName());
                     Optional<ChangeNotes> notes = getChangeNotes(psId.changeId());
-                    if (submissionId == null) {
-                      submissionId = new RequestId(psId.changeId().toString());
-                    }
                     if (notes.isPresent() && notes.get().getChange().getDest().equals(branch)) {
+                      if (submissionId == null) {
+                        submissionId = new SubmissionId(notes.get().getChange());
+                      }
                       existingPatchSets++;
                       bu.addOp(notes.get().getChangeId(), setPrivateOpFactory.create(false, null));
                       bu.addOp(
@@ -3333,7 +3335,7 @@
                     continue;
                   }
                   if (submissionId == null) {
-                    submissionId = new RequestId(id.toString());
+                    submissionId = new SubmissionId(req.notes.getChange());
                   }
                   req.addOps(bu, null);
                   bu.addOp(id, setPrivateOpFactory.create(false, null));
diff --git a/java/com/google/gerrit/server/git/receive/ReplaceOp.java b/java/com/google/gerrit/server/git/receive/ReplaceOp.java
index 6c0d5d3..24154d60 100644
--- a/java/com/google/gerrit/server/git/receive/ReplaceOp.java
+++ b/java/com/google/gerrit/server/git/receive/ReplaceOp.java
@@ -36,6 +36,7 @@
 import com.google.gerrit.entities.PatchSet;
 import com.google.gerrit.entities.PatchSetApproval;
 import com.google.gerrit.entities.PatchSetInfo;
+import com.google.gerrit.entities.SubmissionId;
 import com.google.gerrit.extensions.api.changes.AddReviewerInput;
 import com.google.gerrit.extensions.api.changes.NotifyHandling;
 import com.google.gerrit.extensions.client.ChangeKind;
@@ -61,7 +62,6 @@
 import com.google.gerrit.server.extensions.events.RevisionCreated;
 import com.google.gerrit.server.git.MergedByPushOp;
 import com.google.gerrit.server.git.receive.ReceiveCommits.MagicBranchInput;
-import com.google.gerrit.server.logging.RequestId;
 import com.google.gerrit.server.mail.MailUtil.MailRecipients;
 import com.google.gerrit.server.mail.send.ReplacePatchSetSender;
 import com.google.gerrit.server.notedb.ChangeNotes;
@@ -110,7 +110,8 @@
         PatchSetInfo info,
         List<String> groups,
         @Nullable MagicBranchInput magicBranch,
-        @Nullable PushCertificate pushCertificate);
+        @Nullable PushCertificate pushCertificate,
+        Change change);
   }
 
   private static final String CHANGE_IS_CLOSED = "change is closed";
@@ -143,6 +144,7 @@
   private final PatchSetInfo info;
   private final MagicBranchInput magicBranch;
   private final PushCertificate pushCertificate;
+  private final Change change;
   private List<String> groups;
 
   private final Map<String, Short> approvals = new HashMap<>();
@@ -177,6 +179,7 @@
       ProjectCache projectCache,
       @SendEmailExecutor ExecutorService sendEmailExecutor,
       ReviewerAdder reviewerAdder,
+      Change change,
       @Assisted ProjectState projectState,
       @Assisted BranchNameKey dest,
       @Assisted boolean checkMergedInto,
@@ -218,6 +221,7 @@
     this.groups = groups;
     this.magicBranch = magicBranch;
     this.pushCertificate = pushCertificate;
+    this.change = change;
   }
 
   @Override
@@ -239,7 +243,7 @@
             mergedByPushOpFactory.create(
                 requestScopePropagator,
                 patchSetId,
-                new RequestId(patchSetId.changeId().toString()),
+                new SubmissionId(change),
                 mergedInto,
                 mergeResultRevId);
       }
diff --git a/java/com/google/gerrit/server/notedb/ChangeUpdate.java b/java/com/google/gerrit/server/notedb/ChangeUpdate.java
index 02a4dcc..6a900c0 100644
--- a/java/com/google/gerrit/server/notedb/ChangeUpdate.java
+++ b/java/com/google/gerrit/server/notedb/ChangeUpdate.java
@@ -55,11 +55,11 @@
 import com.google.gerrit.entities.Comment;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.entities.RobotComment;
+import com.google.gerrit.entities.SubmissionId;
 import com.google.gerrit.exceptions.StorageException;
 import com.google.gerrit.mail.Address;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.logging.RequestId;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.util.LabelVote;
 import com.google.inject.assistedinject.Assisted;
@@ -220,10 +220,10 @@
     this.status = status;
   }
 
-  public void fixStatusToMerged(RequestId submissionId) {
+  public void fixStatusToMerged(SubmissionId submissionId) {
     checkArgument(submissionId != null, "submission id must be set for merged changes");
     this.status = Change.Status.MERGED;
-    this.submissionId = submissionId.toStringForStorage();
+    this.submissionId = submissionId.toString();
   }
 
   public void putApproval(String label, short value) {
@@ -242,9 +242,9 @@
     approvals.put(label, reviewer, Optional.empty());
   }
 
-  public void merge(RequestId submissionId, Iterable<SubmitRecord> submitRecords) {
+  public void merge(SubmissionId submissionId, Iterable<SubmitRecord> submitRecords) {
     this.status = Change.Status.MERGED;
-    this.submissionId = submissionId.toStringForStorage();
+    this.submissionId = submissionId.toString();
     this.submitRecords = ImmutableList.copyOf(submitRecords);
     checkArgument(!this.submitRecords.isEmpty(), "no submit records specified at submit time");
   }
diff --git a/java/com/google/gerrit/server/patch/PatchScriptBuilder.java b/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
index 1435c5e..0a17303 100644
--- a/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
+++ b/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
@@ -17,6 +17,7 @@
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.Comparator.comparing;
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.gerrit.common.data.CommentDetail;
 import com.google.gerrit.common.data.PatchScript;
@@ -24,11 +25,11 @@
 import com.google.gerrit.entities.Change;
 import com.google.gerrit.entities.Comment;
 import com.google.gerrit.entities.Patch;
-import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.Patch.ChangeType;
 import com.google.gerrit.extensions.client.DiffPreferencesInfo;
 import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
 import com.google.gerrit.prettify.common.EditList;
-import com.google.gerrit.prettify.common.SparseFileContent;
+import com.google.gerrit.prettify.common.SparseFileContentBuilder;
 import com.google.gerrit.server.mime.FileTypeRegistry;
 import com.google.inject.Inject;
 import eu.medsea.mimeutil.MimeType;
@@ -39,10 +40,8 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.Set;
 import org.eclipse.jgit.diff.Edit;
-import org.eclipse.jgit.errors.CorruptObjectException;
-import org.eclipse.jgit.errors.IncorrectObjectTypeException;
-import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.ObjectId;
@@ -59,28 +58,17 @@
 
   private static final Comparator<Edit> EDIT_SORT = comparing(Edit::getBeginA);
 
-  private Repository db;
-  private Project.NameKey projectKey;
-  private ObjectReader reader;
   private Change change;
   private DiffPreferencesInfo diffPrefs;
-  private ComparisonType comparisonType;
-  private ObjectId aId;
-  private ObjectId bId;
   private List<Edit> edits;
   private final FileTypeRegistry registry;
-  private final PatchListCache patchListCache;
   private int context;
+  private IntraLineDiffCalculator intralineDiffCalculator;
+  private SidesResolver sidesResolver;
 
   @Inject
-  PatchScriptBuilder(FileTypeRegistry ftr, PatchListCache plc) {
+  PatchScriptBuilder(FileTypeRegistry ftr) {
     registry = ftr;
-    patchListCache = plc;
-  }
-
-  void setRepository(Repository r, Project.NameKey projectKey) {
-    this.db = r;
-    this.projectKey = projectKey;
   }
 
   void setChange(Change c) {
@@ -98,62 +86,39 @@
     }
   }
 
-  void setTrees(ComparisonType ct, ObjectId a, ObjectId b) {
-    comparisonType = ct;
-    aId = a;
-    bId = b;
+  void setIntraLineDiffCalculator(IntraLineDiffCalculator calculator) {
+    intralineDiffCalculator = calculator;
   }
 
-  PatchScript toPatchScript(PatchListEntry content, CommentDetail comments, List<Patch> history)
-      throws IOException {
-    reader = db.newObjectReader();
-    try {
-      return build(content, comments, history);
-    } finally {
-      reader.close();
-    }
+  void setSidesResolver(SidesResolver resolver) {
+    sidesResolver = resolver;
   }
 
-  private PatchScript build(PatchListEntry content, CommentDetail comments, List<Patch> history)
+  PatchScript toPatchScript(PatchFileChange content, CommentDetail comments, List<Patch> history)
       throws IOException {
-    boolean intralineFailure = false;
-    boolean intralineTimeout = false;
+    return build(content, comments, history);
+  }
 
-    SideResolver resolver = new SideResolver();
-    Side a = resolver.resolve(oldName(content), null, aId);
-    Side b = resolver.resolve(newName(content), a, bId);
+  private PatchScript build(PatchFileChange content, CommentDetail comments, List<Patch> history)
+      throws IOException {
 
-    edits = new ArrayList<>(content.getEdits());
+    ResolvedSides sides = sidesResolver.resolveSides(registry, oldName(content), newName(content));
+    PatchSide a = sides.a;
+    PatchSide b = sides.b;
+
+    ImmutableList<Edit> contentEdits = content.getEdits();
     ImmutableSet<Edit> editsDueToRebase = content.getEditsDueToRebase();
 
-    if (isModify(content) && diffPrefs.intralineDifference && isIntralineModeAllowed(b)) {
-      IntraLineDiff d =
-          patchListCache.getIntraLineDiff(
-              IntraLineDiffKey.create(a.id, b.id, diffPrefs.ignoreWhitespace),
-              IntraLineDiffArgs.create(
-                  a.src, b.src, edits, editsDueToRebase, projectKey, bId, b.path));
-      if (d != null) {
-        switch (d.getStatus()) {
-          case EDIT_LIST:
-            edits = new ArrayList<>(d.getEdits());
-            break;
+    IntraLineDiffCalculatorResult intralineResult = IntraLineDiffCalculatorResult.NO_RESULT;
 
-          case DISABLED:
-            break;
-
-          case ERROR:
-            intralineFailure = true;
-            break;
-
-          case TIMEOUT:
-            intralineTimeout = true;
-            break;
-        }
-      } else {
-        intralineFailure = true;
-      }
+    if (isModify(content) && intralineDiffCalculator != null && isIntralineModeAllowed(b)) {
+      intralineResult =
+          intralineDiffCalculator.calculateIntraLineDiff(
+              contentEdits, editsDueToRebase, a.id, b.id, a.src, b.src, b.treeId, b.path);
     }
 
+    edits = new ArrayList<>(intralineResult.edits.orElse(contentEdits));
+
     correctForDifferencesInNewlineAtEnd(a, b);
 
     if (comments != null) {
@@ -198,8 +163,8 @@
         b.fileMode,
         content.getHeaderLines(),
         diffPrefs,
-        a.dst,
-        b.dst,
+        a.dst.build(),
+        b.dst.build(),
         edits,
         editsDueToRebase,
         a.displayMethod,
@@ -209,14 +174,14 @@
         comments,
         history,
         hugeFile,
-        intralineFailure,
-        intralineTimeout,
+        intralineResult.failure,
+        intralineResult.timeout,
         content.getPatchType() == Patch.PatchType.BINARY,
-        aId == null ? null : aId.getName(),
-        bId == null ? null : bId.getName());
+        a.treeId == null ? null : a.treeId.getName(),
+        b.treeId == null ? null : b.treeId.getName());
   }
 
-  private static boolean isModify(PatchListEntry content) {
+  private static boolean isModify(PatchFileChange content) {
     switch (content.getChangeType()) {
       case MODIFIED:
       case COPIED:
@@ -231,7 +196,7 @@
     }
   }
 
-  private static String oldName(PatchListEntry entry) {
+  private static String oldName(PatchFileChange entry) {
     switch (entry.getChangeType()) {
       case ADDED:
         return null;
@@ -246,7 +211,7 @@
     }
   }
 
-  private static String newName(PatchListEntry entry) {
+  private static String newName(PatchFileChange entry) {
     switch (entry.getChangeType()) {
       case DELETED:
         return null;
@@ -260,7 +225,7 @@
     }
   }
 
-  private static boolean isIntralineModeAllowed(Side side) {
+  private static boolean isIntralineModeAllowed(PatchSide side) {
     // The intraline diff cache keys are the same for these cases. It's better to not show
     // intraline results than showing completely wrong diffs or to run into a server error.
     return !Patch.isMagic(side.path) && !isSubmoduleCommit(side.mode);
@@ -270,7 +235,7 @@
     return mode.getObjectType() == Constants.OBJ_COMMIT;
   }
 
-  private void correctForDifferencesInNewlineAtEnd(Side a, Side b) {
+  private void correctForDifferencesInNewlineAtEnd(PatchSide a, PatchSide b) {
     // a.src.size() is the size ignoring a newline at the end whereas a.size() considers it.
     int aSize = a.src.size();
     int bSize = b.src.size();
@@ -312,11 +277,11 @@
     return list.isEmpty() ? Optional.empty() : Optional.ofNullable(list.get(list.size() - 1));
   }
 
-  private boolean isNewlineAtEndDeleted(Side a, Side b) {
+  private boolean isNewlineAtEndDeleted(PatchSide a, PatchSide b) {
     return !a.src.isMissingNewlineAtEnd() && b.src.isMissingNewlineAtEnd();
   }
 
-  private boolean isNewlineAtEndAdded(Side a, Side b) {
+  private boolean isNewlineAtEndAdded(PatchSide a, PatchSide b) {
     return a.src.isMissingNewlineAtEnd() && !b.src.isMissingNewlineAtEnd();
   }
 
@@ -434,7 +399,7 @@
     return last.getEndA() + (b - last.getEndB());
   }
 
-  private void packContent(Side a, Side b, boolean ignoredWhitespace) {
+  private void packContent(PatchSide a, PatchSide b, boolean ignoredWhitespace) {
     EditList list = new EditList(edits, context, a.size(), b.size());
     for (EditList.Hunk hunk : list.getHunks()) {
       while (hunk.next()) {
@@ -468,8 +433,8 @@
     }
   }
 
-  private static class Side {
-
+  private static class PatchSide {
+    final ObjectId treeId;
     final String path;
     final ObjectId id;
     final FileMode mode;
@@ -478,9 +443,10 @@
     final MimeType mimeType;
     final DisplayMethod displayMethod;
     final PatchScript.FileMode fileMode;
-    final SparseFileContent dst;
+    final SparseFileContentBuilder dst;
 
-    public Side(
+    private PatchSide(
+        ObjectId treeId,
         String path,
         ObjectId id,
         FileMode mode,
@@ -489,6 +455,7 @@
         MimeType mimeType,
         DisplayMethod displayMethod,
         PatchScript.FileMode fileMode) {
+      this.treeId = treeId;
       this.path = path;
       this.id = id;
       this.mode = mode;
@@ -497,8 +464,7 @@
       this.mimeType = mimeType;
       this.displayMethod = displayMethod;
       this.fileMode = fileMode;
-      dst = new SparseFileContent();
-      dst.setSize(size());
+      dst = new SparseFileContentBuilder(size());
     }
 
     int size() {
@@ -521,15 +487,64 @@
     }
   }
 
-  private class SideResolver {
+  interface SidesResolver {
 
-    Side resolve(final String path, final Side other, final ObjectId within) throws IOException {
+    ResolvedSides resolveSides(FileTypeRegistry typeRegistry, String oldName, String newName)
+        throws IOException;
+  }
+
+  private static class ResolvedSides {
+    // Not an @AutoValue because PatchSide can't be AutoValue
+    public final PatchSide a;
+    public final PatchSide b;
+
+    ResolvedSides(PatchSide a, PatchSide b) {
+      this.a = a;
+      this.b = b;
+    }
+  }
+
+  static class SidesResolverImpl implements SidesResolver {
+
+    private final Repository db;
+    private ComparisonType comparisonType;
+    private ObjectId aId;
+    private ObjectId bId;
+
+    SidesResolverImpl(Repository db) {
+      this.db = db;
+    }
+
+    void setTrees(ComparisonType comparisonType, ObjectId a, ObjectId b) {
+      this.comparisonType = comparisonType;
+      this.aId = a;
+      this.bId = b;
+    }
+
+    @Override
+    public ResolvedSides resolveSides(FileTypeRegistry typeRegistry, String oldName, String newName)
+        throws IOException {
+      try (ObjectReader reader = db.newObjectReader()) {
+        PatchSide a = resolve(typeRegistry, reader, oldName, null, aId);
+        PatchSide b = resolve(typeRegistry, reader, newName, a, bId);
+        return new ResolvedSides(a, b);
+      }
+    }
+
+    PatchSide resolve(
+        final FileTypeRegistry registry,
+        final ObjectReader reader,
+        final String path,
+        final PatchSide other,
+        final ObjectId within)
+        throws IOException {
       try {
         boolean isCommitMsg = Patch.COMMIT_MSG.equals(path);
         boolean isMergeList = Patch.MERGE_LIST.equals(path);
         if (isCommitMsg || isMergeList) {
           if (comparisonType.isAgainstParentOrAutoMerge() && Objects.equals(aId, within)) {
             return createSide(
+                within,
                 path,
                 ObjectId.zeroId(),
                 FileMode.MISSING,
@@ -554,6 +569,7 @@
             displayMethod = DisplayMethod.DIFF;
           }
           return createSide(
+              within,
               path,
               within,
               mode,
@@ -563,7 +579,7 @@
               displayMethod,
               false);
         }
-        final TreeWalk tw = find(path, within);
+        final TreeWalk tw = find(reader, path, within);
         ObjectId id = tw != null ? tw.getObjectId(0) : ObjectId.zeroId();
         FileMode mode = tw != null ? tw.getFileMode(0) : FileMode.MISSING;
         boolean reuse =
@@ -598,13 +614,15 @@
             displayMethod = DisplayMethod.IMG;
           }
         }
-        return createSide(path, id, mode, srcContent, src, mimeType, displayMethod, reuse);
+        return createSide(within, path, id, mode, srcContent, src, mimeType, displayMethod, reuse);
+
       } catch (IOException err) {
         throw new IOException("Cannot read " + within.name() + ":" + path, err);
       }
     }
 
-    private Side createSide(
+    private PatchSide createSide(
+        ObjectId treeId,
         String path,
         ObjectId id,
         FileMode mode,
@@ -629,12 +647,11 @@
       } else if (mode == FileMode.GITLINK) {
         fileMode = PatchScript.FileMode.GITLINK;
       }
-      return new Side(path, id, mode, srcContent, src, mimeType, displayMethod, fileMode);
+      return new PatchSide(
+          treeId, path, id, mode, srcContent, src, mimeType, displayMethod, fileMode);
     }
 
-    private TreeWalk find(String path, ObjectId within)
-        throws MissingObjectException, IncorrectObjectTypeException, CorruptObjectException,
-            IOException {
+    private TreeWalk find(ObjectReader reader, String path, ObjectId within) throws IOException {
       if (path == null || within == null) {
         return null;
       }
@@ -649,4 +666,59 @@
     return (a.getBits() & FileMode.TYPE_FILE) == FileMode.TYPE_FILE
         && (b.getBits() & FileMode.TYPE_FILE) == FileMode.TYPE_FILE;
   }
+
+  static class IntraLineDiffCalculatorResult {
+    // Not an @AutoValue because Edit is mutable
+    final boolean failure;
+    final boolean timeout;
+    private final Optional<ImmutableList<Edit>> edits;
+
+    private IntraLineDiffCalculatorResult(
+        Optional<ImmutableList<Edit>> edits, boolean failure, boolean timeout) {
+      this.failure = failure;
+      this.timeout = timeout;
+      this.edits = edits;
+    }
+
+    static final IntraLineDiffCalculatorResult NO_RESULT =
+        new IntraLineDiffCalculatorResult(Optional.empty(), false, false);
+    static final IntraLineDiffCalculatorResult FAILURE =
+        new IntraLineDiffCalculatorResult(Optional.empty(), true, false);
+    static final IntraLineDiffCalculatorResult TIMEOUT =
+        new IntraLineDiffCalculatorResult(Optional.empty(), false, true);
+
+    static IntraLineDiffCalculatorResult success(ImmutableList<Edit> edits) {
+      return new IntraLineDiffCalculatorResult(Optional.of(edits), false, false);
+    }
+  }
+
+  interface IntraLineDiffCalculator {
+
+    IntraLineDiffCalculatorResult calculateIntraLineDiff(
+        ImmutableList<Edit> edits,
+        Set<Edit> editsDueToRebase,
+        ObjectId aId,
+        ObjectId bId,
+        Text aSrc,
+        Text bSrc,
+        ObjectId bTreeId,
+        String bPath);
+  }
+
+  interface PatchFileChange {
+
+    ImmutableList<Edit> getEdits();
+
+    ImmutableSet<Edit> getEditsDueToRebase();
+
+    List<String> getHeaderLines();
+
+    String getNewName();
+
+    String getOldName();
+
+    ChangeType getChangeType();
+
+    Patch.PatchType getPatchType();
+  }
 }
diff --git a/java/com/google/gerrit/server/patch/PatchScriptFactory.java b/java/com/google/gerrit/server/patch/PatchScriptFactory.java
index ffeda3d..2c8de1d 100644
--- a/java/com/google/gerrit/server/patch/PatchScriptFactory.java
+++ b/java/com/google/gerrit/server/patch/PatchScriptFactory.java
@@ -17,6 +17,8 @@
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkState;
 
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.data.CommentDetail;
@@ -27,6 +29,7 @@
 import com.google.gerrit.entities.Patch;
 import com.google.gerrit.entities.Patch.ChangeType;
 import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
 import com.google.gerrit.extensions.client.DiffPreferencesInfo;
 import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
 import com.google.gerrit.extensions.restapi.AuthException;
@@ -38,6 +41,10 @@
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.LargeObjectException;
 import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.patch.PatchScriptBuilder.IntraLineDiffCalculator;
+import com.google.gerrit.server.patch.PatchScriptBuilder.IntraLineDiffCalculatorResult;
+import com.google.gerrit.server.patch.PatchScriptBuilder.PatchFileChange;
+import com.google.gerrit.server.patch.PatchScriptBuilder.SidesResolverImpl;
 import com.google.gerrit.server.permissions.ChangePermission;
 import com.google.gerrit.server.permissions.PermissionBackend;
 import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -53,15 +60,19 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.Set;
 import java.util.concurrent.Callable;
+import org.eclipse.jgit.diff.Edit;
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Repository;
 
 public class PatchScriptFactory implements Callable<PatchScript> {
+
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
   public interface Factory {
+
     PatchScriptFactory create(
         ChangeNotes notes,
         String fileName,
@@ -225,7 +236,7 @@
 
         loadCommentsAndHistory(content.getChangeType(), content.getOldName(), content.getNewName());
 
-        return b.toPatchScript(content, comments, history);
+        return b.toPatchScript(new PatchListFileChange(content), comments, history);
       } catch (PatchListNotAvailableException e) {
         throw new NoSuchChangeException(changeId, e);
       } catch (IOException e) {
@@ -256,10 +267,15 @@
 
   private PatchScriptBuilder newBuilder(PatchList list, Repository git) {
     final PatchScriptBuilder b = builderFactory.get();
-    b.setRepository(git, notes.getProjectName());
     b.setChange(notes.getChange());
     b.setDiffPrefs(diffPrefs);
-    b.setTrees(list.getComparisonType(), list.getOldId(), list.getNewId());
+    if (diffPrefs.intralineDifference) {
+      b.setIntraLineDiffCalculator(
+          new IntraLineDiffCalculatorImpl(patchListCache, notes.getProjectName(), diffPrefs));
+    }
+    SidesResolverImpl sidesResolver = new SidesResolverImpl(git);
+    sidesResolver.setTrees(list.getComparisonType(), list.getOldId(), list.getNewId());
+    b.setSidesResolver(sidesResolver);
     return b;
   }
 
@@ -402,4 +418,98 @@
       }
     }
   }
+
+  private static class IntraLineDiffCalculatorImpl implements IntraLineDiffCalculator {
+
+    private final PatchListCache patchListCache;
+    private final Project.NameKey projectKey;
+    private final DiffPreferencesInfo diffPrefs;
+
+    public IntraLineDiffCalculatorImpl(
+        PatchListCache patchListCache, Project.NameKey projectKey, DiffPreferencesInfo diffPrefs) {
+      this.patchListCache = patchListCache;
+      this.projectKey = projectKey;
+      this.diffPrefs = diffPrefs;
+    }
+
+    @Override
+    public IntraLineDiffCalculatorResult calculateIntraLineDiff(
+        ImmutableList<Edit> edits,
+        Set<Edit> editsDueToRebase,
+        ObjectId aId,
+        ObjectId bId,
+        Text aSrc,
+        Text bSrc,
+        ObjectId bTreeId,
+        String bPath) {
+      IntraLineDiff d =
+          patchListCache.getIntraLineDiff(
+              IntraLineDiffKey.create(aId, bId, diffPrefs.ignoreWhitespace),
+              IntraLineDiffArgs.create(
+                  aSrc, bSrc, edits, editsDueToRebase, projectKey, bTreeId, bPath));
+      if (d == null) {
+        return IntraLineDiffCalculatorResult.FAILURE;
+      }
+      switch (d.getStatus()) {
+        case EDIT_LIST:
+          return IntraLineDiffCalculatorResult.success(d.getEdits());
+
+        case DISABLED:
+          return IntraLineDiffCalculatorResult.NO_RESULT;
+
+        case ERROR:
+          return IntraLineDiffCalculatorResult.FAILURE;
+
+        case TIMEOUT:
+          return IntraLineDiffCalculatorResult.TIMEOUT;
+
+        default:
+          return IntraLineDiffCalculatorResult.NO_RESULT;
+      }
+    }
+  }
+
+  private static class PatchListFileChange implements PatchFileChange {
+
+    private final PatchListEntry patchListEntry;
+
+    PatchListFileChange(PatchListEntry patchListEntry) {
+      this.patchListEntry = patchListEntry;
+    }
+
+    @Override
+    public ImmutableList<Edit> getEdits() {
+      return patchListEntry.getEdits();
+    }
+
+    @Override
+    public ImmutableSet<Edit> getEditsDueToRebase() {
+      return patchListEntry.getEditsDueToRebase();
+    }
+
+    @Override
+    public String getNewName() {
+      return patchListEntry.getNewName();
+    }
+
+    @Override
+    public String getOldName() {
+      return patchListEntry.getOldName();
+    }
+
+    @Override
+    public ChangeType getChangeType() {
+      return patchListEntry.getChangeType();
+    }
+
+    @Override
+    public List<String> getHeaderLines() {
+      return patchListEntry.getHeaderLines();
+    }
+
+    @Override
+    public Patch.PatchType getPatchType() {
+      return patchListEntry.getPatchType();
+    }
+  }
 }
diff --git a/java/com/google/gerrit/server/restapi/change/GetDiff.java b/java/com/google/gerrit/server/restapi/change/GetDiff.java
index a6536ce..82bdc34 100644
--- a/java/com/google/gerrit/server/restapi/change/GetDiff.java
+++ b/java/com/google/gerrit/server/restapi/change/GetDiff.java
@@ -14,27 +14,18 @@
 
 package com.google.gerrit.server.restapi.change;
 
-import static com.google.common.base.Preconditions.checkState;
 import static com.google.gerrit.util.cli.Localizable.localizable;
 
 import com.google.common.base.MoreObjects;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
 import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.data.PatchScript;
-import com.google.gerrit.common.data.PatchScript.DisplayMethod;
-import com.google.gerrit.entities.Patch;
 import com.google.gerrit.entities.PatchSet;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.extensions.client.DiffPreferencesInfo;
 import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
-import com.google.gerrit.extensions.common.ChangeType;
 import com.google.gerrit.extensions.common.DiffInfo;
-import com.google.gerrit.extensions.common.DiffInfo.ContentEntry;
-import com.google.gerrit.extensions.common.DiffInfo.FileMeta;
-import com.google.gerrit.extensions.common.DiffInfo.IntraLineStatus;
 import com.google.gerrit.extensions.common.DiffWebLinkInfo;
 import com.google.gerrit.extensions.common.WebLinkInfo;
 import com.google.gerrit.extensions.restapi.AuthException;
@@ -44,12 +35,12 @@
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.jgit.diff.ReplaceEdit;
-import com.google.gerrit.prettify.common.SparseFileContent;
 import com.google.gerrit.server.WebLinks;
-import com.google.gerrit.server.change.FileContentUtil;
 import com.google.gerrit.server.change.FileResource;
 import com.google.gerrit.server.change.RevisionResource;
+import com.google.gerrit.server.diff.DiffInfoCreator;
+import com.google.gerrit.server.diff.DiffSide;
+import com.google.gerrit.server.diff.DiffWebLinksProvider;
 import com.google.gerrit.server.git.LargeObjectException;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.patch.PatchScriptFactory;
@@ -60,10 +51,7 @@
 import com.google.gerrit.server.project.ProjectState;
 import com.google.inject.Inject;
 import java.io.IOException;
-import java.util.List;
-import java.util.Set;
 import java.util.concurrent.TimeUnit;
-import org.eclipse.jgit.diff.Edit;
 import org.kohsuke.args4j.CmdLineException;
 import org.kohsuke.args4j.CmdLineParser;
 import org.kohsuke.args4j.NamedOptionDef;
@@ -76,17 +64,6 @@
 public class GetDiff implements RestReadView<FileResource> {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
-  private static final ImmutableMap<Patch.ChangeType, ChangeType> CHANGE_TYPE =
-      Maps.immutableEnumMap(
-          new ImmutableMap.Builder<Patch.ChangeType, ChangeType>()
-              .put(Patch.ChangeType.ADDED, ChangeType.ADDED)
-              .put(Patch.ChangeType.MODIFIED, ChangeType.MODIFIED)
-              .put(Patch.ChangeType.DELETED, ChangeType.DELETED)
-              .put(Patch.ChangeType.RENAMED, ChangeType.RENAMED)
-              .put(Patch.ChangeType.COPIED, ChangeType.COPIED)
-              .put(Patch.ChangeType.REWRITE, ChangeType.REWRITE)
-              .build());
-
   private final ProjectCache projectCache;
   private final PatchScriptFactory.Factory patchScriptFactoryFactory;
   private final Revisions revisions;
@@ -164,111 +141,18 @@
       psf.setLoadHistory(false);
       psf.setLoadComments(context != DiffPreferencesInfo.WHOLE_FILE_CONTEXT);
       PatchScript ps = psf.call();
-      ContentCollector contentCollector = new ContentCollector(ps);
-      Set<Edit> editsDueToRebase = ps.getEditsDueToRebase();
-      for (Edit edit : ps.getEdits()) {
-        logger.atFine().log("next edit = %s", edit);
-
-        if (edit.getType() == Edit.Type.EMPTY) {
-          logger.atFine().log("skip empty edit");
-          continue;
-        }
-        contentCollector.addCommon(edit.getBeginA());
-
-        checkState(
-            contentCollector.nextA == edit.getBeginA(),
-            "nextA = %s; want %s",
-            contentCollector.nextA,
-            edit.getBeginA());
-        checkState(
-            contentCollector.nextB == edit.getBeginB(),
-            "nextB = %s; want %s",
-            contentCollector.nextB,
-            edit.getBeginB());
-        switch (edit.getType()) {
-          case DELETE:
-          case INSERT:
-          case REPLACE:
-            List<Edit> internalEdit =
-                edit instanceof ReplaceEdit ? ((ReplaceEdit) edit).getInternalEdits() : null;
-            boolean dueToRebase = editsDueToRebase.contains(edit);
-            contentCollector.addDiff(edit.getEndA(), edit.getEndB(), internalEdit, dueToRebase);
-            break;
-          case EMPTY:
-          default:
-            throw new IllegalStateException();
-        }
-      }
-      contentCollector.addCommon(ps.getA().size());
-
-      ProjectState state = projectCache.get(resource.getRevision().getChange().getProject());
-
-      DiffInfo result = new DiffInfo();
-      String revA = basePatchSet != null ? basePatchSet.refName() : ps.getFileInfoA().commitId;
-      String revB =
-          resource.getRevision().getEdit().isPresent()
-              ? resource.getRevision().getEdit().get().getRefName()
-              : resource.getRevision().getPatchSet().refName();
-      logger.atFine().log("revA = %s, revB = %s", revA, revB);
-
-      ImmutableList<DiffWebLinkInfo> links =
-          webLinks.getDiffLinks(
-              state.getName(),
-              resource.getPatchKey().patchSetId().changeId().get(),
-              basePatchSet != null ? basePatchSet.id().get() : null,
-              revA,
+      Project.NameKey projectName = resource.getRevision().getChange().getProject();
+      ProjectState state = projectCache.get(projectName);
+      DiffSide sideA =
+          DiffSide.create(
+              ps.getFileInfoA(),
               MoreObjects.firstNonNull(ps.getOldName(), ps.getNewName()),
-              resource.getPatchKey().patchSetId().get(),
-              revB,
-              ps.getNewName());
-      result.webLinks = links.isEmpty() ? null : links;
-
-      if (ps.isBinary()) {
-        result.binary = true;
-      }
-      if (ps.getDisplayMethodA() != DisplayMethod.NONE) {
-        result.metaA = new FileMeta();
-        result.metaA.name = MoreObjects.firstNonNull(ps.getOldName(), ps.getNewName());
-        result.metaA.contentType =
-            FileContentUtil.resolveContentType(
-                state, result.metaA.name, ps.getFileModeA(), ps.getMimeTypeA());
-        result.metaA.lines = ps.getA().size();
-        result.metaA.webLinks = getFileWebLinks(state.getProject(), revA, result.metaA.name);
-        result.metaA.commitId = ps.getFileInfoA().commitId;
-      }
-
-      if (ps.getDisplayMethodB() != DisplayMethod.NONE) {
-        result.metaB = new FileMeta();
-        result.metaB.name = ps.getNewName();
-        result.metaB.contentType =
-            FileContentUtil.resolveContentType(
-                state, result.metaB.name, ps.getFileModeB(), ps.getMimeTypeB());
-        result.metaB.lines = ps.getB().size();
-        result.metaB.webLinks = getFileWebLinks(state.getProject(), revB, result.metaB.name);
-        result.metaB.commitId = ps.getFileInfoB().commitId;
-      }
-
-      if (intraline) {
-        if (ps.hasIntralineTimeout()) {
-          result.intralineStatus = IntraLineStatus.TIMEOUT;
-        } else if (ps.hasIntralineFailure()) {
-          result.intralineStatus = IntraLineStatus.FAILURE;
-        } else {
-          result.intralineStatus = IntraLineStatus.OK;
-        }
-        logger.atFine().log("intralineStatus = %s", result.intralineStatus);
-      }
-
-      result.changeType = CHANGE_TYPE.get(ps.getChangeType());
-      logger.atFine().log("changeType = %s", result.changeType);
-      if (result.changeType == null) {
-        throw new IllegalStateException("unknown change type: " + ps.getChangeType());
-      }
-
-      if (ps.getPatchHeader().size() > 0) {
-        result.diffHeader = ps.getPatchHeader();
-      }
-      result.content = contentCollector.lines;
+              DiffSide.Type.SIDE_A);
+      DiffSide sideB = DiffSide.create(ps.getFileInfoB(), ps.getNewName(), DiffSide.Type.SIDE_B);
+      DiffWebLinksProvider webLinksProvider =
+          new DiffWebLinksProviderImpl(sideA, sideB, projectName, basePatchSet, webLinks, resource);
+      DiffInfoCreator diffInfoCreator = new DiffInfoCreator(state, webLinksProvider, intraline);
+      DiffInfo result = diffInfoCreator.create(ps, sideA, sideB);
 
       Response<DiffInfo> r = Response.ok(result);
       if (resource.isCacheable()) {
@@ -282,9 +166,69 @@
     }
   }
 
-  private List<WebLinkInfo> getFileWebLinks(Project project, String rev, String file) {
-    ImmutableList<WebLinkInfo> links = webLinks.getFileLinks(project.getName(), rev, file);
-    return links.isEmpty() ? null : links;
+  private static class DiffWebLinksProviderImpl implements DiffWebLinksProvider {
+
+    private final WebLinks webLinks;
+    private final Project.NameKey projectName;
+    private final DiffSide sideA;
+    private final DiffSide sideB;
+    private final String revA;
+    private final String revB;
+    private final FileResource resource;
+    @Nullable private final PatchSet basePatchSet;
+
+    DiffWebLinksProviderImpl(
+        DiffSide sideA,
+        DiffSide sideB,
+        Project.NameKey projectName,
+        @Nullable PatchSet basePatchSet,
+        WebLinks webLinks,
+        FileResource resource) {
+      this.projectName = projectName;
+      this.webLinks = webLinks;
+      this.basePatchSet = basePatchSet;
+      this.resource = resource;
+      this.sideA = sideA;
+      this.sideB = sideB;
+
+      revA = basePatchSet != null ? basePatchSet.refName() : sideA.fileInfo().commitId;
+
+      RevisionResource revision = resource.getRevision();
+      revB =
+          revision
+              .getEdit()
+              .map(edit -> edit.getRefName())
+              .orElseGet(() -> revision.getPatchSet().refName());
+
+      logger.atFine().log("revA = %s, revB = %s", revA, revB);
+    }
+
+    @Override
+    public ImmutableList<DiffWebLinkInfo> getDiffLinks() {
+      return webLinks.getDiffLinks(
+          projectName.get(),
+          resource.getPatchKey().patchSetId().changeId().get(),
+          basePatchSet != null ? basePatchSet.id().get() : null,
+          revA,
+          sideA.fileName(),
+          resource.getPatchKey().patchSetId().get(),
+          revB,
+          sideB.fileName());
+    }
+
+    @Override
+    public ImmutableList<WebLinkInfo> getFileWebLinks(DiffSide.Type type) {
+      String rev;
+      DiffSide side;
+      if (type == DiffSide.Type.SIDE_A) {
+        rev = revA;
+        side = sideA;
+      } else {
+        rev = revB;
+        side = sideB;
+      }
+      return webLinks.getFileLinks(projectName.get(), rev, side.fileName());
+    }
   }
 
   public GetDiff setBase(String base) {
@@ -312,141 +256,6 @@
     return this;
   }
 
-  private static class ContentCollector {
-    final List<ContentEntry> lines;
-    final SparseFileContent fileA;
-    final SparseFileContent fileB;
-    final boolean ignoreWS;
-
-    int nextA;
-    int nextB;
-
-    ContentCollector(PatchScript ps) {
-      lines = Lists.newArrayListWithExpectedSize(ps.getEdits().size() + 2);
-      fileA = ps.getA();
-      fileB = ps.getB();
-      ignoreWS = ps.isIgnoreWhitespace();
-    }
-
-    void addCommon(int end) {
-      logger.atFine().log("addCommon: end = %d", end);
-
-      end = Math.min(end, fileA.size());
-      logger.atFine().log("end = %d", end);
-
-      if (nextA >= end) {
-        logger.atFine().log("nextA >= end: nextA = %d, end = %d", nextA, end);
-        return;
-      }
-
-      while (nextA < end) {
-        logger.atFine().log("nextA < end: nextA = %d, end = %d", nextA, end);
-
-        if (!fileA.contains(nextA)) {
-          logger.atFine().log("fileA does not contain nextA: nextA = %d", nextA);
-
-          int endRegion = Math.min(end, nextA == 0 ? fileA.first() : fileA.next(nextA - 1));
-          int len = endRegion - nextA;
-          entry().skip = len;
-          nextA = endRegion;
-          nextB += len;
-
-          logger.atFine().log("setting: nextA = %d, nextB = %d", nextA, nextB);
-          continue;
-        }
-
-        ContentEntry e = null;
-        for (int i = nextA; i == nextA && i < end; i = fileA.next(i), nextA++, nextB++) {
-          if (ignoreWS && fileB.contains(nextB)) {
-            if (e == null || e.common == null) {
-              logger.atFine().log("create new common entry: nextA = %d, nextB = %d", nextA, nextB);
-              e = entry();
-              e.a = Lists.newArrayListWithCapacity(end - nextA);
-              e.b = Lists.newArrayListWithCapacity(end - nextA);
-              e.common = true;
-            }
-            e.a.add(fileA.get(nextA));
-            e.b.add(fileB.get(nextB));
-          } else {
-            if (e == null || e.common != null) {
-              logger.atFine().log(
-                  "create new non-common entry: nextA = %d, nextB = %d", nextA, nextB);
-              e = entry();
-              e.ab = Lists.newArrayListWithCapacity(end - nextA);
-            }
-            e.ab.add(fileA.get(nextA));
-          }
-        }
-        logger.atFine().log("nextA = %d, nextB = %d", nextA, nextB);
-      }
-    }
-
-    void addDiff(int endA, int endB, List<Edit> internalEdit, boolean dueToRebase) {
-      logger.atFine().log(
-          "addDiff: endA = %d, endB = %d, numberOfInternalEdits = %d, dueToRebase = %s",
-          endA, endB, internalEdit != null ? internalEdit.size() : 0, dueToRebase);
-
-      int lenA = endA - nextA;
-      int lenB = endB - nextB;
-      logger.atFine().log("lenA = %d, lenB = %d", lenA, lenB);
-      checkState(lenA > 0 || lenB > 0);
-
-      logger.atFine().log("create non-common entry");
-      ContentEntry e = entry();
-      if (lenA > 0) {
-        logger.atFine().log("lenA > 0: lenA = %d", lenA);
-        e.a = Lists.newArrayListWithCapacity(lenA);
-        for (; nextA < endA; nextA++) {
-          e.a.add(fileA.get(nextA));
-        }
-      }
-      if (lenB > 0) {
-        logger.atFine().log("lenB > 0: lenB = %d", lenB);
-        e.b = Lists.newArrayListWithCapacity(lenB);
-        for (; nextB < endB; nextB++) {
-          e.b.add(fileB.get(nextB));
-        }
-      }
-      if (internalEdit != null && !internalEdit.isEmpty()) {
-        logger.atFine().log("processing internal edits");
-
-        e.editA = Lists.newArrayListWithCapacity(internalEdit.size() * 2);
-        e.editB = Lists.newArrayListWithCapacity(internalEdit.size() * 2);
-        int lastA = 0;
-        int lastB = 0;
-        for (Edit edit : internalEdit) {
-          logger.atFine().log("internal edit = %s", edit);
-
-          if (edit.getBeginA() != edit.getEndA()) {
-            logger.atFine().log(
-                "edit.getBeginA() != edit.getEndA(): edit.getBeginA() = %d, edit.getEndA() = %d",
-                edit.getBeginA(), edit.getEndA());
-            e.editA.add(
-                ImmutableList.of(edit.getBeginA() - lastA, edit.getEndA() - edit.getBeginA()));
-            lastA = edit.getEndA();
-            logger.atFine().log("lastA = %d", lastA);
-          }
-          if (edit.getBeginB() != edit.getEndB()) {
-            logger.atFine().log(
-                "edit.getBeginB() != edit.getEndB(): edit.getBeginB() = %d, edit.getEndB() = %d",
-                edit.getBeginB(), edit.getEndB());
-            e.editB.add(
-                ImmutableList.of(edit.getBeginB() - lastB, edit.getEndB() - edit.getBeginB()));
-            lastB = edit.getEndB();
-            logger.atFine().log("lastB = %d", lastB);
-          }
-        }
-      }
-      e.dueToRebase = dueToRebase ? true : null;
-    }
-
-    private ContentEntry entry() {
-      ContentEntry e = new ContentEntry();
-      lines.add(e);
-      return e;
-    }
-  }
-
   @Deprecated
   enum IgnoreWhitespace {
     NONE(DiffPreferencesInfo.Whitespace.IGNORE_NONE),
@@ -462,6 +271,7 @@
   }
 
   public static class ContextOptionHandler extends OptionHandler<Short> {
+
     public ContextOptionHandler(CmdLineParser parser, OptionDef option, Setter<Short> setter) {
       super(parser, option, setter);
     }
diff --git a/java/com/google/gerrit/server/submit/MergeOp.java b/java/com/google/gerrit/server/submit/MergeOp.java
index 75ae62d..8bdd98e 100644
--- a/java/com/google/gerrit/server/submit/MergeOp.java
+++ b/java/com/google/gerrit/server/submit/MergeOp.java
@@ -41,6 +41,7 @@
 import com.google.gerrit.entities.ChangeMessage;
 import com.google.gerrit.entities.PatchSet;
 import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.SubmissionId;
 import com.google.gerrit.exceptions.StorageException;
 import com.google.gerrit.extensions.api.changes.NotifyHandling;
 import com.google.gerrit.extensions.api.changes.SubmitInput;
@@ -237,7 +238,7 @@
   private final Map<Change.Id, Change> updatedChanges;
 
   private Timestamp ts;
-  private RequestId submissionId;
+  private SubmissionId submissionId;
   private IdentifiedUser caller;
 
   private MergeOpRepoManager orm;
@@ -449,7 +450,7 @@
     this.dryrun = dryrun;
     this.caller = caller;
     this.ts = TimeUtil.nowTs();
-    this.submissionId = new RequestId(change.getId().toString());
+    this.submissionId = new SubmissionId(change);
 
     try (TraceContext traceContext =
         TraceContext.open().addTag(RequestId.Type.SUBMISSION_ID, submissionId)) {
diff --git a/java/com/google/gerrit/server/submit/SubmitStrategy.java b/java/com/google/gerrit/server/submit/SubmitStrategy.java
index 4c68e1b..efb2f76 100644
--- a/java/com/google/gerrit/server/submit/SubmitStrategy.java
+++ b/java/com/google/gerrit/server/submit/SubmitStrategy.java
@@ -21,6 +21,7 @@
 import com.google.common.collect.Sets;
 import com.google.gerrit.entities.BranchNameKey;
 import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.SubmissionId;
 import com.google.gerrit.extensions.api.changes.SubmitInput;
 import com.google.gerrit.extensions.client.SubmitType;
 import com.google.gerrit.extensions.config.FactoryModule;
@@ -42,7 +43,6 @@
 import com.google.gerrit.server.git.MergeUtil;
 import com.google.gerrit.server.git.TagCache;
 import com.google.gerrit.server.git.validators.OnSubmitValidators;
-import com.google.gerrit.server.logging.RequestId;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectConfig;
@@ -94,7 +94,7 @@
           RevFlag canMergeFlag,
           Set<RevCommit> alreadyAccepted,
           Set<CodeReviewCommit> incoming,
-          RequestId submissionId,
+          SubmissionId submissionId,
           SubmitInput submitInput,
           SubmoduleOp submoduleOp,
           boolean dryrun);
@@ -125,7 +125,7 @@
     final MergeTip mergeTip;
     final RevFlag canMergeFlag;
     final Set<RevCommit> alreadyAccepted;
-    final RequestId submissionId;
+    final SubmissionId submissionId;
     final SubmitType submitType;
     final SubmitInput submitInput;
     final SubmoduleOp submoduleOp;
@@ -164,7 +164,7 @@
         @Assisted RevFlag canMergeFlag,
         @Assisted Set<RevCommit> alreadyAccepted,
         @Assisted Set<CodeReviewCommit> incoming,
-        @Assisted RequestId submissionId,
+        @Assisted SubmissionId submissionId,
         @Assisted SubmitType submitType,
         @Assisted SubmitInput submitInput,
         @Assisted SubmoduleOp submoduleOp,
diff --git a/java/com/google/gerrit/server/submit/SubmitStrategyFactory.java b/java/com/google/gerrit/server/submit/SubmitStrategyFactory.java
index cba572bc..a015665 100644
--- a/java/com/google/gerrit/server/submit/SubmitStrategyFactory.java
+++ b/java/com/google/gerrit/server/submit/SubmitStrategyFactory.java
@@ -16,13 +16,13 @@
 
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.entities.BranchNameKey;
+import com.google.gerrit.entities.SubmissionId;
 import com.google.gerrit.extensions.api.changes.SubmitInput;
 import com.google.gerrit.extensions.client.SubmitType;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.git.CodeReviewCommit;
 import com.google.gerrit.server.git.CodeReviewCommit.CodeReviewRevWalk;
 import com.google.gerrit.server.git.MergeTip;
-import com.google.gerrit.server.logging.RequestId;
 import com.google.gerrit.server.submit.MergeOp.CommitStatus;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
@@ -52,7 +52,7 @@
       IdentifiedUser caller,
       MergeTip mergeTip,
       CommitStatus commitStatus,
-      RequestId submissionId,
+      SubmissionId submissionId,
       SubmitInput submitInput,
       SubmoduleOp submoduleOp,
       boolean dryrun)
diff --git a/java/com/google/gerrit/server/submit/SubmitStrategyOp.java b/java/com/google/gerrit/server/submit/SubmitStrategyOp.java
index b7bc58a..6be4a91 100644
--- a/java/com/google/gerrit/server/submit/SubmitStrategyOp.java
+++ b/java/com/google/gerrit/server/submit/SubmitStrategyOp.java
@@ -453,7 +453,7 @@
     Change c = ctx.getChange();
     logger.atFine().log("Setting change %s merged", c.getId());
     c.setStatus(Change.Status.MERGED);
-    c.setSubmissionId(args.submissionId.toStringForStorage());
+    c.setSubmissionId(args.submissionId.toString());
 
     // TODO(dborowitz): We need to be able to change the author of the message,
     // which is not the user from the update context. addMergedMessage was able
diff --git a/java/com/google/gerrit/server/update/RetryHelper.java b/java/com/google/gerrit/server/update/RetryHelper.java
index 4f9a67c..f3636c9 100644
--- a/java/com/google/gerrit/server/update/RetryHelper.java
+++ b/java/com/google/gerrit/server/update/RetryHelper.java
@@ -335,6 +335,12 @@
                 if (retryWithTraceOnFailure
                     && opts.retryWithTrace().isPresent()
                     && opts.retryWithTrace().get().test(t)) {
+                  // Exception hooks may identify exceptions for which retrying with trace should be
+                  // skipped.
+                  if (exceptionHooks.stream().anyMatch(h -> h.skipRetryWithTrace(t))) {
+                    return false;
+                  }
+
                   String caller = opts.caller().orElse("N/A");
                   String cause = formatCause(t);
                   if (!traceContext.isTracing()) {
diff --git a/javatests/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java b/javatests/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java
index 08719d3..ee5f117 100644
--- a/javatests/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java
@@ -29,6 +29,7 @@
 import com.google.gerrit.entities.Change;
 import com.google.gerrit.entities.PatchSet;
 import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.entities.SubmissionId;
 import com.google.gerrit.extensions.api.changes.FixInput;
 import com.google.gerrit.extensions.client.ChangeStatus;
 import com.google.gerrit.extensions.common.ChangeInfo;
@@ -39,7 +40,6 @@
 import com.google.gerrit.server.change.ConsistencyChecker;
 import com.google.gerrit.server.change.NotifyResolver;
 import com.google.gerrit.server.change.PatchSetInserter;
-import com.google.gerrit.server.logging.RequestId;
 import com.google.gerrit.server.notedb.ChangeNoteUtil;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.notedb.Sequences;
@@ -315,7 +315,7 @@
             public boolean updateChange(ChangeContext ctx) {
               ctx.getChange().setStatus(Change.Status.MERGED);
               ctx.getUpdate(ctx.getChange().currentPatchSetId())
-                  .fixStatusToMerged(new RequestId(ctx.getChange().getId().toString()));
+                  .fixStatusToMerged(new SubmissionId(ctx.getChange()));
               return true;
             }
           });
@@ -865,7 +865,7 @@
             public boolean updateChange(ChangeContext ctx) {
               ctx.getChange().setStatus(Change.Status.MERGED);
               ctx.getUpdate(ctx.getChange().currentPatchSetId())
-                  .fixStatusToMerged(new RequestId(ctx.getChange().getId().toString()));
+                  .fixStatusToMerged(new SubmissionId(ctx.getChange()));
               return true;
             }
           });
diff --git a/javatests/com/google/gerrit/integration/git/GitProtocolV2IT.java b/javatests/com/google/gerrit/integration/git/GitProtocolV2IT.java
index 9c4bdef4..8577c16 100644
--- a/javatests/com/google/gerrit/integration/git/GitProtocolV2IT.java
+++ b/javatests/com/google/gerrit/integration/git/GitProtocolV2IT.java
@@ -35,6 +35,7 @@
 import com.google.gerrit.entities.RefNames;
 import com.google.gerrit.extensions.api.GerritApi;
 import com.google.gerrit.extensions.common.ChangeInput;
+import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.group.SystemGroupBackend;
 import com.google.inject.Inject;
@@ -47,12 +48,15 @@
 
 @UseSsh
 public class GitProtocolV2IT extends StandaloneSiteTest {
+  private static final String ADMIN_PASSWORD = "secret";
   private final String[] SSH_KEYGEN_CMD =
       new String[] {"ssh-keygen", "-t", "rsa", "-q", "-P", "", "-f"};
   private final String[] GIT_LS_REMOTE =
       new String[] {"git", "-c", "protocol.version=2", "ls-remote", "-o", "trace=12345"};
   private final String[] GIT_CLONE_MIRROR =
       new String[] {"git", "-c", "protocol.version=2", "clone", "--mirror"};
+  private final String[] GIT_FETCH = new String[] {"git", "-c", "protocol.version=2", "fetch"};
+  private final String[] GIT_INIT = new String[] {"git", "init"};
   private final String GIT_SSH_COMMAND =
       "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i";
 
@@ -61,6 +65,7 @@
   @Inject private ProjectOperations projectOperations;
   @Inject private @TestSshServerAddress InetSocketAddress sshAddress;
   @Inject private @GerritServerConfig Config config;
+  @Inject private AllProjectsName allProjectsName;
 
   @BeforeClass
   public static void assertGitClientVersion() throws Exception {
@@ -131,7 +136,7 @@
               .commit;
 
       // Prepare new change on secret branch
-      in = new ChangeInput(project.get(), "secret", "Test secret change");
+      in = new ChangeInput(project.get(), ADMIN_PASSWORD, "Test secret change");
       in.newBranch = true;
 
       // Create new change and retrieve SHA1 for the created patch set
@@ -257,9 +262,82 @@
     }
   }
 
+  @Test
+  public void testGitWireProtocolV2FetchIndividualRef() throws Exception {
+    try (ServerContext ctx = startServer()) {
+      ctx.getInjector().injectMembers(this);
+
+      // Setup admin password
+      gApi.accounts().id(admin.username()).setHttpPassword(ADMIN_PASSWORD);
+
+      // Get authenticated Git/HTTP URL
+      String urlWithCredentials =
+          config
+              .getString("gerrit", null, "canonicalweburl")
+              .replace("http://", "http://" + admin.username() + ":" + ADMIN_PASSWORD + "@");
+
+      // Create project
+      Project.NameKey privateProject = Project.nameKey("private-project");
+      gApi.projects().create(privateProject.get());
+
+      // Set protocol.version=2 in target repository
+      execute(
+          ImmutableList.of("git", "config", "protocol.version", "2"),
+          sitePaths
+              .site_path
+              .resolve("git")
+              .resolve(privateProject.get() + Constants.DOT_GIT)
+              .toFile());
+
+      // Disallow general read permissions for anonymous users
+      projectOperations
+          .project(allProjectsName)
+          .forUpdate()
+          .add(deny(Permission.READ).ref("refs/*").group(SystemGroupBackend.ANONYMOUS_USERS))
+          .add(
+              allow(Permission.READ)
+                  .ref("refs/heads/master")
+                  .group(SystemGroupBackend.REGISTERED_USERS))
+          .update();
+
+      // Set up project permission to allow registered users fetching changes/*
+      projectOperations
+          .project(privateProject)
+          .forUpdate()
+          .add(
+              allow(Permission.READ)
+                  .ref("refs/changes/*")
+                  .group(SystemGroupBackend.REGISTERED_USERS))
+          .update();
+
+      // Create new change and retrieve refs for the created patch set
+      ChangeInput visibleChangeIn =
+          new ChangeInput(privateProject.get(), "master", "Test private change");
+      visibleChangeIn.newBranch = true;
+      int visibleChangeNumber = gApi.changes().create(visibleChangeIn).info()._number;
+      Change.Id changeId = Change.id(visibleChangeNumber);
+      String visibleChangeNumberRef = RefNames.patchSetRef(PatchSet.id(changeId, 1));
+
+      // Fetch a single ref using git wire protocol v2 over HTTP with authentication
+      execute(GIT_INIT);
+
+      String outFetchRef =
+          execute(
+              ImmutableList.<String>builder()
+                  .add(GIT_FETCH)
+                  .add(urlWithCredentials + "/" + privateProject.get())
+                  .add(visibleChangeNumberRef)
+                  .build(),
+              ImmutableMap.of("GIT_TRACE_PACKET", "1"));
+
+      assertThat(outFetchRef).contains("git< version 2");
+      assertThat(outFetchRef).contains(visibleChangeNumberRef);
+    }
+  }
+
   private void setUpUserAuthentication(String username) throws Exception {
     // Assign HTTP password to user
-    gApi.accounts().id(username).setHttpPassword("secret");
+    gApi.accounts().id(username).setHttpPassword(ADMIN_PASSWORD);
 
     // Generate private/public key for user
     execute(
@@ -285,6 +363,10 @@
     assertThat(out).contains(commit);
   }
 
+  private String execute(String... cmds) throws Exception {
+    return execute(ImmutableList.<String>builder().add(cmds).build());
+  }
+
   private String execute(ImmutableList<String> cmd) throws Exception {
     return execute(cmd, sitePaths.data_dir.toFile(), ImmutableMap.of());
   }
diff --git a/javatests/com/google/gerrit/prettify/BUILD b/javatests/com/google/gerrit/prettify/BUILD
new file mode 100644
index 0000000..0eb7cee
--- /dev/null
+++ b/javatests/com/google/gerrit/prettify/BUILD
@@ -0,0 +1,13 @@
+load("//tools/bzl:junit.bzl", "junit_tests")
+
+junit_tests(
+    name = "prettify_tests",
+    srcs = glob(["**/*.java"]),
+    deps = [
+        "//java/com/google/gerrit/prettify:server",
+        "//java/com/google/gerrit/prettify/common/testing",
+        "//java/com/google/gerrit/testing:gerrit-test-util",
+        "//lib:guava",
+        "//lib/truth",
+    ],
+)
diff --git a/javatests/com/google/gerrit/prettify/common/SparseFileContentBuilderTest.java b/javatests/com/google/gerrit/prettify/common/SparseFileContentBuilderTest.java
new file mode 100644
index 0000000..a751d50
--- /dev/null
+++ b/javatests/com/google/gerrit/prettify/common/SparseFileContentBuilderTest.java
@@ -0,0 +1,170 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.prettify.common;
+
+import static com.google.gerrit.prettify.common.testing.SparseFileContentSubject.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+
+import com.google.common.collect.ImmutableMap;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class SparseFileContentBuilderTest {
+
+  @Test
+  public void addLineWithNegativeNumber() {
+    SparseFileContentBuilder builder = new SparseFileContentBuilder(10);
+    assertThrows(IllegalArgumentException.class, () -> builder.addLine(-1, "First line"));
+
+    assertThrows(IllegalArgumentException.class, () -> builder.addLine(-5, "First line"));
+  }
+
+  @Test
+  @Ignore
+  public void addLineNumberZeroFileSize() {
+    // Temporary ignore - see comments in SparseFileContentBuilder.build() method
+    SparseFileContentBuilder builder = new SparseFileContentBuilder(0);
+    assertThrows(IllegalArgumentException.class, () -> builder.addLine(0, "First line"));
+  }
+
+  @Test
+  @Ignore
+  public void addLineNumberNonZeroFileSize() {
+    // Temporary ignore - see comments in SparseFileContentBuilder.build() method
+    SparseFileContentBuilder builder = new SparseFileContentBuilder(5);
+    assertThrows(IllegalArgumentException.class, () -> builder.addLine(5, "First line"));
+    assertThrows(IllegalArgumentException.class, () -> builder.addLine(6, "First line"));
+    assertThrows(IllegalArgumentException.class, () -> builder.addLine(7, "First line"));
+  }
+
+  @Test
+  public void addLineIncorrectOrder() {
+    SparseFileContentBuilder builder = new SparseFileContentBuilder(5);
+
+    builder.addLine(0, "First line");
+    builder.addLine(1, "Second line");
+    builder.addLine(3, "Third line");
+    builder.addLine(4, "Fourth line");
+    assertThrows(IllegalArgumentException.class, () -> builder.addLine(4, "Other Line"));
+
+    assertThrows(IllegalArgumentException.class, () -> builder.addLine(2, "Other Line"));
+  }
+
+  @Test
+  public void emptyContentZeroSize() {
+    SparseFileContentBuilder builder = new SparseFileContentBuilder(0);
+
+    SparseFileContent content = builder.build();
+    assertThat(content).getSize().isEqualTo(0);
+    assertThat(content).getRangesCount().isEqualTo(0);
+    assertThat(content).lines().isEmpty();
+  }
+
+  @Test
+  public void emptyContentNonZeroSize() {
+    SparseFileContentBuilder builder = new SparseFileContentBuilder(4);
+    SparseFileContent content = builder.build();
+    assertThat(content).getSize().isEqualTo(4);
+    assertThat(content).getRangesCount().isEqualTo(0);
+    assertThat(content).lines().isEmpty();
+  }
+
+  @Test
+  public void oneLineContentLineNumberZero() {
+    SparseFileContentBuilder builder = new SparseFileContentBuilder(1);
+
+    builder.addLine(0, "First line");
+    SparseFileContent content = builder.build();
+    assertThat(content).getSize().isEqualTo(1);
+    assertThat(content).getRangesCount().isEqualTo(1);
+    assertThat(content).lines().containsExactlyEntriesIn(ImmutableMap.of(0, "First line"));
+  }
+
+  @Test
+  public void oneLineContentLineNumberNotZero() {
+    SparseFileContentBuilder builder = new SparseFileContentBuilder(6);
+
+    builder.addLine(5, "First line");
+    SparseFileContent content = builder.build();
+    assertThat(content).getSize().isEqualTo(6);
+    assertThat(content).getRangesCount().isEqualTo(1);
+    assertThat(content).lines().containsExactlyEntriesIn(ImmutableMap.of(5, "First line"));
+  }
+
+  @Test
+  public void multiLineContinuousContentStartingFromZero() {
+    SparseFileContentBuilder builder = new SparseFileContentBuilder(5);
+
+    builder.addLine(0, "First line");
+    builder.addLine(1, "Second line");
+    builder.addLine(2, "Third line");
+    SparseFileContent content = builder.build();
+    assertThat(content).getSize().isEqualTo(5);
+    assertThat(content).getRangesCount().isEqualTo(1);
+    assertThat(content)
+        .lines()
+        .containsExactlyEntriesIn(
+            ImmutableMap.of(
+                0, "First line",
+                1, "Second line",
+                2, "Third line"));
+  }
+
+  @Test
+  public void multiLineContentStartingFromNonZeroLine() {
+    SparseFileContentBuilder builder = new SparseFileContentBuilder(8);
+
+    builder.addLine(5, "First line");
+    builder.addLine(6, "Second line");
+    builder.addLine(7, "Third line");
+    SparseFileContent content = builder.build();
+    assertThat(content).getSize().isEqualTo(8);
+    assertThat(content).getRangesCount().isEqualTo(1);
+    assertThat(content)
+        .lines()
+        .containsExactlyEntriesIn(
+            ImmutableMap.of(
+                5, "First line",
+                6, "Second line",
+                7, "Third line"));
+  }
+
+  @Test
+  public void multiLineContentWithGaps() {
+    SparseFileContentBuilder builder = new SparseFileContentBuilder(10000);
+    builder.addLine(0, "First line");
+    builder.addLine(1, "Second line");
+    builder.addLine(3, "Third line");
+    builder.addLine(4, "Fourth line");
+    builder.addLine(5, "Fifth line");
+    builder.addLine(6, "Sixth line");
+    builder.addLine(10, "Seventh line");
+    SparseFileContent content = builder.build();
+    assertThat(content).getSize().isEqualTo(10000);
+    assertThat(content).getRangesCount().isEqualTo(3);
+    assertThat(content)
+        .lines()
+        .containsExactlyEntriesIn(
+            ImmutableMap.builder()
+                .put(0, "First line")
+                .put(1, "Second line")
+                .put(3, "Third line")
+                .put(4, "Fourth line")
+                .put(5, "Fifth line")
+                .put(6, "Sixth line")
+                .put(10, "Seventh line")
+                .build());
+  }
+}
diff --git a/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java b/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java
index 145e914..db0dec8 100644
--- a/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java
+++ b/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java
@@ -45,6 +45,7 @@
 import com.google.gerrit.entities.CommentRange;
 import com.google.gerrit.entities.PatchSet;
 import com.google.gerrit.entities.PatchSetApproval;
+import com.google.gerrit.entities.SubmissionId;
 import com.google.gerrit.exceptions.StorageException;
 import com.google.gerrit.mail.Address;
 import com.google.gerrit.server.AssigneeStatusUpdate;
@@ -52,7 +53,6 @@
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.ReviewerSet;
 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;
@@ -432,7 +432,7 @@
   @Test
   public void approvalsPostSubmit() throws Exception {
     Change c = newChange();
-    RequestId submissionId = submissionId(c);
+    SubmissionId submissionId = new SubmissionId(c);
     ChangeUpdate update = newUpdate(c, changeOwner);
     update.putApproval("Code-Review", (short) 1);
     update.putApproval("Verified", (short) 1);
@@ -467,7 +467,7 @@
   @Test
   public void approvalsDuringSubmit() throws Exception {
     Change c = newChange();
-    RequestId submissionId = submissionId(c);
+    SubmissionId submissionId = new SubmissionId(c);
     ChangeUpdate update = newUpdate(c, changeOwner);
     update.putApproval("Code-Review", (short) 1);
     update.putApproval("Verified", (short) 1);
@@ -604,7 +604,7 @@
   @Test
   public void submitRecords() throws Exception {
     Change c = newChange();
-    RequestId submissionId = submissionId(c);
+    SubmissionId submissionId = new SubmissionId(c);
     ChangeUpdate update = newUpdate(c, changeOwner);
     update.setSubjectForCommit("Submit patch set 1");
 
@@ -640,13 +640,13 @@
                 null,
                 submitLabel("Verified", "OK", changeOwner.getAccountId()),
                 submitLabel("Alternative-Code-Review", "NEED", null)));
-    assertThat(notes.getChange().getSubmissionId()).isEqualTo(submissionId.toStringForStorage());
+    assertThat(notes.getChange().getSubmissionId()).isEqualTo(submissionId.toString());
   }
 
   @Test
   public void latestSubmitRecordsOnly() throws Exception {
     Change c = newChange();
-    RequestId submissionId = submissionId(c);
+    SubmissionId submissionId = new SubmissionId(c);
     ChangeUpdate update = newUpdate(c, changeOwner);
     update.setSubjectForCommit("Submit patch set 1");
     update.merge(
@@ -669,7 +669,7 @@
     assertThat(notes.getSubmitRecords())
         .containsExactly(
             submitRecord("OK", null, submitLabel("Code-Review", "OK", changeOwner.getAccountId())));
-    assertThat(notes.getChange().getSubmissionId()).isEqualTo(submissionId.toStringForStorage());
+    assertThat(notes.getChange().getSubmissionId()).isEqualTo(submissionId.toString());
   }
 
   @Test
@@ -977,7 +977,7 @@
     // Finish off by merging the change.
     update = newUpdate(c, changeOwner);
     update.merge(
-        submissionId(c),
+        new SubmissionId(c),
         ImmutableList.of(
             submitRecord(
                 "NOT_READY",
@@ -3140,8 +3140,4 @@
     update.commit();
     return tr.parseBody(commit);
   }
-
-  private RequestId submissionId(Change c) {
-    return new RequestId(c.getId().toString());
-  }
 }
diff --git a/javatests/com/google/gerrit/server/notedb/CommitMessageOutputTest.java b/javatests/com/google/gerrit/server/notedb/CommitMessageOutputTest.java
index 97781a4..5e25c13 100644
--- a/javatests/com/google/gerrit/server/notedb/CommitMessageOutputTest.java
+++ b/javatests/com/google/gerrit/server/notedb/CommitMessageOutputTest.java
@@ -21,9 +21,9 @@
 import com.google.common.collect.ImmutableList;
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.SubmissionId;
 import com.google.gerrit.mail.Address;
 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;
@@ -151,7 +151,7 @@
     ChangeUpdate update = newUpdate(c, changeOwner);
     update.setSubjectForCommit("Submit patch set 1");
 
-    RequestId submissionId = submissionId(c);
+    SubmissionId submissionId = new SubmissionId(c);
     update.merge(
         submissionId,
         ImmutableList.of(
@@ -174,7 +174,7 @@
             + "Patch-set: 1\n"
             + "Status: merged\n"
             + "Submission-id: "
-            + submissionId.toStringForStorage()
+            + submissionId.toString()
             + "\n"
             + "Submitted-with: NOT_READY\n"
             + "Submitted-with: OK: Verified: Gerrit User 1 <1@gerrit>\n"
@@ -223,7 +223,7 @@
     ChangeUpdate update = newUpdate(c, changeOwner);
     update.setSubjectForCommit("Submit patch set 1");
 
-    RequestId submissionId = submissionId(c);
+    SubmissionId submissionId = new SubmissionId(c);
     update.merge(
         submissionId, ImmutableList.of(submitRecord("RULE_ERROR", "Problem with patch set:\n1")));
     update.commit();
@@ -234,7 +234,7 @@
             + "Patch-set: 1\n"
             + "Status: merged\n"
             + "Submission-id: "
-            + submissionId.toStringForStorage()
+            + submissionId.toString()
             + "\n"
             + "Submitted-with: RULE_ERROR Problem with patch set: 1\n",
         update.getResult());
@@ -427,8 +427,4 @@
     RevCommit commit = parseCommit(commitId);
     assertThat(commit.getFullMessage()).isEqualTo(expected);
   }
-
-  private RequestId submissionId(Change c) {
-    return new RequestId(c.getId().toString());
-  }
 }
diff --git a/javatests/com/google/gerrit/server/update/BatchUpdateTest.java b/javatests/com/google/gerrit/server/update/BatchUpdateTest.java
index 5860c48..c7ca887 100644
--- a/javatests/com/google/gerrit/server/update/BatchUpdateTest.java
+++ b/javatests/com/google/gerrit/server/update/BatchUpdateTest.java
@@ -26,12 +26,12 @@
 import com.google.gerrit.entities.PatchSet;
 import com.google.gerrit.entities.Project;
 import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.entities.SubmissionId;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.change.ChangeInserter;
 import com.google.gerrit.server.change.PatchSetInserter;
 import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.logging.RequestId;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.notedb.ChangeUpdate;
 import com.google.gerrit.server.notedb.Sequences;
@@ -332,7 +332,7 @@
       cr.label = "Code-Review";
       sr.labels = ImmutableList.of(cr);
       ChangeUpdate update = ctx.getUpdate(ctx.getChange().currentPatchSetId());
-      update.merge(new RequestId(), ImmutableList.of(sr));
+      update.merge(new SubmissionId(ctx.getChange()), ImmutableList.of(sr));
       update.setChangeMessage("Submitted");
       return true;
     }
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.html b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.html
index b116171..d801638 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog_test.html
@@ -182,6 +182,11 @@
       test('input text change triggers function', () => {
         sandbox.spy(element, '_getRecentChanges');
         element.$.parentInput.noDebounce = true;
+        MockInteractions.pressAndReleaseKeyOn(
+            element.$.parentInput.$.input,
+            13,
+            null,
+            'enter');
         element._text = '1';
         assert.isTrue(element._getRecentChanges.calledOnce);
         element._text = '12';
diff --git a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js
index d169728..3f2f4bd 100644
--- a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js
+++ b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js
@@ -24,7 +24,6 @@
     CATEGORY_RPC: 'RPC Timing',
     // Reported events - alphabetize below.
     APP_STARTED: 'App Started',
-    PAGE_LOADED: 'Page Loaded',
   };
 
   // Plugin-related reporting constants.
@@ -298,38 +297,21 @@
         console.error('pageLoaded should be called after window.onload');
         this.async(this.pageLoaded, 100);
       } else {
-        const loadTime = this.performanceTiming.loadEventEnd -
+        const perfEvents = Object.keys(this.performanceTiming.toJSON());
+        perfEvents.forEach(
+            eventName => this._reportPerformanceTiming(eventName)
+        );
+      }
+    },
+
+    _reportPerformanceTiming(eventName) {
+      const eventTiming = this.performanceTiming[eventName];
+      if (eventTiming > 0) {
+        const elapsedTime = eventTiming -
             this.performanceTiming.navigationStart;
+        // NavResTime - Navigation and resource timings.
         this.reporter(TIMING.TYPE, TIMING.CATEGORY_UI_LATENCY,
-            TIMING.PAGE_LOADED, loadTime, true);
-
-        const requestStart = this.performanceTiming.requestStart -
-            this.performanceTiming.navigationStart;
-        this.reporter(TIMING.TYPE, TIMING.CATEGORY_UI_LATENCY,
-            'requestStart', requestStart, true);
-
-        const responseEnd = this.performanceTiming.responseEnd -
-            this.performanceTiming.navigationStart;
-        this.reporter(TIMING.TYPE, TIMING.CATEGORY_UI_LATENCY,
-            'responseEnd', responseEnd, true);
-
-        const domLoading = this.performanceTiming.domLoading -
-          this.performanceTiming.navigationStart;
-        this.reporter(TIMING.TYPE, TIMING.CATEGORY_UI_LATENCY,
-            'domLoading', domLoading, true);
-
-        const domContentLoadedEventStart =
-          this.performanceTiming.domContentLoadedEventStart -
-          this.performanceTiming.navigationStart;
-        this.reporter(TIMING.TYPE, TIMING.CATEGORY_UI_LATENCY,
-            'domContentLoadedEventStart', domContentLoadedEventStart, true);
-
-        if (this.performanceTiming.redirectEnd > 0) {
-          const redirectEnd = this.performanceTiming.redirectEnd -
-              this.performanceTiming.navigationStart;
-          this.reporter(TIMING.TYPE, TIMING.CATEGORY_UI_LATENCY,
-              'redirectEnd', redirectEnd, true);
-        }
+            `NavResTime - ${eventName}`, elapsedTime, true);
       }
     },
 
diff --git a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html
index c2c0297..430b41e 100644
--- a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html
+++ b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html
@@ -52,6 +52,7 @@
         navigationStart: 1,
         loadEventEnd: 2,
       };
+      fakePerformance.toJSON = () => fakePerformance;
       sinon.stub(element, 'performanceTiming',
           {get() { return fakePerformance; }});
       sandbox.stub(element, 'reporter');
@@ -83,7 +84,7 @@
       element.pageLoaded();
       assert.isTrue(
           element.reporter.calledWithExactly(
-              'timing-report', 'UI Latency', 'Page Loaded',
+              'timing-report', 'UI Latency', 'NavResTime - loadEventEnd',
               fakePerformance.loadEventEnd - fakePerformance.navigationStart,
               true)
       );
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js
index 3a60255..be3c48f 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js
@@ -264,13 +264,23 @@
         return;
       }
 
+      // Reset _suggestions for every update
+      // This will also prevent from carrying over suggestions:
+      // @see Issue 12039
+      this._suggestions = [];
+
+      // TODO(taoalpha): Also skip if text has not changed
+
       if (this._disableSuggestions) { return; }
       if (text.length < threshold) {
-        this._suggestions = [];
         this.value = '';
         return;
       }
 
+      if (!this._focused) {
+        return;
+      }
+
       const update = () => {
         this.query(text).then(suggestions => {
           if (text !== this.text) {
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html
index 39329e5..9df3cfc 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html
@@ -38,6 +38,10 @@
   suite('gr-autocomplete tests', () => {
     let element;
     let sandbox;
+    const focusOnInput = element => {
+      MockInteractions.pressAndReleaseKeyOn(element.$.input, 13, null,
+          'enter');
+    };
 
     setup(() => {
       element = fixture('basic');
@@ -63,6 +67,7 @@
       assert.isTrue(element.$.suggestions.isHidden);
       assert.equal(element.$.suggestions.$.cursor.index, -1);
 
+      focusOnInput(element);
       element.text = 'blah';
 
       assert.isTrue(queryStub.called);
@@ -186,6 +191,7 @@
         return promise = Promise.resolve([{name: 'suggestion', value: 0}]);
       });
       element.query = queryStub;
+      focusOnInput(element);
       element.text = 'blah';
 
       promise.then(() => {
@@ -207,6 +213,7 @@
         return promise = Promise.resolve([{name: 'suggestion', value: 0}]);
       });
       element.query = queryStub;
+      focusOnInput(element);
       element.text = 'blah';
       element.clearOnCommit = true;
 
@@ -228,15 +235,11 @@
         return Promise.resolve([]);
       });
       element.query = queryStub;
-
       element.threshold = 2;
-
+      focusOnInput(element);
       element.text = 'a';
-
       assert.isFalse(queryStub.called);
-
       element.text = 'ab';
-
       assert.isTrue(queryStub.called);
     });
 
@@ -249,6 +252,7 @@
           (name, cb) => { callback = cb; });
       element.query = queryStub;
       element.noDebounce = false;
+      focusOnInput(element);
       element.text = 'a';
       assert.isFalse(queryStub.called);
       assert.isTrue(debounceStub.called);
@@ -269,11 +273,60 @@
       assert.equal(element._suggestions.length, 0);
     });
 
+    test('when focused', done => {
+      let promise;
+      const queryStub = sandbox.stub()
+          .returns(promise = Promise.resolve([{name: 'suggestion', value: 0}]));
+      element.query = queryStub;
+      element.suggestOnlyWhenFocus = true;
+      focusOnInput(element);
+      element.text = 'bla';
+      assert.equal(element._focused, true);
+      flushAsynchronousOperations();
+      promise.then(() => {
+        assert.equal(element._suggestions.length, 1);
+        assert.equal(queryStub.notCalled, false);
+        done();
+      });
+    });
+
+    test('when not focused', done => {
+      let promise;
+      const queryStub = sandbox.stub()
+          .returns(promise = Promise.resolve([{name: 'suggestion', value: 0}]));
+      element.query = queryStub;
+      element.suggestOnlyWhenFocus = true;
+      element.text = 'bla';
+      assert.equal(element._focused, false);
+      flushAsynchronousOperations();
+      promise.then(() => {
+        assert.equal(element._suggestions.length, 0);
+        done();
+      });
+    });
+
+    test('suggestions should not carry over', done => {
+      let promise;
+      const queryStub = sandbox.stub()
+          .returns(promise = Promise.resolve([{name: 'suggestion', value: 0}]));
+      element.query = queryStub;
+      focusOnInput(element);
+      element.text = 'bla';
+      flushAsynchronousOperations();
+      promise.then(() => {
+        assert.equal(element._suggestions.length, 1);
+        element._updateSuggestions('', 0, false);
+        assert.equal(element._suggestions.length, 0);
+        done();
+      });
+    });
+
     test('multi completes only the last part of the query', done => {
       let promise;
       const queryStub = sandbox.stub()
           .returns(promise = Promise.resolve([{name: 'suggestion', value: 0}]));
       element.query = queryStub;
+      focusOnInput(element);
       element.text = 'blah blah';
       element.multi = true;