Merge "Fork off a custom workspace_status.py with more heuristics" into stable-3.1
diff --git a/.bazelversion b/.bazelversion
index fd2a018..47b322c 100644
--- a/.bazelversion
+++ b/.bazelversion
@@ -1 +1 @@
-3.1.0
+3.4.1
diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt
index 47f5a2a..7961b7e 100644
--- a/Documentation/access-control.txt
+++ b/Documentation/access-control.txt
@@ -856,6 +856,14 @@
private changes (even without having the `View Private Changes` access
right assigned).
+[[category_toggle_work_in_progress_state]]
+=== Toggle Work In Progress state
+
+This category controls who is able to flip the Work In Progress bit.
+
+Change owner, server administrators and project owners can always flip
+the Work In Progress bit of the change (even without having the
+`Toggle Work In Progress state` access right assigned).
[[category_delete_own_changes]]
=== Delete Own Changes
diff --git a/Documentation/dev-crafting-changes.txt b/Documentation/dev-crafting-changes.txt
index 00748a5..1e77c3b 100644
--- a/Documentation/dev-crafting-changes.txt
+++ b/Documentation/dev-crafting-changes.txt
@@ -146,7 +146,7 @@
link:https://github.com/google/google-java-format[`google-java-format`]
tool (version 1.7), and to format Bazel BUILD, WORKSPACE and .bzl files the
link:https://github.com/bazelbuild/buildtools/tree/master/buildifier[`buildifier`]
-tool (version 3.2.1). Unused dependencies are found and removed using the
+tool (version 3.3.0). Unused dependencies are found and removed using the
link:https://github.com/bazelbuild/buildtools/tree/master/unused_deps[`unused_deps`]
build tool, a sibling of `buildifier`.
diff --git a/Documentation/dev-e2e-tests.txt b/Documentation/dev-e2e-tests.txt
index bac5169..3c534eb 100644
--- a/Documentation/dev-e2e-tests.txt
+++ b/Documentation/dev-e2e-tests.txt
@@ -114,7 +114,7 @@
"cmd": "clone"
},
{
- "url": "http://HOSTNAME:HTTP_PORT/_PROJECT",
+ "url": "HTTP_SCHEME://HOSTNAME:HTTP_PORT/_PROJECT",
"cmd": "clone"
}
]
@@ -151,10 +151,12 @@
* `-Dcom.google.gerrit.scenarios.hostname=localhost`
* `-Dcom.google.gerrit.scenarios.ssh_port=29418`
* `-Dcom.google.gerrit.scenarios.http_port=8080`
+* `-Dcom.google.gerrit.scenarios.http_scheme=http`
Above, the properties can be set with values matching specific deployment topologies under test.
-The example values shown above are the currently coded default ones. The framework could support
-differing or more properties over time.
+The example values shown above are the currently coded default ones. For example, the `http` scheme
+above could be replaced with `https`. The framework could support differing or more properties over
+time.
Plugin or otherwise non-core scenarios may do so just as well. The core java package
`com.google.gerrit.scenarios` from the example above has to be replaced with the one under which
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index aad81bd..4408d93 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -2058,6 +2058,14 @@
No Guice bindings or modules are required. Gerrit will automatically
discover and bind the implementation.
+[[gerrit-replica]]
+== Gerrit Replica
+
+Gerrit can be run as a read-only replica. Some plugins may need to know
+whether Gerrit is run as a primary- or a replica instance. For that purpose
+Gerrit exposes the `@GerritIsReplica` annotation. A boolean annotated with
+this annotation will indicate whether Gerrit is run as a replica.
+
[[accountcreation]]
== Account Creation
diff --git a/Documentation/dev-release.txt b/Documentation/dev-release.txt
index 9e1744c..f79918f 100644
--- a/Documentation/dev-release.txt
+++ b/Documentation/dev-release.txt
@@ -108,13 +108,14 @@
+
----
bazel build release Documentation:searchfree
+ ./tools/maven/api.sh war_install
./tools/maven/api.sh install
----
* Verify the WAR version:
+
----
- java -jar ~/dl/gerrit-$version.war --version
+ java -jar bazel-bin/release.war --version
----
* Try upgrading a test site and launching the daemon
diff --git a/Documentation/intro-user.txt b/Documentation/intro-user.txt
index 0608659..a54774b 100644
--- a/Documentation/intro-user.txt
+++ b/Documentation/intro-user.txt
@@ -568,8 +568,9 @@
Alternatively, click *Start Review* from the Change screen.
Change owners, project owners, site administrators and members of a group that
-was granted "Toggle Work In Progress state" permission can mark changes as
-`work-in-progress` and `ready`.
+was granted link:access-control.html#category_toggle_work_in_progress_state[
+Toggle Work In Progress state] permission can mark changes as `work-in-progress`
+and `ready`.
[[private-changes]]
== Private Changes
diff --git a/Documentation/rest-api-plugins.txt b/Documentation/rest-api-plugins.txt
index 77b180e..ce26280 100644
--- a/Documentation/rest-api-plugins.txt
+++ b/Documentation/rest-api-plugins.txt
@@ -48,6 +48,7 @@
"id": "delete-project",
"index_url": "plugins/delete-project/",
"filename": "delete-project.jar",
+ "api_version": "2.9.3-SNAPSHOT",
"version": "2.9-SNAPSHOT"
}
}
@@ -455,12 +456,13 @@
[options="header",cols="1,^2,4"]
|=======================
-|Field Name ||Description
-|`id` ||The ID of the plugin.
-|`version` ||The version of the plugin.
-|`index_url`|optional|URL of the plugin's default page.
-|`filename` |optional|The plugin's filename.
-|`disabled` |not set if `false`|Whether the plugin is disabled.
+|Field Name ||Description
+|`id` ||The ID of the plugin.
+|`version` ||The version of the plugin.
+|`api_version`|optional|The version of the Gerrit Api used by the plugin.
+|`index_url` |optional|URL of the plugin's default page.
+|`filename` |optional|The plugin's filename.
+|`disabled` |not set if `false`|Whether the plugin is disabled.
|=======================
[[plugin-input]]
diff --git a/WORKSPACE b/WORKSPACE
index 6c2e5cd..3c68232 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -869,54 +869,54 @@
sha1 = "7e060dd5b19431e6d198e91ff670644372f60fbd",
)
-JETTY_VERS = "9.4.27.v20200227"
+JETTY_VERS = "9.4.30.v20200611"
maven_jar(
name = "jetty-servlet",
artifact = "org.eclipse.jetty:jetty-servlet:" + JETTY_VERS,
- sha1 = "c6354d1e53c41f839ae56f4d8622c866a1ad8487",
+ sha1 = "ca3dea2cd34ee88cec017001603af0c9e74781d6",
)
maven_jar(
name = "jetty-security",
artifact = "org.eclipse.jetty:jetty-security:" + JETTY_VERS,
- sha1 = "aead56f2a1ac49d720a192cb7c1568e61e34ddae",
+ sha1 = "1a5261f6ad4081ad9e9bb01416d639931d391273",
)
maven_jar(
name = "jetty-server",
artifact = "org.eclipse.jetty:jetty-server:" + JETTY_VERS,
- sha1 = "4ef690ce1277e3767d457f87621f2c436a001881",
+ sha1 = "e5ede3724d062717d0c04e4c77f74fe8115c2a6f",
)
maven_jar(
name = "jetty-jmx",
artifact = "org.eclipse.jetty:jetty-jmx:" + JETTY_VERS,
- sha1 = "df66265ec011d8b33a7fa541774257deb957ecb4",
+ sha1 = "653559eaec0f9a335a0d12e90bc764b28f341241",
)
maven_jar(
name = "jetty-continuation",
artifact = "org.eclipse.jetty:jetty-continuation:" + JETTY_VERS,
- sha1 = "ac504b371dea5316850362a835d14317dfabd5d0",
+ sha1 = "2a9cd8c4cf392a7697a57665e7b0caf5bce4cd48",
)
maven_jar(
name = "jetty-http",
artifact = "org.eclipse.jetty:jetty-http:" + JETTY_VERS,
- sha1 = "722ba6ef20eb58c55868f1ce85411e6af13be98e",
+ sha1 = "cd6223382e4f82b9ea807d8cdb04a23e5d629f1c",
)
maven_jar(
name = "jetty-io",
artifact = "org.eclipse.jetty:jetty-io:" + JETTY_VERS,
- sha1 = "e85e7c4f298efb36b80cc53d635f2da776aa54c2",
+ sha1 = "9c360d08e903b2dbd5d1f8e889a32046948628ce",
)
maven_jar(
name = "jetty-util",
artifact = "org.eclipse.jetty:jetty-util:" + JETTY_VERS,
- sha1 = "44087a126227af5196e3e327a5e11aad1b28852c",
+ sha1 = "39ec6aa4745952077f5407cb1394d8ba2db88b13",
)
maven_jar(
diff --git a/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/ApproveChange.json b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/ApproveChange.json
index 3577a6a..665cc4d 100644
--- a/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/ApproveChange.json
+++ b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/ApproveChange.json
@@ -1,6 +1,6 @@
[
{
- "url": "http://HOSTNAME:HTTP_PORT/a/changes/",
+ "url": "HTTP_SCHEME://HOSTNAME:HTTP_PORT/a/changes/",
"number": "NUMBER"
}
]
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
index 54c54f8..5b892aa 100644
--- 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
@@ -1,5 +1,5 @@
[
{
- "url": "http://HOSTNAME:HTTP_PORT1/a/projects/PROJECT/branches/master"
+ "url": "HTTP_SCHEME://HOSTNAME:HTTP_PORT1/a/projects/PROJECT/branches/master"
}
]
diff --git a/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CheckProjectsCacheFlushEntries.json b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CheckProjectsCacheFlushEntries.json
index 6210deb..467661b 100644
--- a/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CheckProjectsCacheFlushEntries.json
+++ b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CheckProjectsCacheFlushEntries.json
@@ -1,6 +1,6 @@
[
{
- "url": "http://HOSTNAME:HTTP_PORT/a/config/server/caches/projects",
+ "url": "HTTP_SCHEME://HOSTNAME:HTTP_PORT/a/config/server/caches/projects",
"entries": "PROJECTS_ENTRIES"
}
]
diff --git a/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CloneUsingBothProtocols.json b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CloneUsingBothProtocols.json
index 2389124..30f5f23 100644
--- a/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CloneUsingBothProtocols.json
+++ b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CloneUsingBothProtocols.json
@@ -4,7 +4,7 @@
"cmd": "clone"
},
{
- "url": "http://HOSTNAME:HTTP_PORT/_PROJECT",
+ "url": "HTTP_SCHEME://HOSTNAME:HTTP_PORT/_PROJECT",
"cmd": "clone"
}
]
diff --git a/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CreateChange.json b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CreateChange.json
index b4ee549..70e79ca 100644
--- a/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CreateChange.json
+++ b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CreateChange.json
@@ -1,6 +1,6 @@
[
{
- "url": "http://HOSTNAME:HTTP_PORT/a/changes/",
+ "url": "HTTP_SCHEME://HOSTNAME:HTTP_PORT/a/changes/",
"project": "PROJECT"
}
]
diff --git a/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CreateProject.json b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CreateProject.json
index 40e5a45..cd90739 100644
--- a/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CreateProject.json
+++ b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/CreateProject.json
@@ -1,5 +1,5 @@
[
{
- "url": "http://HOSTNAME:HTTP_PORT/a/projects/PROJECT"
+ "url": "HTTP_SCHEME://HOSTNAME:HTTP_PORT/a/projects/PROJECT"
}
]
diff --git a/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/DeleteChange.json b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/DeleteChange.json
index 3577a6a..665cc4d 100644
--- a/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/DeleteChange.json
+++ b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/DeleteChange.json
@@ -1,6 +1,6 @@
[
{
- "url": "http://HOSTNAME:HTTP_PORT/a/changes/",
+ "url": "HTTP_SCHEME://HOSTNAME:HTTP_PORT/a/changes/",
"number": "NUMBER"
}
]
diff --git a/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/DeleteProject.json b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/DeleteProject.json
index 7cc8293..5720f53 100644
--- a/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/DeleteProject.json
+++ b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/DeleteProject.json
@@ -1,5 +1,5 @@
[
{
- "url": "http://HOSTNAME:HTTP_PORT/a/projects/PROJECT/delete-project~delete"
+ "url": "HTTP_SCHEME://HOSTNAME:HTTP_PORT/a/projects/PROJECT/delete-project~delete"
}
]
diff --git a/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/FlushProjectsCache.json b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/FlushProjectsCache.json
index 9ff15a7..e30a2cf 100644
--- a/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/FlushProjectsCache.json
+++ b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/FlushProjectsCache.json
@@ -1,5 +1,5 @@
[
{
- "url": "http://HOSTNAME:HTTP_PORT/a/config/server/caches/projects/flush"
+ "url": "HTTP_SCHEME://HOSTNAME:HTTP_PORT/a/config/server/caches/projects/flush"
}
]
diff --git a/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/FlushProjectsCacheThenRebuild.json b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/FlushProjectsCacheThenRebuild.json
new file mode 100644
index 0000000..e30a2cf
--- /dev/null
+++ b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/FlushProjectsCacheThenRebuild.json
@@ -0,0 +1,5 @@
+[
+ {
+ "url": "HTTP_SCHEME://HOSTNAME:HTTP_PORT/a/config/server/caches/projects/flush"
+ }
+]
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
index 2b8809a..86a3c28 100644
--- 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
@@ -1,5 +1,5 @@
[
{
- "url": "http://HOSTNAME:HTTP_PORT/a/projects/PROJECT/branches/master"
+ "url": "HTTP_SCHEME://HOSTNAME:HTTP_PORT/a/projects/PROJECT/branches/master"
}
]
diff --git a/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/GetProjectsCacheEntries.json b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/GetProjectsCacheEntries.json
index fcf4bc9..e4e2643 100644
--- a/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/GetProjectsCacheEntries.json
+++ b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/GetProjectsCacheEntries.json
@@ -1,5 +1,5 @@
[
{
- "url": "http://HOSTNAME:HTTP_PORT/a/config/server/caches/projects"
+ "url": "HTTP_SCHEME://HOSTNAME:HTTP_PORT/a/config/server/caches/projects"
}
]
diff --git a/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/ListProjects.json b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/ListProjects.json
new file mode 100644
index 0000000..f6350be
--- /dev/null
+++ b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/ListProjects.json
@@ -0,0 +1,5 @@
+[
+ {
+ "url": "HTTP_SCHEME://HOSTNAME:HTTP_PORT/a/projects/"
+ }
+]
diff --git a/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/ReplayRecordsFromFeeder.json b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/ReplayRecordsFromFeeder.json
index 2389124..30f5f23 100644
--- a/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/ReplayRecordsFromFeeder.json
+++ b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/ReplayRecordsFromFeeder.json
@@ -4,7 +4,7 @@
"cmd": "clone"
},
{
- "url": "http://HOSTNAME:HTTP_PORT/_PROJECT",
+ "url": "HTTP_SCHEME://HOSTNAME:HTTP_PORT/_PROJECT",
"cmd": "clone"
}
]
diff --git a/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/SubmitChange.json b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/SubmitChange.json
index a371757..301c65b 100644
--- a/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/SubmitChange.json
+++ b/e2e-tests/src/test/resources/data/com/google/gerrit/scenarios/SubmitChange.json
@@ -1,5 +1,5 @@
[
{
- "url": "http://HOSTNAME:HTTP_PORT/a/changes/"
+ "url": "HTTP_SCHEME://HOSTNAME:HTTP_PORT/a/changes/"
}
]
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 3dd8493..2e63fd5 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
@@ -26,7 +26,7 @@
override def relativeRuntimeWeight = 2
- private val flushCache: ScenarioBuilder = scenario(unique)
+ private val test: ScenarioBuilder = scenario(unique)
.feed(data)
.exec(httpRequest)
@@ -44,7 +44,7 @@
nothingFor(stepWaitTime(getCacheEntriesAfterProject) seconds),
atOnceUsers(single)
),
- flushCache.inject(
+ test.inject(
nothingFor(stepWaitTime(this) seconds),
atOnceUsers(single)
),
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/FlushProjectsCacheThenRebuild.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/FlushProjectsCacheThenRebuild.scala
new file mode 100644
index 0000000..7d732ea
--- /dev/null
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/FlushProjectsCacheThenRebuild.scala
@@ -0,0 +1,47 @@
+// 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 scala.concurrent.duration._
+
+class FlushProjectsCacheThenRebuild extends GerritSimulation {
+ private val data: FeederBuilder = jsonFile(resource).convert(keys).queue
+
+ private val test: ScenarioBuilder = scenario(unique)
+ .feed(data)
+ .exec(httpRequest)
+
+ private val checkCacheEntriesAfterFlush = new CheckProjectsCacheFlushEntries
+ private val rebuildCache = new ListProjects
+
+ setUp(
+ test.inject(
+ nothingFor(stepWaitTime(this) seconds),
+ atOnceUsers(single)
+ ),
+ checkCacheEntriesAfterFlush.test.inject(
+ nothingFor(stepWaitTime(checkCacheEntriesAfterFlush) seconds),
+ atOnceUsers(single)
+ ),
+ rebuildCache.test.inject(
+ nothingFor(stepWaitTime(rebuildCache) seconds),
+ 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 fc68f97..4832392 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
@@ -62,6 +62,7 @@
var in = replaceOverride(url.toString)
in = replaceProperty("hostname", "localhost", in)
in = replaceProperty("http_port", 8080, in)
+ in = replaceProperty("http_scheme", "http", in)
replaceProperty("ssh_port", 29418, in)
case ("number", number) =>
val precedes = replaceKeyWith("_number", 0, number.toString)
diff --git a/e2e-tests/src/test/scala/com/google/gerrit/scenarios/ListProjects.scala b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/ListProjects.scala
new file mode 100644
index 0000000..bfc97f4
--- /dev/null
+++ b/e2e-tests/src/test/scala/com/google/gerrit/scenarios/ListProjects.scala
@@ -0,0 +1,33 @@
+// 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.http
+
+class ListProjects extends GerritSimulation {
+ private val data: FeederBuilder = jsonFile(resource).convert(keys).queue
+
+ val test: ScenarioBuilder = scenario(unique)
+ .feed(data)
+ .exec(http(unique).get("${url}"))
+
+ setUp(
+ test.inject(
+ atOnceUsers(single)
+ )).protocols(httpProtocol)
+}
diff --git a/java/com/google/gerrit/extensions/common/PluginInfo.java b/java/com/google/gerrit/extensions/common/PluginInfo.java
index 0df6235..47f9b6a 100644
--- a/java/com/google/gerrit/extensions/common/PluginInfo.java
+++ b/java/com/google/gerrit/extensions/common/PluginInfo.java
@@ -17,13 +17,21 @@
public class PluginInfo {
public final String id;
public final String version;
+ public final String apiVersion;
public final String indexUrl;
public final String filename;
public final Boolean disabled;
- public PluginInfo(String id, String version, String indexUrl, String filename, Boolean disabled) {
+ public PluginInfo(
+ String id,
+ String version,
+ String apiVersion,
+ String indexUrl,
+ String filename,
+ Boolean disabled) {
this.id = id;
this.version = version;
+ this.apiVersion = apiVersion;
this.indexUrl = indexUrl;
this.filename = filename;
this.disabled = disabled;
diff --git a/java/com/google/gerrit/pgm/http/jetty/HttpLogJsonLayout.java b/java/com/google/gerrit/pgm/http/jetty/HttpLogJsonLayout.java
index 73d9ee4..7772660 100644
--- a/java/com/google/gerrit/pgm/http/jetty/HttpLogJsonLayout.java
+++ b/java/com/google/gerrit/pgm/http/jetty/HttpLogJsonLayout.java
@@ -16,6 +16,7 @@
import static com.google.gerrit.pgm.http.jetty.HttpLog.P_CONTENT_LENGTH;
import static com.google.gerrit.pgm.http.jetty.HttpLog.P_HOST;
+import static com.google.gerrit.pgm.http.jetty.HttpLog.P_LATENCY;
import static com.google.gerrit.pgm.http.jetty.HttpLog.P_METHOD;
import static com.google.gerrit.pgm.http.jetty.HttpLog.P_PROTOCOL;
import static com.google.gerrit.pgm.http.jetty.HttpLog.P_REFERER;
@@ -52,6 +53,7 @@
public String protocol;
public String status;
public String contentLength;
+ public String latency;
public String referer;
public String userAgent;
@@ -65,6 +67,7 @@
this.protocol = getMdcString(event, P_PROTOCOL);
this.status = getMdcString(event, P_STATUS);
this.contentLength = getMdcString(event, P_CONTENT_LENGTH);
+ this.latency = getMdcString(event, P_LATENCY);
this.referer = getMdcString(event, P_REFERER);
this.userAgent = getMdcString(event, P_USER_AGENT);
}
diff --git a/java/com/google/gerrit/server/config/GerritIsReplica.java b/java/com/google/gerrit/server/config/GerritIsReplica.java
new file mode 100644
index 0000000..154fdcd
--- /dev/null
+++ b/java/com/google/gerrit/server/config/GerritIsReplica.java
@@ -0,0 +1,25 @@
+// 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.server.config;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.google.inject.BindingAnnotation;
+import java.lang.annotation.Retention;
+
+/* Marker on {@link Boolean} indicating whether Gerrit is run as a read-only replica. */
+@Retention(RUNTIME)
+@BindingAnnotation
+public @interface GerritIsReplica {}
diff --git a/java/com/google/gerrit/server/config/GerritIsReplicaProvider.java b/java/com/google/gerrit/server/config/GerritIsReplicaProvider.java
new file mode 100644
index 0000000..bd07f7d
--- /dev/null
+++ b/java/com/google/gerrit/server/config/GerritIsReplicaProvider.java
@@ -0,0 +1,46 @@
+// 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.server.config;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import org.eclipse.jgit.lib.Config;
+
+/**
+ * Provides {@link Boolean} annotated with {@link GerritIsReplica}.
+ *
+ * <p>The returned boolean indicates whether Gerrit is run as a read-only replica.
+ */
+@Singleton
+public final class GerritIsReplicaProvider implements Provider<Boolean> {
+ public static final String CONFIG_SECTION = "container";
+ public static final String REPLICA_KEY = "replica";
+ public static final String DEPRECATED_REPLICA_KEY = "slave";
+
+ public final boolean isReplica;
+
+ @Inject
+ public GerritIsReplicaProvider(@GerritServerConfig Config config) {
+ this.isReplica =
+ config.getBoolean(CONFIG_SECTION, REPLICA_KEY, false)
+ || config.getBoolean(CONFIG_SECTION, DEPRECATED_REPLICA_KEY, false);
+ }
+
+ @Override
+ public Boolean get() {
+ return isReplica;
+ }
+}
diff --git a/java/com/google/gerrit/server/config/GerritServerConfigModule.java b/java/com/google/gerrit/server/config/GerritServerConfigModule.java
index 25ee759..3777a55 100644
--- a/java/com/google/gerrit/server/config/GerritServerConfigModule.java
+++ b/java/com/google/gerrit/server/config/GerritServerConfigModule.java
@@ -78,5 +78,8 @@
.annotatedWith(GerritServerConfig.class)
.toProvider(GerritServerConfigProvider.class);
bind(SecureStore.class).toProvider(SecureStoreProvider.class).in(SINGLETON);
+ bind(Boolean.class)
+ .annotatedWith(GerritIsReplica.class)
+ .toProvider(GerritIsReplicaProvider.class);
}
}
diff --git a/java/com/google/gerrit/server/index/change/AllChangesIndexer.java b/java/com/google/gerrit/server/index/change/AllChangesIndexer.java
index 8434c10..005f4c5 100644
--- a/java/com/google/gerrit/server/index/change/AllChangesIndexer.java
+++ b/java/com/google/gerrit/server/index/change/AllChangesIndexer.java
@@ -21,7 +21,6 @@
import static com.google.gerrit.server.git.QueueProvider.QueueType.BATCH;
import com.google.common.base.Stopwatch;
-import com.google.common.collect.ComparisonChain;
import com.google.common.flogger.FluentLogger;
import com.google.common.primitives.Ints;
import com.google.common.util.concurrent.ListenableFuture;
@@ -42,10 +41,9 @@
import com.google.inject.Inject;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Objects;
-import java.util.SortedSet;
-import java.util.TreeSet;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.RejectedExecutionException;
@@ -57,6 +55,7 @@
public class AllChangesIndexer extends SiteIndexer<Change.Id, ChangeData, ChangeIndex> {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+ private static final int PROJECT_SLICE_MAX_REFS = 1000;
private final ChangeData.Factory changeDataFactory;
private final GitRepositoryManager repoManager;
@@ -81,22 +80,27 @@
this.projectCache = projectCache;
}
- private static class ProjectHolder implements Comparable<ProjectHolder> {
- final Project.NameKey name;
- private final long size;
+ private static class ProjectSlice {
+ private final Project.NameKey name;
+ private final int slice;
+ private final int slices;
- ProjectHolder(Project.NameKey name, long size) {
+ ProjectSlice(Project.NameKey name, int slice, int slices) {
this.name = name;
- this.size = size;
+ this.slice = slice;
+ this.slices = slices;
}
- @Override
- public int compareTo(ProjectHolder other) {
- // Sort projects based on size first to maximize utilization of threads early on.
- return ComparisonChain.start()
- .compare(other.size, size)
- .compare(other.name.get(), name.get())
- .result();
+ public Project.NameKey getName() {
+ return name;
+ }
+
+ public int getSlice() {
+ return slice;
+ }
+
+ public int getSlices() {
+ return slices;
}
}
@@ -104,19 +108,39 @@
public Result indexAll(ChangeIndex index) {
ProgressMonitor pm = new TextProgressMonitor();
pm.beginTask("Collecting projects", ProgressMonitor.UNKNOWN);
- SortedSet<ProjectHolder> projects = new TreeSet<>();
+ List<ProjectSlice> projectSlices = new ArrayList<>();
int changeCount = 0;
Stopwatch sw = Stopwatch.createStarted();
int projectsFailed = 0;
for (Project.NameKey name : projectCache.all()) {
try (Repository repo = repoManager.openRepository(name)) {
+ // The simplest approach to distribute indexing would be to let each thread grab a project
+ // and index it fully. But if a site has one big project and 100s of small projects, then
+ // in the beginning all CPUs would be busy reindexing projects. But soon enough all small
+ // projects have been reindexed, and only the thread that reindexes the big project is
+ // still working. The other threads would idle. Reindexing the big project on a single
+ // thread becomes the critical path. Bringing in more CPUs would not speed up things.
+ //
+ // To avoid such situations, we split big repos into smaller parts and let
+ // the thread pool index these smaller parts. This splitting introduces an overhead in the
+ // workload setup and there might be additional slow-downs from multiple threads
+ // concurrently working on different parts of the same project. But for Wikimedia's Gerrit,
+ // which had 2 big projects, many middle sized ones, and lots of smaller ones, the
+ // splitting of repos into smaller parts reduced indexing time from 1.5 hours to 55 minutes
+ // in 2020.
int size = estimateSize(repo);
changeCount += size;
- projects.add(new ProjectHolder(name, size));
+ int slices = 1 + size / PROJECT_SLICE_MAX_REFS;
+ if (slices > 1) {
+ verboseWriter.println("Submitting " + name + " for indexing in " + slices + " slices");
+ }
+ for (int slice = 0; slice < slices; slice++) {
+ projectSlices.add(new ProjectSlice(name, slice, slices));
+ }
} catch (IOException e) {
logger.atSevere().withCause(e).log("Error collecting project %s", name);
projectsFailed++;
- if (projectsFailed > projects.size() / 2) {
+ if (projectsFailed > projectCache.all().size() / 2) {
logger.atSevere().log("Over 50%% of the projects could not be collected: aborted");
return new Result(sw, false, 0, 0);
}
@@ -125,7 +149,15 @@
}
pm.endTask();
setTotalWork(changeCount);
- return indexAll(index, projects);
+
+ // projectSlices are currently grouped by projects. First all slices for project1, followed
+ // by all slices for project2, and so on. As workers pick tasks sequentially, multiple threads
+ // would typically work concurrently on different slices of the same project. While this is not
+ // a big issue, shuffling the list beforehand helps with ungrouping the project slices, so
+ // different slices are less likely to be worked on concurrently.
+ // This shuffling gave a 6% runtime reduction for Wikimedia's Gerrit in 2020.
+ Collections.shuffle(projectSlices);
+ return indexAll(index, projectSlices);
}
private int estimateSize(Repository repo) throws IOException {
@@ -141,10 +173,10 @@
return Ints.saturatedCast(size);
}
- private SiteIndexer.Result indexAll(ChangeIndex index, SortedSet<ProjectHolder> projects) {
+ private SiteIndexer.Result indexAll(ChangeIndex index, List<ProjectSlice> projectSlices) {
Stopwatch sw = Stopwatch.createStarted();
MultiProgressMonitor mpm = new MultiProgressMonitor(progressOut, "Reindexing changes");
- Task projTask = mpm.beginSubTask("projects", projects.size());
+ Task projTask = mpm.beginSubTask("project-slices", projectSlices.size());
checkState(totalWork >= 0);
Task doneTask = mpm.beginSubTask(null, totalWork);
Task failedTask = mpm.beginSubTask("failed", MultiProgressMonitor.UNKNOWN);
@@ -152,12 +184,21 @@
List<ListenableFuture<?>> futures = new ArrayList<>();
AtomicBoolean ok = new AtomicBoolean(true);
- for (ProjectHolder project : projects) {
+ for (ProjectSlice projectSlice : projectSlices) {
+ Project.NameKey name = projectSlice.getName();
+ int slice = projectSlice.getSlice();
+ int slices = projectSlice.getSlices();
ListenableFuture<?> future =
executor.submit(
reindexProject(
- indexerFactory.create(executor, index), project.name, doneTask, failedTask));
- addErrorListener(future, "project " + project.name, projTask, ok);
+ indexerFactory.create(executor, index),
+ name,
+ slice,
+ slices,
+ doneTask,
+ failedTask));
+ String description = "project " + name + " (" + slice + "/" + slices + ")";
+ addErrorListener(future, description, projTask, ok);
futures.add(future);
}
@@ -192,22 +233,38 @@
public Callable<Void> reindexProject(
ChangeIndexer indexer, Project.NameKey project, Task done, Task failed) {
- return new ProjectIndexer(indexer, project, done, failed);
+ return reindexProject(indexer, project, 0, 1, done, failed);
+ }
+
+ public Callable<Void> reindexProject(
+ ChangeIndexer indexer,
+ Project.NameKey project,
+ int slice,
+ int slices,
+ Task done,
+ Task failed) {
+ return new ProjectIndexer(indexer, project, slice, slices, done, failed);
}
private class ProjectIndexer implements Callable<Void> {
private final ChangeIndexer indexer;
private final Project.NameKey project;
+ private final int slice;
+ private final int slices;
private final ProgressMonitor done;
private final ProgressMonitor failed;
private ProjectIndexer(
ChangeIndexer indexer,
Project.NameKey project,
+ int slice,
+ int slices,
ProgressMonitor done,
ProgressMonitor failed) {
this.indexer = indexer;
this.project = project;
+ this.slice = slice;
+ this.slices = slices;
this.done = done;
this.failed = failed;
}
@@ -222,7 +279,7 @@
// It does mean that reindexing after invalidating the DiffSummary cache will be expensive,
// but the goal is to invalidate that cache as infrequently as we possibly can. And besides,
// we don't have concrete proof that improving packfile locality would help.
- notesFactory.scan(repo, project).forEach(r -> index(r));
+ notesFactory.scan(repo, project, id -> (id.get() % slices) == slice).forEach(r -> index(r));
} catch (RepositoryNotFoundException rnfe) {
logger.atSevere().log(rnfe.getMessage());
} finally {
diff --git a/java/com/google/gerrit/server/notedb/ChangeNotes.java b/java/com/google/gerrit/server/notedb/ChangeNotes.java
index b7ce2a8..bd673d6 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNotes.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNotes.java
@@ -206,9 +206,18 @@
public Stream<ChangeNotesResult> scan(Repository repo, Project.NameKey project)
throws IOException {
- ScanResult sr = scanChangeIds(repo);
+ return scan(repo, project, null);
+ }
- return sr.all().stream().map(id -> scanOneChange(project, sr, id)).filter(Objects::nonNull);
+ public Stream<ChangeNotesResult> scan(
+ Repository repo, Project.NameKey project, Predicate<Change.Id> changeIdPredicate)
+ throws IOException {
+ ScanResult sr = scanChangeIds(repo);
+ Stream<Change.Id> idStream = sr.all().stream();
+ if (changeIdPredicate != null) {
+ idStream = idStream.filter(changeIdPredicate);
+ }
+ return idStream.map(id -> scanOneChange(project, sr, id)).filter(Objects::nonNull);
}
@Nullable
diff --git a/java/com/google/gerrit/server/plugins/CopyConfigModule.java b/java/com/google/gerrit/server/plugins/CopyConfigModule.java
index 090d257..9b74341 100644
--- a/java/com/google/gerrit/server/plugins/CopyConfigModule.java
+++ b/java/com/google/gerrit/server/plugins/CopyConfigModule.java
@@ -17,6 +17,8 @@
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.GerritPersonIdentProvider;
import com.google.gerrit.server.config.AnonymousCowardName;
+import com.google.gerrit.server.config.GerritIsReplica;
+import com.google.gerrit.server.config.GerritIsReplicaProvider;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.config.SitePath;
import com.google.gerrit.server.config.SitePaths;
@@ -101,6 +103,14 @@
return secureStore;
}
+ @Inject private GerritIsReplicaProvider isReplicaProvider;
+
+ @Provides
+ @GerritIsReplica
+ boolean getIsReplica() {
+ return isReplicaProvider.get();
+ }
+
@Inject
CopyConfigModule() {}
diff --git a/java/com/google/gerrit/server/plugins/ListPlugins.java b/java/com/google/gerrit/server/plugins/ListPlugins.java
index 465d041..0408efc 100644
--- a/java/com/google/gerrit/server/plugins/ListPlugins.java
+++ b/java/com/google/gerrit/server/plugins/ListPlugins.java
@@ -146,12 +146,14 @@
public static PluginInfo toPluginInfo(Plugin p) {
String id;
String version;
+ String apiVersion;
String indexUrl;
String filename;
Boolean disabled;
id = Url.encode(p.getName());
version = p.getVersion();
+ apiVersion = p.getApiVersion();
disabled = p.isDisabled() ? true : null;
if (p.getSrcFile() != null) {
indexUrl = String.format("plugins/%s/", p.getName());
@@ -161,6 +163,6 @@
filename = null;
}
- return new PluginInfo(id, version, indexUrl, filename, disabled);
+ return new PluginInfo(id, version, apiVersion, indexUrl, filename, disabled);
}
}
diff --git a/java/com/google/gerrit/server/plugins/Plugin.java b/java/com/google/gerrit/server/plugins/Plugin.java
index 5759705..238066b 100644
--- a/java/com/google/gerrit/server/plugins/Plugin.java
+++ b/java/com/google/gerrit/server/plugins/Plugin.java
@@ -116,6 +116,11 @@
return apiType;
}
+ @Nullable
+ public String getApiVersion() {
+ return null;
+ }
+
public Plugin.CacheKey getCacheKey() {
return cacheKey;
}
diff --git a/java/com/google/gerrit/server/plugins/PluginUtil.java b/java/com/google/gerrit/server/plugins/PluginUtil.java
index 932a01d..4f00cd0 100644
--- a/java/com/google/gerrit/server/plugins/PluginUtil.java
+++ b/java/com/google/gerrit/server/plugins/PluginUtil.java
@@ -53,7 +53,9 @@
}
static Path asTemp(InputStream in, String prefix, String suffix, Path dir) throws IOException {
- Files.createDirectories(dir);
+ if (!Files.exists(dir)) {
+ Files.createDirectories(dir);
+ }
Path tmp = Files.createTempFile(dir, prefix, suffix);
boolean keep = false;
try (OutputStream out = Files.newOutputStream(tmp)) {
diff --git a/java/com/google/gerrit/server/plugins/ServerPlugin.java b/java/com/google/gerrit/server/plugins/ServerPlugin.java
index f236202..320b618 100644
--- a/java/com/google/gerrit/server/plugins/ServerPlugin.java
+++ b/java/com/google/gerrit/server/plugins/ServerPlugin.java
@@ -154,6 +154,13 @@
}
@Override
+ @Nullable
+ public String getApiVersion() {
+ Attributes main = manifest.getMainAttributes();
+ return main.getValue("Gerrit-ApiVersion");
+ }
+
+ @Override
protected boolean canReload() {
Attributes main = manifest.getMainAttributes();
String v = main.getValue("Gerrit-ReloadMode");
diff --git a/java/com/google/gerrit/sshd/commands/PluginLsCommand.java b/java/com/google/gerrit/sshd/commands/PluginLsCommand.java
index e5dad7e..3a952f0 100644
--- a/java/com/google/gerrit/sshd/commands/PluginLsCommand.java
+++ b/java/com/google/gerrit/sshd/commands/PluginLsCommand.java
@@ -49,15 +49,17 @@
.toJson(output, new TypeToken<Map<String, PluginInfo>>() {}.getType(), stdout);
stdout.print('\n');
} else {
- stdout.format("%-30s %-10s %-8s %s\n", "Name", "Version", "Status", "File");
+ String template = "%-30s %-10s %-16s %-8s %s\n";
+ stdout.format(template, "Name", "Version", "Api-Version", "Status", "File");
stdout.print(
"-------------------------------------------------------------------------------\n");
for (Map.Entry<String, PluginInfo> p : output.entrySet()) {
PluginInfo info = p.getValue();
stdout.format(
- "%-30s %-10s %-8s %s\n",
+ template,
p.getKey(),
Strings.nullToEmpty(info.version),
+ Strings.nullToEmpty(info.apiVersion),
status(info.disabled),
Strings.nullToEmpty(info.filename));
}
diff --git a/javatests/com/google/gerrit/acceptance/api/plugin/PluginIT.java b/javatests/com/google/gerrit/acceptance/api/plugin/PluginIT.java
index a120eac..67da084 100644
--- a/javatests/com/google/gerrit/acceptance/api/plugin/PluginIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/plugin/PluginIT.java
@@ -37,7 +37,12 @@
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.server.plugins.MandatoryPluginsCollection;
import com.google.inject.Inject;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
import java.util.List;
+import java.util.jar.Attributes;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
import org.junit.Test;
@NoHttpd
@@ -51,7 +56,14 @@
private static final ImmutableList<String> PLUGINS =
ImmutableList.of(
- "plugin-a.js", "plugin-b.html", "plugin-c.js", "plugin-d.html", "plugin_e.js");
+ "plugin-a.js",
+ "plugin-b.html",
+ "plugin-c.js",
+ "plugin-d.html",
+ "plugin-normal.jar",
+ "plugin-empty.jar",
+ "plugin-unset.jar",
+ "plugin_e.js");
@Inject private RequestScopeOperations requestScopeOperations;
@Inject private MandatoryPluginsCollection mandatoryPluginsCollection;
@@ -67,13 +79,14 @@
// Install all the plugins
InstallPluginInput input = new InstallPluginInput();
for (String plugin : PLUGINS) {
- input.raw = plugin.endsWith(".js") ? JS_PLUGIN_CONTENT : HTML_PLUGIN_CONTENT;
+ input.raw = pluginContent(plugin);
api = gApi.plugins().install(plugin, input);
assertThat(api).isNotNull();
PluginInfo info = api.get();
String name = pluginName(plugin);
assertThat(info.id).isEqualTo(name);
assertThat(info.version).isEqualTo(pluginVersion(plugin));
+ assertThat(info.apiVersion).isEqualTo(pluginApiVersion(plugin));
assertThat(info.indexUrl).isEqualTo(String.format("plugins/%s/", name));
assertThat(info.filename).isEqualTo(plugin);
assertThat(info.disabled).isNull();
@@ -168,12 +181,52 @@
return plugin.substring(0, dot);
}
+ private RawInput pluginJarContent(String plugin) throws IOException {
+ ByteArrayOutputStream arrayStream = new ByteArrayOutputStream();
+ Manifest manifest = new Manifest();
+ Attributes attributes = manifest.getMainAttributes();
+ attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");
+ if (!plugin.endsWith("-unset.jar")) {
+ attributes.put(Attributes.Name.IMPLEMENTATION_VERSION, pluginVersion(plugin));
+ attributes.put(new Attributes.Name("Gerrit-ApiVersion"), pluginApiVersion(plugin));
+ }
+ try (JarOutputStream jarStream = new JarOutputStream(arrayStream, manifest)) {}
+ return RawInputUtil.create(arrayStream.toByteArray());
+ }
+
+ private RawInput pluginContent(String plugin) throws IOException {
+ if (plugin.endsWith(".js")) {
+ return JS_PLUGIN_CONTENT;
+ }
+ if (plugin.endsWith(".html")) {
+ return HTML_PLUGIN_CONTENT;
+ }
+ assertThat(plugin).endsWith(".jar");
+ return pluginJarContent(plugin);
+ }
+
private String pluginVersion(String plugin) {
String name = pluginName(plugin);
+ if (name.endsWith("empty")) {
+ return "";
+ }
+ if (name.endsWith("unset")) {
+ return null;
+ }
int dash = name.lastIndexOf("-");
return dash > 0 ? name.substring(dash + 1) : "";
}
+ private String pluginApiVersion(String plugin) {
+ if (plugin.endsWith("normal.jar")) {
+ return "2.16.19-SNAPSHOT";
+ }
+ if (plugin.endsWith("empty.jar")) {
+ return "";
+ }
+ return null;
+ }
+
private void assertBadRequest(ListRequest req) throws Exception {
assertThrows(BadRequestException.class, () -> req.get());
}
diff --git a/javatests/com/google/gerrit/acceptance/server/config/BUILD b/javatests/com/google/gerrit/acceptance/server/config/BUILD
new file mode 100644
index 0000000..17802bd
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/server/config/BUILD
@@ -0,0 +1,7 @@
+load("//javatests/com/google/gerrit/acceptance:tests.bzl", "acceptance_tests")
+
+acceptance_tests(
+ srcs = glob(["*IT.java"]),
+ group = "server_config",
+ labels = ["server"],
+)
diff --git a/javatests/com/google/gerrit/acceptance/server/config/GerritIsReplicaIT.java b/javatests/com/google/gerrit/acceptance/server/config/GerritIsReplicaIT.java
new file mode 100644
index 0000000..d01a81d
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/server/config/GerritIsReplicaIT.java
@@ -0,0 +1,46 @@
+// 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.acceptance.server.config;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.Sandboxed;
+import com.google.gerrit.server.config.GerritIsReplicaProvider;
+import com.google.gerrit.testing.ConfigSuite;
+import com.google.inject.Inject;
+import org.eclipse.jgit.lib.Config;
+import org.junit.Test;
+
+public class GerritIsReplicaIT extends AbstractDaemonTest {
+ @ConfigSuite.Default
+ public static Config defaultConfig() {
+ return new Config();
+ }
+
+ @Inject GerritIsReplicaProvider isReplicaProvider;
+
+ @Test
+ public void isNotReplica() {
+ assertThat(isReplicaProvider.get()).isFalse();
+ }
+
+ @Test
+ @Sandboxed
+ public void isReplica() throws Exception {
+ restartAsSlave();
+ assertThat(isReplicaProvider.get()).isTrue();
+ }
+}
diff --git a/plugins/replication b/plugins/replication
index a2f3dba..a23367b 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit a2f3dba7722e68a7ac1ee917c2bf4d59c3c8d80b
+Subproject commit a23367b33527cba1d7efdcdc77f1e4180eb6d6da
diff --git a/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.html b/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.html
index 6ef84bf..ee5dd83 100644
--- a/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.html
+++ b/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.html
@@ -26,7 +26,11 @@
<dom-module id="gr-plugin-list">
<template>
<style include="shared-styles"></style>
- <style include="gr-table-styles"></style>
+ <style include="gr-table-styles">
+ .placeholder {
+ color: var(--deemphasized-text-color);
+ }
+ </style>
<gr-list-view
filter="[[_filter]]"
items-per-page="[[_pluginsPerPage]]"
@@ -38,6 +42,7 @@
<tr class="headerRow">
<th class="name topHeader">Plugin Name</th>
<th class="version topHeader">Version</th>
+ <th class="apiVersion topHeader">API Version</th>
<th class="status topHeader">Status</th>
</tr>
<tr id="loading" class$="loadingMsg [[computeLoadingClass(_loading)]]">
@@ -54,7 +59,22 @@
[[item.id]]
</template>
</td>
- <td class="version">[[item.version]]</td>
+ <td class="version">
+ <template is="dom-if" if="[[item.version]]">
+ [[item.version]]
+ </template>
+ <template is="dom-if" if="[[!item.version]]">
+ <span class="placeholder">--</span>
+ </template>
+ </td>
+ <td class="apiVersion">
+ <template is="dom-if" if="[[item.api_version]]">
+ [[item.api_version]]
+ </template>
+ <template is="dom-if" if="[[!item.api_version]]">
+ <span class="placeholder">--</span>
+ </template>
+ </td>
<td class="status">[[_status(item)]]</td>
</tr>
</template>
diff --git a/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list_test.html b/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list_test.html
index 96fff60..2485687 100644
--- a/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list_test.html
@@ -40,13 +40,18 @@
const pluginGenerator = () => {
const plugin = {
id: `test${++counter}`,
- version: '3.0-SNAPSHOT',
disabled: false,
};
if (counter !== 2) {
plugin.index_url = `plugins/test${counter}/`;
}
+ if (counter !== 3) {
+ plugin.version = `version-${counter}`;
+ }
+ if (counter !== 4) {
+ plugin.api_version = `api-version-${counter}`;
+ }
return plugin;
};
@@ -81,10 +86,11 @@
test('plugin in the list is formatted correctly', done => {
flush(() => {
- assert.equal(element._plugins[2].id, 'test3');
- assert.equal(element._plugins[2].index_url, 'plugins/test3/');
- assert.equal(element._plugins[2].version, '3.0-SNAPSHOT');
- assert.equal(element._plugins[2].disabled, false);
+ assert.equal(element._plugins[4].id, 'test5');
+ assert.equal(element._plugins[4].index_url, 'plugins/test5/');
+ assert.equal(element._plugins[4].version, 'version-5');
+ assert.equal(element._plugins[4].api_version, 'api-version-5');
+ assert.equal(element._plugins[4].disabled, false);
done();
});
});
@@ -100,6 +106,25 @@
});
});
+ test('versions', done => {
+ flush(() => {
+ const versions = Polymer.dom(element.root).querySelectorAll('.version');
+ assert.equal(versions[2].innerText, 'version-2');
+ assert.equal(versions[3].innerText, '--');
+ done();
+ });
+ });
+
+ test('api versions', done => {
+ flush(() => {
+ const apiVersions = Polymer.dom(element.root).querySelectorAll(
+ '.apiVersion');
+ assert.equal(apiVersions[3].innerText, 'api-version-3');
+ assert.equal(apiVersions[4].innerText, '--');
+ done();
+ });
+ });
+
test('_shownPlugins', () => {
assert.equal(element._shownPlugins.length, 25);
});
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
index 0c929b4..02cf861 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
@@ -324,6 +324,7 @@
[this.Shortcut.UP_TO_DASHBOARD]: '_handleUpToDashboard',
[this.Shortcut.EXPAND_ALL_MESSAGES]: '_handleExpandAllMessages',
[this.Shortcut.COLLAPSE_ALL_MESSAGES]: '_handleCollapseAllMessages',
+ [this.Shortcut.EXPAND_ALL_DIFF_CONTEXT]: '_expandAllDiffs',
[this.Shortcut.OPEN_DIFF_PREFS]: '_handleOpenDiffPrefsShortcut',
[this.Shortcut.EDIT_TOPIC]: '_handleEditTopic',
};
diff --git a/tools/bzl/plugin.bzl b/tools/bzl/plugin.bzl
index d49e700..7534501 100644
--- a/tools/bzl/plugin.bzl
+++ b/tools/bzl/plugin.bzl
@@ -66,7 +66,7 @@
"export TZ",
"GEN_VERSION=$$(cat bazel-out/stable-status.txt | grep -w STABLE_BUILD_%s_LABEL | cut -d ' ' -f 2)" % dir_name.upper(),
"cd $$TMP",
- "unzip -q $$ROOT/$<",
+ "unzip -qo $$ROOT/$<",
"echo \"Implementation-Version: $$GEN_VERSION\n$$(cat META-INF/MANIFEST.MF)\" > META-INF/MANIFEST.MF",
"find . -exec touch '{}' ';'",
"zip -Xqr $$ROOT/$@ .",