Merge "Add comment count to patchset-changed events"
diff --git a/Documentation/intro-user.txt b/Documentation/intro-user.txt
index b91adbb..2483ba3 100644
--- a/Documentation/intro-user.txt
+++ b/Documentation/intro-user.txt
@@ -527,9 +527,6 @@
 informational grouping; changes with the same hashtags are not necessarily
 submitted together.
 
-The hashtag feature is only available when running under
-link:note-db.html[NoteDb].
-
 .Set Hashtag on Push
 ----
   $ git push origin HEAD:refs/for/master%t=stable-bugfix
diff --git a/Documentation/note-db.txt b/Documentation/note-db.txt
index 89758a0..a13cbfb 100644
--- a/Documentation/note-db.txt
+++ b/Documentation/note-db.txt
@@ -1,9 +1,10 @@
 :linkattrs:
 = Gerrit Code Review - NoteDb Backend
 
-NoteDb is the next generation of Gerrit storage backend, which replaces the
-traditional SQL backend for change, account and group metadata with storing
-data in the same repository as code changes.
+NoteDb is the storage backend for code review metadata. It is based on
+Git, so code reviews are stored together with the code under review.
+NoteDb replaced the traditional SQL backend for change, account and group
+metadata that was used in the 2.x series.
 
 .Advantages
 - *Simplicity*: All data is stored in one location in the site directory, rather
@@ -22,33 +23,19 @@
 - *New features*: Enables simple federation between Gerrit servers, as well as
   offline code review and interoperation with other tools.
 
-== Current Status
-
-- Storing change metadata is fully implemented in the 2.15 release, and is the
-  default for new sites.
-- Admins may use an link:#offline-migration[offline] or
-  link:#online-migration[online] tool to migrate change data in an existing
-  site from ReviewDb.
-- Storing link:config-accounts.html[account data] is fully implemented in the
-  2.15 release. Account data is migrated automatically during the upgrade
-  process by running `gerrit.war init`.
-- Storing link:config-groups.html[group metadata] is fully implemented
-  in the 2.16 release. Group data is migrated automatically during
-  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,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
-  available in 3.0.
-
 For an example NoteDb change, poke around at this one:
 ----
   git fetch https://gerrit.googlesource.com/gerrit refs/changes/70/98070/meta \
       && git log -p FETCH_HEAD
 ----
 
+== Current Status
+
+NoteDb is the only database format supported by Gerrit 3.0+. The
+change data migration tools are only included in Gerrit 2.16; they are
+not available in 3.0, so any upgrade from Gerrit 2.x to 3.x must go through
+2.16 to effect the NoteDb upgrade.
+
 [[migration]]
 == Migration
 
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index b483db8..25d6518 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -1009,8 +1009,6 @@
 Returns a list of every user ever assigned to a change, in the order in which
 they were first assigned.
 
-[NOTE] Past assignees are only available when NoteDb is enabled.
-
 .Request
 ----
   GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/past_assignees HTTP/1.0
@@ -2497,8 +2495,6 @@
 
 Gets the hashtags associated with a change.
 
-[NOTE] Hashtags are only available when NoteDb is enabled.
-
 .Request
 ----
   GET /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/hashtags HTTP/1.0
@@ -2527,8 +2523,6 @@
 
 Adds and/or removes hashtags from a change.
 
-[NOTE] Hashtags are only available when NoteDb is enabled.
-
 The hashtags to add or remove must be provided in the request body inside a
 link:#hashtags-input[HashtagsInput] entity.
 
@@ -6291,8 +6285,7 @@
 |`reviewer_updates`|optional|
 Updates to reviewers set for the change as
 link:#review-update-info[ReviewerUpdateInfo] entities.
-Only set if link:#reviewer-updates[reviewer updates] are requested and
-if NoteDb is enabled.
+Only set if link:#reviewer-updates[reviewer updates] are requested.
 |`messages`|optional|
 Messages associated with the change as a list of
 link:#change-message-info[ChangeMessageInfo] entities. +
