Merge branch 'stable-2.15'
* stable-2.15:
Include trailing slash in link to settings
Remove two-way databinding from patch range select
Doc: Set default character set for MySQL
Change-Id: I1879695346144e547bd7dd72719e182f985ea41d
diff --git a/Documentation/dev-plugin-pg-styling.txt b/Documentation/dev-plugin-pg-styling.txt
deleted file mode 100644
index 618d984..0000000
--- a/Documentation/dev-plugin-pg-styling.txt
+++ /dev/null
@@ -1,61 +0,0 @@
-= Gerrit Code Review - PolyGerrit Plugin Styling
-
-CAUTION: Work in progress. Hard hat area. +
-This document will be populated with details along with implementation. +
-link:https://groups.google.com/d/topic/repo-discuss/vb8WJ4m0hK0/discussion[Join the discussion.]
-
-== Plugin styles
-
-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.
-
-PolyGerrit UI implements number of styling endpoints, which apply CSS mixins link:https://tabatkins.github.io/specs/css-apply-rule/[using @apply] to its direct contents.
-
-NOTE: Only items (ie 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 usecase to track and maintain for future releases.
-
-Plugin should be html-based and imported following PolyGerrit's link:dev-plugins-pg.html#loading[dev guide].
-
-Plugin should provide Style Module, for example:
-
-``` html
- <dom-module id="some-style">
- <style>
- :root {
- --css-mixin-name: {
- property: value;
- }
- }
- </style>
- </dom-module>
-```
-
-Plugin should register style module with a styling endpoint using `Plugin.prototype.registerStyleModule(endpointName, styleModuleName)`, for example:
-
-``` js
- Gerrit.install(function(plugin) {
- plugin.registerStyleModule('some-endpoint', 'some-style');
- });
-```
-
-== Available styling endpoints
-=== change-metadata
-Following custom css mixins are recognized:
-
-* `--change-metadata-assignee`
-+
-is applied to `gr-change-metadata section.assignee`
-* `--change-metadata-label-status`
-+
-is applied to `gr-change-metadata section.labelStatus`
-* `--change-metadata-strategy`
-+
-is applied to `gr-change-metadata section.strategy`
-* `--change-metadata-topic`
-+
-is applied to `gr-change-metadata section.topic`
-
-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]:
-
-* `display`
-+
-can be set to `none` to hide a section.
diff --git a/Documentation/dev-release.txt b/Documentation/dev-release.txt
index 039d545..ddf7e7f 100644
--- a/Documentation/dev-release.txt
+++ b/Documentation/dev-release.txt
@@ -73,27 +73,11 @@
== Create the Actual Release
-To create a Gerrit release the following steps have to be done:
-
-. link:#build-gerrit[Build the Gerrit Release]
-. link:#publish-gerrit[Publish the Gerrit Release]
-.. link:#publish-to-maven-central[Publish the Gerrit artifacts to Maven Central]
-.. link:#publish-to-google-storage[Publish the Gerrit WAR to Google Storage]
-.. link:#push-stable[Push the Stable Branch]
-.. link:#push-tag[Push the Release Tag]
-.. link:#upload-documentation[Upload the Documentation]
-.. link:#finalize-release-notes[Finalize Release Notes]
-.. link:#update-issues[Update the Issues]
-.. link:#announce[Announce on Mailing List]
-. link:#increase-version[Increase Gerrit Version for Current Development]
-. link:#merge-stable[Merge `stable` into `master`]
-
-
[[update-versions]]
=== Update Versions and Create Release Tag
Before doing the release build, the `GERRIT_VERSION` in the `version.bzl`
-file must be updated, e.g. change it from `2.5-SNAPSHOT` to `2.5`.
+file must be updated, e.g. change it from `$version-SNAPSHOT` to `$version`.
In addition the version must be updated in a number of pom.xml files.
@@ -107,13 +91,14 @@
Commit the changes and create a signed release tag on the new commit:
----
- git tag -s -m "v2.5" v2.5
+ version=2.15
+ git tag -s -m "v$version" "v$version"
----
Tag the plugins:
----
- git submodule foreach git tag -s -m "v2.5" v2.5
+ git submodule foreach git tag -s -m "v$version" "v$version"
----
[[build-gerrit]]
@@ -126,8 +111,12 @@
./tools/maven/api.sh install
----
-* Sanity check WAR
-* Test the new Gerrit version
+* Verify the WAR version:
++
+----
+ java -jar ~/dl/gerrit-$version.war --version
+----
+* Try upgrading a test site and launching the daemon
* Verify plugin versions
+
@@ -257,11 +246,11 @@
[[push-stable]]
==== Push the Stable Branch
-* Create the stable branch `stable-2.5` in the `gerrit` project via the
+* Create the stable branch `stable-$version` in the `gerrit` project via the
link:https://gerrit-review.googlesource.com/#/admin/projects/gerrit,branches[
Gerrit Web UI] or by push.
-* Push the commits done on `stable-2.5` to `refs/for/stable-2.5` and
+* Push the commits done on `stable-$version` to `refs/for/stable-$version` and
get them merged
@@ -271,13 +260,13 @@
Push the new Release Tag:
----
- git push gerrit-review tag v2.5
+ git push gerrit-review tag v$version
----
Push the new Release Tag on the plugins:
----
- git submodule foreach git push gerrit-review tag v2.5
+ git submodule foreach git push gerrit-review tag v$version
----
@@ -314,11 +303,11 @@
Update the issues by hand. There is no script for this.
Our current process is an issue should be updated to say `Status =
-Submitted, FixedIn-2.5` once the change is submitted, but before the
+Submitted, FixedIn-$version` once the change is submitted, but before the
release.
After the release is actually made, you can search in Google Code for
-`Status=Submitted FixedIn=2.5` and then batch update these changes
+`Status=Submitted FixedIn=$version` and then batch update these changes
to say `Status=Released`. Make sure the pulldown says `All Issues`
because `Status=Submitted` is considered a closed issue.
diff --git a/Documentation/dev-plugins-pg.txt b/Documentation/pg-plugin-dev.txt
similarity index 66%
rename from Documentation/dev-plugins-pg.txt
rename to Documentation/pg-plugin-dev.txt
index e1bf39e..92c52f6 100644
--- a/Documentation/dev-plugins-pg.txt
+++ b/Documentation/pg-plugin-dev.txt
@@ -1,10 +1,5 @@
= Gerrit Code Review - PolyGerrit Plugin Development
-CAUTION: Work in progress. Hard hat area. +
-This document will be populated with details along with implementation. +
-link:https://groups.google.com/d/topic/repo-discuss/vb8WJ4m0hK0/discussion[Join
-the discussion.]
-
[[loading]]
== Plugin loading and initialization
@@ -33,8 +28,8 @@
</dom-module>
```
-[[low-level-api]]
-== Low-level DOM API
+[[low-level-api-concepts]]
+== Low-level DOM API concepts
Basically, the DOM is the API surface. Low-level API provides methods for
decorating, replacing, and styling DOM elements exposed through a set of
@@ -126,3 +121,119 @@
</style>
</dom-module>
```
+
+[[high-level-api-concepts]]
+== High-level DOM API concepts
+
+High leve API is based on low-level DOM API and is essentially a standartized
+way for doing common tasks. It's less flexible, but will be a bit more stable.
+
+Common way to access high-leve API is through `plugin` instance passed into
+setup callback parameter of `Gerrit.install()`, also sometimes referred as
+`self`.
+
+[[low-level-api]]
+== Low-level DOM API
+
+Low-level DOM API methods are the base of all UI customization.
+
+=== attributeHelper
+`plugin.attributeHelper(element)`
+
+Note: TODO
+
+=== eventHelper
+`plugin.eventHelper(element)`
+
+Note: TODO
+
+=== hook
+`plugin.hook(endpointName, opt_options)`
+
+Note: TODO
+
+=== registerCustomComponent
+`plugin.registerCustomComponent(endpointName, opt_moduleName, opt_options)`
+
+Note: TODO
+
+=== registerStyleModule
+`plugin.registerStyleModule(endpointName, moduleName)`
+
+Note: TODO
+
+[[high-level-api]]
+== High-level API
+
+Plugin instance provides access to number of more specific APIs and methods
+to be used by plugin authors.
+
+=== changeReply
+`plugin.changeReply()`
+
+Note: TODO
+
+=== changeView
+`plugin.changeView()`
+
+Note: TODO
+
+=== delete
+`plugin.delete(url, opt_callback)`
+
+Note: TODO
+
+=== get
+`plugin.get(url, opt_callback)`
+
+Note: TODO
+
+=== getPluginName
+`plugin.getPluginName()`
+
+Note: TODO
+
+=== getServerInfo
+`plugin.getServerInfo()`
+
+Note: TODO
+
+=== on
+`plugin.on(eventName, callback)`
+
+Note: TODO
+
+=== popup
+`plugin.popup(moduleName)`
+
+Note: TODO
+
+=== post
+`plugin.post(url, payload, opt_callback)`
+
+Note: TODO
+
+[plugin-project]
+=== project
+`plugin.project()`
+
+.Params:
+- none
+
+.Returns:
+- Instance of link:pg-plugin-project-api.html[GrProjectApi].
+
+=== put
+`plugin.put(url, payload, opt_callback)`
+
+Note: TODO
+
+=== theme
+`plugin.theme()`
+
+Note: TODO
+
+=== url
+`plugin.url(opt_path)`
+
+Note: TODO
diff --git a/Documentation/pg-plugin-project-api.txt b/Documentation/pg-plugin-project-api.txt
new file mode 100644
index 0000000..897430c
--- /dev/null
+++ b/Documentation/pg-plugin-project-api.txt
@@ -0,0 +1,36 @@
+= Gerrit Code Review - Project admin customization API
+
+This API is provided by link:pg-plugin-dev.html#plugin-project[plugin.project()]
+and provides customization to admin page.
+
+== createCommand
+`projectApi.createCommand(title, checkVisibleCallback)`
+
+Create a project command in the admin panel.
+
+.Params
+- *title* String title.
+- *checkVisibleCallback* function to configure command visibility.
+
+.Returns
+- GrProjectApi for chainging.
+
+`checkVisibleCallback(projectName, projectConfig)`
+
+.Params
+- *projectName* String project name.
+- *projectConfig* Object REST API response for project config.
+
+.Returns
+- `false` to hide the command for the specific project.
+
+== onTap
+`projectApi.onTap(tapCalback)`
+
+Add a command tap callback.
+
+.Params
+- *tapCallback* function that's excuted on command tap.
+
+.Returns
+- Nothing
diff --git a/Documentation/pg-plugin-styling.txt b/Documentation/pg-plugin-styling.txt
new file mode 100644
index 0000000..58b6d7a
--- /dev/null
+++ b/Documentation/pg-plugin-styling.txt
@@ -0,0 +1,69 @@
+= Gerrit Code Review - PolyGerrit Plugin Styling
+
+== Plugin styles
+
+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.
+
+PolyGerrit UI implements number of styling endpoints, which apply CSS mixins
+link:https://tabatkins.github.io/specs/css-apply-rule/[using @apply] to its
+direct contents.
+
+NOTE: Only items (ie 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 usecase to track and maintain for future releases.
+
+Plugin should be html-based and imported following PolyGerrit's
+link:pg-plugin-dev.html#loading[dev guide].
+
+Plugin should provide Style Module, for example:
+
+``` html
+ <dom-module id="some-style">
+ <style>
+ :root {
+ --css-mixin-name: {
+ property: value;
+ }
+ }
+ </style>
+ </dom-module>
+```
+
+Plugin should register style module with a styling endpoint using
+`Plugin.prototype.registerStyleModule(endpointName, styleModuleName)`, for
+example:
+
+``` js
+ Gerrit.install(function(plugin) {
+ plugin.registerStyleModule('some-endpoint', 'some-style');
+ });
+```
+
+== Available styling endpoints
+=== change-metadata
+Following custom css mixins are recognized:
+
+* `--change-metadata-assignee`
++
+is applied to `gr-change-metadata section.assignee`
+* `--change-metadata-label-status`
++
+is applied to `gr-change-metadata section.labelStatus`
+* `--change-metadata-strategy`
++
+is applied to `gr-change-metadata section.strategy`
+* `--change-metadata-topic`
++
+is applied to `gr-change-metadata section.topic`
+
+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]:
+
+* `display`
++
+can be set to `none` to hide a section.
diff --git a/WORKSPACE b/WORKSPACE
index ac4d04b..6458571 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -192,18 +192,6 @@
sha1 = "de80fe047052445869b96f6def6baca7182c95af",
)
-maven_jar(
- name = "joda_time",
- artifact = "joda-time:joda-time:2.9.9",
- sha1 = "f7b520c458572890807d143670c9b24f4de90897",
-)
-
-maven_jar(
- name = "joda_convert",
- artifact = "org.joda:joda-convert:1.8.1",
- sha1 = "675642ac208e0b741bc9118dcbcae44c271b992a",
-)
-
load("//lib:guava.bzl", "GUAVA_VERSION", "GUAVA_BIN_SHA1")
maven_jar(
@@ -922,8 +910,8 @@
# When upgrading Elasticsearch, make sure it's compatible with Lucene
maven_jar(
name = "elasticsearch",
- artifact = "org.elasticsearch:elasticsearch:2.4.5",
- sha1 = "daafe48ae06592029a2fedca1fe2ac0f5eec3185",
+ artifact = "org.elasticsearch:elasticsearch:2.4.6",
+ sha1 = "d2954e1173a608a9711f132d1768a676a8b1fb81",
)
# Java REST client for Elasticsearch.
@@ -942,6 +930,18 @@
)
maven_jar(
+ name = "joda_time",
+ artifact = "joda-time:joda-time:2.9.9",
+ sha1 = "f7b520c458572890807d143670c9b24f4de90897",
+)
+
+maven_jar(
+ name = "joda_convert",
+ artifact = "org.joda:joda-convert:1.8.1",
+ sha1 = "675642ac208e0b741bc9118dcbcae44c271b992a",
+)
+
+maven_jar(
name = "compress_lzf",
artifact = "com.ning:compress-lzf:1.0.2",
sha1 = "62896e6fca184c79cc01a14d143f3ae2b4f4b4ae",
diff --git a/gerrit-acceptance-framework/pom.xml b/gerrit-acceptance-framework/pom.xml
index 2b1dcb0..a0f2e67 100644
--- a/gerrit-acceptance-framework/pom.xml
+++ b/gerrit-acceptance-framework/pom.xml
@@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-acceptance-framework</artifactId>
- <version>2.15-rc0</version>
+ <version>2.16-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Gerrit Code Review - Acceptance Test Framework</name>
<description>Framework for Gerrit's acceptance tests</description>
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/plugin/PluginIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/plugin/PluginIT.java
index 0fa09af..0de6a30 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/plugin/PluginIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/plugin/PluginIT.java
@@ -28,6 +28,7 @@
import com.google.gerrit.extensions.api.plugins.Plugins.ListRequest;
import com.google.gerrit.extensions.common.InstallPluginInput;
import com.google.gerrit.extensions.common.PluginInfo;
+import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.RawInput;
@@ -107,12 +108,21 @@
api = gApi.plugins().name("plugin-a");
assertThat(api.get().disabled).isNull();
assertPlugins(list().get(), PLUGINS);
+
+ // Non-admin cannot disable
+ setApiUser(user);
+ try {
+ gApi.plugins().name("plugin-a").disable();
+ fail("Expected AuthException");
+ } catch (AuthException expected) {
+ // Expected
+ }
}
@Test
public void installNotAllowed() throws Exception {
exception.expect(MethodNotAllowedException.class);
- exception.expectMessage("remote installation is disabled");
+ exception.expectMessage("remote plugin administration is disabled");
gApi.plugins().install("test.js", new InstallPluginInput());
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/DashboardIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/DashboardIT.java
index b140a6e..6f4495e 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/DashboardIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/DashboardIT.java
@@ -16,12 +16,15 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static java.util.stream.Collectors.toList;
+import com.google.common.collect.ImmutableList;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.api.projects.DashboardInfo;
+import com.google.gerrit.extensions.api.projects.DashboardSectionInfo;
import com.google.gerrit.extensions.api.projects.ProjectApi;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -56,20 +59,40 @@
@Test
public void getDashboard() throws Exception {
- assertThat(dashboards()).isEmpty();
- DashboardInfo info = createDashboard(DashboardsCollection.DEFAULT_DASHBOARD_NAME, "test");
+ DashboardInfo info = createTestDashboard();
DashboardInfo result = project().dashboard(info.id).get();
- assertThat(result.id).isEqualTo(info.id);
- assertThat(result.path).isEqualTo(info.path);
- assertThat(result.ref).isEqualTo(info.ref);
- assertThat(result.project).isEqualTo(project.get());
- assertThat(result.definingProject).isEqualTo(project.get());
- assertThat(dashboards()).hasSize(1);
+ assertDashboardInfo(result, info);
+ }
+
+ @Test
+ public void getDashboardWithNoDescription() throws Exception {
+ DashboardInfo info = newDashboardInfo(DashboardsCollection.DEFAULT_DASHBOARD_NAME, "test");
+ info.description = null;
+ DashboardInfo created = createDashboard(info);
+ assertThat(created.description).isNull();
+ DashboardInfo result = project().dashboard(created.id).get();
+ assertThat(result.description).isNull();
+ }
+
+ @Test
+ public void getDashboardNonDefault() throws Exception {
+ DashboardInfo info = createTestDashboard("my", "test");
+ DashboardInfo result = project().dashboard(info.id).get();
+ assertDashboardInfo(result, info);
+ }
+
+ @Test
+ public void listDashboards() throws Exception {
+ assertThat(dashboards()).isEmpty();
+ DashboardInfo info1 = createTestDashboard(DashboardsCollection.DEFAULT_DASHBOARD_NAME, "test1");
+ DashboardInfo info2 = createTestDashboard(DashboardsCollection.DEFAULT_DASHBOARD_NAME, "test2");
+ assertThat(dashboards().stream().map(d -> d.id).collect(toList()))
+ .containsExactly(info1.id, info2.id);
}
@Test
public void setDefaultDashboard() throws Exception {
- DashboardInfo info = createDashboard(DashboardsCollection.DEFAULT_DASHBOARD_NAME, "test");
+ DashboardInfo info = createTestDashboard();
assertThat(info.isDefault).isNull();
project().dashboard(info.id).setDefault();
assertThat(project().dashboard(info.id).get().isDefault).isTrue();
@@ -78,7 +101,7 @@
@Test
public void setDefaultDashboardByProject() throws Exception {
- DashboardInfo info = createDashboard(DashboardsCollection.DEFAULT_DASHBOARD_NAME, "test");
+ DashboardInfo info = createTestDashboard();
assertThat(info.isDefault).isNull();
project().defaultDashboard(info.id);
assertThat(project().dashboard(info.id).get().isDefault).isTrue();
@@ -93,8 +116,8 @@
@Test
public void replaceDefaultDashboard() throws Exception {
- DashboardInfo d1 = createDashboard(DashboardsCollection.DEFAULT_DASHBOARD_NAME, "test1");
- DashboardInfo d2 = createDashboard(DashboardsCollection.DEFAULT_DASHBOARD_NAME, "test2");
+ DashboardInfo d1 = createTestDashboard(DashboardsCollection.DEFAULT_DASHBOARD_NAME, "test1");
+ DashboardInfo d2 = createTestDashboard(DashboardsCollection.DEFAULT_DASHBOARD_NAME, "test2");
assertThat(d1.isDefault).isNull();
assertThat(d2.isDefault).isNull();
project().dashboard(d1.id).setDefault();
@@ -109,12 +132,28 @@
@Test
public void cannotGetDashboardWithInheritedForNonDefault() throws Exception {
- DashboardInfo info = createDashboard(DashboardsCollection.DEFAULT_DASHBOARD_NAME, "test");
+ DashboardInfo info = createTestDashboard();
exception.expect(BadRequestException.class);
exception.expectMessage("inherited flag can only be used with default");
project().dashboard(info.id).get(true);
}
+ private void assertDashboardInfo(DashboardInfo actual, DashboardInfo expected) throws Exception {
+ assertThat(actual.id).isEqualTo(expected.id);
+ assertThat(actual.path).isEqualTo(expected.path);
+ assertThat(actual.ref).isEqualTo(expected.ref);
+ assertThat(actual.project).isEqualTo(project.get());
+ assertThat(actual.definingProject).isEqualTo(project.get());
+ assertThat(actual.description).isEqualTo(expected.description);
+ assertThat(actual.title).isEqualTo(expected.title);
+ assertThat(actual.foreach).isEqualTo(expected.foreach);
+ if (expected.sections == null) {
+ assertThat(actual.sections).isNull();
+ } else {
+ assertThat(actual.sections.size()).isEqualTo(expected.sections.size());
+ }
+ }
+
private List<DashboardInfo> dashboards() throws Exception {
return project().dashboards().get();
}
@@ -123,8 +162,27 @@
return gApi.projects().name(project.get());
}
- private DashboardInfo createDashboard(String ref, String path) throws Exception {
+ private DashboardInfo newDashboardInfo(String ref, String path) {
DashboardInfo info = DashboardsCollection.newDashboardInfo(ref, path);
+ info.title = "Reviewer";
+ info.description = "Own review requests";
+ info.foreach = "owner:self";
+ DashboardSectionInfo section = new DashboardSectionInfo();
+ section.name = "Open";
+ section.query = "is:open";
+ info.sections = ImmutableList.of(section);
+ return info;
+ }
+
+ private DashboardInfo createTestDashboard() throws Exception {
+ return createTestDashboard(DashboardsCollection.DEFAULT_DASHBOARD_NAME, "test");
+ }
+
+ private DashboardInfo createTestDashboard(String ref, String path) throws Exception {
+ return createDashboard(newDashboardInfo(ref, path));
+ }
+
+ private DashboardInfo createDashboard(DashboardInfo info) throws Exception {
String canonicalRef = DashboardsCollection.normalizeDashboardRef(info.ref);
try {
project().branch(canonicalRef).create(new BranchInput());
@@ -137,13 +195,23 @@
try (Repository r = repoManager.openRepository(project)) {
TestRepository<Repository>.CommitBuilder cb =
new TestRepository<>(r).branch(canonicalRef).commit();
- String content =
- "[dashboard]\n"
- + "Description = Test\n"
- + "foreach = owner:self\n"
- + "[section \"Mine\"]\n"
- + "query = is:open";
- cb.add(info.path, content);
+ StringBuilder content = new StringBuilder("[dashboard]\n");
+ if (info.title != null) {
+ content.append("title = ").append(info.title).append("\n");
+ }
+ if (info.description != null) {
+ content.append("description = ").append(info.description).append("\n");
+ }
+ if (info.foreach != null) {
+ content.append("foreach = ").append(info.foreach).append("\n");
+ }
+ if (info.sections != null) {
+ for (DashboardSectionInfo section : info.sections) {
+ content.append("[section \"").append(section.name).append("\"]\n");
+ content.append("query = ").append(section.query).append("\n");
+ }
+ }
+ cb.add(info.path, content.toString());
RevCommit c = cb.create();
project().commit(c.name());
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/BUILD b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/BUILD
index 990bad6..6bfecfa 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/BUILD
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/BUILD
@@ -4,7 +4,4 @@
srcs = ["ChangeEditIT.java"],
group = "edit",
labels = ["edit"],
- deps = [
- "//lib/joda:joda-time",
- ],
)
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/BUILD b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/BUILD
index 43ec5bc..897b99f 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/BUILD
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/BUILD
@@ -16,7 +16,6 @@
srcs = ["AbstractPushForReview.java"],
deps = [
"//gerrit-acceptance-tests:lib",
- "//lib/joda:joda-time",
],
)
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUILD b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUILD
index b7ed2e8..49f00f9 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUILD
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUILD
@@ -15,7 +15,6 @@
labels = ["rest"],
deps = [
":submit_util",
- "//lib/joda:joda-time",
],
)
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/mail/AbstractMailIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/mail/AbstractMailIT.java
index 6f4bdab..32f1ce5 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/mail/AbstractMailIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/mail/AbstractMailIT.java
@@ -23,8 +23,8 @@
import com.google.gerrit.extensions.client.Comment;
import com.google.gerrit.extensions.client.Side;
import com.google.gerrit.server.mail.receive.MailMessage;
+import java.time.Instant;
import java.util.HashMap;
-import org.joda.time.DateTime;
import org.junit.Ignore;
@Ignore
@@ -36,7 +36,7 @@
b.from(user.emailAddress);
b.addTo(user.emailAddress); // Not evaluated
b.subject("");
- b.dateReceived(new DateTime());
+ b.dateReceived(Instant.now());
return b;
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/mail/BUILD b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/mail/BUILD
index 71a6135..c3a4e20 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/mail/BUILD
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/mail/BUILD
@@ -2,7 +2,6 @@
DEPS = [
"//lib/greenmail",
- "//lib/joda:joda-time",
"//lib/mail",
]
diff --git a/gerrit-common/BUILD b/gerrit-common/BUILD
index 4389080..d9d4392 100644
--- a/gerrit-common/BUILD
+++ b/gerrit-common/BUILD
@@ -28,7 +28,6 @@
"//lib:gwtorm_client",
"//lib:servlet-api-3_1",
"//lib/jgit/org.eclipse.jgit:jgit",
- "//lib/joda:joda-time",
"//lib/log:api",
],
gwt_xml = SRC + "Common.gwt.xml",
@@ -53,7 +52,6 @@
"//lib:gwtorm",
"//lib:servlet-api-3_1",
"//lib/jgit/org.eclipse.jgit:jgit",
- "//lib/joda:joda-time",
"//lib/log:api",
],
)
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/TimeUtil.java b/gerrit-common/src/main/java/com/google/gerrit/common/TimeUtil.java
index a8e40c6..b1697dc 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/TimeUtil.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/TimeUtil.java
@@ -15,14 +15,21 @@
package com.google.gerrit.common;
import com.google.common.annotations.GwtIncompatible;
+import com.google.common.annotations.VisibleForTesting;
import java.sql.Timestamp;
-import org.joda.time.DateTimeUtils;
+import java.util.function.LongSupplier;
/** Static utility methods for dealing with dates and times. */
-@GwtIncompatible("Unemulated org.joda.time.DateTimeUtils")
+@GwtIncompatible("Unemulated Java 8 functionalities")
public class TimeUtil {
+ private static final LongSupplier SYSTEM_CURRENT_MILLIS_SUPPLIER = System::currentTimeMillis;
+
+ private static volatile LongSupplier currentMillisSupplier = SYSTEM_CURRENT_MILLIS_SUPPLIER;
+
public static long nowMs() {
- return DateTimeUtils.currentTimeMillis();
+ // We should rather use Instant.now(Clock).toEpochMilli() instead but this would require some
+ // changes in our testing code as we wouldn't have clock steps anymore.
+ return currentMillisSupplier.getAsLong();
}
public static Timestamp nowTs() {
@@ -33,5 +40,15 @@
return new Timestamp((t.getTime() / 1000) * 1000);
}
+ @VisibleForTesting
+ public static void setCurrentMillisSupplier(LongSupplier customCurrentMillisSupplier) {
+ currentMillisSupplier = customCurrentMillisSupplier;
+ }
+
+ @VisibleForTesting
+ public static void resetCurrentMillisSupplier() {
+ currentMillisSupplier = SYSTEM_CURRENT_MILLIS_SUPPLIER;
+ }
+
private TimeUtil() {}
}
diff --git a/gerrit-elasticsearch/BUILD b/gerrit-elasticsearch/BUILD
index fb86aaf..d278bcf 100644
--- a/gerrit-elasticsearch/BUILD
+++ b/gerrit-elasticsearch/BUILD
@@ -17,10 +17,10 @@
"//lib/elasticsearch",
"//lib/elasticsearch:jest",
"//lib/elasticsearch:jest-common",
+ "//lib/elasticsearch:joda-time",
"//lib/guice",
"//lib/guice:guice-assistedinject",
"//lib/jgit/org.eclipse.jgit:jgit",
- "//lib/joda:joda-time",
"//lib/log:api",
"//lib/lucene:lucene-analyzers-common",
"//lib/lucene:lucene-core",
diff --git a/gerrit-extension-api/pom.xml b/gerrit-extension-api/pom.xml
index 7179f46..a8ae2e6 100644
--- a/gerrit-extension-api/pom.xml
+++ b/gerrit-extension-api/pom.xml
@@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-extension-api</artifactId>
- <version>2.15-rc0</version>
+ <version>2.16-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Gerrit Code Review - Extension API</name>
<description>API for Gerrit Extensions</description>
diff --git a/gerrit-httpd/BUILD b/gerrit-httpd/BUILD
index dbca10c..cc2160f 100644
--- a/gerrit-httpd/BUILD
+++ b/gerrit-httpd/BUILD
@@ -77,6 +77,5 @@
"//lib/guice:guice-servlet",
"//lib/jgit/org.eclipse.jgit:jgit",
"//lib/jgit/org.eclipse.jgit.junit:junit",
- "//lib/joda:joda-time",
],
)
diff --git a/gerrit-httpd/src/main/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy b/gerrit-httpd/src/main/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy
index 51c60af..c3b522a 100644
--- a/gerrit-httpd/src/main/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy
+++ b/gerrit-httpd/src/main/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy
@@ -21,7 +21,7 @@
* @param staticResourcePath
* @param? versionInfo
*/
-{template .Index autoescape="strict" kind="html"}
+{template .Index kind="html"}
<!DOCTYPE html>{\n}
<html lang="en">{\n}
<meta charset="utf-8">{\n}
diff --git a/gerrit-httpd/src/test/java/com/google/gerrit/httpd/raw/ResourceServletTest.java b/gerrit-httpd/src/test/java/com/google/gerrit/httpd/raw/ResourceServletTest.java
index 18256c6..6dd15bc 100644
--- a/gerrit-httpd/src/test/java/com/google/gerrit/httpd/raw/ResourceServletTest.java
+++ b/gerrit-httpd/src/test/java/com/google/gerrit/httpd/raw/ResourceServletTest.java
@@ -36,9 +36,11 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.FileTime;
+import java.time.LocalDateTime;
+import java.time.Month;
+import java.time.ZoneOffset;
import java.util.concurrent.atomic.AtomicLong;
import java.util.zip.GZIPInputStream;
-import org.joda.time.format.ISODateTimeFormat;
import org.junit.Before;
import org.junit.Test;
@@ -91,7 +93,12 @@
@Before
public void setUp() {
fs = Jimfs.newFileSystem(Configuration.unix());
- ts = new AtomicLong(ISODateTimeFormat.dateTime().parseMillis("2010-01-30T12:00:00.000-08:00"));
+ ts =
+ new AtomicLong(
+ LocalDateTime.of(2010, Month.JANUARY, 30, 12, 0, 0)
+ .atOffset(ZoneOffset.ofHours(-8))
+ .toInstant()
+ .toEpochMilli());
}
@Test
diff --git a/gerrit-pgm/BUILD b/gerrit-pgm/BUILD
index 1fd3165..60663d7 100644
--- a/gerrit-pgm/BUILD
+++ b/gerrit-pgm/BUILD
@@ -22,7 +22,6 @@
"//lib/guice:guice-assistedinject",
"//lib/guice:guice-servlet",
"//lib/jgit/org.eclipse.jgit:jgit",
- "//lib/joda:joda-time",
"//lib/log:api",
"//lib/log:log4j",
]
diff --git a/gerrit-plugin-api/BUILD b/gerrit-plugin-api/BUILD
index fe9ce19..5ed4b8c 100644
--- a/gerrit-plugin-api/BUILD
+++ b/gerrit-plugin-api/BUILD
@@ -29,7 +29,6 @@
"//lib/httpcomponents:httpcore",
"//lib/jgit/org.eclipse.jgit.http.server:jgit-servlet",
"//lib/jgit/org.eclipse.jgit:jgit",
- "//lib/joda:joda-time",
"//lib/log:api",
"//lib/log:log4j",
"//lib/mina:sshd",
diff --git a/gerrit-plugin-api/pom.xml b/gerrit-plugin-api/pom.xml
index c99220f..84df44a 100644
--- a/gerrit-plugin-api/pom.xml
+++ b/gerrit-plugin-api/pom.xml
@@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-plugin-api</artifactId>
- <version>2.15-rc0</version>
+ <version>2.16-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Gerrit Code Review - Plugin API</name>
<description>API for Gerrit Plugins</description>
diff --git a/gerrit-plugin-gwtui/pom.xml b/gerrit-plugin-gwtui/pom.xml
index 122f54a..cc9aafc 100644
--- a/gerrit-plugin-gwtui/pom.xml
+++ b/gerrit-plugin-gwtui/pom.xml
@@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-plugin-gwtui</artifactId>
- <version>2.15-rc0</version>
+ <version>2.16-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Gerrit Code Review - Plugin GWT UI</name>
<description>Common Classes for Gerrit GWT UI Plugins</description>
diff --git a/gerrit-server/BUILD b/gerrit-server/BUILD
index e124e89..e285ee5 100644
--- a/gerrit-server/BUILD
+++ b/gerrit-server/BUILD
@@ -92,7 +92,6 @@
"//lib/guice:guice-servlet",
"//lib/jgit/org.eclipse.jgit.archive:jgit-archive",
"//lib/jgit/org.eclipse.jgit:jgit",
- "//lib/joda:joda-time",
"//lib/jsoup",
"//lib/log:api",
"//lib/log:jsonevent-layout",
@@ -181,7 +180,6 @@
"//lib/guice:guice-servlet",
"//lib/jgit/org.eclipse.jgit:jgit",
"//lib/jgit/org.eclipse.jgit.junit:junit",
- "//lib/joda:joda-time",
"//lib/log:api",
"//lib/log:impl_log4j",
"//lib/log:log4j",
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java
index 21da0b8..dc180cc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java
@@ -42,6 +42,7 @@
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.patch.PatchListKey;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
+import com.google.gerrit.server.patch.PatchListObjectTooLargeException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -240,6 +241,8 @@
try {
return copy(res.files(), res.patchSetId(), resource, userId);
+ } catch (PatchListObjectTooLargeException e) {
+ log.warn("Cannot copy patch review flags: " + e.getMessage());
} catch (IOException | PatchListNotAvailableException e) {
log.warn("Cannot copy patch review flags", e);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ScheduleConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ScheduleConfig.java
index 4a87474..c5d60a3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/ScheduleConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ScheduleConfig.java
@@ -16,16 +16,16 @@
import com.google.common.annotations.VisibleForTesting;
import java.text.MessageFormat;
+import java.time.DayOfWeek;
+import java.time.Duration;
+import java.time.LocalTime;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.time.temporal.ChronoUnit;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.lib.Config;
-import org.joda.time.DateTime;
-import org.joda.time.LocalDateTime;
-import org.joda.time.LocalTime;
-import org.joda.time.MutableDateTime;
-import org.joda.time.format.DateTimeFormat;
-import org.joda.time.format.DateTimeFormatter;
-import org.joda.time.format.ISODateTimeFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -49,16 +49,16 @@
}
public ScheduleConfig(Config rc, String section, String subsection) {
- this(rc, section, subsection, DateTime.now());
+ this(rc, section, subsection, ZonedDateTime.now());
}
public ScheduleConfig(
Config rc, String section, String subsection, String keyInterval, String keyStartTime) {
- this(rc, section, subsection, keyInterval, keyStartTime, DateTime.now());
+ this(rc, section, subsection, keyInterval, keyStartTime, ZonedDateTime.now());
}
@VisibleForTesting
- ScheduleConfig(Config rc, String section, String subsection, DateTime now) {
+ ScheduleConfig(Config rc, String section, String subsection, ZonedDateTime now) {
this(rc, section, subsection, KEY_INTERVAL, KEY_STARTTIME, now);
}
@@ -69,7 +69,7 @@
String subsection,
String keyInterval,
String keyStartTime,
- DateTime now) {
+ ZonedDateTime now) {
this.rc = rc;
this.section = section;
this.subsection = subsection;
@@ -122,31 +122,24 @@
String section,
String subsection,
String keyStartTime,
- DateTime now,
+ ZonedDateTime now,
long interval) {
long delay = MISSING_CONFIG;
String start = rc.getString(section, subsection, keyStartTime);
try {
if (start != null) {
- DateTimeFormatter formatter;
- MutableDateTime startTime = now.toMutableDateTime();
+ DateTimeFormatter formatter =
+ DateTimeFormatter.ofPattern("[E ]HH:mm").withLocale(Locale.US);
+ LocalTime firstStartTime = LocalTime.parse(start, formatter);
+ ZonedDateTime startTime = now.with(firstStartTime);
try {
- formatter = ISODateTimeFormat.hourMinute();
- LocalTime firstStartTime = formatter.parseLocalTime(start);
- startTime.hourOfDay().set(firstStartTime.getHourOfDay());
- startTime.minuteOfHour().set(firstStartTime.getMinuteOfHour());
- } catch (IllegalArgumentException e1) {
- formatter = DateTimeFormat.forPattern("E HH:mm").withLocale(Locale.US);
- LocalDateTime firstStartDateTime = formatter.parseLocalDateTime(start);
- startTime.dayOfWeek().set(firstStartDateTime.getDayOfWeek());
- startTime.hourOfDay().set(firstStartDateTime.getHourOfDay());
- startTime.minuteOfHour().set(firstStartDateTime.getMinuteOfHour());
+ DayOfWeek dayOfWeek = formatter.parse(start, DayOfWeek::from);
+ startTime = startTime.with(dayOfWeek);
+ } catch (DateTimeParseException ignored) {
+ // Day of week is an optional parameter.
}
- startTime.secondOfMinute().set(0);
- startTime.millisOfSecond().set(0);
- long s = startTime.getMillis();
- long n = now.getMillis();
- delay = (s - n) % interval;
+ startTime = startTime.truncatedTo(ChronoUnit.MINUTES);
+ delay = Duration.between(now, startTime).toMillis() % interval;
if (delay <= 0) {
delay += interval;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
index afd78dc..2614eaf 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
@@ -56,6 +56,7 @@
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.patch.PatchListEntry;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
+import com.google.gerrit.server.patch.PatchListObjectTooLargeException;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gwtorm.server.OrmException;
@@ -425,6 +426,8 @@
p.insertions = patch.getInsertions();
patchSetAttribute.files.add(p);
}
+ } catch (PatchListObjectTooLargeException e) {
+ log.warn("Cannot get patch list: " + e.getMessage());
} catch (PatchListNotAvailableException e) {
log.warn("Cannot get patch list", e);
}
@@ -498,6 +501,8 @@
p.kind = changeKindCache.getChangeKind(db, change, patchSet);
} catch (IOException | OrmException e) {
log.error("Cannot load patch set data for " + patchSet.getId(), e);
+ } catch (PatchListObjectTooLargeException e) {
+ log.warn(String.format("Cannot get size information for %s: %s", pId, e.getMessage()));
} catch (PatchListNotAvailableException e) {
log.error(String.format("Cannot get size information for %s.", pId), e);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ChangeAbandoned.java b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ChangeAbandoned.java
index f9fc60a..1415f3b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ChangeAbandoned.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ChangeAbandoned.java
@@ -25,6 +25,7 @@
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.GpgException;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
+import com.google.gerrit.server.patch.PatchListObjectTooLargeException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import java.io.IOException;
@@ -70,6 +71,8 @@
util.logEventListenerError(this, l, e);
}
}
+ } catch (PatchListObjectTooLargeException e) {
+ log.warn("Couldn't fire event: " + e.getMessage());
} catch (PatchListNotAvailableException | GpgException | IOException | OrmException e) {
log.error("Couldn't fire event", e);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ChangeMerged.java b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ChangeMerged.java
index feaa54a..3bba164 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ChangeMerged.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ChangeMerged.java
@@ -25,6 +25,7 @@
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.GpgException;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
+import com.google.gerrit.server.patch.PatchListObjectTooLargeException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import java.io.IOException;
@@ -64,6 +65,8 @@
util.logEventListenerError(this, l, e);
}
}
+ } catch (PatchListObjectTooLargeException e) {
+ log.warn("Couldn't fire event: " + e.getMessage());
} catch (PatchListNotAvailableException | GpgException | IOException | OrmException e) {
log.error("Couldn't fire event", e);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ChangeRestored.java b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ChangeRestored.java
index 03a6f1f..0437623 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ChangeRestored.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ChangeRestored.java
@@ -25,6 +25,7 @@
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.GpgException;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
+import com.google.gerrit.server.patch.PatchListObjectTooLargeException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import java.io.IOException;
@@ -63,6 +64,8 @@
util.logEventListenerError(this, l, e);
}
}
+ } catch (PatchListObjectTooLargeException e) {
+ log.warn("Couldn't fire event: " + e.getMessage());
} catch (PatchListNotAvailableException | GpgException | IOException | OrmException e) {
log.error("Couldn't fire event", e);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/CommentAdded.java b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/CommentAdded.java
index e76a032..1676c2c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/CommentAdded.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/CommentAdded.java
@@ -26,6 +26,7 @@
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.GpgException;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
+import com.google.gerrit.server.patch.PatchListObjectTooLargeException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import java.io.IOException;
@@ -74,6 +75,8 @@
util.logEventListenerError(this, l, e);
}
}
+ } catch (PatchListObjectTooLargeException e) {
+ log.warn("Couldn't fire event: " + e.getMessage());
} catch (PatchListNotAvailableException | GpgException | IOException | OrmException e) {
log.error("Couldn't fire event", e);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ReviewerAdded.java b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ReviewerAdded.java
index e4f8572..d785f38 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ReviewerAdded.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ReviewerAdded.java
@@ -26,6 +26,7 @@
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.GpgException;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
+import com.google.gerrit.server.patch.PatchListObjectTooLargeException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import java.io.IOException;
@@ -67,6 +68,8 @@
util.logEventListenerError(this, l, e);
}
}
+ } catch (PatchListObjectTooLargeException e) {
+ log.warn("Couldn't fire event: " + e.getMessage());
} catch (PatchListNotAvailableException | GpgException | IOException | OrmException e) {
log.error("Couldn't fire event", e);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ReviewerDeleted.java b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ReviewerDeleted.java
index 033efe2..9914563 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ReviewerDeleted.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ReviewerDeleted.java
@@ -26,6 +26,7 @@
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.GpgException;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
+import com.google.gerrit.server.patch.PatchListObjectTooLargeException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import java.io.IOException;
@@ -78,6 +79,8 @@
util.logEventListenerError(this, listener, e);
}
}
+ } catch (PatchListObjectTooLargeException e) {
+ log.warn("Couldn't fire event: " + e.getMessage());
} catch (PatchListNotAvailableException | GpgException | IOException | OrmException e) {
log.error("Couldn't fire event", e);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/RevisionCreated.java b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/RevisionCreated.java
index 8a781d0..475e4b5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/RevisionCreated.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/RevisionCreated.java
@@ -25,6 +25,7 @@
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.GpgException;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
+import com.google.gerrit.server.patch.PatchListObjectTooLargeException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import java.io.IOException;
@@ -64,6 +65,8 @@
util.logEventListenerError(this, l, e);
}
}
+ } catch (PatchListObjectTooLargeException e) {
+ log.warn("Couldn't fire event: " + e.getMessage());
} catch (PatchListNotAvailableException | GpgException | IOException | OrmException e) {
log.error("Couldn't fire event", e);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/VoteDeleted.java b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/VoteDeleted.java
index 71a603c..5f20293 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/VoteDeleted.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/VoteDeleted.java
@@ -26,6 +26,7 @@
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.GpgException;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
+import com.google.gerrit.server.patch.PatchListObjectTooLargeException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import java.io.IOException;
@@ -78,6 +79,8 @@
util.logEventListenerError(this, l, e);
}
}
+ } catch (PatchListObjectTooLargeException e) {
+ log.warn("Couldn't fire event: " + e.getMessage());
} catch (PatchListNotAvailableException | GpgException | IOException | OrmException e) {
log.error("Couldn't fire event", e);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/MailMessage.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/MailMessage.java
index 68b3c23..0d20464 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/MailMessage.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/MailMessage.java
@@ -18,7 +18,7 @@
import com.google.common.collect.ImmutableList;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.server.mail.Address;
-import org.joda.time.DateTime;
+import java.time.Instant;
/**
* A simplified representation of an RFC 2045-2047 mime email message used for representing received
@@ -40,7 +40,7 @@
public abstract ImmutableList<Address> cc();
// Metadata
- public abstract DateTime dateReceived();
+ public abstract Instant dateReceived();
public abstract ImmutableList<String> additionalHeaders();
// Content
@@ -84,7 +84,7 @@
return this;
}
- public abstract Builder dateReceived(DateTime val);
+ public abstract Builder dateReceived(Instant instant);
public abstract ImmutableList.Builder<String> additionalHeadersBuilder();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/RawMailParser.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/RawMailParser.java
index d2f91ed..57fe21f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/RawMailParser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/RawMailParser.java
@@ -33,7 +33,6 @@
import org.apache.james.mime4j.dom.TextBody;
import org.apache.james.mime4j.dom.address.Mailbox;
import org.apache.james.mime4j.message.DefaultMessageBuilder;
-import org.joda.time.DateTime;
/** Parses raw email content received through POP3 or IMAP into an internal {@link MailMessage}. */
public class RawMailParser {
@@ -66,7 +65,9 @@
if (mimeMessage.getSubject() != null) {
messageBuilder.subject(mimeMessage.getSubject());
}
- messageBuilder.dateReceived(new DateTime(mimeMessage.getDate()));
+ if (mimeMessage.getDate() != null) {
+ messageBuilder.dateReceived(mimeMessage.getDate().toInstant());
+ }
// Add From, To and Cc
if (mimeMessage.getFrom() != null && mimeMessage.getFrom().size() > 0) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/ChangeEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/ChangeEmail.java
index 53e7d22..a7826cb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/ChangeEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/ChangeEmail.java
@@ -37,6 +37,7 @@
import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListEntry;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
+import com.google.gerrit.server.patch.PatchListObjectTooLargeException;
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gerrit.server.permissions.ChangePermission;
import com.google.gerrit.server.permissions.GlobalPermission;
@@ -539,6 +540,9 @@
// Currently these always have a null oldId in the PatchList.
return "[Octopus merge; cannot be formatted as a diff.]\n";
}
+ } catch (PatchListObjectTooLargeException e) {
+ log.warn("Cannot format patch " + e.getMessage());
+ return "";
} catch (PatchListNotAvailableException e) {
log.error("Cannot format patch", e);
return "";
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/CommentSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/CommentSender.java
index 5b7d3b7..e8f2522 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/CommentSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/CommentSender.java
@@ -35,6 +35,7 @@
import com.google.gerrit.server.patch.PatchFile;
import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
+import com.google.gerrit.server.patch.PatchListObjectTooLargeException;
import com.google.gerrit.server.util.LabelVote;
import com.google.gwtorm.client.KeyUtil;
import com.google.gwtorm.server.OrmException;
@@ -232,6 +233,8 @@
if (repo != null) {
try {
patchList = getPatchList();
+ } catch (PatchListObjectTooLargeException e) {
+ log.warn("Failed to get patch list: " + e.getMessage());
} catch (PatchListNotAvailableException e) {
log.error("Failed to get patch list", e);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
index 7777400..8900a15 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
@@ -103,7 +103,7 @@
try {
PatchList pl = fileCache.get(key, fileLoaderFactory.create(key, project));
if (pl instanceof LargeObjectTombstone) {
- throw new PatchListNotAvailableException(
+ throw new PatchListObjectTooLargeException(
"Error computing " + key + ". Previous attempt failed with LargeObjectException");
}
if (key.getAlgorithm() == PatchListKey.Algorithm.OPTIMIZED_DIFF) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListObjectTooLargeException.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListObjectTooLargeException.java
new file mode 100644
index 0000000..54e0e6c
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListObjectTooLargeException.java
@@ -0,0 +1,27 @@
+// Copyright (C) 2017 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.patch;
+
+/**
+ * Exception thrown when the PatchList could not be computed because previous attempts failed with
+ * {@code LargeObjectException}. This is not thrown on the first computation.
+ */
+public class PatchListObjectTooLargeException extends PatchListNotAvailableException {
+ private static final long serialVersionUID = 1L;
+
+ public PatchListObjectTooLargeException(String message) {
+ super(message);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/DisablePlugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/DisablePlugin.java
index a2da580..ac37af4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/DisablePlugin.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/DisablePlugin.java
@@ -15,32 +15,42 @@
package com.google.gerrit.server.plugins;
import com.google.common.collect.ImmutableSet;
-import com.google.gerrit.common.data.GlobalCapability;
-import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.common.PluginInfo;
-import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
+import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.plugins.DisablePlugin.Input;
import com.google.inject.Inject;
+import com.google.inject.Provider;
import com.google.inject.Singleton;
-@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
@Singleton
public class DisablePlugin implements RestModifyView<PluginResource, Input> {
public static class Input {}
private final PluginLoader loader;
+ private final Provider<IdentifiedUser> user;
+ private final PermissionBackend permissionBackend;
@Inject
- DisablePlugin(PluginLoader loader) {
+ DisablePlugin(
+ PluginLoader loader, Provider<IdentifiedUser> user, PermissionBackend permissionBackend) {
this.loader = loader;
+ this.user = user;
+ this.permissionBackend = permissionBackend;
}
@Override
- public PluginInfo apply(PluginResource resource, Input input) throws MethodNotAllowedException {
- if (!loader.isRemoteAdminEnabled()) {
- throw new MethodNotAllowedException("remote plugin administration is disabled");
+ public PluginInfo apply(PluginResource resource, Input input) throws RestApiException {
+ try {
+ permissionBackend.user(user).check(GlobalPermission.ADMINISTRATE_SERVER);
+ } catch (PermissionBackendException e) {
+ throw new RestApiException("Could not check permission", e);
}
+ loader.checkRemoteAdminEnabled();
String name = resource.getName();
loader.disablePlugins(ImmutableSet.of(name));
return ListPlugins.toPluginInfo(loader.get(name));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/EnablePlugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/EnablePlugin.java
index f29e36b..c6db147 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/EnablePlugin.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/EnablePlugin.java
@@ -18,8 +18,8 @@
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.common.PluginInfo;
-import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.server.plugins.EnablePlugin.Input;
import com.google.inject.Inject;
@@ -40,11 +40,8 @@
}
@Override
- public PluginInfo apply(PluginResource resource, Input input)
- throws ResourceConflictException, MethodNotAllowedException {
- if (!loader.isRemoteAdminEnabled()) {
- throw new MethodNotAllowedException("remote plugin administration is disabled");
- }
+ public PluginInfo apply(PluginResource resource, Input input) throws RestApiException {
+ loader.checkRemoteAdminEnabled();
String name = resource.getName();
try {
loader.enablePlugins(ImmutableSet.of(name));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/InstallPlugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/InstallPlugin.java
index 531e9ac..ee9099e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/InstallPlugin.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/InstallPlugin.java
@@ -19,8 +19,8 @@
import com.google.gerrit.extensions.common.InstallPluginInput;
import com.google.gerrit.extensions.common.PluginInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
-import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.inject.Inject;
@@ -56,10 +56,8 @@
@Override
public Response<PluginInfo> apply(TopLevelResource resource, InstallPluginInput input)
- throws BadRequestException, MethodNotAllowedException, IOException {
- if (!loader.isRemoteAdminEnabled()) {
- throw new MethodNotAllowedException("remote installation is disabled");
- }
+ throws RestApiException, IOException {
+ loader.checkRemoteAdminEnabled();
try {
try (InputStream in = openStream(input)) {
String pluginName = loader.installPluginFromStream(name, in);
@@ -104,7 +102,7 @@
@Override
public Response<PluginInfo> apply(PluginResource resource, InstallPluginInput input)
- throws BadRequestException, MethodNotAllowedException, IOException {
+ throws RestApiException, IOException {
return install.get().setName(resource.getName()).apply(TopLevelResource.INSTANCE, input);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java
index d972087..954ea29 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java
@@ -27,6 +27,7 @@
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;
import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.systemstatus.ServerInformation;
import com.google.gerrit.server.PluginUser;
import com.google.gerrit.server.cache.PersistentCacheFactory;
@@ -138,6 +139,12 @@
return remoteAdmin;
}
+ public void checkRemoteAdminEnabled() throws MethodNotAllowedException {
+ if (!remoteAdmin) {
+ throw new MethodNotAllowedException("remote plugin administration is disabled");
+ }
+ }
+
public Plugin get(String name) {
Plugin p = running.get(name);
if (p != null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginsCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginsCollection.java
index 768aa86..9dbc956 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginsCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginsCollection.java
@@ -17,8 +17,8 @@
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AcceptsCreate;
import com.google.gerrit.extensions.restapi.IdString;
-import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestCollection;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
@@ -67,11 +67,8 @@
}
@Override
- public InstallPlugin create(TopLevelResource parent, IdString id)
- throws ResourceNotFoundException, MethodNotAllowedException {
- if (!loader.isRemoteAdminEnabled()) {
- throw new MethodNotAllowedException("remote installation is disabled");
- }
+ public InstallPlugin create(TopLevelResource parent, IdString id) throws RestApiException {
+ loader.checkRemoteAdminEnabled();
return install.get().setName(id.get()).setCreated(true);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/DashboardsCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/DashboardsCollection.java
index d43a066..a9e8fd3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/DashboardsCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/DashboardsCollection.java
@@ -211,8 +211,8 @@
DashboardInfo info = newDashboardInfo(refName, path);
info.project = project;
info.definingProject = definingProject.getName();
- String query = config.getString("dashboard", null, "title");
- info.title = replace(project, query == null ? info.path : query);
+ String title = config.getString("dashboard", null, "title");
+ info.title = replace(project, title == null ? info.path : title);
info.description = replace(project, config.getString("dashboard", null, "description"));
info.foreach = config.getString("dashboard", null, "foreach");
@@ -238,8 +238,8 @@
return info;
}
- private static String replace(String project, String query) {
- return query.replace("${project}", project);
+ private static String replace(String project, String input) {
+ return input == null ? input : input.replace("${project}", project);
}
private static String defaultOf(Project proj) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OutputStreamQuery.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
index ee9c570..dde577d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
@@ -42,17 +42,19 @@
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.lang.reflect.Field;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.util.io.DisabledOutputStream;
-import org.joda.time.format.DateTimeFormat;
-import org.joda.time.format.DateTimeFormatter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -65,7 +67,10 @@
public class OutputStreamQuery {
private static final Logger log = LoggerFactory.getLogger(OutputStreamQuery.class);
- private static final DateTimeFormatter dtf = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss zzz");
+ private static final DateTimeFormatter dtf =
+ DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss zzz")
+ .withLocale(Locale.US)
+ .withZone(ZoneId.systemDefault());
public enum OutputFormat {
TEXT,
@@ -402,7 +407,7 @@
out.print('\n');
} else if (value instanceof Long && isDateField(field)) {
out.print(' ');
- out.print(dtf.print(((Long) value) * 1000L));
+ out.print(dtf.format(Instant.ofEpochSecond((Long) value)));
out.print('\n');
} else if (isPrimitive(value)) {
out.print(' ');
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Abandoned.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Abandoned.soy
index 50c5fc3..623cfe26 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Abandoned.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Abandoned.soy
@@ -24,7 +24,7 @@
* @param email
* @param fromName
*/
-{template .Abandoned autoescape="strict" kind="text"}
+{template .Abandoned kind="text"}
{$fromName} has abandoned this change.
{if $email.changeUrl} ( {$email.changeUrl} ){/if}{\n}
{\n}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/AbandonedHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/AbandonedHtml.soy
index c7d4699..fb8ff78 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/AbandonedHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/AbandonedHtml.soy
@@ -21,7 +21,7 @@
* @param email
* @param fromName
*/
-{template .AbandonedHtml autoescape="strict" kind="html"}
+{template .AbandonedHtml kind="html"}
<p>
{$fromName} <strong>abandoned</strong> this change.
</p>
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/AddKey.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/AddKey.soy
index aa2b27d..af99569 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/AddKey.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/AddKey.soy
@@ -21,7 +21,7 @@
* adding a new SSH or GPG key to an account.
* @param email
*/
-{template .AddKey autoescape="strict" kind="text"}
+{template .AddKey kind="text"}
One or more new {$email.keyType} keys have been added to Gerrit Code Review at
{sp}{$email.gerritHost}:
@@ -68,4 +68,4 @@
This is a send-only email address. Replies to this message will not be read
or answered.
-{/template}
\ No newline at end of file
+{/template}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/AddKeyHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/AddKeyHtml.soy
index 017fd6d..21161ea 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/AddKeyHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/AddKeyHtml.soy
@@ -19,7 +19,7 @@
/**
* @param email
*/
-{template .AddKeyHtml autoescape="strict" kind="html"}
+{template .AddKeyHtml kind="html"}
<p>
One or more new {$email.keyType} keys have been added to Gerrit Code Review
at {$email.gerritHost}:
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooter.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooter.soy
index 37ac126..f1d201b 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooter.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooter.soy
@@ -21,7 +21,7 @@
* that will be appended to ALL emails related to changes.
* @param email
*/
-{template .ChangeFooter autoescape="strict" kind="text"}
+{template .ChangeFooter kind="text"}
--{sp}
{\n}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooterHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooterHtml.soy
index 00f21db..dea6724 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooterHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooterHtml.soy
@@ -20,7 +20,7 @@
* @param change
* @param email
*/
-{template .ChangeFooterHtml autoescape="strict" kind="html"}
+{template .ChangeFooterHtml kind="html"}
{if $email.changeUrl or $email.settingsUrl}
<p>
{if $email.changeUrl}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeSubject.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeSubject.soy
index 98de6e7..d8cffc4 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeSubject.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeSubject.soy
@@ -23,6 +23,6 @@
* @param change
* @param shortProjectName
*/
-{template .ChangeSubject autoescape="strict" kind="text"}
+{template .ChangeSubject kind="text"}
Change in {$shortProjectName}[{$branch.shortName}]: {$change.shortSubject}
{/template}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Comment.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Comment.soy
index 7bedc1c..7f3062c 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Comment.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Comment.soy
@@ -25,7 +25,7 @@
* @param fromName
* @param commentFiles
*/
-{template .Comment autoescape="strict" kind="text"}
+{template .Comment kind="text"}
{$fromName} has posted comments on this change.
{if $email.changeUrl} ( {$email.changeUrl} ){/if}{\n}
{\n}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentFooter.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentFooter.soy
index 73fdfba..3998438 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentFooter.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentFooter.soy
@@ -21,5 +21,5 @@
* that will be appended to emails related to a user submitting comments on
* changes.
*/
-{template .CommentFooter autoescape="strict" kind="text"}
+{template .CommentFooter kind="text"}
{/template}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentFooterHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentFooterHtml.soy
index 7bf28e7..c54f926 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentFooterHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentFooterHtml.soy
@@ -16,5 +16,5 @@
{namespace com.google.gerrit.server.mail.template}
-{template .CommentFooterHtml autoescape="strict" kind="html"}
+{template .CommentFooterHtml kind="html"}
{/template}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentHtml.soy
index 870ad46..9b96d69 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentHtml.soy
@@ -24,7 +24,7 @@
* @param patchSet
* @param patchSetCommentBlocks
*/
-{template .CommentHtml autoescape="strict" kind="html"}
+{template .CommentHtml kind="html"}
{let $commentHeaderStyle kind="css"}
margin-bottom: 4px;
{/let}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteReviewer.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteReviewer.soy
index 888ee4b..fc1d60f 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteReviewer.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteReviewer.soy
@@ -24,7 +24,7 @@
* @param email
* @param fromName
*/
-{template .DeleteReviewer autoescape="strict" kind="text"}
+{template .DeleteReviewer kind="text"}
{$fromName} has removed{sp}
{foreach $reviewerName in $email.reviewerNames}
{if not isFirst($reviewerName)},{sp}{/if}
@@ -41,4 +41,4 @@
{$coverLetter}
{\n}
{/if}
-{/template}
\ No newline at end of file
+{/template}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteReviewerHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteReviewerHtml.soy
index 5faa411..74e5ee5 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteReviewerHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteReviewerHtml.soy
@@ -20,7 +20,7 @@
* @param email
* @param fromName
*/
-{template .DeleteReviewerHtml autoescape="strict" kind="html"}
+{template .DeleteReviewerHtml kind="html"}
<p>
{$fromName}{sp}
<strong>
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteVote.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteVote.soy
index b249ded..724e90d 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteVote.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteVote.soy
@@ -23,7 +23,7 @@
* @param coverLetter
* @param fromName
*/
-{template .DeleteVote autoescape="strict" kind="text"}
+{template .DeleteVote kind="text"}
{$fromName} has removed a vote on this change.{\n}
{\n}
Change subject: {$change.subject}{\n}
@@ -34,4 +34,4 @@
{$coverLetter}
{\n}
{/if}
-{/template}
\ No newline at end of file
+{/template}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteVoteHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteVoteHtml.soy
index 3d76ae2..06f5456 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteVoteHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteVoteHtml.soy
@@ -21,7 +21,7 @@
* @param email
* @param fromName
*/
-{template .DeleteVoteHtml autoescape="strict" kind="html"}
+{template .DeleteVoteHtml kind="html"}
<p>
{$fromName} <strong>removed a vote</strong> from this change.
</p>
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Footer.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Footer.soy
index 24db2fd..2b146ec 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Footer.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Footer.soy
@@ -22,7 +22,7 @@
* CommentFooter.
* @param footers
*/
-{template .Footer autoescape="strict" kind="text"}
+{template .Footer kind="text"}
{foreach $footer in $footers}
{$footer}{\n}
{/foreach}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/FooterHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/FooterHtml.soy
index 9f9c503..d9f13ce 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/FooterHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/FooterHtml.soy
@@ -19,7 +19,7 @@
/**
* @param footers
*/
-{template .FooterHtml autoescape="strict" kind="html"}
+{template .FooterHtml kind="html"}
{\n}
{\n}
{foreach $footer in $footers}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/HeaderHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/HeaderHtml.soy
index fdc3fee..85b56ec 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/HeaderHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/HeaderHtml.soy
@@ -16,5 +16,5 @@
{namespace com.google.gerrit.server.mail.template}
-{template .HeaderHtml autoescape="strict" kind="html"}
+{template .HeaderHtml kind="html"}
{/template}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Merged.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Merged.soy
index d483264..40924e6 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Merged.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Merged.soy
@@ -24,7 +24,7 @@
* @param email
* @param fromName
*/
-{template .Merged autoescape="strict" kind="text"}
+{template .Merged kind="text"}
{$fromName} has submitted this change and it was merged.
{if $email.changeUrl} ( {$email.changeUrl} ){/if}{\n}
{\n}
@@ -39,4 +39,4 @@
{$email.unifiedDiff}
{\n}
{/if}
-{/template}
\ No newline at end of file
+{/template}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/MergedHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/MergedHtml.soy
index 927601b..08d37cc 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/MergedHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/MergedHtml.soy
@@ -21,7 +21,7 @@
* @param email
* @param fromName
*/
-{template .MergedHtml autoescape="strict" kind="html"}
+{template .MergedHtml kind="html"}
<p>
{$fromName} <strong>merged</strong> this change.
</p>
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/NewChange.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/NewChange.soy
index 9f7429f..ca24d19 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/NewChange.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/NewChange.soy
@@ -25,7 +25,7 @@
* @param patchSet
* @param projectName
*/
-{template .NewChange autoescape="strict" kind="text"}
+{template .NewChange kind="text"}
{if $email.reviewerNames}
Hello{sp}
{foreach $reviewerName in $email.reviewerNames}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/NewChangeHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/NewChangeHtml.soy
index 8026666..676f019 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/NewChangeHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/NewChangeHtml.soy
@@ -24,7 +24,7 @@
* @param patchSet
* @param projectName
*/
-{template .NewChangeHtml autoescape="strict" kind="html"}
+{template .NewChangeHtml kind="html"}
<p>
{if $email.reviewerNames}
{$fromName} would like{sp}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Private.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Private.soy
index b26535b..c1ac5b6 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Private.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Private.soy
@@ -24,7 +24,7 @@
* Private template to generate "View Change" buttons.
* @param email
*/
-{template .ViewChangeButton autoescape="strict" kind="html"}
+{template .ViewChangeButton kind="html"}
<a href="{$email.changeUrl}">View Change</a>
{/template}
@@ -32,7 +32,7 @@
* Private template to render PRE block with consistent font-sizing.
* @param content
*/
-{template .Pre autoescape="strict" kind="html"}
+{template .Pre kind="html"}
{let $preStyle kind="css"}
font-family: monospace,monospace; // Use this to avoid browsers scaling down
// monospace text.
@@ -56,7 +56,7 @@
*
* @param content
*/
-{template .WikiFormat autoescape="strict" kind="html"}
+{template .WikiFormat kind="html"}
{let $blockquoteStyle kind="css"}
border-left: 1px solid #aaa;
margin: 10px 0;
@@ -90,7 +90,7 @@
/**
* @param diffLines
*/
-{template .UnifiedDiff autoescape="strict" kind="html"}
+{template .UnifiedDiff kind="html"}
{let $addStyle kind="css"}
color: hsl(120, 100%, 40%);
{/let}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RegisterNewEmail.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RegisterNewEmail.soy
index 2b30ae6..2886cc0 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RegisterNewEmail.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RegisterNewEmail.soy
@@ -21,7 +21,7 @@
* related to registering new email accounts.
* @param email
*/
-{template .RegisterNewEmail autoescape="strict" kind="text"}
+{template .RegisterNewEmail kind="text"}
Welcome to Gerrit Code Review at {$email.gerritHost}.{\n}
{\n}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ReplacePatchSet.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ReplacePatchSet.soy
index e41bdda..124cdf3 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ReplacePatchSet.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ReplacePatchSet.soy
@@ -26,7 +26,7 @@
* @param patchSet
* @param projectName
*/
-{template .ReplacePatchSet autoescape="strict" kind="text"}
+{template .ReplacePatchSet kind="text"}
{if $email.reviewerNames and $fromEmail == $change.ownerEmail}
Hello{sp}
{foreach $reviewerName in $email.reviewerNames}
@@ -60,4 +60,4 @@
{$patchSet.refName}
{\n}
{/if}
-{/template}
\ No newline at end of file
+{/template}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ReplacePatchSetHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ReplacePatchSetHtml.soy
index 05c60a1..221a4e6 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ReplacePatchSetHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ReplacePatchSetHtml.soy
@@ -24,7 +24,7 @@
* @param patchSet
* @param projectName
*/
-{template .ReplacePatchSetHtml autoescape="strict" kind="html"}
+{template .ReplacePatchSetHtml kind="html"}
<p>
{$fromName} <strong>uploaded patch set #{$patchSet.patchSetId}</strong>{sp}
to{sp}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Restored.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Restored.soy
index 14ae0f3..4fc6d8c 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Restored.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Restored.soy
@@ -24,7 +24,7 @@
* @param email
* @param fromName
*/
-{template .Restored autoescape="strict" kind="text"}
+{template .Restored kind="text"}
{$fromName} has restored this change.
{if $email.changeUrl} ( {$email.changeUrl} ){/if}{\n}
{\n}
@@ -36,4 +36,4 @@
{$coverLetter}
{\n}
{/if}
-{/template}
\ No newline at end of file
+{/template}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RestoredHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RestoredHtml.soy
index ea4f615..fdc68b0 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RestoredHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RestoredHtml.soy
@@ -20,7 +20,7 @@
* @param email
* @param fromName
*/
-{template .RestoredHtml autoescape="strict" kind="html"}
+{template .RestoredHtml kind="html"}
<p>
{$fromName} <strong>restored</strong> this change.
</p>
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Reverted.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Reverted.soy
index 7f74df9..09e32ff 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Reverted.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Reverted.soy
@@ -24,7 +24,7 @@
* @param email
* @param fromName
*/
-{template .Reverted autoescape="strict" kind="text"}
+{template .Reverted kind="text"}
{$fromName} has reverted this change.
{if $email.changeUrl} ( {$email.changeUrl} ){/if}{\n}
{\n}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RevertedHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RevertedHtml.soy
index d6407e7..479eae1 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RevertedHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RevertedHtml.soy
@@ -20,7 +20,7 @@
* @param email
* @param fromName
*/
-{template .RevertedHtml autoescape="strict" kind="html"}
+{template .RevertedHtml kind="html"}
<p>
{$fromName} <strong>reverted</strong> this change.
</p>
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/SetAssignee.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/SetAssignee.soy
index ca4f267..98290e9 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/SetAssignee.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/SetAssignee.soy
@@ -25,7 +25,7 @@
* @param patchSet
* @param projectName
*/
-{template .SetAssignee autoescape="strict" kind="text"}
+{template .SetAssignee kind="text"}
Hello{sp}
{$email.assigneeName},
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/SetAssigneeHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/SetAssigneeHtml.soy
index 31cfbd6..d057ba3 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/SetAssigneeHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/SetAssigneeHtml.soy
@@ -23,7 +23,7 @@
* @param patchSet
* @param projectName
*/
-{template .SetAssigneeHtml autoescape="strict" kind="html"}
+{template .SetAssigneeHtml kind="html"}
<p>
{$fromName} has <strong>assigned</strong> a change to{sp}
{$email.assigneeName}.{sp}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/config/ScheduleConfigTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/config/ScheduleConfigTest.java
index e6f36b9..0423a53 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/config/ScheduleConfigTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/config/ScheduleConfigTest.java
@@ -20,15 +20,19 @@
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.junit.Assert.assertEquals;
+import java.time.LocalDateTime;
+import java.time.Month;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.lib.Config;
-import org.joda.time.DateTime;
import org.junit.Test;
public class ScheduleConfigTest {
// Friday June 13, 2014 10:00 UTC
- private static final DateTime NOW = DateTime.parse("2014-06-13T10:00:00-00:00");
+ private static final ZonedDateTime NOW =
+ LocalDateTime.of(2014, Month.JUNE, 13, 10, 0, 0).atOffset(ZoneOffset.UTC).toZonedDateTime();
@Test
public void initialDelay() throws Exception {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/AbstractParserTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/AbstractParserTest.java
index 19ad8bb..7309437 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/AbstractParserTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/AbstractParserTest.java
@@ -20,9 +20,9 @@
import com.google.gerrit.reviewdb.client.Comment;
import com.google.gerrit.server.mail.Address;
import java.sql.Timestamp;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
-import org.joda.time.DateTime;
import org.junit.Ignore;
@Ignore
@@ -85,7 +85,7 @@
MailMessage.Builder b = MailMessage.builder();
b.id("id");
b.from(new Address("Foo Bar", "foo@bar.com"));
- b.dateReceived(new DateTime());
+ b.dateReceived(Instant.now());
b.subject("");
return b;
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/MetadataParserTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/MetadataParserTest.java
index 84bae96..dc25939 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/MetadataParserTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/MetadataParserTest.java
@@ -20,8 +20,10 @@
import com.google.gerrit.server.mail.Address;
import com.google.gerrit.server.mail.MetadataName;
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.Month;
+import java.time.ZoneOffset;
import org.junit.Test;
public class MetadataParserTest {
@@ -31,7 +33,7 @@
// email headers of the message.
MailMessage.Builder b = MailMessage.builder();
b.id("");
- b.dateReceived(new DateTime());
+ b.dateReceived(Instant.now());
b.subject("");
b.addAdditionalHeader(toHeaderWithDelimiter(MetadataName.CHANGE_NUMBER) + "123");
@@ -48,8 +50,11 @@
assertThat(meta.changeNumber).isEqualTo(123);
assertThat(meta.patchSet).isEqualTo(1);
assertThat(meta.messageType).isEqualTo("comment");
- assertThat(meta.timestamp.getTime())
- .isEqualTo(new DateTime(2016, 10, 25, 9, 11, 35, 0, DateTimeZone.UTC).getMillis());
+ assertThat(meta.timestamp.toInstant())
+ .isEqualTo(
+ LocalDateTime.of(2016, Month.OCTOBER, 25, 9, 11, 35)
+ .atOffset(ZoneOffset.UTC)
+ .toInstant());
}
@Test
@@ -58,7 +63,7 @@
// the text body of the message.
MailMessage.Builder b = MailMessage.builder();
b.id("");
- b.dateReceived(new DateTime());
+ b.dateReceived(Instant.now());
b.subject("");
StringBuilder stringBuilder = new StringBuilder();
@@ -77,8 +82,11 @@
assertThat(meta.changeNumber).isEqualTo(123);
assertThat(meta.patchSet).isEqualTo(1);
assertThat(meta.messageType).isEqualTo("comment");
- assertThat(meta.timestamp.getTime())
- .isEqualTo(new DateTime(2016, 10, 25, 9, 11, 35, 0, DateTimeZone.UTC).getMillis());
+ assertThat(meta.timestamp.toInstant())
+ .isEqualTo(
+ LocalDateTime.of(2016, Month.OCTOBER, 25, 9, 11, 35)
+ .atOffset(ZoneOffset.UTC)
+ .toInstant());
}
@Test
@@ -87,7 +95,7 @@
// the HTML body of the message.
MailMessage.Builder b = MailMessage.builder();
b.id("");
- b.dateReceived(new DateTime());
+ b.dateReceived(Instant.now());
b.subject("");
StringBuilder stringBuilder = new StringBuilder();
@@ -111,7 +119,10 @@
assertThat(meta.changeNumber).isEqualTo(123);
assertThat(meta.patchSet).isEqualTo(1);
assertThat(meta.messageType).isEqualTo("comment");
- assertThat(meta.timestamp.getTime())
- .isEqualTo(new DateTime(2016, 10, 25, 9, 11, 35, 0, DateTimeZone.UTC).getMillis());
+ assertThat(meta.timestamp.toInstant())
+ .isEqualTo(
+ LocalDateTime.of(2016, Month.OCTOBER, 25, 9, 11, 35)
+ .atOffset(ZoneOffset.UTC)
+ .toInstant());
}
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/RawMailParserTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/RawMailParserTest.java
index 4efa817..001d12d 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/RawMailParserTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/RawMailParserTest.java
@@ -65,7 +65,7 @@
assertThat(have.to()).isEqualTo(want.to());
assertThat(have.from()).isEqualTo(want.from());
assertThat(have.cc()).isEqualTo(want.cc());
- assertThat(have.dateReceived().getMillis()).isEqualTo(want.dateReceived().getMillis());
+ assertThat(have.dateReceived()).isEqualTo(want.dateReceived());
assertThat(have.additionalHeaders()).isEqualTo(want.additionalHeaders());
assertThat(have.subject()).isEqualTo(want.subject());
assertThat(have.textContent()).isEqualTo(want.textContent());
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/AttachmentMessage.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/AttachmentMessage.java
index be8d882..eb4d180 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/AttachmentMessage.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/AttachmentMessage.java
@@ -16,8 +16,9 @@
import com.google.gerrit.server.mail.Address;
import com.google.gerrit.server.mail.receive.MailMessage;
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
+import java.time.LocalDateTime;
+import java.time.Month;
+import java.time.ZoneOffset;
import org.junit.Ignore;
/**
@@ -82,7 +83,10 @@
.htmlContent("<div dir=\"ltr\">Contains unwanted attachment</div>")
.subject("Test Subject")
.addAdditionalHeader("MIME-Version: 1.0")
- .dateReceived(new DateTime(2016, 10, 25, 9, 11, 35, 0, DateTimeZone.UTC));
+ .dateReceived(
+ LocalDateTime.of(2016, Month.OCTOBER, 25, 9, 11, 35)
+ .atOffset(ZoneOffset.UTC)
+ .toInstant());
return expect.build();
}
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/Base64HeaderMessage.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/Base64HeaderMessage.java
index affa3bd..91dc6f1 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/Base64HeaderMessage.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/Base64HeaderMessage.java
@@ -16,8 +16,9 @@
import com.google.gerrit.server.mail.Address;
import com.google.gerrit.server.mail.receive.MailMessage;
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
+import java.time.LocalDateTime;
+import java.time.Month;
+import java.time.ZoneOffset;
import org.junit.Ignore;
/** Tests parsing a Base64 encoded subject. */
@@ -58,7 +59,10 @@
.addTo(new Address("ekempin", "ekempin@google.com"))
.textContent(textContent)
.subject("\uD83D\uDE1B test")
- .dateReceived(new DateTime(2016, 10, 25, 9, 11, 35, 0, DateTimeZone.UTC));
+ .dateReceived(
+ LocalDateTime.of(2016, Month.OCTOBER, 25, 9, 11, 35)
+ .atOffset(ZoneOffset.UTC)
+ .toInstant());
return expect.build();
}
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/HtmlMimeMessage.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/HtmlMimeMessage.java
index 487e9dd..756581f 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/HtmlMimeMessage.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/HtmlMimeMessage.java
@@ -16,8 +16,9 @@
import com.google.gerrit.server.mail.Address;
import com.google.gerrit.server.mail.receive.MailMessage;
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
+import java.time.LocalDateTime;
+import java.time.Month;
+import java.time.ZoneOffset;
import org.junit.Ignore;
/** Tests a message containing mime/alternative (text + html) content. */
@@ -98,7 +99,10 @@
.htmlContent(unencodedHtmlContent)
.subject("Change in gerrit[master]: Implement receiver class structure and bindings")
.addAdditionalHeader("MIME-Version: 1.0")
- .dateReceived(new DateTime(2016, 10, 25, 9, 11, 35, 0, DateTimeZone.UTC));
+ .dateReceived(
+ LocalDateTime.of(2016, Month.OCTOBER, 25, 9, 11, 35)
+ .atOffset(ZoneOffset.UTC)
+ .toInstant());
return expect.build();
}
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/NonUTF8Message.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/NonUTF8Message.java
index 9f2af0d..3fafd4b 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/NonUTF8Message.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/NonUTF8Message.java
@@ -15,8 +15,9 @@
import com.google.gerrit.server.mail.Address;
import com.google.gerrit.server.mail.receive.MailMessage;
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
+import java.time.LocalDateTime;
+import java.time.Month;
+import java.time.ZoneOffset;
import org.junit.Ignore;
/** Tests that non-UTF8 encodings are handled correctly. */
@@ -62,7 +63,10 @@
.addTo(new Address("ekempin", "ekempin@google.com"))
.textContent(textContent)
.subject("\uD83D\uDE1B test")
- .dateReceived(new DateTime(2016, 10, 25, 9, 11, 35, 0, DateTimeZone.UTC));
+ .dateReceived(
+ LocalDateTime.of(2016, Month.OCTOBER, 25, 9, 11, 35)
+ .atOffset(ZoneOffset.UTC)
+ .toInstant());
return expect.build();
}
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/QuotedPrintableHeaderMessage.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/QuotedPrintableHeaderMessage.java
index 2c17859..2dc48b5 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/QuotedPrintableHeaderMessage.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/QuotedPrintableHeaderMessage.java
@@ -16,8 +16,9 @@
import com.google.gerrit.server.mail.Address;
import com.google.gerrit.server.mail.receive.MailMessage;
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
+import java.time.LocalDateTime;
+import java.time.Month;
+import java.time.ZoneOffset;
import org.junit.Ignore;
/** Tests parsing a quoted printable encoded subject */
@@ -59,7 +60,10 @@
.addTo(new Address("ekempin", "ekempin@google.com"))
.textContent(textContent)
.subject("âme vulgaire")
- .dateReceived(new DateTime(2016, 10, 25, 9, 11, 35, 0, DateTimeZone.UTC));
+ .dateReceived(
+ LocalDateTime.of(2016, Month.OCTOBER, 25, 9, 11, 35)
+ .atOffset(ZoneOffset.UTC)
+ .toInstant());
return expect.build();
}
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/SimpleTextMessage.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/SimpleTextMessage.java
index ce833d5..aa5b78a 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/SimpleTextMessage.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/SimpleTextMessage.java
@@ -16,8 +16,9 @@
import com.google.gerrit.server.mail.Address;
import com.google.gerrit.server.mail.receive.MailMessage;
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
+import java.time.LocalDateTime;
+import java.time.Month;
+import java.time.ZoneOffset;
import org.junit.Ignore;
/** Tests parsing a simple text message with different headers. */
@@ -124,7 +125,10 @@
.addCc(new Address("Patrick Hiesel", "hiesel@google.com"))
.textContent(textContent)
.subject("Change in gerrit[master]: (Re)enable voting buttons for merged changes")
- .dateReceived(new DateTime(2016, 10, 25, 9, 11, 35, 0, DateTimeZone.UTC))
+ .dateReceived(
+ LocalDateTime.of(2016, Month.OCTOBER, 25, 9, 11, 35)
+ .atOffset(ZoneOffset.UTC)
+ .toInstant())
.addAdditionalHeader(
"Authentication-Results: mx.google.com; dkim=pass header.i=@google.com;")
.addAdditionalHeader(
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeBundleTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeBundleTest.java
index 90e6800..33e1005 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeBundleTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeBundleTest.java
@@ -46,13 +46,14 @@
import com.google.gwtorm.protobuf.CodecFactory;
import com.google.gwtorm.protobuf.ProtobufCodec;
import java.sql.Timestamp;
+import java.time.LocalDate;
+import java.time.Month;
+import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.TimeZone;
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -67,6 +68,7 @@
CodecFactory.encoder(PatchSetApproval.class);
private static final ProtobufCodec<PatchLineComment> PATCH_LINE_COMMENT_CODEC =
CodecFactory.encoder(PatchLineComment.class);
+ private static final String TIMEZONE_ID = "US/Eastern";
private String systemTimeZoneProperty;
private TimeZone systemTimeZone;
@@ -76,10 +78,9 @@
@Before
public void setUp() {
- String tz = "US/Eastern";
- systemTimeZoneProperty = System.setProperty("user.timezone", tz);
+ systemTimeZoneProperty = System.setProperty("user.timezone", TIMEZONE_ID);
systemTimeZone = TimeZone.getDefault();
- TimeZone.setDefault(TimeZone.getTimeZone(tz));
+ TimeZone.setDefault(TimeZone.getTimeZone(TIMEZONE_ID));
long maxMs = ChangeRebuilderImpl.MAX_WINDOW_MS;
assertThat(maxMs).isGreaterThan(1000L);
TestTimeUtil.resetWithClockStep(maxMs * 2, MILLISECONDS);
@@ -1517,8 +1518,11 @@
PatchSetApproval a2 = clone(a1);
a2.setGranted(
new Timestamp(
- new DateTime(1900, 1, 1, 0, 0, 0, DateTimeZone.forTimeZone(TimeZone.getDefault()))
- .getMillis()));
+ LocalDate.of(1900, Month.JANUARY, 1)
+ .atStartOfDay()
+ .atZone(ZoneId.of(TIMEZONE_ID))
+ .toInstant()
+ .toEpochMilli()));
// Both are ReviewDb, exact match is required.
ChangeBundle b1 =
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index 1e722fc..7234acc 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -1232,7 +1232,7 @@
long thirtyHoursInMs = MILLISECONDS.convert(30, HOURS);
resetTimeWithClockStep(thirtyHoursInMs, MILLISECONDS);
TestRepository<Repo> repo = createProject("repo");
- long startMs = TestTimeUtil.START.getMillis();
+ long startMs = TestTimeUtil.START.toEpochMilli();
Change change1 = insert(repo, newChange(repo), null, new Timestamp(startMs));
Change change2 = insert(repo, newChange(repo), null, new Timestamp(startMs + thirtyHoursInMs));
@@ -1259,7 +1259,7 @@
long thirtyHoursInMs = MILLISECONDS.convert(30, HOURS);
resetTimeWithClockStep(thirtyHoursInMs, MILLISECONDS);
TestRepository<Repo> repo = createProject("repo");
- long startMs = TestTimeUtil.START.getMillis();
+ long startMs = TestTimeUtil.START.toEpochMilli();
Change change1 = insert(repo, newChange(repo), null, new Timestamp(startMs));
Change change2 = insert(repo, newChange(repo), null, new Timestamp(startMs + thirtyHoursInMs));
TestTimeUtil.setClockStep(0, MILLISECONDS);
@@ -1281,7 +1281,7 @@
long thirtyHoursInMs = MILLISECONDS.convert(30, HOURS);
resetTimeWithClockStep(thirtyHoursInMs, MILLISECONDS);
TestRepository<Repo> repo = createProject("repo");
- long startMs = TestTimeUtil.START.getMillis();
+ long startMs = TestTimeUtil.START.toEpochMilli();
Change change1 = insert(repo, newChange(repo), null, new Timestamp(startMs));
Change change2 = insert(repo, newChange(repo), null, new Timestamp(startMs + thirtyHoursInMs));
TestTimeUtil.setClockStep(0, MILLISECONDS);
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/TestTimeUtil.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/TestTimeUtil.java
index dd44cb9ae..5bbe3b6 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/TestTimeUtil.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/TestTimeUtil.java
@@ -17,18 +17,21 @@
import static com.google.common.base.Preconditions.checkState;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import com.google.gerrit.common.TimeUtil;
import java.sql.Timestamp;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.Month;
+import java.time.ZoneOffset;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeUtils;
-import org.joda.time.DateTimeUtils.MillisProvider;
-import org.joda.time.DateTimeZone;
/** Static utility methods for dealing with dates and times in tests. */
public class TestTimeUtil {
- public static final DateTime START =
- new DateTime(2009, 9, 30, 17, 0, 0, DateTimeZone.forOffsetHours(-4));
+ public static final Instant START =
+ LocalDateTime.of(2009, Month.SEPTEMBER, 30, 17, 0, 0)
+ .atOffset(ZoneOffset.ofHours(-4))
+ .toInstant();
private static Long clockStepMs;
private static AtomicLong clockMs;
@@ -43,7 +46,7 @@
*/
public static synchronized void resetWithClockStep(long clockStep, TimeUnit clockStepUnit) {
// Set an arbitrary start point so tests are more repeatable.
- clockMs = new AtomicLong(START.getMillis());
+ clockMs = new AtomicLong(START.toEpochMilli());
setClockStep(clockStep, clockStepUnit);
}
@@ -56,13 +59,7 @@
public static synchronized void setClockStep(long clockStep, TimeUnit clockStepUnit) {
checkState(clockMs != null, "call resetWithClockStep first");
clockStepMs = MILLISECONDS.convert(clockStep, clockStepUnit);
- DateTimeUtils.setCurrentMillisProvider(
- new MillisProvider() {
- @Override
- public long getMillis() {
- return clockMs.getAndAdd(clockStepMs);
- }
- });
+ TimeUtil.setCurrentMillisSupplier(() -> clockMs.getAndAdd(clockStepMs));
}
/**
@@ -89,7 +86,7 @@
/** Reset the clock to use the actual system clock. */
public static synchronized void useSystemTime() {
clockMs = null;
- DateTimeUtils.setCurrentMillisSystem();
+ TimeUtil.resetCurrentMillisSupplier();
}
private TestTimeUtil() {}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginAdminSshCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginAdminSshCommand.java
new file mode 100644
index 0000000..7e32615
--- /dev/null
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginAdminSshCommand.java
@@ -0,0 +1,36 @@
+// Copyright (C) 2017 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.sshd.commands;
+
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.server.plugins.PluginLoader;
+import com.google.gerrit.sshd.SshCommand;
+import com.google.inject.Inject;
+
+@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
+public abstract class PluginAdminSshCommand extends SshCommand {
+ @Inject protected PluginLoader loader;
+
+ abstract void doRun() throws UnloggedFailure;
+
+ @Override
+ protected final void run() throws UnloggedFailure {
+ if (!loader.isRemoteAdminEnabled()) {
+ throw die("remote plugin administration is disabled");
+ }
+ doRun();
+ }
+}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginEnableCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginEnableCommand.java
index d7c8f3a..baaf715 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginEnableCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginEnableCommand.java
@@ -17,29 +17,18 @@
import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
import com.google.common.collect.Sets;
-import com.google.gerrit.common.data.GlobalCapability;
-import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.server.plugins.PluginInstallException;
-import com.google.gerrit.server.plugins.PluginLoader;
import com.google.gerrit.sshd.CommandMetaData;
-import com.google.gerrit.sshd.SshCommand;
-import com.google.inject.Inject;
import java.util.List;
import org.kohsuke.args4j.Argument;
-@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
@CommandMetaData(name = "enable", description = "Enable plugins", runsAt = MASTER_OR_SLAVE)
-final class PluginEnableCommand extends SshCommand {
+final class PluginEnableCommand extends PluginAdminSshCommand {
@Argument(index = 0, metaVar = "NAME", required = true, usage = "plugin(s) to enable")
List<String> names;
- @Inject private PluginLoader loader;
-
@Override
- protected void run() throws UnloggedFailure {
- if (!loader.isRemoteAdminEnabled()) {
- throw die("remote plugin administration is disabled");
- }
+ protected void doRun() throws UnloggedFailure {
if (names != null && !names.isEmpty()) {
try {
loader.enablePlugins(Sets.newHashSet(names));
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginInstallCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginInstallCommand.java
index 820052c..337eadb 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginInstallCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginInstallCommand.java
@@ -17,13 +17,8 @@
import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
import com.google.common.base.Strings;
-import com.google.gerrit.common.data.GlobalCapability;
-import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.server.plugins.PluginInstallException;
-import com.google.gerrit.server.plugins.PluginLoader;
import com.google.gerrit.sshd.CommandMetaData;
-import com.google.gerrit.sshd.SshCommand;
-import com.google.inject.Inject;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@@ -33,9 +28,8 @@
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option;
-@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
@CommandMetaData(name = "install", description = "Install/Add a plugin", runsAt = MASTER_OR_SLAVE)
-final class PluginInstallCommand extends SshCommand {
+final class PluginInstallCommand extends PluginAdminSshCommand {
@Option(
name = "--name",
aliases = {"-n"},
@@ -51,14 +45,9 @@
@Argument(index = 0, metaVar = "-|URL", usage = "JAR to load")
private String source;
- @Inject private PluginLoader loader;
-
@SuppressWarnings("resource")
@Override
- protected void run() throws UnloggedFailure {
- if (!loader.isRemoteAdminEnabled()) {
- throw die("remote installation is disabled");
- }
+ protected void doRun() throws UnloggedFailure {
if (Strings.isNullOrEmpty(source)) {
throw die("Argument \"-|URL\" is required");
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginReloadCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginReloadCommand.java
index 0f2c912..86a74d1 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginReloadCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginReloadCommand.java
@@ -16,30 +16,19 @@
import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
-import com.google.gerrit.common.data.GlobalCapability;
-import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.server.plugins.InvalidPluginException;
import com.google.gerrit.server.plugins.PluginInstallException;
-import com.google.gerrit.server.plugins.PluginLoader;
import com.google.gerrit.sshd.CommandMetaData;
-import com.google.gerrit.sshd.SshCommand;
-import com.google.inject.Inject;
import java.util.List;
import org.kohsuke.args4j.Argument;
-@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
@CommandMetaData(name = "reload", description = "Reload/Restart plugins", runsAt = MASTER_OR_SLAVE)
-final class PluginReloadCommand extends SshCommand {
+final class PluginReloadCommand extends PluginAdminSshCommand {
@Argument(index = 0, metaVar = "NAME", usage = "plugins to reload/restart")
private List<String> names;
- @Inject private PluginLoader loader;
-
@Override
- protected void run() throws UnloggedFailure {
- if (!loader.isRemoteAdminEnabled()) {
- throw die("remote plugin administration is disabled");
- }
+ protected void doRun() throws UnloggedFailure {
if (names == null || names.isEmpty()) {
loader.rescan();
} else {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginRemoveCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginRemoveCommand.java
index 8a38739..0119349b 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginRemoveCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginRemoveCommand.java
@@ -17,28 +17,17 @@
import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
import com.google.common.collect.Sets;
-import com.google.gerrit.common.data.GlobalCapability;
-import com.google.gerrit.extensions.annotations.RequiresCapability;
-import com.google.gerrit.server.plugins.PluginLoader;
import com.google.gerrit.sshd.CommandMetaData;
-import com.google.gerrit.sshd.SshCommand;
-import com.google.inject.Inject;
import java.util.List;
import org.kohsuke.args4j.Argument;
-@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
@CommandMetaData(name = "remove", description = "Disable plugins", runsAt = MASTER_OR_SLAVE)
-final class PluginRemoveCommand extends SshCommand {
+final class PluginRemoveCommand extends PluginAdminSshCommand {
@Argument(index = 0, metaVar = "NAME", required = true, usage = "plugin to remove")
List<String> names;
- @Inject private PluginLoader loader;
-
@Override
- protected void run() throws UnloggedFailure {
- if (!loader.isRemoteAdminEnabled()) {
- throw die("remote plugin administration is disabled");
- }
+ protected void doRun() throws UnloggedFailure {
if (names != null && !names.isEmpty()) {
loader.disablePlugins(Sets.newHashSet(names));
}
diff --git a/gerrit-war/pom.xml b/gerrit-war/pom.xml
index 2dd6ba6..c43c098 100644
--- a/gerrit-war/pom.xml
+++ b/gerrit-war/pom.xml
@@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-war</artifactId>
- <version>2.15-rc0</version>
+ <version>2.16-SNAPSHOT</version>
<packaging>war</packaging>
<name>Gerrit Code Review - WAR</name>
<description>Gerrit WAR</description>
diff --git a/lib/elasticsearch/BUILD b/lib/elasticsearch/BUILD
index c40925e..18c62af 100644
--- a/lib/elasticsearch/BUILD
+++ b/lib/elasticsearch/BUILD
@@ -8,13 +8,13 @@
":compress-lzf",
":hppc",
":jna",
+ ":joda-time",
":jsr166e",
":netty",
":t-digest",
"//lib/jackson:jackson-core",
"//lib/jackson:jackson-dataformat-cbor",
"//lib/jackson:jackson-dataformat-smile",
- "//lib/joda:joda-time",
"//lib/lucene:lucene-codecs",
"//lib/lucene:lucene-highlighter",
"//lib/lucene:lucene-join",
@@ -48,6 +48,19 @@
)
java_library(
+ name = "joda-time",
+ data = ["//lib:LICENSE-Apache2.0"],
+ exports = ["@joda_time//jar"],
+ runtime_deps = ["joda-convert"],
+)
+
+java_library(
+ name = "joda-convert",
+ data = ["//lib:LICENSE-Apache2.0"],
+ exports = ["@joda_convert//jar"],
+)
+
+java_library(
name = "compress-lzf",
data = ["//lib:LICENSE-Apache2.0"],
visibility = ["//lib/elasticsearch:__pkg__"],
diff --git a/lib/joda/BUILD b/lib/joda/BUILD
deleted file mode 100644
index e1a1924..0000000
--- a/lib/joda/BUILD
+++ /dev/null
@@ -1,13 +0,0 @@
-java_library(
- name = "joda-time",
- data = ["//lib:LICENSE-Apache2.0"],
- visibility = ["//visibility:public"],
- exports = ["@joda_time//jar"],
- runtime_deps = ["joda-convert"],
-)
-
-java_library(
- name = "joda-convert",
- data = ["//lib:LICENSE-Apache2.0"],
- exports = ["@joda_convert//jar"],
-)
diff --git a/polygerrit-ui/app/elements/admin/gr-project-command/gr-project-command.html b/polygerrit-ui/app/elements/admin/gr-project-command/gr-project-command.html
new file mode 100644
index 0000000..6bf2211
--- /dev/null
+++ b/polygerrit-ui/app/elements/admin/gr-project-command/gr-project-command.html
@@ -0,0 +1,32 @@
+<!--
+Copyright (C) 2017 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.
+-->
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../../styles/shared-styles.html">
+<link rel="import" href="../../shared/gr-button/gr-button.html">
+
+<dom-module id="gr-project-command">
+ <template>
+ <style include="shared-styles">
+ :host {
+ display: block;
+ margin-bottom: 2em;
+ }
+ </style>
+ <h3>[[title]]</h3>
+ <gr-button on-tap="_onCommandTap">[[title]]</gr-button>
+ </template>
+ <script src="gr-project-command.js"></script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/admin/gr-project-command/gr-project-command.js b/polygerrit-ui/app/elements/admin/gr-project-command/gr-project-command.js
new file mode 100644
index 0000000..48789b0
--- /dev/null
+++ b/polygerrit-ui/app/elements/admin/gr-project-command/gr-project-command.js
@@ -0,0 +1,34 @@
+// Copyright (C) 2017 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.
+(function() {
+ 'use strict';
+
+ Polymer({
+ is: 'gr-project-command',
+
+ properties: {
+ title: String,
+ },
+
+ /**
+ * Fired when command button is tapped.
+ *
+ * @event command-tap
+ */
+
+ _onCommandTap() {
+ this.dispatchEvent(new CustomEvent('command-tap', {bubbles: true}));
+ },
+ });
+})();
diff --git a/polygerrit-ui/app/elements/admin/gr-project-command/gr-project-command_test.html b/polygerrit-ui/app/elements/admin/gr-project-command/gr-project-command_test.html
new file mode 100644
index 0000000..8fae4f8
--- /dev/null
+++ b/polygerrit-ui/app/elements/admin/gr-project-command/gr-project-command_test.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<!--
+Copyright (C) 2017 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.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-project-command</title>
+
+<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
+<link rel="import" href="gr-project-command.html">
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+ <template>
+ <gr-project-command></gr-project-command>
+ </template>
+</test-fixture>
+
+<script>
+ suite('gr-project-command tests', () => {
+ let element;
+
+ setup(() => {
+ element = fixture('basic');
+ });
+
+ test('dispatched command-tap on button tap', done => {
+ element.addEventListener('command-tap', () => {
+ done();
+ });
+ MockInteractions.tap(
+ Polymer.dom(element.root).querySelector('gr-button'));
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/elements/admin/gr-project-commands/gr-project-commands.html b/polygerrit-ui/app/elements/admin/gr-project-commands/gr-project-commands.html
index 6c0908a..f43403a 100644
--- a/polygerrit-ui/app/elements/admin/gr-project-commands/gr-project-commands.html
+++ b/polygerrit-ui/app/elements/admin/gr-project-commands/gr-project-commands.html
@@ -19,10 +19,12 @@
<link rel="import" href="../../../bower_components/iron-input/iron-input.html">
<link rel="import" href="../../../styles/gr-form-styles.html">
<link rel="import" href="../../../styles/shared-styles.html">
+<link rel="import" href="../../plugins/gr-endpoint-decorator/gr-endpoint-decorator.html">
<link rel="import" href="../../shared/gr-confirm-dialog/gr-confirm-dialog.html">
<link rel="import" href="../../shared/gr-overlay/gr-overlay.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../gr-create-change-dialog/gr-create-change-dialog.html">
+<link rel="import" href="../gr-project-command/gr-project-command.html">
<dom-module id="gr-project-commands">
<template>
@@ -47,24 +49,23 @@
<div id="loadedContent" class$="[[_computeLoadingClass(_loading)]]">
<h2 id="options">Command</h2>
<div id="form">
- <fieldset>
- <h3 id="createChange">Create Change</h3>
- <fieldset>
- <gr-button id="createNewChange" on-tap="_createNewChange">
- Create Change
- </gr-button>
- </fieldset>
- <h3 id="runGC" hidden$="[[!_projectConfig.actions.gc.enabled]]">
- Run GC
- </h3>
- <fieldset>
- <gr-button
- on-tap="_handleRunningGC"
- hidden$="[[!_projectConfig.actions.gc.enabled]]">
- Run GC
- </gr-button>
- </fieldset>
- </fieldset>
+ <gr-project-command
+ title="Create Change"
+ on-command-tap="_createNewChange">
+ </gr-project-command>
+
+ <gr-project-command
+ title="Run GC"
+ hidden$="[[!_projectConfig.actions.gc.enabled]]"
+ on-command-tap="_handleRunningGC">
+ </gr-project-command>
+
+ <gr-endpoint-decorator name="project-command">
+ <gr-endpoint-param name="config" value="[[_projectConfig]]">
+ </gr-endpoint-param>
+ <gr-endpoint-param name="projectName" value="[[project]]">
+ </gr-endpoint-param>
+ </gr-endpoint-decorator>
</div>
</div>
</main>
diff --git a/polygerrit-ui/app/elements/admin/gr-project-detail-list/gr-project-detail-list_test.html b/polygerrit-ui/app/elements/admin/gr-project-detail-list/gr-project-detail-list_test.html
index c424808..62870df 100644
--- a/polygerrit-ui/app/elements/admin/gr-project-detail-list/gr-project-detail-list_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-project-detail-list/gr-project-detail-list_test.html
@@ -69,6 +69,7 @@
element = fixture('basic');
element.detailType = 'branches';
counter = 0;
+ sandbox.stub(page, 'show');
});
teardown(() => {
@@ -314,6 +315,7 @@
element = fixture('basic');
element.detailType = 'tags';
counter = 0;
+ sandbox.stub(page, 'show');
});
teardown(() => {
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js
index 6c5bad3..750db92 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js
@@ -73,7 +73,7 @@
},
observers: [
- '_userChanged(params.user)',
+ '_paramsChanged(params.*)',
],
behaviors: [
@@ -95,20 +95,28 @@
return 'Dashboard for ' + user;
},
- /**
- * Allows a refresh if menu item is selected again.
- */
- _userChanged(user) {
- if (!user) { return; }
+ _paramsChanged(paramsChangeRecord) {
+ const params = paramsChangeRecord.base;
+
+ if (!params.user && !params.sections) {
+ return;
+ }
+
+ const user = params.user || 'self';
+ const sections = (params.sections || DEFAULT_SECTIONS).filter(
+ section => (user === 'self' || !section.selfOnly));
+ const title = params.title || this._computeTitle(user);
// NOTE: This method may be called before attachment. Fire title-change
// in an async so that attachment to the DOM can take place first.
- this.async(
- () => this.fire('title-change', {title: this._computeTitle(user)}));
+ this.async(() => this.fire('title-change', {title}));
+
+ // Return if params indicate no longer in view.
+ if (!user && sections === DEFAULT_SECTIONS) {
+ return;
+ }
this._loading = true;
- const sections = this._sectionMetadata.filter(
- section => (user === 'self' || !section.selfOnly));
const queries =
sections.map(
section => this._dashboardQueryForSection(section, user));
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html
index 2edf26f..40376ad 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html
@@ -64,17 +64,18 @@
});
test('viewing another user\'s dashboard omits selfOnly sections', () => {
- element._sectionMetadata = [
- {query: '1'},
- {query: '2', selfOnly: true},
- ];
-
- element.params = {user: 'self'};
+ element.params = {
+ sections: [
+ {query: '1'},
+ {query: '2', selfOnly: true},
+ ],
+ user: 'self',
+ };
flushAsynchronousOperations();
assert.isTrue(
getChangesStub.calledWith(null, ['1', '2'], null, element.options));
- element.params = {user: 'user'};
+ element.set('params.user', 'user');
flushAsynchronousOperations();
assert.isTrue(
getChangesStub.calledWith(null, ['1'], null, element.options));
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.html b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.html
index cb3d58a..fd07d4e 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.html
@@ -46,12 +46,11 @@
border-top: 1px solid #ddd;
display: flex;
min-height: 3.2em;
- padding: .5em calc(var(--default-horizontal-margin) / 2);
+ padding: .5em var(--default-horizontal-margin);
}
.patchInfo-header-wrapper {
align-items: center;
display: flex;
- margin: 0 .25em;
width: 100%;
}
.patchInfo-left {
@@ -113,7 +112,7 @@
.separator {
background-color: rgba(0, 0, 0, .3);
height: 1.5em;
- margin: 0 .4em;
+ margin: 0 .6em;
width: 1px;
}
.separator.transparent {
@@ -141,7 +140,7 @@
<div class$="patchInfo-header [[_computeEditLoadedClass(editLoaded)]] [[_computePatchInfoClass(patchNum, allPatchSets)]]">
<div class="patchInfo-header-wrapper">
<div class="patchInfo-left">
- <span class="label">Files</span>
+ <h3 class="label">Files</h3>
<gr-patch-range-select
id="rangeSelect"
comments="[[comments]]"
@@ -190,7 +189,6 @@
</div>
</div>
<div class="fileList-header">
- <div>Files</div>
<div class="rightControls">
<template is="dom-if"
if="[[_fileListActionsVisible(shownFileCount, _maxFilesForBulkActions)]]">
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
index 1a164b6..f80ff0a 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
@@ -23,6 +23,7 @@
<link rel="import" href="../../diff/gr-comment-api/gr-comment-api.html">
<link rel="import" href="../../diff/gr-diff/gr-diff.html">
<link rel="import" href="../../diff/gr-diff-cursor/gr-diff-cursor.html">
+<link rel="import" href="../../edit/gr-edit-file-controls/gr-edit-file-controls.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../../shared/gr-cursor-manager/gr-cursor-manager.html">
<link rel="import" href="../../shared/gr-linked-text/gr-linked-text.html">
@@ -42,7 +43,7 @@
border-top: 1px solid #ddd;
display: flex;
height: 2.25em;
- padding: 0 calc(var(--default-horizontal-margin) / 2 + .25em);
+ padding: 0 var(--default-horizontal-margin);
}
:host(.loading) .row {
opacity: .5;
@@ -50,6 +51,12 @@
:host(.editLoaded) .hideOnEdit {
display: none;
}
+ .showOnEdit {
+ display: none;
+ }
+ :host(.editLoaded) .showOnEdit {
+ display: initial;
+ }
.reviewed,
.status {
align-items: center;
@@ -184,6 +191,10 @@
display: initial;
opacity: 100;
}
+ .editFileControls {
+ margin-left: 1em;
+ width: 4em;
+ }
@media screen and (max-width: 50em) {
.desktop {
display: none;
@@ -299,6 +310,12 @@
<span class="markReviewed" title="Mark as reviewed (shortcut: r)">[[_computeReviewedText(file.isReviewed)]]</span>
</label>
</div>
+ <div class="editFileControls showOnEdit">
+ <gr-edit-file-controls
+ class$="[[_computeClass('', file.__path)]]"
+ file-path="[[file.__path]]"
+ on-edit-tap="_handleEditTap"></gr-edit-file-controls>
+ </div>
</div>
<template is="dom-if"
if="[[_isFileExpanded(file.__path, _expandedFilePaths.*)]]">
@@ -338,6 +355,7 @@
</div>
<!-- Empty div here exists to keep spacing in sync with file rows. -->
<div class="reviewed hideOnEdit" hidden$="[[!_loggedIn]]" hidden></div>
+ <div class="editFileControls showOnEdit"></div>
</div>
<div
class$="row totalChanges [[_computeExpandInlineClass(_userPrefs)]]"
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
index 11a4a29..70f1d4d 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
@@ -926,5 +926,10 @@
_computeReviewedText(isReviewed) {
return isReviewed ? 'MARK UNREVIEWED' : 'MARK REVIEWED';
},
+
+ _handleEditTap(e) {
+ const url = Gerrit.Nav.getEditUrlForDiff(this.change, e.detail.path);
+ Gerrit.Nav.navigateToRelativeUrl(url);
+ },
});
})();
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
index b80a20f..b01c105 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
@@ -1238,6 +1238,20 @@
});
});
});
+
+ test('editing actions', () => {
+ element.editLoaded = true;
+ element.change = {_number: '42', project: 'test'};
+ const navStub = sandbox.stub(Gerrit.Nav, 'navigateToRelativeUrl');
+ const editControls =
+ Polymer.dom(element.root).querySelectorAll('.row:not(.header)')
+ .map(row => row.querySelector('gr-edit-file-controls'));
+
+ // Commit message should not have edit controls.
+ assert.isTrue(editControls[0].classList.contains('invisible'));
+ MockInteractions.tap(editControls[1].$.edit);
+ assert.isTrue(navStub.called);
+ });
});
a11ySuite('basic');
</script>
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.html b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.html
index 0fa6f4d..ab494b4 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.html
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.html
@@ -60,7 +60,7 @@
.separator {
background-color: rgba(0, 0, 0, .3);
height: 1.5em;
- margin: 0 .4em;
+ margin: 0 .6em;
width: 1px;
}
.separator.transparent {
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
index 4b02c3d..068cacc 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
@@ -56,6 +56,13 @@
padding: .5em .75em;
width: 100%;
}
+ .actions {
+ display: flex;
+ justify-content: space-between;
+ }
+ .actions gr-button {
+ margin-left: 1em;
+ }
.peopleContainer,
.labelsContainer {
flex-shrink: 0;
@@ -135,9 +142,6 @@
#savingLabel.saving {
display: inline;
}
- #cancelButton {
- float: right;
- }
@media screen and (max-width: 50em) {
:host {
max-height: none;
@@ -259,33 +263,40 @@
Saving comments...
</span>
</section>
- <section>
- <gr-button
- primary
- disabled="[[_computeSendButtonDisabled(knownLatestState, _sendButtonLabel, diffDrafts, draft, _reviewersMutated, _labelsChanged, _includeComments)]]"
- class="action send"
- on-tap="_sendTapHandler">[[_sendButtonLabel]]</gr-button>
- <template is="dom-if" if="[[canBeStarted]]">
+ <section class="actions">
+ <div class="left">
+ <span
+ id="checkingStatusLabel"
+ hidden$="[[!_isState(knownLatestState, 'checking')]]">
+ Checking whether patch [[patchNum]] is latest...
+ </span>
+ <span
+ id="notLatestLabel"
+ hidden$="[[!_isState(knownLatestState, 'not-latest')]]">
+ Patch [[patchNum]] is not latest.
+ <gr-button link on-tap="_reload">Reload</gr-button>
+ </span>
+ </div>
+ <div class="right">
<gr-button
- disabled="[[_isState(knownLatestState, 'not-latest')]]"
- class="action save"
- on-tap="_saveTapHandler">Save</gr-button>
- </template>
- <span
- id="checkingStatusLabel"
- hidden$="[[!_isState(knownLatestState, 'checking')]]">
- Checking whether patch [[patchNum]] is latest...
- </span>
- <span
- id="notLatestLabel"
- hidden$="[[!_isState(knownLatestState, 'not-latest')]]">
- Patch [[patchNum]] is not latest.
- <gr-button link on-tap="_reload">Reload</gr-button>
- </span>
- <gr-button
- id="cancelButton"
- class="action cancel"
- on-tap="_cancelTapHandler">Cancel</gr-button>
+ link
+ id="cancelButton"
+ class="action cancel"
+ on-tap="_cancelTapHandler">Cancel</gr-button>
+ <gr-button
+ link
+ primary
+ disabled="[[_computeSendButtonDisabled(knownLatestState, _sendButtonLabel, diffDrafts, draft, _reviewersMutated, _labelsChanged, _includeComments)]]"
+ class="action send"
+ on-tap="_sendTapHandler">[[_sendButtonLabel]]</gr-button>
+ <template is="dom-if" if="[[canBeStarted]]">
+ <gr-button
+ link
+ disabled="[[_isState(knownLatestState, 'not-latest')]]"
+ class="action save"
+ on-tap="_saveTapHandler">Save</gr-button>
+ </template>
+ </div>
</section>
</div>
<gr-js-api-interface id="jsAPI"></gr-js-api-interface>
diff --git a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html
index b03f2e5..8453da9 100644
--- a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html
+++ b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html
@@ -57,6 +57,7 @@
console.warn('Use of uninitialized routing');
};
+ const EDIT_PATCHNUM = 'edit';
const PARENT_PATCHNUM = 'PARENT';
window.Gerrit.Nav = {
@@ -277,6 +278,7 @@
changeNum,
project,
path,
+ patchNum: EDIT_PATCHNUM,
});
},
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router.js b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
index 5b9f4f6..708cb17 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.js
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
@@ -126,6 +126,16 @@
*/
const LINE_ADDRESS_PATTERN = /^([ab]?)(\d+)$/;
+ /**
+ * Pattern to recognize '+' in url-encoded strings for replacement with ' '.
+ */
+ const PLUS_PATTERN = /\+/g;
+
+ /**
+ * Pattern to recognize leading '?' in window.location.search, for stripping.
+ */
+ const QUESTION_PATTERN = /^\?*/;
+
// Polymer makes `app` intrinsically defined on the window by virtue of the
// custom element having the id "app", but it is made explicit here.
const app = document.querySelector('#app');
@@ -181,8 +191,9 @@
_generateUrl(params) {
const base = this.getBaseUrl();
let url = '';
+ const Views = Gerrit.Nav.View;
- if (params.view === Gerrit.Nav.View.SEARCH) {
+ if (params.view === Views.SEARCH) {
const operators = [];
if (params.owner) {
operators.push('owner:' + this.encodeURL(params.owner, false));
@@ -213,7 +224,7 @@
}
}
url = '/q/' + operators.join('+');
- } else if (params.view === Gerrit.Nav.View.CHANGE) {
+ } else if (params.view === Views.CHANGE) {
let range = this._getPatchRangeExpression(params);
if (range.length) { range = '/' + range; }
if (params.project) {
@@ -221,13 +232,30 @@
} else {
url = `/c/${params.changeNum}${range}`;
}
- } else if (params.view === Gerrit.Nav.View.DASHBOARD) {
- url = `/dashboard/${params.user || 'self'}`;
- } else if (params.view === Gerrit.Nav.View.DIFF) {
+ } else if (params.view === Views.DASHBOARD) {
+ if (params.sections) {
+ // Custom dashboard.
+ const queryParams = params.sections.map(section => {
+ return encodeURIComponent(section.name) + '=' +
+ encodeURIComponent(section.query);
+ });
+ if (params.title) {
+ queryParams.push('title=' + encodeURIComponent(params.title));
+ }
+ const user = params.user ? params.user : '';
+ url = `/dashboard/${user}?${queryParams.join('&')}`;
+ } else {
+ // User dashboard.
+ url = `/dashboard/${params.user || 'self'}`;
+ }
+ } else if (params.view === Views.DIFF || params.view === Views.EDIT) {
let range = this._getPatchRangeExpression(params);
if (range.length) { range = '/' + range; }
let suffix = `${range}/${this.encodeURL(params.path, true)}`;
+
+ if (params.view === Views.EDIT) { suffix += ',edit'; }
+
if (params.lineNum) {
suffix += '#';
if (params.leftSide) { suffix += 'b'; }
@@ -239,9 +267,6 @@
} else {
url = `/c/${params.changeNum}${suffix}`;
}
- if (params.edit) {
- url += ',edit';
- }
} else {
throw new Error('Can\'t generate');
}
@@ -593,19 +618,101 @@
});
},
- _handleDashboardRoute(data) {
- if (!data.params[0]) {
- this._redirect('/dashboard/self');
- return;
+ /**
+ * Decode an application/x-www-form-urlencoded string.
+ *
+ * @param {string} qs The application/x-www-form-urlencoded string.
+ * @return {string} The decoded string.
+ */
+ _decodeQueryString(qs) {
+ return decodeURIComponent(qs.replace(PLUS_PATTERN, ' '));
+ },
+
+ /**
+ * Parse a query string (e.g. window.location.search) into an array of
+ * name/value pairs.
+ *
+ * @param {string} qs The application/x-www-form-urlencoded query string.
+ * @return {!Array<!Array<string>>} An array of name/value pairs, where each
+ * element is a 2-element array.
+ */
+ _parseQueryString(qs) {
+ qs = qs.replace(QUESTION_PATTERN, '');
+ if (!qs) {
+ return [];
+ }
+ const params = [];
+ qs.split('&').forEach(param => {
+ const idx = param.indexOf('=');
+ let name;
+ let value;
+ if (idx < 0) {
+ name = this._decodeQueryString(param);
+ value = '';
+ } else {
+ name = this._decodeQueryString(param.substring(0, idx));
+ value = this._decodeQueryString(param.substring(idx + 1));
+ }
+ if (name) {
+ params.push([name, value]);
+ }
+ });
+ return params;
+ },
+
+ /**
+ * Handle dashboard routes. These may be user, custom, or project
+ * dashboards.
+ *
+ * @param {!Object} data The parsed route data.
+ * @param {string=} opt_qs Optional query string associated with the route.
+ * If not given, window.location.search is used. (Used by tests).
+ */
+ _handleDashboardRoute(data, opt_qs) {
+ // opt_qs may be provided by a test, and it may have a falsy value
+ const qs = opt_qs !== undefined ? opt_qs : window.location.search;
+ const queryParams = this._parseQueryString(qs);
+ let title = 'Custom Dashboard';
+ const titleParam = queryParams.find(
+ elem => elem[0].toLowerCase() === 'title');
+ if (titleParam) {
+ title = titleParam[1];
+ }
+ const sectionParams = queryParams.filter(
+ elem => elem[0] && elem[1] && elem[0].toLowerCase() !== 'title');
+ const sections = sectionParams.map(elem => {
+ return {
+ name: elem[0],
+ query: elem[1],
+ };
+ });
+
+ if (sections.length > 0) {
+ // Custom dashboard view.
+ this._setParams({
+ view: Gerrit.Nav.View.DASHBOARD,
+ user: data.params[0] || 'self',
+ sections,
+ title,
+ });
+ return Promise.resolve();
}
+ if (!data.params[0] && sections.length === 0) {
+ // Redirect /dashboard/ -> /dashboard/self.
+ this._redirect('/dashboard/self');
+ return Promise.resolve();
+ }
+
+ // User dashboard. We require viewing user to be logged in, else we
+ // redirect to login for self dashboard or simple owner search for
+ // other user dashboard.
return this.$.restAPI.getLoggedIn().then(loggedIn => {
if (!loggedIn) {
if (data.params[0].toLowerCase() === 'self') {
this._redirectToLogin(data.canonicalPath);
} else {
- // TODO: encode user or use _generateUrl.
- this._redirect('/q/owner:' + data.params[0]);
+ this._redirect('/q/owner:' + encodeURIComponent(data.params[0]));
}
} else {
this._setParams({
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
index ca00e19..4aae65f 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
@@ -280,6 +280,17 @@
'/c/test/+/42/2/file.cpp#b123');
});
+ test('edit', () => {
+ const params = {
+ view: Gerrit.Nav.View.EDIT,
+ changeNum: '42',
+ project: 'test',
+ path: 'x+y/path.cpp',
+ };
+ assert.equal(element._generateUrl(params),
+ '/c/test/+/42/x%252By/path.cpp,edit');
+ });
+
test('_getPatchRangeExpression', () => {
const params = {};
let actual = element._getPatchRangeExpression(params);
@@ -297,6 +308,48 @@
actual = element._getPatchRangeExpression(params);
assert.equal(actual, '2..');
});
+
+ suite('dashboard', () => {
+ test('self dashboard', () => {
+ const params = {
+ view: Gerrit.Nav.View.DASHBOARD,
+ };
+ assert.equal(element._generateUrl(params), '/dashboard/self');
+ });
+
+ test('user dashboard', () => {
+ const params = {
+ view: Gerrit.Nav.View.DASHBOARD,
+ user: 'user',
+ };
+ assert.equal(element._generateUrl(params), '/dashboard/user');
+ });
+
+ test('custom self dashboard, no title', () => {
+ const params = {
+ view: Gerrit.Nav.View.DASHBOARD,
+ sections: [
+ {name: 'section 1', query: 'query 1'},
+ {name: 'section 2', query: 'query 2'},
+ ],
+ };
+ assert.equal(
+ element._generateUrl(params),
+ '/dashboard/?section%201=query%201§ion%202=query%202');
+ });
+
+ test('custom user dashboard, with title', () => {
+ const params = {
+ view: Gerrit.Nav.View.DASHBOARD,
+ user: 'user',
+ sections: [{name: 'name', query: 'query'}],
+ title: 'custom dashboard',
+ };
+ assert.equal(
+ element._generateUrl(params),
+ '/dashboard/user?name=query&title=custom%20dashboard');
+ });
+ });
});
suite('param normalization', () => {
@@ -514,7 +567,7 @@
assert.isFalse(redirectStub.called);
});
- test('redirects to dahsboard if logged in', () => {
+ test('redirects to dashboard if logged in', () => {
sandbox.stub(element.$.restAPI, 'getLoggedIn')
.returns(Promise.resolve(true));
const data = {
@@ -625,35 +678,31 @@
});
test('no user specified', () => {
- const data = {canonicalPath: '/dashboard', params: {}};
- const result = element._handleDashboardRoute(data);
- assert.isNotOk(result);
- assert.isFalse(setParamsStub.called);
- assert.isFalse(redirectToLoginStub.called);
- assert.isTrue(redirectStub.called);
- assert.equal(redirectStub.lastCall.args[0], '/dashboard/self');
+ const data = {canonicalPath: '/dashboard/', params: {0: ''}};
+ return element._handleDashboardRoute(data, '').then(() => {
+ assert.isFalse(setParamsStub.called);
+ assert.isFalse(redirectToLoginStub.called);
+ assert.isTrue(redirectStub.called);
+ assert.equal(redirectStub.lastCall.args[0], '/dashboard/self');
+ });
});
- test('own dahsboard but signed out redirects to login', () => {
+ test('own dashboard but signed out redirects to login', () => {
sandbox.stub(element.$.restAPI, 'getLoggedIn')
.returns(Promise.resolve(false));
- const data = {canonicalPath: '/dashboard', params: {0: 'seLF'}};
- const result = element._handleDashboardRoute(data);
- assert.isOk(result);
- return result.then(() => {
+ const data = {canonicalPath: '/dashboard/', params: {0: 'seLF'}};
+ return element._handleDashboardRoute(data, '').then(() => {
assert.isTrue(redirectToLoginStub.calledOnce);
assert.isFalse(redirectStub.called);
assert.isFalse(setParamsStub.called);
});
});
- test('non-self dahsboard but signed out does not redirect', () => {
+ test('non-self dashboard but signed out does not redirect', () => {
sandbox.stub(element.$.restAPI, 'getLoggedIn')
.returns(Promise.resolve(false));
- const data = {canonicalPath: '/dashboard', params: {0: 'foo'}};
- const result = element._handleDashboardRoute(data);
- assert.isOk(result);
- return result.then(() => {
+ const data = {canonicalPath: '/dashboard/', params: {0: 'foo'}};
+ return element._handleDashboardRoute(data, '').then(() => {
assert.isFalse(redirectToLoginStub.called);
assert.isFalse(setParamsStub.called);
assert.isTrue(redirectStub.calledOnce);
@@ -661,13 +710,11 @@
});
});
- test('dahsboard while signed in sets params', () => {
+ test('dashboard while signed in sets params', () => {
sandbox.stub(element.$.restAPI, 'getLoggedIn')
.returns(Promise.resolve(true));
- const data = {canonicalPath: '/dashboard', params: {0: 'foo'}};
- const result = element._handleDashboardRoute(data);
- assert.isOk(result);
- return result.then(() => {
+ const data = {canonicalPath: '/dashboard/', params: {0: 'foo'}};
+ return element._handleDashboardRoute(data, '').then(() => {
assert.isFalse(redirectToLoginStub.called);
assert.isFalse(redirectStub.called);
assert.isTrue(setParamsStub.calledOnce);
@@ -677,6 +724,42 @@
});
});
});
+
+ test('custom dashboard without title', () => {
+ const data = {canonicalPath: '/dashboard/', params: {0: ''}};
+ return element._handleDashboardRoute(data, '?a=b&c&d=e').then(() => {
+ assert.isFalse(redirectToLoginStub.called);
+ assert.isFalse(redirectStub.called);
+ assert.isTrue(setParamsStub.calledOnce);
+ assert.deepEqual(setParamsStub.lastCall.args[0], {
+ view: Gerrit.Nav.View.DASHBOARD,
+ user: 'self',
+ sections: [
+ {name: 'a', query: 'b'},
+ {name: 'd', query: 'e'},
+ ],
+ title: 'Custom Dashboard',
+ });
+ });
+ });
+
+ test('custom dashboard with title', () => {
+ const data = {canonicalPath: '/dashboard/', params: {0: ''}};
+ return element._handleDashboardRoute(data, '?a=b&c&d=&=e&title=t')
+ .then(() => {
+ assert.isFalse(redirectToLoginStub.called);
+ assert.isFalse(redirectStub.called);
+ assert.isTrue(setParamsStub.calledOnce);
+ assert.deepEqual(setParamsStub.lastCall.args[0], {
+ view: Gerrit.Nav.View.DASHBOARD,
+ user: 'self',
+ sections: [
+ {name: 'a', query: 'b'},
+ ],
+ title: 't',
+ });
+ });
+ });
});
suite('group routes', () => {
@@ -1170,5 +1253,31 @@
});
});
});
+
+ suite('_parseQueryString', () => {
+ test('empty queries', () => {
+ assert.deepEqual(element._parseQueryString(''), []);
+ assert.deepEqual(element._parseQueryString('?'), []);
+ assert.deepEqual(element._parseQueryString('??'), []);
+ assert.deepEqual(element._parseQueryString('&&&'), []);
+ });
+
+ test('url decoding', () => {
+ assert.deepEqual(element._parseQueryString('+'), [[' ', '']]);
+ assert.deepEqual(element._parseQueryString('???+%3d+'), [[' = ', '']]);
+ assert.deepEqual(
+ element._parseQueryString('%6e%61%6d%65=%76%61%6c%75%65'),
+ [['name', 'value']]);
+ });
+
+ test('multiple parameters', () => {
+ assert.deepEqual(
+ element._parseQueryString('a=b&c=d&e=f'),
+ [['a', 'b'], ['c', 'd'], ['e', 'f']]);
+ assert.deepEqual(
+ element._parseQueryString('&a=b&&&e=f&'),
+ [['a', 'b'], ['e', 'f']]);
+ });
+ });
});
</script>
diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.html b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.html
index 95a6167..f532e3f 100644
--- a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.html
+++ b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.html
@@ -32,8 +32,17 @@
max-width: 15em;
}
.arrow {
+ color: rgba(0,0,0,.7);
margin: 0 .5em;
}
+ gr-dropdown-list {
+ --trigger-style: {
+ color: rgba(0,0,0,.7);
+ text-transform: none;
+ font-family: var(--font-family);
+ }
+ --trigger-hover-color: rgba(0,0,0,.6);
+ }
@media screen and (max-width: 50em) {
.filesWeblinks {
display: none;
diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.js b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.js
index 5eef391..0b2985f 100644
--- a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.js
+++ b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.js
@@ -96,8 +96,9 @@
dropdownContent.push({
disabled: this._computeRightDisabled(patchNum, basePatchNum,
_sortedRevisions),
- triggerText: `Patchset ${patchNum}`,
- text: `Patchset ${patchNum}` +
+ triggerText: `${patchNum === 'edit' ? '': 'Patchset '}` +
+ patchNum,
+ text: `${patchNum === 'edit' ? '': 'Patchset '}${patchNum}` +
`${this._computePatchSetCommentsString(
this.comments, patchNum)}`,
mobileText: this._computeMobileText(patchNum, this.comments,
diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.html b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.html
index 8ba3709..682956a 100644
--- a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.html
@@ -246,8 +246,8 @@
},
{
disabled: false,
- triggerText: 'Patchset edit',
- text: 'Patchset edit',
+ triggerText: 'edit',
+ text: 'edit',
mobileText: 'edit',
bottomText: '',
value: 'edit',
diff --git a/polygerrit-ui/app/elements/diff/gr-syntax-lib-loader/gr-syntax-lib-loader.js b/polygerrit-ui/app/elements/diff/gr-syntax-lib-loader/gr-syntax-lib-loader.js
index bfd8e90..ca3cf62 100644
--- a/polygerrit-ui/app/elements/diff/gr-syntax-lib-loader/gr-syntax-lib-loader.js
+++ b/polygerrit-ui/app/elements/diff/gr-syntax-lib-loader/gr-syntax-lib-loader.js
@@ -26,6 +26,7 @@
// NOTE: intended singleton.
value: {
+ configured: false,
loading: false,
callbacks: [],
},
@@ -60,12 +61,13 @@
},
_getHighlightLib() {
- return window.hljs;
- },
+ const lib = window.hljs;
+ if (lib && !this._state.configured) {
+ this._state.configured = true;
- _configureHighlightLib() {
- this._getHighlightLib().configure(
- {classPrefix: 'gr-diff gr-syntax gr-syntax-'});
+ lib.configure({classPrefix: 'gr-diff gr-syntax gr-syntax-'});
+ }
+ return lib;
},
_getLibRoot() {
@@ -93,10 +95,8 @@
}
script.src = src;
- script.onload = function() {
- this._configureHighlightLib();
- resolve();
- }.bind(this);
+ script.onload = resolve;
+ script.onerror = reject;
Polymer.dom(document.head).appendChild(script);
});
},
diff --git a/polygerrit-ui/app/elements/diff/gr-syntax-lib-loader/gr-syntax-lib-loader_test.html b/polygerrit-ui/app/elements/diff/gr-syntax-lib-loader/gr-syntax-lib-loader_test.html
index 6ddde46..6e88ed1 100644
--- a/polygerrit-ui/app/elements/diff/gr-syntax-lib-loader/gr-syntax-lib-loader_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-syntax-lib-loader/gr-syntax-lib-loader_test.html
@@ -55,6 +55,7 @@
loadStub.restore();
// Because the element state is a singleton, clean it up.
+ element._state.configured = false;
element._state.loading = false;
element._state.callbacks = [];
});
@@ -88,8 +89,13 @@
});
suite('preloaded', () => {
+ let hljsStub;
+
setup(() => {
- window.hljs = 'test-object';
+ hljsStub = {
+ configure: sinon.stub(),
+ };
+ window.hljs = hljsStub;
});
teardown(() => {
@@ -101,7 +107,14 @@
element.get().then(firstCallHandler);
flush(() => {
assert.isTrue(firstCallHandler.called);
- assert.isTrue(firstCallHandler.calledWith('test-object'));
+ assert.isTrue(firstCallHandler.calledWith(hljsStub));
+ done();
+ });
+ });
+
+ test('configures hljs', done => {
+ element.get().then(() => {
+ assert.isTrue(window.hljs.configure.calledOnce);
done();
});
});
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.html b/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.html
new file mode 100644
index 0000000..bfbe11a
--- /dev/null
+++ b/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.html
@@ -0,0 +1,48 @@
+<!--
+Copyright (C) 2017 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.
+-->
+
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+
+<link rel="import" href="../../shared/gr-button/gr-button.html">
+<link rel="import" href="../../shared/gr-dropdown/gr-dropdown.html">
+
+<link rel="import" href="../../../styles/shared-styles.html">
+
+<dom-module id="gr-edit-file-controls">
+ <template>
+ <style include="shared-styles">
+ :host {
+ align-items: center;
+ display: flex;
+ justify-content: flex-end;
+ }
+ #edit {
+ margin-right: .5em;
+ text-decoration: none;
+ }
+ </style>
+ <gr-button
+ id="edit"
+ link
+ on-tap="_handleEditTap">Edit</gr-button>
+ <!-- TODO(kaspern): implement more menu. -->
+ <gr-dropdown
+ id="more"
+ hidden
+ link>More</gr-dropdown>
+ </template>
+ <script src="gr-edit-file-controls.js"></script>
+</dom-module>
\ No newline at end of file
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.js b/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.js
new file mode 100644
index 0000000..1c87621
--- /dev/null
+++ b/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.js
@@ -0,0 +1,34 @@
+// Copyright (C) 2017 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.
+(function() {
+ 'use strict';
+
+ Polymer({
+ is: 'gr-edit-file-controls',
+
+ /**
+ * Fired when the edit button is pressed.
+ *
+ * @event edit-tap
+ */
+
+ properties: {
+ filePath: String,
+ },
+
+ _handleEditTap() {
+ this.fire('edit-tap', {path: this.filePath});
+ },
+ });
+})();
\ No newline at end of file
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls_test.html b/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls_test.html
new file mode 100644
index 0000000..250e208
--- /dev/null
+++ b/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls_test.html
@@ -0,0 +1,56 @@
+<!--
+Copyright (C) 2017 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.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-edit-file-controls</title>
+
+<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
+
+<link rel="import" href="gr-edit-file-controls.html">
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+ <template>
+ <gr-edit-file-controls></gr-edit-file-controls>
+ </template>
+</test-fixture>
+
+<script>
+suite('gr-edit-file-controls tests', () => {
+ let element;
+ let sandbox;
+
+ setup(() => {
+ sandbox = sinon.sandbox.create();
+ element = fixture('basic');
+ });
+
+ teardown(() => { sandbox.restore(); });
+
+ test('edit tap emits event', () => {
+ const handler = sandbox.stub();
+ element.addEventListener('edit-tap', handler);
+ element.filePath = 'foo';
+
+ MockInteractions.tap(element.$.edit);
+ assert.isTrue(handler.called);
+ assert.equal(handler.lastCall.args[0].detail.path, 'foo');
+ });
+});
+</script>
\ No newline at end of file
diff --git a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.html b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.html
new file mode 100644
index 0000000..df2ac93
--- /dev/null
+++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.html
@@ -0,0 +1,102 @@
+<!--
+Copyright (C) 2017 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.
+-->
+
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+
+<link rel="import" href="../../../behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html">
+<link rel="import" href="../../../behaviors/gr-path-list-behavior/gr-path-list-behavior.html">
+<link rel="import" href="../../../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html">
+<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
+<link rel="import" href="../../shared/gr-button/gr-button.html">
+<link rel="import" href="../../shared/gr-editable-label/gr-editable-label.html">
+<link rel="import" href="../../shared/gr-fixed-panel/gr-fixed-panel.html">
+<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
+<link rel="import" href="../../../styles/shared-styles.html">
+
+
+<dom-module id="gr-editor-view">
+ <template>
+ <style include="shared-styles">
+ :host {
+ background-color: var(--view-background-color);
+ }
+ gr-fixed-panel {
+ background-color: #fff;
+ border-bottom: 1px #eee solid;
+ z-index: 10;
+ }
+ header,
+ .subHeader {
+ align-items: center;
+ display: flex;
+ justify-content: space-between;
+ padding: .75em var(--default-horizontal-margin);
+ }
+ header gr-editable-label {
+ font-size: 1.2em;
+ font-weight: bold;
+ }
+ .textareaWrapper {
+ margin: var(--default-horizontal-margin);
+ }
+ .textareaWrapper textarea {
+ border: 1px solid #ddd;
+ border-radius: 3px;
+ box-sizing: border-box;
+ font-family: var(--monospace-font-family);
+ min-height: 60vh;
+ resize: none;
+ white-space: pre;
+ width: 100%;
+ }
+ .textareaWrapper textarea:focus {
+ outline: none;
+ }
+ .textareaWrapper .editButtons {
+ display: none;
+ }
+ .rightControls {
+ justify-content: flex-end
+ }
+ </style>
+ <gr-fixed-panel
+ class$="[[_computeContainerClass(_editLoaded)]]"
+ floating-disabled="[[_panelFloatingDisabled]]"
+ keep-on-scroll
+ ready-for-measure="[[!_loading]]">
+ <header>
+ <gr-editable-label
+ label-text="File path"
+ value="[[_path]]"
+ placeholder="File path..."
+ on-changed="_handlePathChanged"></gr-editable-label>
+ <span class="rightControls">
+ <gr-button
+ id="save"
+ disabled$="[[_saveDisabled]]"
+ primary
+ on-tap="_saveEdit">Save</gr-button>
+ <gr-button id="cancel" on-tap="_handleCancelTap">Cancel</gr-button>
+ </span>
+ </header>
+ </gr-fixed-panel>
+ <div class="textareaWrapper">
+ <textarea value="{{_newContent::input}}" id="file"></textarea>
+ </div>
+ <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
+ </template>
+ <script src="gr-editor-view.js"></script>
+</dom-module>
\ No newline at end of file
diff --git a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.js b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.js
new file mode 100644
index 0000000..86594d3
--- /dev/null
+++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.js
@@ -0,0 +1,139 @@
+// Copyright (C) 2017 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.
+(function() {
+ 'use strict';
+
+ Polymer({
+ is: 'gr-editor-view',
+
+ /**
+ * Fired when the title of the page should change.
+ *
+ * @event title-change
+ */
+
+ properties: {
+ /**
+ * URL params passed from the router.
+ */
+ params: {
+ type: Object,
+ observer: '_paramsChanged',
+ },
+
+ _change: Object,
+ _changeEditDetail: Object,
+ _changeNum: String,
+ _loggedIn: Boolean,
+ _path: String,
+ _content: String,
+ _newContent: String,
+ _saveDisabled: {
+ type: Boolean,
+ value: true,
+ computed: '_computeSaveDisabled(_content, _newContent)',
+ },
+ },
+
+ behaviors: [
+ Gerrit.KeyboardShortcutBehavior,
+ Gerrit.PatchSetBehavior,
+ Gerrit.PathListBehavior,
+ ],
+
+ attached() {
+ this._getLoggedIn().then(loggedIn => { this._loggedIn = loggedIn; });
+ },
+
+ _getLoggedIn() {
+ return this.$.restAPI.getLoggedIn();
+ },
+
+ _paramsChanged(value) {
+ if (value.view !== Gerrit.Nav.View.EDIT) { return; }
+
+ this._changeNum = value.changeNum;
+ this._path = value.path;
+
+ // NOTE: This may be called before attachment (e.g. while parentElement is
+ // null). Fire title-change in an async so that, if attachment to the DOM
+ // has been queued, the event can bubble up to the handler in gr-app.
+ this.async(() => {
+ const title = `Editing ${this.computeTruncatedPath(this._path)}`;
+ this.fire('title-change', {title});
+ });
+
+ const promises = [];
+
+ promises.push(this._getChangeDetail(this._changeNum));
+ promises.push(this._getFileContent(this._changeNum, this._path)
+ .then(fileContent => {
+ this._content = fileContent;
+ this._newContent = fileContent;
+ }));
+ return Promise.all(promises);
+ },
+
+ _getChangeDetail(changeNum) {
+ return this.$.restAPI.getDiffChangeDetail(changeNum).then(change => {
+ this._change = change;
+ });
+ },
+
+ _handlePathChanged(e) {
+ const path = e.detail;
+ if (path === this._path) { return Promise.resolve(); }
+ return this.$.restAPI.renameFileInChangeEdit(this._changeNum,
+ this._path, path).then(res => {
+ if (!res.ok) { return; }
+ this._viewEditInChangeView();
+ });
+ },
+
+ _viewEditInChangeView() {
+ Gerrit.Nav.navigateToChange(this._change, this.EDIT_NAME);
+ },
+
+ _getFileContent(changeNum, path) {
+ return this.$.restAPI.getFileInChangeEdit(changeNum, path).then(res => {
+ if (!res.ok) {
+ if (res.status === 404) {
+ // No edits have been made yet.
+ return this.$.restAPI.getFileInChangeEdit(changeNum, path, true)
+ .then(res => res.text);
+ }
+ return '';
+ }
+ return res.text;
+ });
+ },
+
+ _saveEdit() {
+ return this.$.restAPI.saveChangeEdit(this._changeNum, this._path,
+ this._newContent).then(res => {
+ if (!res.ok) { return; }
+ this._viewEditInChangeView();
+ });
+ },
+
+ _computeSaveDisabled(content, newContent) {
+ return content === newContent;
+ },
+
+ _handleCancelTap() {
+ // TODO(kaspern): Add a confirm dialog if there are unsaved changes.
+ this._viewEditInChangeView();
+ },
+ });
+})();
diff --git a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.html b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.html
new file mode 100644
index 0000000..e3e6474
--- /dev/null
+++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.html
@@ -0,0 +1,183 @@
+<!--
+Copyright (C) 2017 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.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-editor-view</title>
+
+<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
+
+<link rel="import" href="gr-editor-view.html">
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+ <template>
+ <gr-editor-view></gr-editor-view>
+ </template>
+</test-fixture>
+
+<script>
+suite('gr-editor-view tests', () => {
+ let element;
+ let sandbox;
+ let savePathStub;
+ let saveFileStub;
+ let changeDetailStub;
+ let navigateStub;
+ const mockParams = {
+ changeNum: '42',
+ path: 'foo/bar.baz',
+ };
+
+ setup(() => {
+ stub('gr-rest-api-interface', {
+ getLoggedIn() { return Promise.resolve(true); },
+ });
+ sandbox = sinon.sandbox.create();
+ element = fixture('basic');
+ savePathStub = sandbox.stub(element.$.restAPI, 'renameFileInChangeEdit');
+ saveFileStub = sandbox.stub(element.$.restAPI, 'saveChangeEdit');
+ changeDetailStub = sandbox.stub(element.$.restAPI, 'getDiffChangeDetail');
+ navigateStub = sandbox.stub(element, '_viewEditInChangeView');
+ });
+
+ teardown(() => { sandbox.restore(); });
+
+ suite('_paramsChanged', () => {
+ test('incorrect view returns immediately', () => {
+ element._paramsChanged(
+ Object.assign({}, mockParams, {view: Gerrit.Nav.View.DIFF}));
+ assert.notOk(element._changeNum);
+ });
+
+ test('good params proceed', () => {
+ changeDetailStub.returns(Promise.resolve({}));
+ const fileStub = sandbox.stub(element, '_getFileContent')
+ .returns(Promise.resolve('text'));
+
+ const promises = element._paramsChanged(
+ Object.assign({}, mockParams, {view: Gerrit.Nav.View.EDIT}));
+
+ flushAsynchronousOperations();
+ assert.equal(element._changeNum, mockParams.changeNum);
+ assert.equal(element._path, mockParams.path);
+ assert.deepEqual(changeDetailStub.lastCall.args[0],
+ mockParams.changeNum);
+ assert.deepEqual(fileStub.lastCall.args,
+ [mockParams.changeNum, mockParams.path]);
+
+ return promises.then(() => {
+ assert.equal(element._content, 'text');
+ assert.equal(element._newContent, 'text');
+ });
+ });
+ });
+
+ test('edit file path', done => {
+ element._changeNum = mockParams.changeNum;
+ element._path = mockParams.path;
+ savePathStub.onFirstCall().returns(Promise.resolve({}));
+ savePathStub.onSecondCall().returns(Promise.resolve({ok: true}));
+
+ // Calling with the same path should not navigate.
+ element._handlePathChanged({detail: mockParams.path}).then(() => {
+ assert.isFalse(savePathStub.called);
+ // !ok response
+ element._handlePathChanged({detail: 'newPath'}).then(() => {
+ assert.isTrue(savePathStub.called);
+ assert.isFalse(navigateStub.called);
+ // ok response
+ element._handlePathChanged({detail: 'newPath'}).then(() => {
+ assert.isTrue(navigateStub.called);
+ done();
+ });
+ });
+ });
+ });
+
+ suite('edit file content', () => {
+ const originalText = 'file text';
+ const newText = 'file text changed';
+
+ setup(() => {
+ element._changeNum = mockParams.changeNum;
+ element._path = mockParams.path;
+ element._content = originalText;
+ element._newContent = originalText;
+ flushAsynchronousOperations();
+ });
+
+ test('initial load', () => {
+ assert.equal(element.$.file.value, originalText);
+ assert.isTrue(element.$.save.hasAttribute('disabled'));
+ });
+
+ test('file modification and save, !ok response', done => {
+ const saveSpy = sandbox.spy(element, '_saveEdit');
+ saveFileStub.returns(Promise.resolve({ok: false}));
+ element._newContent = newText;
+ flushAsynchronousOperations();
+
+ assert.equal(element.$.file.value, newText);
+ assert.isFalse(element.$.save.hasAttribute('disabled'));
+
+ MockInteractions.tap(element.$.save);
+ assert(saveSpy.called);
+ saveSpy.lastCall.returnValue.then(() => {
+ assert.isTrue(saveFileStub.called);
+ assert.deepEqual(saveFileStub.lastCall.args,
+ [mockParams.changeNum, mockParams.path, newText]);
+ assert.isFalse(navigateStub.called);
+ done();
+ });
+ });
+
+ test('file modification and save', done => {
+ const saveSpy = sandbox.spy(element, '_saveEdit');
+ saveFileStub.returns(Promise.resolve({ok: true}));
+ element._newContent = newText;
+ flushAsynchronousOperations();
+
+ assert.equal(element.$.file.value, newText);
+ assert.isFalse(element.$.save.hasAttribute('disabled'));
+
+ MockInteractions.tap(element.$.save);
+ assert.isTrue(saveSpy.called);
+ saveSpy.lastCall.returnValue.then(() => {
+ assert.isTrue(saveFileStub.called);
+ assert.isTrue(navigateStub.called);
+ done();
+ });
+ });
+
+ test('file modification and cancel', () => {
+ const cancelSpy = sandbox.spy(element, '_handleCancelTap');
+ element._newContent = newText;
+ flushAsynchronousOperations();
+
+ assert.equal(element.$.file.value, newText);
+ assert.isFalse(element.$.save.hasAttribute('disabled'));
+
+ MockInteractions.tap(element.$.cancel);
+ assert.isTrue(cancelSpy.called);
+ assert.isFalse(saveFileStub.called);
+ assert.isTrue(navigateStub.called);
+ });
+ });
+});
+</script>
\ No newline at end of file
diff --git a/polygerrit-ui/app/elements/gr-app.html b/polygerrit-ui/app/elements/gr-app.html
index 6f8a4a1..eac4131 100644
--- a/polygerrit-ui/app/elements/gr-app.html
+++ b/polygerrit-ui/app/elements/gr-app.html
@@ -46,6 +46,7 @@
<link rel="import" href="./core/gr-reporting/gr-reporting.html">
<link rel="import" href="./core/gr-router/gr-router.html">
<link rel="import" href="./diff/gr-diff-view/gr-diff-view.html">
+<link rel="import" href="./edit/gr-editor-view/gr-editor-view.html">
<link rel="import" href="./plugins/gr-endpoint-decorator/gr-endpoint-decorator.html">
<link rel="import" href="./plugins/gr-external-style/gr-external-style.html">
<link rel="import" href="./plugins/gr-plugin-host/gr-plugin-host.html">
@@ -151,11 +152,15 @@
view-state="{{_viewState.changeView}}"
back-page="[[_lastSearchPage]]"></gr-change-view>
</template>
- <template is="dom-if" if="[[_showDiffView]]" restamp="true">
- <gr-diff-view
- params="[[params]]"
- change-view-state="{{_viewState.changeView}}"></gr-diff-view>
+ <template is="dom-if" if="[[_showEditorView]]" restamp="true">
+ <gr-editor-view
+ params="[[params]]"></gr-editor-view>
</template>
+ <template is="dom-if" if="[[_showDiffView]]" restamp="true">
+ <gr-diff-view
+ params="[[params]]"
+ change-view-state="{{_viewState.changeView}}"></gr-diff-view>
+ </template>
<template is="dom-if" if="[[_showSettingsView]]" restamp="true">
<gr-settings-view
params="[[params]]"
diff --git a/polygerrit-ui/app/elements/gr-app.js b/polygerrit-ui/app/elements/gr-app.js
index 4a38b85..8da8e16 100644
--- a/polygerrit-ui/app/elements/gr-app.js
+++ b/polygerrit-ui/app/elements/gr-app.js
@@ -56,6 +56,7 @@
_showSettingsView: Boolean,
_showAdminView: Boolean,
_showCLAView: Boolean,
+ _showEditorView: Boolean,
/** @type {?} */
_viewState: Object,
/** @type {?} */
@@ -139,6 +140,7 @@
this.set('_showSettingsView', view === Gerrit.Nav.View.SETTINGS);
this.set('_showAdminView', view === Gerrit.Nav.View.ADMIN);
this.set('_showCLAView', view === Gerrit.Nav.View.AGREEMENTS);
+ this.set('_showEditorView', view === Gerrit.Nav.View.EDIT);
if (this.params.justRegistered) {
this.$.registration.open();
}
diff --git a/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper.js b/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper.js
index e750c07..18eeb87 100644
--- a/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper.js
+++ b/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper.js
@@ -20,6 +20,17 @@
}
/**
+ * Add a callback to arbitrary event.
+ * The callback may return false to prevent event bubbling.
+ * @param {string} event Event name
+ * @param {function(Event):boolean} callback
+ * @return {function()} Unsubscribe function.
+ */
+ GrEventHelper.prototype.on = function(event, callback) {
+ return this._listen(this.element, callback, {event});
+ };
+
+ /**
* Add a callback to element click or touch.
* The callback may return false to prevent event bubbling.
* @param {function(Event):boolean} callback
@@ -43,6 +54,7 @@
GrEventHelper.prototype._listen = function(container, callback, opt_options) {
const capture = opt_options && opt_options.capture;
+ const event = opt_options && opt_options.event || 'tap';
const handler = e => {
if (e.path.indexOf(this.element) !== -1) {
let mayContinue = true;
@@ -58,9 +70,9 @@
}
}
};
- container.addEventListener('tap', handler, capture);
+ container.addEventListener(event, handler, capture);
const unsubscribe = () =>
- container.removeEventListener('tap', handler, capture);
+ container.removeEventListener(event, handler, capture);
this._unsubscribers.push(unsubscribe);
return unsubscribe;
};
diff --git a/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper_test.html b/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper_test.html
index 9d42851..43c42a9 100644
--- a/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper_test.html
@@ -92,5 +92,12 @@
flushAsynchronousOperations();
assert.isFalse(tapStub.called);
});
+
+ test('on()', done => {
+ instance.on('foo', () => {
+ done();
+ });
+ element.fire('foo');
+ });
});
</script>
diff --git a/polygerrit-ui/app/elements/plugins/gr-project-api/gr-plugin-project-command.html b/polygerrit-ui/app/elements/plugins/gr-project-api/gr-plugin-project-command.html
new file mode 100644
index 0000000..87d11ad
--- /dev/null
+++ b/polygerrit-ui/app/elements/plugins/gr-project-api/gr-plugin-project-command.html
@@ -0,0 +1,34 @@
+<!--
+Copyright (C) 2017 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.
+-->
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../admin/gr-project-command/gr-project-command.html">
+
+<dom-module id="gr-plugin-project-command">
+ <template>
+ <gr-project-command title="[[title]]">
+ </gr-project-command>
+ </template>
+ <script>
+ Polymer({
+ is: 'gr-plugin-project-command',
+ properties: {
+ title: String,
+ projectName: String,
+ config: Object,
+ },
+ });
+ </script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/plugins/gr-project-api/gr-project-api.html b/polygerrit-ui/app/elements/plugins/gr-project-api/gr-project-api.html
new file mode 100644
index 0000000..0106533
--- /dev/null
+++ b/polygerrit-ui/app/elements/plugins/gr-project-api/gr-project-api.html
@@ -0,0 +1,23 @@
+<!--
+Copyright (C) 2017 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.
+-->
+
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../shared/gr-js-api-interface/gr-js-api-interface.html">
+<link rel="import" href="gr-plugin-project-command.html">
+
+<dom-module id="gr-project-api">
+ <script src="gr-project-api.js"></script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/plugins/gr-project-api/gr-project-api.js b/polygerrit-ui/app/elements/plugins/gr-project-api/gr-project-api.js
new file mode 100644
index 0000000..a173edd
--- /dev/null
+++ b/polygerrit-ui/app/elements/plugins/gr-project-api/gr-project-api.js
@@ -0,0 +1,60 @@
+// Copyright (C) 2017 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.
+(function(window) {
+ 'use strict';
+
+ // Prevent redefinition.
+ if (window.GrProjectApi) { return; }
+
+ function GrProjectApi(plugin) {
+ this._hook = null;
+ this.plugin = plugin;
+ }
+
+ GrProjectApi.prototype._createHook = function(title) {
+ this._hook = this.plugin.hook('project-command').onAttached(element => {
+ const pluginCommand =
+ document.createElement('gr-plugin-project-command');
+ pluginCommand.title = title;
+ element.appendChild(pluginCommand);
+ });
+ };
+
+ GrProjectApi.prototype.createCommand = function(title, callback) {
+ if (this._hook) {
+ console.warn('Already set up.');
+ return this._hook;
+ }
+ this._createHook(title);
+ this._hook.onAttached(element => {
+ if (callback(element.projectName, element.config) === false) {
+ element.hidden = true;
+ }
+ });
+ return this;
+ };
+
+ GrProjectApi.prototype.onTap = function(callback) {
+ if (!this._hook) {
+ console.warn('Call createCommand first.');
+ return this;
+ }
+ this._hook.onAttached(element => {
+ this.plugin.eventHelper(element).on('command-tap', callback);
+ });
+ return this;
+ };
+
+ window.GrProjectApi = GrProjectApi;
+})(window);
diff --git a/polygerrit-ui/app/elements/plugins/gr-project-api/gr-project-api_test.html b/polygerrit-ui/app/elements/plugins/gr-project-api/gr-project-api_test.html
new file mode 100644
index 0000000..b0719f5
--- /dev/null
+++ b/polygerrit-ui/app/elements/plugins/gr-project-api/gr-project-api_test.html
@@ -0,0 +1,80 @@
+<!DOCTYPE html>
+<!--
+Copyright (C) 2017 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.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-project-api</title>
+
+<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
+<link rel="import" href="../gr-endpoint-decorator/gr-endpoint-decorator.html">
+<link rel="import" href="gr-project-api.html">
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+ <template>
+ <gr-endpoint-decorator name="project-command">
+ </gr-endpoint-decorator>
+ </template>
+</test-fixture>
+
+<script>
+ suite('gr-project-api tests', () => {
+ let sandbox;
+ let projectApi;
+
+ setup(() => {
+ sandbox = sinon.sandbox.create();
+ let plugin;
+ Gerrit.install(p => { plugin = p; }, '0.1',
+ 'http://test.com/plugins/testplugin/static/test.js');
+ sandbox.stub(Gerrit, '_arePluginsLoaded').returns(true);
+ projectApi = plugin.project();
+ });
+
+ teardown(() => {
+ projectApi = null;
+ sandbox.restore();
+ });
+
+ test('exists', () => {
+ assert.isOk(projectApi);
+ });
+
+ test('works', done => {
+ const attachedStub = sandbox.stub();
+ const tapStub = sandbox.stub();
+ projectApi
+ .createCommand('foo', attachedStub)
+ .onTap(tapStub);
+ const element = fixture('basic');
+ flush(() => {
+ assert.isTrue(attachedStub.called);
+ const pluginCommand = element.$$('gr-plugin-project-command');
+ assert.isOk(pluginCommand);
+ const command = pluginCommand.$$('gr-project-command');
+ assert.isOk(command);
+ assert.equal(command.title, 'foo');
+ assert.isFalse(tapStub.called);
+ MockInteractions.tap(command.$$('gr-button'));
+ assert.isTrue(tapStub.called);
+ done();
+ });
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.html b/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.html
index dfaa6eb..c665df4 100644
--- a/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.html
+++ b/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.html
@@ -44,7 +44,9 @@
<template is="dom-repeat" items="[[_agreements]]">
<tr>
<td class="nameColumn">
- <a href$="[[getUrlBase(item.url)]]">[[item.name]]</a>
+ <a href$="[[getUrlBase(item.url)]]" rel="external">
+ [[item.name]]
+ </a>
</td>
<td class="descriptionColumn">[[item.description]]</td>
</tr>
diff --git a/polygerrit-ui/app/elements/shared/gr-button/gr-button.html b/polygerrit-ui/app/elements/shared/gr-button/gr-button.html
index c0b17af..9be6497 100644
--- a/polygerrit-ui/app/elements/shared/gr-button/gr-button.html
+++ b/polygerrit-ui/app/elements/shared/gr-button/gr-button.html
@@ -116,7 +116,7 @@
color: #aaa;
}
</style>
- <paper-button raised="[[!link]]" disabled="[[disabled]]">
+ <paper-button raised="[[!link]]" disabled="[[disabled]]" tabindex="-1">
<content></content>
<i class="downArrow"></i>
</paper-button>
diff --git a/polygerrit-ui/app/elements/shared/gr-confirm-dialog/gr-confirm-dialog.html b/polygerrit-ui/app/elements/shared/gr-confirm-dialog/gr-confirm-dialog.html
index 27c0355..575353d 100644
--- a/polygerrit-ui/app/elements/shared/gr-confirm-dialog/gr-confirm-dialog.html
+++ b/polygerrit-ui/app/elements/shared/gr-confirm-dialog/gr-confirm-dialog.html
@@ -45,20 +45,23 @@
footer {
padding: .5em .65em;
}
+ gr-button {
+ margin-left: 1em;
+ }
footer {
display: flex;
flex-shrink: 0;
- justify-content: space-between;
+ justify-content: flex-end;
}
</style>
<div class="container">
<header><content select=".header"></content></header>
<main><content select=".main"></content></main>
<footer>
- <gr-button primary on-tap="_handleConfirmTap" disabled="[[disabled]]">
+ <gr-button link on-tap="_handleCancelTap">Cancel</gr-button>
+ <gr-button link primary on-tap="_handleConfirmTap" disabled="[[disabled]]">
[[confirmLabel]]
</gr-button>
- <gr-button on-tap="_handleCancelTap">Cancel</gr-button>
</footer>
</div>
</template>
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.html b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.html
index 91c2def..0916a89 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.html
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.html
@@ -72,10 +72,6 @@
background-color: #f2f2f2;
}
}
- gr-button {
- --gr-button-arrow-color: var(--color-link);
- --gr-button-arrow-hover-color: var(--color-link-hover);
- }
paper-item:not(:last-of-type) {
border-bottom: 1px solid #ddd;
}
@@ -95,6 +91,12 @@
flex-direction: row;
width: 100%;
}
+ gr-button {
+ --gr-button: {
+ @apply --trigger-style;
+ }
+ --gr-button-hover-color: var(--trigger-hover-color);
+ }
gr-date-formatter {
color: rgba(0,0,0,.54);
margin-left: 2em;
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.html b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.html
index 6eb7c8d..ef78f3a 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.html
+++ b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.html
@@ -92,8 +92,8 @@
label="[[labelText]]"
value="{{_inputText}}"></paper-input>
<div class="buttons">
- <gr-button id="cancelBtn" on-tap="_cancel">cancel</gr-button>
- <gr-button id="saveBtn" on-tap="_save">save</gr-button>
+ <gr-button link id="cancelBtn" on-tap="_cancel">cancel</gr-button>
+ <gr-button link id="saveBtn" on-tap="_save">save</gr-button>
</div>
</div>
</div>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.html
index 4133600..c5b0441 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.html
@@ -21,6 +21,7 @@
<link rel="import" href="../../plugins/gr-dom-hooks/gr-dom-hooks.html">
<link rel="import" href="../../plugins/gr-event-helper/gr-event-helper.html">
<link rel="import" href="../../plugins/gr-popup-interface/gr-popup-interface.html">
+<link rel="import" href="../../plugins/gr-project-api/gr-project-api.html">
<link rel="import" href="../../plugins/gr-theme-api/gr-theme-api.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../gr-rest-api-interface/gr-rest-api-interface.html">
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
index 467e012..4e6b8cf 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
@@ -198,6 +198,10 @@
return new GrThemeApi(this);
};
+ Plugin.prototype.project = function() {
+ return new GrProjectApi(this);
+ };
+
Plugin.prototype.attributeHelper = function(element) {
return new GrAttributeHelper(element);
};
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
index 5153fb0..c206b20 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
@@ -765,6 +765,11 @@
// Response may be an array of changes OR an array of arrays of
// changes.
if (opt_query instanceof Array) {
+ // Normalize the response to look like a multi-query response
+ // when there is only one query.
+ if (opt_query.length === 1) {
+ response = [response];
+ }
for (const arr of response) {
iterateOverChanges(arr);
}
@@ -1249,9 +1254,25 @@
.then(response => this.getResponseObject(response));
},
- getFileInChangeEdit(changeNum, path) {
+ /**
+ * Gets a file in a change edit.
+ * @param {number|string} changeNum
+ * @param {string} path
+ * @param {boolean=} opt_base If specified, file contents come from change
+ * edit's base patchset.
+ */
+ getFileInChangeEdit(changeNum, path, opt_base) {
const e = '/edit/' + encodeURIComponent(path);
- return this.getChangeURLAndSend(changeNum, 'GET', null, e);
+ let payload = null;
+ if (opt_base) { payload = {base: true}; }
+ return this.getChangeURLAndSend(changeNum, 'GET', null, e, payload)
+ .then(res => {
+ if (!res.ok) { return res; }
+ return res.text().then(text => {
+ res.text = atob(text);
+ return res;
+ });
+ });
},
rebaseChangeEdit(changeNum) {
@@ -1279,7 +1300,8 @@
saveChangeEdit(changeNum, path, contents) {
const e = '/edit/' + encodeURIComponent(path);
- return this.getChangeURLAndSend(changeNum, 'PUT', null, e, contents);
+ return this.getChangeURLAndSend(changeNum, 'PUT', null, e, contents, null,
+ null, 'text/plain');
},
// Deprecated, prefer to use putChangeCommitMessage instead.
@@ -1845,7 +1867,7 @@
* @param {?string} endpoint gets passed as null.
* @param {?Object|number|string=} opt_payload gets passed as null, string,
* Object, or number.
- * @param {function(?Response, string=)=} opt_errFn
+ * @param {?function(?Response, string=)=} opt_errFn
* @param {?=} opt_ctx
* @param {?=} opt_contentType
* @return {!Promise<!Object>}
diff --git a/polygerrit-ui/app/samples/project-command.html b/polygerrit-ui/app/samples/project-command.html
new file mode 100644
index 0000000..8131a02
--- /dev/null
+++ b/polygerrit-ui/app/samples/project-command.html
@@ -0,0 +1,42 @@
+<dom-module id="sample-project-command">
+ <script>
+ Gerrit.install(plugin => {
+ // High-level API
+ plugin.project()
+ .createCommand('Bork', (projectName, projectConfig) => {
+ if (projectName !== 'All-Projects') {
+ return false;
+ }
+ }).onTap(() => {
+ alert('Bork, bork!');
+ });
+
+ // Low-level API
+ plugin.registerCustomComponent(
+ 'project-command', 'project-command-low');
+ });
+ </script>
+</dom-module>
+
+<!-- Low-level custom component for project command. -->
+<dom-module id="project-command-low">
+ <template>
+ <gr-project-command
+ title="Low-level bork"
+ on-command-tap="_handleCommandTap">
+ </gr-project-command>
+ </template>
+ <script>
+ Polymer({
+ is: 'project-command-low',
+ attached() {
+ console.log(this.projectName);
+ console.log(this.config);
+ this.hidden = this.projectName !== 'All-Projects';
+ },
+ _handleCommandTap() {
+ alert('(softly) bork, bork.');
+ },
+ });
+ </script>
+</dom-module>
diff --git a/polygerrit-ui/app/test/index.html b/polygerrit-ui/app/test/index.html
index 7080eb7..b0310c6 100644
--- a/polygerrit-ui/app/test/index.html
+++ b/polygerrit-ui/app/test/index.html
@@ -44,6 +44,7 @@
'admin/gr-permission/gr-permission_test.html',
'admin/gr-plugin-list/gr-plugin-list_test.html',
'admin/gr-project-access/gr-project-access_test.html',
+ 'admin/gr-project-command/gr-project-command_test.html',
'admin/gr-project-commands/gr-project-commands_test.html',
'admin/gr-project-detail-list/gr-project-detail-list_test.html',
'admin/gr-project-list/gr-project-list_test.html',
@@ -102,10 +103,13 @@
'diff/gr-selection-action-box/gr-selection-action-box_test.html',
'diff/gr-syntax-layer/gr-syntax-layer_test.html',
'diff/gr-syntax-lib-loader/gr-syntax-lib-loader_test.html',
+ 'edit/gr-edit-file-controls/gr-edit-file-controls_test.html',
+ 'edit/gr-editor-view/gr-editor-view_test.html',
'plugins/gr-attribute-helper/gr-attribute-helper_test.html',
'plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.html',
'plugins/gr-event-helper/gr-event-helper_test.html',
'plugins/gr-external-style/gr-external-style_test.html',
+ 'plugins/gr-project-api/gr-project-api_test.html',
'plugins/gr-plugin-host/gr-plugin-host_test.html',
'plugins/gr-popup-interface/gr-plugin-popup_test.html',
'plugins/gr-popup-interface/gr-popup-interface_test.html',
diff --git a/version.bzl b/version.bzl
index 3be5283..62d841f 100644
--- a/version.bzl
+++ b/version.bzl
@@ -2,4 +2,4 @@
# Used by :api_install and :api_deploy targets
# when talking to the destination repository.
#
-GERRIT_VERSION = "2.15-rc0"
+GERRIT_VERSION = "2.16-SNAPSHOT"