diff --git a/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CheckMasterBranchReplica1.json b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CheckMasterBranchReplica1.json
new file mode 100644
index 0000000..54c54f8
--- /dev/null
+++ b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CheckMasterBranchReplica1.json
@@ -0,0 +1,5 @@
+[
+  {
+    "url": "http://HOSTNAME:HTTP_PORT1/a/projects/PROJECT/branches/master"
+  }
+]
diff --git a/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/GetMasterBranchRevision.json b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/GetMasterBranchRevision.json
new file mode 100644
index 0000000..2b8809a
--- /dev/null
+++ b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/GetMasterBranchRevision.json
@@ -0,0 +1,5 @@
+[
+  {
+    "url": "http://HOSTNAME:HTTP_PORT/a/projects/PROJECT/branches/master"
+  }
+]
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/ApproveChange.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/ApproveChange.scala
index fe46bd6..e808d0d 100644
--- a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/ApproveChange.scala
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/ApproveChange.scala
@@ -43,6 +43,6 @@
 
   setUp(
     test.inject(
-      atOnceUsers(1)
+      atOnceUsers(single)
     )).protocols(httpProtocol)
 }
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CheckMasterBranchReplica1.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CheckMasterBranchReplica1.scala
new file mode 100644
index 0000000..96269f2
--- /dev/null
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CheckMasterBranchReplica1.scala
@@ -0,0 +1,73 @@
+// Copyright (C) 2020 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.scenarios
+
+import com.typesafe.config.ConfigFactory
+import io.gatling.core.Predef._
+import io.gatling.core.feeder.FeederBuilder
+import io.gatling.core.structure.ScenarioBuilder
+import io.gatling.http.Predef._
+
+import scala.concurrent.duration._
+
+class CheckMasterBranchReplica1 extends ProjectSimulation {
+  private val data: FeederBuilder = jsonFile(resource).convert(keys).queue
+
+  override def replaceOverride(in: String): String = {
+    val next = replaceProperty("http_port1", 8081, in)
+    super.replaceOverride(next)
+  }
+
+  private val httpForReplica = http.basicAuth(
+    conf.httpConfiguration.userName,
+    ConfigFactory.load().getString("http.password_replica"))
+
+  private val createChange = new CreateChange
+  private val approveChange = new ApproveChange(createChange)
+  private val submitChange = new SubmitChange(createChange)
+  private val getBranch = new GetMasterBranchRevision
+
+  private val test: ScenarioBuilder = scenario(unique)
+      .feed(data)
+      .exec(session => {
+        session.set(getBranch.revisionKey, getBranch.revision.get)
+      })
+      .exec(http(unique).get("${url}")
+          .check(regex(getBranch.revisionPattern)
+              .is(session => session(getBranch.revisionKey).as[String])))
+
+  setUp(
+    createChange.test.inject(
+      nothingFor(stepWaitTime(createChange) seconds),
+      atOnceUsers(single)
+    ),
+    approveChange.test.inject(
+      nothingFor(stepWaitTime(approveChange) seconds),
+      atOnceUsers(single)
+    ),
+    submitChange.test.inject(
+      nothingFor(stepWaitTime(submitChange) seconds),
+      atOnceUsers(single)
+    ),
+    getBranch.test.inject(
+      nothingFor(stepWaitTime(getBranch) seconds),
+      atOnceUsers(single)
+    ),
+    test.inject(
+      nothingFor(stepWaitTime(this) seconds),
+      atOnceUsers(single)
+    ).protocols(httpForReplica),
+  ).protocols(httpProtocol)
+}
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CheckProjectsCacheFlushEntries.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CheckProjectsCacheFlushEntries.scala
index 2424209..f1966b3 100644
--- a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CheckProjectsCacheFlushEntries.scala
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CheckProjectsCacheFlushEntries.scala
@@ -42,6 +42,6 @@
 
   setUp(
     test.inject(
-      atOnceUsers(1)
+      atOnceUsers(single)
     )).protocols(httpProtocol)
 }
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CloneUsingBothProtocols.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CloneUsingBothProtocols.scala
index c70c393..9f01e9f 100644
--- a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CloneUsingBothProtocols.scala
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CloneUsingBothProtocols.scala
@@ -39,15 +39,15 @@
   setUp(
     createProject.test.inject(
       nothingFor(stepWaitTime(createProject) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
     test.inject(
       nothingFor(stepWaitTime(this) seconds),
-      constantUsersPerSec(1) during (duration seconds)
+      constantUsersPerSec(single) during (duration seconds)
     ),
     deleteProject.test.inject(
       nothingFor(stepWaitTime(deleteProject) + duration seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
   ).protocols(gitProtocol, httpProtocol)
 }
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CreateChange.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CreateChange.scala
index c7fb8ed..ce37777 100644
--- a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CreateChange.scala
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CreateChange.scala
@@ -50,19 +50,19 @@
   setUp(
     createProject.test.inject(
       nothingFor(stepWaitTime(createProject) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
     test.inject(
       nothingFor(stepWaitTime(this) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
     deleteChange.test.inject(
       nothingFor(stepWaitTime(deleteChange) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
     deleteProject.test.inject(
       nothingFor(stepWaitTime(deleteProject) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
   ).protocols(httpProtocol)
 }
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CreateProject.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CreateProject.scala
index 3d5e677..d631292 100644
--- a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CreateProject.scala
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/CreateProject.scala
@@ -32,6 +32,6 @@
 
   setUp(
     test.inject(
-      atOnceUsers(1)
+      atOnceUsers(single)
     )).protocols(httpProtocol)
 }
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/DeleteChange.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/DeleteChange.scala
index aa6fe0d..5a06ff7 100644
--- a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/DeleteChange.scala
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/DeleteChange.scala
@@ -43,6 +43,6 @@
 
   setUp(
     test.inject(
-      atOnceUsers(1)
+      atOnceUsers(single)
     )).protocols(httpProtocol)
 }
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/DeleteProject.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/DeleteProject.scala
index 983ac0b..2007eba 100644
--- a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/DeleteProject.scala
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/DeleteProject.scala
@@ -32,6 +32,6 @@
 
   setUp(
     test.inject(
-      atOnceUsers(1)
+      atOnceUsers(single)
     )).protocols(httpProtocol)
 }
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/FlushProjectsCache.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/FlushProjectsCache.scala
index 94f4ae3..3dd8493 100644
--- a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/FlushProjectsCache.scala
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/FlushProjectsCache.scala
@@ -38,23 +38,23 @@
   setUp(
     createProject.test.inject(
       nothingFor(stepWaitTime(createProject) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
     getCacheEntriesAfterProject.test.inject(
       nothingFor(stepWaitTime(getCacheEntriesAfterProject) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
     flushCache.inject(
       nothingFor(stepWaitTime(this) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
     checkCacheEntriesAfterFlush.test.inject(
       nothingFor(stepWaitTime(checkCacheEntriesAfterFlush) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
     deleteProject.test.inject(
       nothingFor(stepWaitTime(deleteProject) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
   ).protocols(httpProtocol)
 }
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GerritSimulation.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GerritSimulation.scala
index 5d6176d..fc68f97 100644
--- a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GerritSimulation.scala
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GerritSimulation.scala
@@ -30,6 +30,7 @@
   protected val resource: String = s"$pathName.json"
   protected val body: String = s"$pathName-body.json"
   protected val unique: String = name + "-" + this.hashCode()
+  protected val single = 1
 
   private val powerFactor: Double = replaceProperty("power_factor", 1.0).toDouble
   protected val SecondsPerWeightUnit: Int = 2
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GetMasterBranchRevision.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GetMasterBranchRevision.scala
new file mode 100644
index 0000000..4ceba60
--- /dev/null
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GetMasterBranchRevision.scala
@@ -0,0 +1,41 @@
+// Copyright (C) 2020 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.scenarios
+
+import io.gatling.core.Predef._
+import io.gatling.core.feeder.FeederBuilder
+import io.gatling.core.structure.ScenarioBuilder
+import io.gatling.http.Predef._
+
+class GetMasterBranchRevision extends ProjectSimulation {
+  private val data: FeederBuilder = jsonFile(resource).convert(keys).queue
+  var revision: Option[String] = None
+  val revisionKey = "revision"
+  val revisionPattern: String = "\"" + revisionKey + "\": \"(.+)\""
+
+  val test: ScenarioBuilder = scenario(unique)
+      .feed(data)
+      .exec(http(unique).get("${url}")
+          .check(regex(revisionPattern).saveAs(revisionKey)))
+      .exec(session => {
+        revision = Some(session(revisionKey).as[String])
+        session
+      })
+
+  setUp(
+    test.inject(
+      atOnceUsers(single)
+    )).protocols(httpProtocol)
+}
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GetProjectsCacheEntries.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GetProjectsCacheEntries.scala
index 27e3f19..e73559e 100644
--- a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GetProjectsCacheEntries.scala
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/GetProjectsCacheEntries.scala
@@ -40,6 +40,6 @@
 
   setUp(
     test.inject(
-      atOnceUsers(1)
+      atOnceUsers(single)
     )).protocols(httpProtocol)
 }
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/ReplayRecordsFromFeeder.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/ReplayRecordsFromFeeder.scala
index e5b41b3..1af2dc5 100644
--- a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/ReplayRecordsFromFeeder.scala
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/ReplayRecordsFromFeeder.scala
@@ -43,7 +43,7 @@
   setUp(
     createProject.test.inject(
       nothingFor(stepWaitTime(createProject) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
     test.inject(
       nothingFor(stepWaitTime(this) seconds),
@@ -54,7 +54,7 @@
     ),
     deleteProject.test.inject(
       nothingFor(maxBeforeDelete seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
   ).protocols(gitProtocol, httpProtocol)
       .maxDuration(maxExecutionTime seconds)
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/SubmitChange.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/SubmitChange.scala
index 2f67274..48f9fa8 100644
--- a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/SubmitChange.scala
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/SubmitChange.scala
@@ -24,8 +24,16 @@
 class SubmitChange extends GerritSimulation {
   private val data: FeederBuilder = jsonFile(resource).convert(keys).queue
   private val default: String = name
+  private var createChange = new CreateChange(default)
 
-  private val test: ScenarioBuilder = scenario(unique)
+  override def relativeRuntimeWeight = 10
+
+  def this(createChange: CreateChange) {
+    this()
+    this.createChange = createChange
+  }
+
+  val test: ScenarioBuilder = scenario(unique)
       .feed(data)
       .exec(session => {
         session.set("number", createChange.number)
@@ -33,30 +41,29 @@
       .exec(http(unique).post("${url}${number}/submit"))
 
   private val createProject = new CreateProject(default)
-  private val createChange = new CreateChange(default)
   private val approveChange = new ApproveChange(createChange)
   private val deleteProject = new DeleteProject(default)
 
   setUp(
     createProject.test.inject(
       nothingFor(stepWaitTime(createProject) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
     createChange.test.inject(
       nothingFor(stepWaitTime(createChange) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
     approveChange.test.inject(
       nothingFor(stepWaitTime(approveChange) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
     test.inject(
       nothingFor(stepWaitTime(this) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
     deleteProject.test.inject(
       nothingFor(stepWaitTime(deleteProject) seconds),
-      atOnceUsers(1)
+      atOnceUsers(single)
     ),
   ).protocols(httpProtocol)
 }
diff --git a/java/com/google/gerrit/server/project/GroupList.java b/java/com/google/gerrit/server/project/GroupList.java
index 7237bb6..9b65413 100644
--- a/java/com/google/gerrit/server/project/GroupList.java
+++ b/java/com/google/gerrit/server/project/GroupList.java
@@ -74,7 +74,7 @@
   public GroupReference byName(String name) {
     return byUUID.entrySet().stream()
         .map(Map.Entry::getValue)
-        .filter(groupReference -> name.equals(groupReference.getName()))
+        .filter(groupReference -> groupReference.getName().equals(name))
         .findAny()
         .orElse(null);
   }
diff --git a/polygerrit-ui/package.json b/polygerrit-ui/package.json
index 6e85b77..7de55aa 100644
--- a/polygerrit-ui/package.json
+++ b/polygerrit-ui/package.json
@@ -14,7 +14,7 @@
     "karma-mocha": "^2.0.1",
     "karma-mocha-reporter": "^2.2.5",
     "lodash": "^4.17.15",
-    "mocha": "^7.1.1",
+    "mocha": "7.2.0",
     "sinon": "^9.0.2"
   },
   "license": "Apache-2.0",
diff --git a/polygerrit-ui/yarn.lock b/polygerrit-ui/yarn.lock
index 0f42737..dfc5a43 100644
--- a/polygerrit-ui/yarn.lock
+++ b/polygerrit-ui/yarn.lock
@@ -1980,22 +1980,22 @@
   dependencies:
     is-arrayish "^0.2.1"
 
-es-abstract@^1.17.0-next.1:
-  version "1.17.4"
-  resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.4.tgz#e3aedf19706b20e7c2594c35fc0d57605a79e184"
-  integrity sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==
+es-abstract@^1.17.0-next.1, es-abstract@^1.17.5:
+  version "1.17.6"
+  resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.6.tgz#9142071707857b2cacc7b89ecb670316c3e2d52a"
+  integrity sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==
   dependencies:
     es-to-primitive "^1.2.1"
     function-bind "^1.1.1"
     has "^1.0.3"
     has-symbols "^1.0.1"
-    is-callable "^1.1.5"
-    is-regex "^1.0.5"
+    is-callable "^1.2.0"
+    is-regex "^1.1.0"
     object-inspect "^1.7.0"
     object-keys "^1.1.1"
     object.assign "^4.1.0"
-    string.prototype.trimleft "^2.1.1"
-    string.prototype.trimright "^2.1.1"
+    string.prototype.trimend "^1.0.1"
+    string.prototype.trimstart "^1.0.1"
 
 es-dev-server@^1.56.0:
   version "1.56.0"
@@ -2237,7 +2237,12 @@
   resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
   integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
 
-fsevents@~2.1.1, fsevents@~2.1.2:
+fsevents@~2.1.1:
+  version "2.1.3"
+  resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e"
+  integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==
+
+fsevents@~2.1.2:
   version "2.1.2"
   resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.2.tgz#4c0a1fb34bc68e543b4b82a9ec392bfbda840805"
   integrity sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==
@@ -2520,10 +2525,10 @@
   resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.4.tgz#3e572f23c8411a5cfd9557c849e3665e0b290623"
   integrity sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==
 
-is-callable@^1.1.4, is-callable@^1.1.5:
-  version "1.1.5"
-  resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab"
-  integrity sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==
+is-callable@^1.1.4, is-callable@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.0.tgz#83336560b54a38e35e3a2df7afd0454d691468bb"
+  integrity sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==
 
 is-date-object@^1.0.1:
   version "1.0.2"
@@ -2567,12 +2572,12 @@
   resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
   integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
 
-is-regex@^1.0.5:
-  version "1.0.5"
-  resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.5.tgz#39d589a358bf18967f726967120b8fc1aed74eae"
-  integrity sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==
+is-regex@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.0.tgz#ece38e389e490df0dc21caea2bd596f987f767ff"
+  integrity sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==
   dependencies:
-    has "^1.0.3"
+    has-symbols "^1.0.1"
 
 is-stream@^2.0.0:
   version "2.0.0"
@@ -3040,24 +3045,17 @@
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
   integrity sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=
 
-mkdirp@0.5.3:
-  version "0.5.3"
-  resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.3.tgz#5a514b7179259287952881e94410ec5465659f8c"
-  integrity sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg==
-  dependencies:
-    minimist "^1.2.5"
-
-mkdirp@^0.5.1:
+mkdirp@0.5.5, mkdirp@^0.5.1:
   version "0.5.5"
   resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
   integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
   dependencies:
     minimist "^1.2.5"
 
-mocha@^7.1.1:
-  version "7.1.1"
-  resolved "https://registry.yarnpkg.com/mocha/-/mocha-7.1.1.tgz#89fbb30d09429845b1bb893a830bf5771049a441"
-  integrity sha512-3qQsu3ijNS3GkWcccT5Zw0hf/rWvu1fTN9sPvEd81hlwsr30GX2GcDSSoBxo24IR8FelmrAydGC6/1J5QQP4WA==
+mocha@7.2.0:
+  version "7.2.0"
+  resolved "https://registry.yarnpkg.com/mocha/-/mocha-7.2.0.tgz#01cc227b00d875ab1eed03a75106689cfed5a604"
+  integrity sha512-O9CIypScywTVpNaRrCAgoUnJgozpIofjKUYmJhiCIJMiuYnLI6otcb1/kpW9/n/tJODHGZ7i8aLQoDVsMtOKQQ==
   dependencies:
     ansi-colors "3.2.3"
     browser-stdout "1.3.1"
@@ -3072,7 +3070,7 @@
     js-yaml "3.13.1"
     log-symbols "3.0.0"
     minimatch "3.0.4"
-    mkdirp "0.5.3"
+    mkdirp "0.5.5"
     ms "2.1.1"
     node-environment-flags "1.0.6"
     object.assign "4.1.0"
@@ -3180,9 +3178,9 @@
   integrity sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=
 
 object-inspect@^1.7.0:
-  version "1.7.0"
-  resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67"
-  integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==
+  version "1.8.0"
+  resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.8.0.tgz#df807e5ecf53a609cc6bfe93eac3cc7be5b3a9d0"
+  integrity sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==
 
 object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1:
   version "1.1.1"
@@ -3840,21 +3838,21 @@
     is-fullwidth-code-point "^2.0.0"
     strip-ansi "^5.1.0"
 
-string.prototype.trimleft@^2.1.1:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz#9bdb8ac6abd6d602b17a4ed321870d2f8dcefc74"
-  integrity sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==
+string.prototype.trimend@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913"
+  integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==
   dependencies:
     define-properties "^1.1.3"
-    function-bind "^1.1.1"
+    es-abstract "^1.17.5"
 
-string.prototype.trimright@^2.1.1:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz#440314b15996c866ce8a0341894d45186200c5d9"
-  integrity sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==
+string.prototype.trimstart@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54"
+  integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==
   dependencies:
     define-properties "^1.1.3"
-    function-bind "^1.1.1"
+    es-abstract "^1.17.5"
 
 strip-ansi@^4.0.0:
   version "4.0.0"
@@ -4245,14 +4243,6 @@
     camelcase "^5.0.0"
     decamelize "^1.2.0"
 
-yargs-parser@^13.1.1:
-  version "13.1.1"
-  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.1.tgz#d26058532aa06d365fe091f6a1fc06b2f7e5eca0"
-  integrity sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==
-  dependencies:
-    camelcase "^5.0.0"
-    decamelize "^1.2.0"
-
 yargs-unparser@1.6.0:
   version "1.6.0"
   resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-1.6.0.tgz#ef25c2c769ff6bd09e4b0f9d7c605fb27846ea9f"
@@ -4262,7 +4252,7 @@
     lodash "^4.17.15"
     yargs "^13.3.0"
 
-yargs@13.3.2:
+yargs@13.3.2, yargs@^13.3.0:
   version "13.3.2"
   resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd"
   integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==
@@ -4278,22 +4268,6 @@
     y18n "^4.0.0"
     yargs-parser "^13.1.2"
 
-yargs@^13.3.0:
-  version "13.3.0"
-  resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.0.tgz#4c657a55e07e5f2cf947f8a366567c04a0dedc83"
-  integrity sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==
-  dependencies:
-    cliui "^5.0.0"
-    find-up "^3.0.0"
-    get-caller-file "^2.0.1"
-    require-directory "^2.1.1"
-    require-main-filename "^2.0.0"
-    set-blocking "^2.0.0"
-    string-width "^3.0.0"
-    which-module "^2.0.0"
-    y18n "^4.0.0"
-    yargs-parser "^13.1.1"
-
 yeast@0.1.2:
   version "0.1.2"
   resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419"
diff --git a/resources/com/google/gerrit/server/mail/Comment.soy b/resources/com/google/gerrit/server/mail/Comment.soy
index 1eb016b..fc92b31 100644
--- a/resources/com/google/gerrit/server/mail/Comment.soy
+++ b/resources/com/google/gerrit/server/mail/Comment.soy
@@ -41,7 +41,7 @@
   {for $group in $commentFiles}
     // Insert a space before the newline so that Gmail does not mistakenly link
     // the following line with the file link. See issue 9201.
-    {$group.link}{sp}{\n}
+    {if $group.link}{$group.link}{sp}{/if}{\n}
     {$group.title}:{\n}
     {\n}
 
diff --git a/tools/eclipse/project.py b/tools/eclipse/project.py
index f360fa5..b1d5242 100755
--- a/tools/eclipse/project.py
+++ b/tools/eclipse/project.py
@@ -240,7 +240,8 @@
             # Exceptions: both source and lib
             if p.endswith('libquery_parser.jar') or \
                p.endswith('libgerrit-prolog-common.jar') or \
-               p.endswith('com_google_protobuf/libprotobuf_java.jar') or \
+               p.endswith('external/com_google_protobuf/java/core/libcore.jar') or \
+               p.endswith('external/com_google_protobuf/java/core/liblite.jar') or \
                p.endswith('lucene-core-and-backward-codecs-merged_deploy.jar'):
                 lib.add(p)
             if proto_library.match(p) :