Merge "ProjectAccess: increase font sizes for project rights"
diff --git a/Documentation/dev-contributing.txt b/Documentation/dev-contributing.txt
index 6efabff..dd230d3 100644
--- a/Documentation/dev-contributing.txt
+++ b/Documentation/dev-contributing.txt
@@ -164,7 +164,8 @@
contributors may also like to open several editors side by
side while editing new changes.
* Use 2 spaces for indent (no tabs)
- * Use brackets in all ifs, spaces before/after if parens.
+ * Use braces in all if/else/for/do/while/catch blocks, spaces before/after
+ if/for/while/catch parens.
* Use /** */ style Javadocs for variables.
Additionally, you will notice that most of the newline spacing
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index e32e30d..2870cf8 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -415,6 +415,10 @@
+
Garbage collection ran on a project
+* `com.google.gerrit.server.extensions.events.ChangeIndexedListener`:
++
+Update of the secondary index
+
[[stream-events]]
== Sending Events to the Events Stream
@@ -433,6 +437,13 @@
`com.google.gerrit.server.events.EventDeserializer` class requires
that the event be registered in EventTypes.
+== Modifying the Stream Event Flow
+
+It is possible to modify the stream event flow from plugins by registering
+an `com.google.gerrit.server.events.EventDispatcher`. A plugin may register
+a Dispatcher class to replace the internal Dispatcher. EventDispatcher is
+a DynamicItem, so Gerrit may only have one copy.
+
[[validation]]
== Validation Listeners
diff --git a/Documentation/dev-release.txt b/Documentation/dev-release.txt
index 3157214..2a6cb1c 100644
--- a/Documentation/dev-release.txt
+++ b/Documentation/dev-release.txt
@@ -165,6 +165,15 @@
* Sanity check WAR
* Test the new Gerrit version
+* Verify plugin versions
++
+Sometimes `buck` doesn't rebuild plugins after they are tagged, and the
+versions don't reflect the tag. Verify the versions:
++
+----
+ java -jar ./buck-out/gen/release/release.war init --list-plugins
+----
+
[[publish-gerrit]]
=== Publish the Gerrit Release
@@ -336,16 +345,11 @@
make -C ReleaseNotes
----
-* Build the documentation:
-+
-----
- buck build docs
-----
+* Extract the documentation files from the zip file generated during
+the release build: `buck-out/gen/Documentation/html/html.zip`.
-* Extract the documentation html files from the generated zip file
-`buck-out/gen/Documentation/searchfree.zip`.
-
-* Upload the html files manually via web browser to the
+* Upload the files manually via web browser to the appropriate folder
+in the
link:https://console.developers.google.com/project/164060093628/storage/gerrit-documentation/[
gerrit-documentation] storage bucket.
@@ -383,13 +387,12 @@
** A link to the release and the release notes (if a final release)
** A link to the docs
** Describe the type of release (stable, bug fix, RC)
-
-* Add an entry to the `NEWS` section of the main Gerrit project web page
-** Go to: http://code.google.com/p/gerrit/admin
-** Add entry like:
-----
- * Jun 14, 2012 - Gerrit 2.4.1 [https://groups.google.com/d/topic/repo-discuss/jHg43gixqzs/discussion Released]
-----
+** Hash values (SHA1, SHA256, MD5) for the release WAR file.
++
+The SHA1 and MD5 can be taken from the artifact page on Sonatype. The
+SHA256 can be generated with
+`openssl sha -sha256 buck-out/gen/release/release.war` or an equivalent
+command.
* Update the new discussion group announcement to be sticky
** Go to: http://groups.google.com/group/repo-discuss/topics
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 446c3c6..5603d36 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -1050,8 +1050,7 @@
Submits a change.
The request body only needs to include a link:#submit-input[
-SubmitInput] entity if the request should wait for the merge to
-complete.
+SubmitInput] entity if submitting on behalf of another user.
.Request
----
@@ -1059,7 +1058,7 @@
Content-Type: application/json; charset=UTF-8
{
- "wait_for_merge": true
+ "on_behalf_of": 1001439
}
----
@@ -4780,10 +4779,6 @@
|Field Name ||Description
|`status` ||
The status of the change after submitting is `MERGED`.
-+
-As `wait_for_merge` in the link:#submit-input[SubmitInput] is deprecated and
-the request always waits for the merge to be completed, you can expect
-`MERGED` to be returned here.
|`on_behalf_of`|optional|
The link:rest-api-accounts.html#account-id[\{account-id\}] of the user on
whose behalf the action should be done. To use this option the caller must
@@ -4808,8 +4803,6 @@
API]. Using this option requires
link:access-control.html#category_submit_on_behalf_of[Submit (On Behalf Of)]
permission on the branch.
-|`wait_for_merge`|Deprecated, always `true`|
-Whether the request should wait for the merge to complete.
|===========================
[[submit-record]]
diff --git a/Documentation/user-review-ui.txt b/Documentation/user-review-ui.txt
index 57dca8c..8553634 100644
--- a/Documentation/user-review-ui.txt
+++ b/Documentation/user-review-ui.txt
@@ -186,10 +186,6 @@
The `Submit` button is available if the change is submittable and
the link:access-control.html#category_submit[Submit] access right is
assigned.
-+
-It is also possible to submit changes that have merge conflicts. This
-allows to do the conflict resolution for a change series in a single
-merge commit and submit the changes in reverse order.
** [[abandon]]`Abandon`:
+
diff --git a/Documentation/user-search.txt b/Documentation/user-search.txt
index eda4b5d..b2b3614 100644
--- a/Documentation/user-search.txt
+++ b/Documentation/user-search.txt
@@ -299,7 +299,7 @@
+
True if the change is either merged or abandoned.
-is:submitted, is:merged, is:abandoned::
+is:merged, is:abandoned::
+
Same as <<status,status:'STATE'>>.
@@ -320,10 +320,6 @@
more recently than the last update (comment or patch set) from the
change owner.
-status:submitted::
-+
-Change has been submitted, but is waiting for a dependency.
-
status:closed::
+
True if the change is either 'merged' or 'abandoned'.
diff --git a/ReleaseNotes/ReleaseNotes-2.11.8.txt b/ReleaseNotes/ReleaseNotes-2.11.8.txt
new file mode 100644
index 0000000..0f9dc21
--- /dev/null
+++ b/ReleaseNotes/ReleaseNotes-2.11.8.txt
@@ -0,0 +1,43 @@
+Release notes for Gerrit 2.11.8
+===============================
+
+Gerrit 2.11.8 is now available:
+
+link:https://gerrit-releases.storage.googleapis.com/gerrit-2.11.8.war[
+https://gerrit-releases.storage.googleapis.com/gerrit-2.11.8.war]
+
+There are no schema changes from link:ReleaseNotes-2.11.7.html[2.11.7].
+
+Bug Fixes
+---------
+
+* Upgrade Apache commons-collections to version 3.2.2.
++
+Includes a fix for a link:https://issues.apache.org/jira/browse/COLLECTIONS-580[
+remote code execution exploit].
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=1207[Issue 1207]:
+Fix keyboard shortcuts for non-US keyboards on side-by-side diff screen.
++
+The forward/backward navigation keys `[` and `]` only worked on keyboards where
+these characters could be typed without using any modifier key (like CTRL, ALT,
+etc.).
++
+Note that the problem still exists on the unified diff screen.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=3919[Issue 3919]:
+Explicitly set parent project to 'All-Projects' when a project is created
+without giving the parent.
+
+* Don't add message twice on abandon or restore via ssh review command.
++
+When abandoning or reviewing a change via the ssh `review` command, and
+providing a message with the `--message` option, the message was added to
+the change twice.
+
+* Clear the input box after cancelling add reviewer action.
++
+When the action was cancelled, the content of the input box was still
+there when opening it again.
+
+* Fix internal server error when aborting ssh command.
diff --git a/ReleaseNotes/ReleaseNotes-2.12.1.txt b/ReleaseNotes/ReleaseNotes-2.12.1.txt
index 379572e..f49de7d 100644
--- a/ReleaseNotes/ReleaseNotes-2.12.1.txt
+++ b/ReleaseNotes/ReleaseNotes-2.12.1.txt
@@ -11,31 +11,48 @@
link:ReleaseNotes-2.11.7.html[Gerrit 2.11.7]. These bug fixes are *not*
listed in these release notes.
+Schema Upgrade
+--------------
+
*WARNING:* This version includes a manual schema upgrade when upgrading
from 2.12.
-+
-If you have already upgraded to 2.12, you need to issue this SQL statement
-manually (e.g. using the `gerrit gsql` SSH command or the `gqsl` site
-program):
-+
+
+When upgrading a site that is already running version 2.12, the `patch_sets`
+table must be manually migrated using the `gerrit gsql` SSH command or the
+`gqsl` site program.
+
+For the default H2 database, execute the command:
+
+----
alter table patch_sets modify push_certficate clob;
-+
-Or with this command if the site is configured to use PostgreSQL:
-+
+----
+
+For MySQL, execute the command:
+
+----
+ alter table patch_sets modify push_certficate text;
+----
+
+For PostgreSQL, execute the command:
+
+----
alter table patch_sets alter column push_certficate type text;
-+
+----
+
+For other database types, execute the appropriate equivalent command.
+
Note that the misspelled `push_certficate` is the actual name of the
column.
-+
-If you are upgrading from a version earlier than 2.12, this manual step is
-not necessary and should be omitted.
+
+When upgrading from a version earlier than 2.12, this manual step is not
+necessary and should be omitted.
Bug Fixes
---------
General
-^^^^^^^
+~~~~~~~
* Fix column type for signed push certificates.
+
@@ -69,7 +86,7 @@
'Internal Server Error' that was difficult to track down. Now an error is
raised earlier which will help administrators to find the root cause.
-* https://code.google.com/p/gerrit/issues/detail?id=3743[Issue 3743]:
+* link:https://code.google.com/p/gerrit/issues/detail?id=3743[Issue 3743]:
Use submitter identity as committer when using 'Rebase if Necessary' merge
strategy.
+
@@ -125,8 +142,29 @@
Explicitly set parent project to 'All-Projects' when a project is created
without giving the parent.
+* link:https://code.google.com/p/gerrit/issues/detail?id=3948[Issue 3948]:
+Fix submit of project parent updates on `refs/meta/config`.
++
+When submitting a change on `refs/meta/config` to update a project's parent,
+the error 'The change must be submitted by a Gerrit administrator' was being
+displayed even when the submitter was an admin. The submit was successful
+when clicking 'Submit' a second time.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=3811[Issue 3811]:
+Fix submittability of merge commits that resolve merge conflicts.
++
+If a series of changes contained a change that conflicted with the destination
+branch, but the conflict was solved by a merge commit at the tip of the
+series, the series was not submittable.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=3883[Issue 3883]:
+Respect the `core.commentchar` setting from `.gitconfig` in `commit-msg` hook.
+
UI
-^^
+~~
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=3894[Issue 3894]:
+Fix display of 'Related changes' after change is rebased in web UI:
* link:https://code.google.com/p/gerrit/issues/detail?id=3071[Issue 3071]:
Fix display of submodule differences in side-by-side view.
@@ -153,22 +191,11 @@
+
Note that the problem still exists on the unified diff screen.
+* Improve tooltip on 'Submit' button when 'Submit whole topic' is enabled
+and the topic can't be submitted due to some changes not being ready.
+
Plugins
-^^^^^^^
-
-* Allow plugins to get the caller in merge validation requests.
-+
-Plugins that implement the `MergeValidationListener` interface now get the
-caller (the user who initiated the merge) in the `onPreMerge` method.
-+
-Existing plugins that implement this interface must be adapted to the new
-method signature.
-
-* link:https://code.google.com/p/gerrit/issues/detail?id=3741[Issue 3741]:
-Fix handling of merge validation exceptions emitted by plugins.
-+
-If a plugin raised an exception, it was reported to the user as 'Change is
-new', rather than 'Missing dependency'.
+~~~~~~~
* link:https://code.google.com/p/gerrit/issues/detail?id=3821[Issue 3821]:
Fix repeated reloading of plugins when running on OpenJDK 8.
@@ -178,11 +205,31 @@
comparing the plugin's JAR file timestamp, resulting in the plugin being
reloaded every minute.
+* link:https://code.google.com/p/gerrit/issues/detail?id=3741[Issue 3741]:
+Fix handling of merge validation exceptions emitted by plugins.
++
+If a plugin raised an exception, it was reported to the user as 'Change is
+new', rather than 'Missing dependency'.
+
+* Allow plugins to get the caller in merge validation requests.
++
+Plugins that implement the `MergeValidationListener` interface now get the
+caller (the user who initiated the merge) in the `onPreMerge` method.
++
+Existing plugins that implement this interface must be adapted to the new
+method signature.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=3892[Issue 3892]:
+Allow plugins to suggest reviewers based on either change or project
+resources.
+
Documentation
-^^^^^^^^^^^^^
+~~~~~~~~~~~~~
* Update documentation of `commentlink` to reflect changed search URL.
+* Add missing documentation of valid `database.type` values.
+
Upgrades
--------
diff --git a/ReleaseNotes/ReleaseNotes-2.12.2.txt b/ReleaseNotes/ReleaseNotes-2.12.2.txt
new file mode 100644
index 0000000..5582bf9
--- /dev/null
+++ b/ReleaseNotes/ReleaseNotes-2.12.2.txt
@@ -0,0 +1,38 @@
+Release notes for Gerrit 2.12.2
+===============================
+
+Gerrit 2.12.2 is now available:
+
+link:https://gerrit-releases.storage.googleapis.com/gerrit-2.12.2.war[
+https://gerrit-releases.storage.googleapis.com/gerrit-2.12.2.war]
+
+There are no schema changes from link:ReleaseNotes-2.12.1.html[2.12.1].
+
+Bug Fixes
+---------
+
+* Upgrade Apache commons-collections to version 3.2.2.
++
+Includes a fix for a link:https://issues.apache.org/jira/browse/COLLECTIONS-580[
+remote code execution exploit].
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=3919[Issue 3919]:
+Explicitly set parent project to 'All-Projects' when a project is created
+without giving the parent.
+
+* Don't add message twice on abandon or restore via ssh review command.
++
+When abandoning or reviewing a change via the ssh `review` command, and
+providing a message with the `--message` option, the message was added to
+the change twice.
+
+* Clear the input box after cancelling add reviewer action.
++
+When the action was cancelled, the content of the input box was still
+there when opening it again.
+
+* Fix internal server error when aborting ssh command.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=3969[Issue 3969]:
+Fix internal server error when submitting a change with 'Rebase If Necessary'
+strategy.
diff --git a/ReleaseNotes/index.txt b/ReleaseNotes/index.txt
index af33d7a..4cab151 100644
--- a/ReleaseNotes/index.txt
+++ b/ReleaseNotes/index.txt
@@ -9,12 +9,14 @@
[[2_12]]
Version 2.12.x
--------------
+* link:ReleaseNotes-2.12.2.html[2.12.2]
* link:ReleaseNotes-2.12.1.html[2.12.1]
* link:ReleaseNotes-2.12.html[2.12]
[[2_11]]
Version 2.11.x
--------------
+* link:ReleaseNotes-2.11.8.html[2.11.8]
* link:ReleaseNotes-2.11.7.html[2.11.7]
* link:ReleaseNotes-2.11.6.html[2.11.6]
* link:ReleaseNotes-2.11.5.html[2.11.5]
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index c1b2eb9..1172d16 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -69,6 +69,7 @@
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gerrit.testutil.ConfigSuite;
+import com.google.gerrit.testutil.FakeEmailSender;
import com.google.gerrit.testutil.TempFileUtil;
import com.google.gerrit.testutil.TestNotesMigration;
import com.google.gson.Gson;
@@ -187,6 +188,9 @@
@Inject
protected Revisions revisions;
+ @Inject
+ protected FakeEmailSender sender;
+
protected TestRepository<InMemoryRepository> testRepo;
protected GerritServer server;
protected TestAccount admin;
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/TestAccount.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/TestAccount.java
index 4a6d22d..7f08b6f 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/TestAccount.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/TestAccount.java
@@ -17,6 +17,7 @@
import com.google.common.base.Function;
import com.google.common.collect.FluentIterable;
import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.server.mail.Address;
import com.jcraft.jsch.KeyPair;
@@ -58,6 +59,7 @@
public final Account.Id id;
public final String username;
public final String email;
+ public final Address emailAddress;
public final String fullName;
public final KeyPair sshKey;
public final String httpPassword;
@@ -67,6 +69,7 @@
this.id = id;
this.username = username;
this.email = email;
+ this.emailAddress = new Address(fullName, email);
this.fullName = fullName;
this.sshKey = sshKey;
this.httpPassword = httpPassword;
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
index a5274fa..f2886e1 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -67,6 +67,7 @@
import com.google.gerrit.server.notedb.ChangeNoteUtil;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.Util;
+import com.google.gerrit.testutil.FakeEmailSender.Message;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.PersonIdent;
@@ -512,6 +513,14 @@
.id(r.getChangeId())
.addReviewer(in);
+ List<Message> messages = sender.getMessages();
+ assertThat(messages).hasSize(1);
+ Message m = messages.get(0);
+ assertThat(m.rcpt()).containsExactly(user.emailAddress);
+ assertThat(m.body()).contains("Hello " + user.fullName + ",\n");
+ assertThat(m.body()).contains("I'd like you to do a code review.");
+ assertThat(m.body()).contains("Change subject: " + PushOneCommit.SUBJECT + "\n");
+
ChangeInfo c = gApi.changes()
.id(r.getChangeId())
.get();
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java
index 7add9a2..def8317 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java
@@ -19,6 +19,7 @@
import static com.google.gerrit.acceptance.PushOneCommit.FILE_NAME;
import static com.google.gerrit.acceptance.PushOneCommit.PATCH;
import static com.google.gerrit.acceptance.PushOneCommit.SUBJECT;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.eclipse.jgit.lib.Constants.HEAD;
import static org.junit.Assert.fail;
@@ -29,6 +30,7 @@
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.common.data.Permission;
import com.google.gerrit.extensions.api.changes.ChangeApi;
import com.google.gerrit.extensions.api.changes.CherryPickInput;
import com.google.gerrit.extensions.api.changes.DraftApi;
@@ -38,6 +40,7 @@
import com.google.gerrit.extensions.api.changes.RevisionApi;
import com.google.gerrit.extensions.api.changes.SubmitInput;
import com.google.gerrit.extensions.api.projects.BranchInput;
+import com.google.gerrit.extensions.client.ChangeStatus;
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ChangeMessageInfo;
@@ -49,9 +52,13 @@
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.ETagView;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.server.change.GetRevisionActions;
import com.google.gerrit.server.change.RevisionResource;
+import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.group.SystemGroupBackend;
+import com.google.gerrit.server.project.Util;
import com.google.inject.Inject;
import org.eclipse.jgit.lib.ObjectId;
@@ -119,18 +126,68 @@
@Test
public void submit() throws Exception {
PushOneCommit.Result r = createChange();
+ String changeId = project.get() + "~master~" + r.getChangeId();
gApi.changes()
- .id(project.get() + "~master~" + r.getChangeId())
+ .id(changeId)
.current()
.review(ReviewInput.approve());
gApi.changes()
- .id(project.get() + "~master~" + r.getChangeId())
+ .id(changeId)
.current()
.submit();
+ assertThat(gApi.changes().id(changeId).get().status)
+ .isEqualTo(ChangeStatus.MERGED);
}
- @Test(expected = AuthException.class)
+ private void allowSubmitOnBehalfOf() throws Exception {
+ ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
+ Util.allow(cfg,
+ Permission.SUBMIT_AS,
+ SystemGroupBackend.getGroup(REGISTERED_USERS).getUUID(),
+ "refs/heads/*");
+ saveProjectConfig(project, cfg);
+ }
+
+ @Test
public void submitOnBehalfOf() throws Exception {
+ allowSubmitOnBehalfOf();
+ PushOneCommit.Result r = createChange();
+ String changeId = project.get() + "~master~" + r.getChangeId();
+ gApi.changes()
+ .id(changeId)
+ .current()
+ .review(ReviewInput.approve());
+ SubmitInput in = new SubmitInput();
+ in.onBehalfOf = admin2.email;
+ gApi.changes()
+ .id(changeId)
+ .current()
+ .submit(in);
+ assertThat(gApi.changes().id(changeId).get().status)
+ .isEqualTo(ChangeStatus.MERGED);
+ }
+
+ @Test
+ public void submitOnBehalfOfInvalidUser() throws Exception {
+ allowSubmitOnBehalfOf();
+ PushOneCommit.Result r = createChange();
+ String changeId = project.get() + "~master~" + r.getChangeId();
+ gApi.changes()
+ .id(changeId)
+ .current()
+ .review(ReviewInput.approve());
+ SubmitInput in = new SubmitInput();
+ in.onBehalfOf = "doesnotexist";
+ exception.expect(UnprocessableEntityException.class);
+ exception.expectMessage("Account Not Found: doesnotexist");
+ gApi.changes()
+ .id(changeId)
+ .current()
+ .submit(in);
+ }
+
+ @Test
+ public void submitOnBehalfOfNotPermitted() throws Exception {
PushOneCommit.Result r = createChange();
gApi.changes()
.id(project.get() + "~master~" + r.getChangeId())
@@ -138,6 +195,8 @@
.review(ReviewInput.approve());
SubmitInput in = new SubmitInput();
in.onBehalfOf = admin2.email;
+ exception.expect(AuthException.class);
+ exception.expectMessage("submit on behalf of not permitted");
gApi.changes()
.id(project.get() + "~master~" + r.getChangeId())
.current()
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
index 442eecd..3e7d0d5 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
@@ -31,6 +31,7 @@
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.api.changes.ReviewInput.NotifyHandling;
import com.google.gerrit.extensions.client.InheritableBoolean;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.EditInfo;
@@ -42,6 +43,7 @@
import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.gerrit.server.project.Util;
import com.google.gerrit.testutil.TestTimeUtil;
+import com.google.gerrit.testutil.FakeEmailSender.Message;
import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.AfterClass;
@@ -127,6 +129,40 @@
}
@Test
+ public void testPushForMasterWithNotify() throws Exception {
+ TestAccount user2 = accounts.user2();
+ String pushSpec = "refs/for/master"
+ + "%reviewer=" + user.email
+ + ",cc=" + user2.email;
+
+ sender.clear();
+ PushOneCommit.Result r =
+ pushTo(pushSpec + ",notify=" + NotifyHandling.NONE);
+ r.assertOkStatus();
+ assertThat(sender.getMessages()).hasSize(0);
+
+ sender.clear();
+ r = pushTo(pushSpec + ",notify=" + NotifyHandling.OWNER);
+ r.assertOkStatus();
+ // no email notification about own changes
+ assertThat(sender.getMessages()).hasSize(0);
+
+ sender.clear();
+ r = pushTo(pushSpec + ",notify=" + NotifyHandling.OWNER_REVIEWERS);
+ r.assertOkStatus();
+ assertThat(sender.getMessages()).hasSize(1);
+ Message m = sender.getMessages().get(0);
+ assertThat(m.rcpt()).containsExactly(user.emailAddress);
+
+ sender.clear();
+ r = pushTo(pushSpec + ",notify=" + NotifyHandling.ALL);
+ r.assertOkStatus();
+ assertThat(sender.getMessages()).hasSize(1);
+ m = sender.getMessages().get(0);
+ assertThat(m.rcpt()).containsExactly(user.emailAddress, user2.emailAddress);
+ }
+
+ @Test
public void testPushForMasterWithCc() throws Exception {
// cc one user
String topic = "my/topic";
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ActionsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ActionsIT.java
index e5281b1..e26747b 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ActionsIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ActionsIT.java
@@ -103,7 +103,7 @@
assertThat(info.label).isEqualTo("Submit whole topic");
assertThat(info.method).isEqualTo("POST");
assertThat(info.title).isEqualTo(
- "See the \"Submitted Together\" tab for problems");
+ "See the \"Submitted Together\" tab for problems, specially see: 2");
} else {
noSubmitWholeTopicAssertions(actions, 1);
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ConfigChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ConfigChangeIT.java
new file mode 100644
index 0000000..c4216cd
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ConfigChangeIT.java
@@ -0,0 +1,154 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.rest.change;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.junit.Assert.fail;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.TestProjectInput;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.api.projects.ProjectInput;
+import com.google.gerrit.extensions.client.ChangeStatus;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.project.Util;
+
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.revwalk.RevTree;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.transport.RefSpec;
+import org.junit.Before;
+import org.junit.Test;
+
+public class ConfigChangeIT extends AbstractDaemonTest {
+ @Before
+ public void setUp() throws Exception {
+ ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
+ Util.allow(cfg, Permission.OWNER, REGISTERED_USERS, "refs/*");
+ Util.allow(
+ cfg, Permission.PUSH, REGISTERED_USERS, "refs/for/refs/meta/config");
+ Util.allow(cfg, Permission.SUBMIT, REGISTERED_USERS, "refs/meta/config");
+ saveProjectConfig(project, cfg);
+
+ setApiUser(user);
+ fetchRefsMetaConfig();
+ }
+
+ @Test
+ @TestProjectInput(cloneAs = "user")
+ public void updateProjectConfig() throws Exception {
+ Config cfg = readProjectConfig();
+ assertThat(cfg.getString("project", null, "description")).isNull();
+ String desc = "new project description";
+ cfg.setString("project", null, "description", desc);
+
+ PushOneCommit.Result r = createConfigChange(cfg);
+ String id = r.getChangeId();
+
+ gApi.changes().id(id).current().review(ReviewInput.approve());
+ gApi.changes().id(id).current().submit();
+
+ assertThat(gApi.changes().id(id).info().status)
+ .isEqualTo(ChangeStatus.MERGED);
+ assertThat(gApi.projects().name(project.get()).get().description)
+ .isEqualTo(desc);
+ fetchRefsMetaConfig();
+ assertThat(readProjectConfig().getString("project", null, "description"))
+ .isEqualTo(desc);
+ }
+
+ @Test
+ @TestProjectInput(cloneAs = "user")
+ public void onlyAdminMayUpdateProjectParent() throws Exception {
+ setApiUser(admin);
+ ProjectInput parent = new ProjectInput();
+ parent.name = name("parent");
+ parent.permissionsOnly = true;
+ gApi.projects().create(parent);
+
+ setApiUser(user);
+ Config cfg = readProjectConfig();
+ assertThat(cfg.getString("access", null, "inheritFrom"))
+ .isAnyOf(null, allProjects.get());
+ cfg.setString("access", null, "inheritFrom", parent.name);
+
+ PushOneCommit.Result r = createConfigChange(cfg);
+ String id = r.getChangeId();
+
+ gApi.changes().id(id).current().review(ReviewInput.approve());
+ try {
+ gApi.changes().id(id).current().submit();
+ fail("expected submit to fail");
+ } catch (ResourceConflictException e) {
+ int n = gApi.changes().id(id).info()._number;
+ assertThat(e).hasMessage(
+ "Failed to submit 1 change due to the following problems:\n"
+ + "Change " + n + ": Change contains a project configuration that"
+ +" changes the parent project.\n"
+ + "The change must be submitted by a Gerrit administrator.");
+ }
+
+ assertThat(gApi.projects().name(project.get()).get().parent)
+ .isEqualTo(allProjects.get());
+ fetchRefsMetaConfig();
+ assertThat(readProjectConfig().getString("access", null, "inheritFrom"))
+ .isAnyOf(null, allProjects.get());
+
+ setApiUser(admin);
+ gApi.changes().id(id).current().submit();
+ assertThat(gApi.changes().id(id).info().status)
+ .isEqualTo(ChangeStatus.MERGED);
+ assertThat(gApi.projects().name(project.get()).get().parent)
+ .isEqualTo(parent.name);
+ fetchRefsMetaConfig();
+ assertThat(readProjectConfig().getString("access", null, "inheritFrom"))
+ .isEqualTo(parent.name);
+ }
+
+ private void fetchRefsMetaConfig() throws Exception {
+ git().fetch().setRefSpecs(new RefSpec("refs/meta/config:refs/meta/config"))
+ .call();
+ testRepo.reset("refs/meta/config");
+ }
+
+ private Config readProjectConfig() throws Exception {
+ RevWalk rw = testRepo.getRevWalk();
+ RevTree tree = rw.parseTree(testRepo.getRepository().resolve("HEAD"));
+ RevObject obj = rw.parseAny(testRepo.get(tree, "project.config"));
+ ObjectLoader loader = rw.getObjectReader().open(obj);
+ String text = new String(loader.getCachedBytes(), UTF_8);
+ Config cfg = new Config();
+ cfg.fromText(text);
+ return cfg;
+ }
+
+ private PushOneCommit.Result createConfigChange(Config cfg) throws Exception {
+ PushOneCommit.Result r = pushFactory.create(
+ db, user.getIdent(), testRepo,
+ "Update project config",
+ "project.config",
+ cfg.toText())
+ .to("refs/for/refs/meta/config");
+ r.assertOkStatus();
+ return r;
+ }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitResolvingMergeCommitIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitResolvingMergeCommitIT.java
index a7c75ef..5a6c36a 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitResolvingMergeCommitIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitResolvingMergeCommitIT.java
@@ -294,7 +294,7 @@
OrmException {
ChangeSet cs =
mergeSuperSet.completeChangeSet(db, change.change(), user(admin));
- assertThat(submit.isPatchSetMergeable(cs)).isEqualTo(expected);
+ assertThat(submit.unmergeableChanges(cs).isEmpty()).isEqualTo(expected);
}
private void assertMergeable(ChangeData change, boolean expected)
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java
index 0b86ad9..fd352d1 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java
@@ -23,9 +23,7 @@
import com.google.gerrit.server.git.NotifyConfig;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.mail.Address;
-import com.google.gerrit.testutil.FakeEmailSender;
import com.google.gerrit.testutil.FakeEmailSender.Message;
-import com.google.inject.Inject;
import org.junit.Test;
@@ -34,9 +32,6 @@
@NoHttpd
public class ProjectWatchIT extends AbstractDaemonTest {
- @Inject
- private FakeEmailSender sender;
-
/**
* Tests message project watches on new patch sets
* <p>
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/AbandonRestoreIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/AbandonRestoreIT.java
new file mode 100644
index 0000000..e07405f
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/AbandonRestoreIT.java
@@ -0,0 +1,89 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.ssh;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assert_;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.PushOneCommit.Result;
+import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.common.ChangeMessageInfo;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+@NoHttpd
+public class AbandonRestoreIT extends AbstractDaemonTest {
+
+ @Test
+ public void withMessage() throws Exception {
+ Result result = createChange();
+ String commit = result.getCommit().name();
+ executeCmd(commit, "abandon", "'abandon it'");
+ executeCmd(commit, "restore", "'restore it'");
+ assertChangeMessages(result.getChangeId(), ImmutableList.of(
+ "Uploaded patch set 1.",
+ "Abandoned\n\nabandon it",
+ "Restored\n\nrestore it"));
+ }
+
+ @Test
+ public void withoutMessage() throws Exception {
+ Result result = createChange();
+ String commit = result.getCommit().name();
+ executeCmd(commit, "abandon", null);
+ executeCmd(commit, "restore", null);
+ assertChangeMessages(result.getChangeId(), ImmutableList.of(
+ "Uploaded patch set 1.",
+ "Abandoned",
+ "Restored"));
+ }
+
+ private void executeCmd(String commit, String op, String message)
+ throws Exception {
+ StringBuilder command = new StringBuilder("gerrit review ")
+ .append(commit)
+ .append(" --")
+ .append(op);
+ if (message != null) {
+ command.append(" --message ").append(message);
+ }
+ String response = sshSession.exec(command.toString());
+ assert_()
+ .withFailureMessage(sshSession.getError())
+ .that(sshSession.hasError())
+ .isFalse();
+ assertThat(response.toLowerCase(Locale.US)).doesNotContain("error");
+ }
+
+ private void assertChangeMessages(String changeId, List<String> expected)
+ throws Exception {
+ ChangeInfo c = get(changeId);
+ Iterable<ChangeMessageInfo> messages = c.messages;
+ assertThat(messages).isNotNull();
+ assertThat(messages).hasSize(expected.size());
+ List<String> actual = new ArrayList<>();
+ for (ChangeMessageInfo info : messages) {
+ actual.add(info.message);
+ }
+ assertThat(actual).containsExactlyElementsIn(expected);
+ }
+}
\ No newline at end of file
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/SubmitInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/SubmitInput.java
index 4e08f8d..053248f 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/SubmitInput.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/SubmitInput.java
@@ -15,7 +15,9 @@
package com.google.gerrit.extensions.api.changes;
public class SubmitInput {
+ /** Not used anymore, kept for backward compatibility */
@Deprecated
public boolean waitForMerge;
+
public String onBehalfOf;
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java
index f605abf..2b01b59 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java
@@ -159,15 +159,6 @@
final Grid formGrid = new Grid(12 + (flashClippy ? 1 : 0), 2);
int row = 0;
- formGrid.setText(row, labelIdx, "");
- formGrid.setWidget(row, fieldIdx, showSiteHeader);
- row++;
-
- if (flashClippy) {
- formGrid.setText(row, labelIdx, "");
- formGrid.setWidget(row, fieldIdx, useFlashClipboard);
- row++;
- }
formGrid.setText(row, labelIdx, Util.C.reviewCategoryLabel());
formGrid.setWidget(row, fieldIdx, reviewCategoryStrategy);
@@ -181,6 +172,18 @@
formGrid.setWidget(row, fieldIdx, dateTimePanel);
row++;
+ formGrid.setText(row, labelIdx, Util.C.emailFieldLabel());
+ formGrid.setWidget(row, fieldIdx, emailStrategy);
+ row++;
+
+ formGrid.setText(row, labelIdx, Util.C.diffViewLabel());
+ formGrid.setWidget(row, fieldIdx, diffView);
+ row++;
+
+ formGrid.setText(row, labelIdx, "");
+ formGrid.setWidget(row, fieldIdx, showSiteHeader);
+ row++;
+
formGrid.setText(row, labelIdx, "");
formGrid.setWidget(row, fieldIdx, relativeDateInChangeTable);
row++;
@@ -197,15 +200,14 @@
formGrid.setWidget(row, fieldIdx, muteCommonPathPrefixes);
row++;
- formGrid.setText(row, labelIdx, Util.C.emailFieldLabel());
- formGrid.setWidget(row, fieldIdx, emailStrategy);
-
formGrid.setText(row, labelIdx, "");
formGrid.setWidget(row, fieldIdx, signedOffBy);
row++;
- formGrid.setText(row, labelIdx, Util.C.diffViewLabel());
- formGrid.setWidget(row, fieldIdx, diffView);
+ if (flashClippy) {
+ formGrid.setText(row, labelIdx, "");
+ formGrid.setWidget(row, fieldIdx, useFlashClipboard);
+ }
add(formGrid);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.java
index e04509b..71942ce 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Reviewers.java
@@ -142,6 +142,7 @@
addReviewerIcon.setVisible(true);
UIObject.setVisible(form, false);
suggestBox.setFocus(false);
+ suggestBox.setText("");
}
private void addReviewer(final String reviewer, boolean confirmed) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java
index 8314e3e..bff3f47 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeApi.java
@@ -192,8 +192,7 @@
/** Submit a specific revision of a change. */
public static void submit(int id, String commit, AsyncCallback<SubmitInfo> cb) {
- SubmitInput in = SubmitInput.create();
- in.waitForMerge(true);
+ JavaScriptObject in = JavaScriptObject.createObject();
call(id, commit, "submit").post(in, cb);
}
@@ -287,17 +286,6 @@
}
}
- private static class SubmitInput extends JavaScriptObject {
- final native void waitForMerge(boolean b) /*-{ this.wait_for_merge=b; }-*/;
-
- static SubmitInput create() {
- return (SubmitInput) createObject();
- }
-
- protected SubmitInput() {
- }
- }
-
private static RestApi call(int id, String action) {
return change(id).view(action);
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide.java
index 2996c07..33a2fb8 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide.java
@@ -637,17 +637,18 @@
String contents,
Element parent) {
return CodeMirror.create(parent, Configuration.create()
- .set("readOnly", true)
.set("cursorBlinkRate", prefs.cursorBlinkRate())
.set("cursorHeight", 0.85)
- .set("lineNumbers", prefs.showLineNumbers())
- .set("tabSize", prefs.tabSize())
- .set("mode", fileSize == FileSize.SMALL ? getContentType(meta) : null)
- .set("lineWrapping", false)
- .set("scrollbarStyle", "overlay")
- .set("styleSelectedText", true)
- .set("showTrailingSpace", prefs.showWhitespaceErrors())
.set("keyMap", "vim_ro")
+ .set("lineNumbers", prefs.showLineNumbers())
+ .set("lineWrapping", false)
+ .set("matchBrackets", prefs.matchBrackets())
+ .set("mode", fileSize == FileSize.SMALL ? getContentType(meta) : null)
+ .set("readOnly", true)
+ .set("scrollbarStyle", "overlay")
+ .set("showTrailingSpace", prefs.showWhitespaceErrors())
+ .set("styleSelectedText", true)
+ .set("tabSize", prefs.tabSize())
.set("theme", prefs.theme().name().toLowerCase())
.set("value", meta != null ? contents : "")
.set("viewportMargin", renderEntireFile() ? POSITIVE_INFINITY : 10));
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java
index 3a3a33f..7d8d22c 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java
@@ -16,8 +16,8 @@
import static java.util.concurrent.TimeUnit.HOURS;
-import com.google.gerrit.common.data.HostPageData;
import com.google.common.base.Strings;
+import com.google.gerrit.common.data.HostPageData;
import com.google.gerrit.httpd.WebSessionManager.Key;
import com.google.gerrit.httpd.WebSessionManager.Val;
import com.google.gerrit.reviewdb.client.Account;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
index 5108315..1edf4a1 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
@@ -20,6 +20,7 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
import com.google.gerrit.common.ChangeHookRunner;
+import com.google.gerrit.common.EventBroker;
import com.google.gerrit.gpg.GpgModule;
import com.google.gerrit.httpd.AllRequestFilter;
import com.google.gerrit.httpd.GerritOptions;
@@ -340,6 +341,7 @@
modules.add(new WorkQueue.Module());
modules.add(new ChangeHookRunner.Module());
+ modules.add(new EventBroker.Module());
modules.add(new ReceiveCommitsExecutorModule());
modules.add(new DiffExecutorModule());
modules.add(new MimeUtil2Module());
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/RebuildNotedb.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/RebuildNotedb.java
index 5bbb798..b586bc5 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/RebuildNotedb.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/RebuildNotedb.java
@@ -37,6 +37,7 @@
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MultiProgressMonitor;
import com.google.gerrit.server.git.MultiProgressMonitor.Task;
@@ -51,11 +52,11 @@
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
import com.google.inject.Injector;
-import com.google.inject.Key;
-import com.google.inject.TypeLiteral;
import org.eclipse.jgit.lib.BatchRefUpdate;
+import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.NullProgressMonitor;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
@@ -85,6 +86,28 @@
private Injector dbInjector;
private Injector sysInjector;
+ @Inject
+ private AllUsersName allUsersName;
+
+ @Inject
+ private ChangeRebuilder rebuilder;
+
+ @Inject
+ @GerritServerConfig
+ private Config cfg;
+
+ @Inject
+ private GitRepositoryManager repoManager;
+
+ @Inject
+ private NotesMigration notesMigration;
+
+ @Inject
+ private SchemaFactory<ReviewDb> schemaFactory;
+
+ @Inject
+ private WorkQueue workQueue;
+
@Override
public int run() throws Exception {
mustHaveValidSite();
@@ -96,8 +119,7 @@
dbManager.start();
sysInjector = createSysInjector();
- NotesMigration notesMigration = sysInjector.getInstance(
- NotesMigration.class);
+ sysInjector.injectMembers(this);
if (!notesMigration.enabled()) {
die("Notedb is not enabled.");
}
@@ -107,16 +129,11 @@
ListeningExecutorService executor = newExecutor();
System.out.println("Rebuilding the notedb");
- ChangeRebuilder rebuilder = sysInjector.getInstance(ChangeRebuilder.class);
Multimap<Project.NameKey, Change.Id> changesByProject =
getChangesByProject();
AtomicBoolean ok = new AtomicBoolean(true);
Stopwatch sw = Stopwatch.createStarted();
- GitRepositoryManager repoManager =
- sysInjector.getInstance(GitRepositoryManager.class);
- Project.NameKey allUsersName =
- sysInjector.getInstance(AllUsersName.class);
try (Repository allUsersRepo =
repoManager.openMetadataRepository(allUsersName)) {
deleteRefs(RefNames.REFS_DRAFT_COMMENTS, allUsersRepo);
@@ -206,8 +223,7 @@
private ListeningExecutorService newExecutor() {
if (threads > 0) {
return MoreExecutors.listeningDecorator(
- dbInjector.getInstance(WorkQueue.class)
- .createQueue(threads, "RebuildChange"));
+ workQueue.createQueue(threads, "RebuildChange"));
} else {
return MoreExecutors.newDirectExecutorService();
}
@@ -217,8 +233,6 @@
throws OrmException {
// Memorize all changes so we can close the db connection and allow
// rebuilder threads to use the full connection pool.
- SchemaFactory<ReviewDb> schemaFactory = sysInjector.getInstance(Key.get(
- new TypeLiteral<SchemaFactory<ReviewDb>>() {}));
Multimap<Project.NameKey, Change.Id> changesByProject =
ArrayListMultimap.create();
try (ReviewDb db = schemaFactory.open()) {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPlugins.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPlugins.java
index 6a3d7cb..7fdd7e2 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPlugins.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPlugins.java
@@ -14,6 +14,7 @@
package com.google.gerrit.pgm.init;
+import com.google.common.collect.FluentIterable;
import com.google.common.collect.Lists;
import com.google.gerrit.common.PluginData;
import com.google.gerrit.pgm.init.api.ConsoleUI;
@@ -30,6 +31,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
+import java.util.Comparator;
import java.util.List;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
@@ -65,7 +67,11 @@
result.add(new PluginData(pluginName, pluginVersion, tmpPlugin));
}
});
- return result;
+ return FluentIterable.from(result).toSortedList(new Comparator<PluginData>() {
+ @Override
+ public int compare(PluginData a, PluginData b) {
+ return a.name.compareTo(b.name);
+ }});
}
private final ConsoleUI ui;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
index 400c1c0..3daeaaf 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
@@ -24,6 +24,7 @@
import com.google.gerrit.common.data.LabelTypes;
import com.google.gerrit.extensions.events.LifecycleListener;
import com.google.gerrit.extensions.events.NewProjectCreatedListener;
+import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.reviewdb.client.Account;
@@ -32,7 +33,6 @@
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.config.AnonymousCowardName;
@@ -44,7 +44,6 @@
import com.google.gerrit.server.data.PatchSetAttribute;
import com.google.gerrit.server.data.RefUpdateAttribute;
import com.google.gerrit.server.events.ChangeAbandonedEvent;
-import com.google.gerrit.server.events.ChangeEvent;
import com.google.gerrit.server.events.ChangeMergedEvent;
import com.google.gerrit.server.events.ChangeRestoredEvent;
import com.google.gerrit.server.events.CommentAddedEvent;
@@ -54,17 +53,12 @@
import com.google.gerrit.server.events.MergeFailedEvent;
import com.google.gerrit.server.events.PatchSetCreatedEvent;
import com.google.gerrit.server.events.ProjectCreatedEvent;
-import com.google.gerrit.server.events.ProjectEvent;
-import com.google.gerrit.server.events.RefEvent;
import com.google.gerrit.server.events.RefUpdatedEvent;
import com.google.gerrit.server.events.ReviewerAddedEvent;
import com.google.gerrit.server.events.TopicChangedEvent;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.WorkQueue;
-import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.project.ProjectControl;
-import com.google.gerrit.server.project.ProjectState;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -100,8 +94,8 @@
/** Spawns local executables when a hook action occurs. */
@Singleton
-public class ChangeHookRunner implements ChangeHooks, EventDispatcher,
- LifecycleListener, NewProjectCreatedListener {
+public class ChangeHookRunner implements ChangeHooks, LifecycleListener,
+ NewProjectCreatedListener {
/** A logger for this class. */
private static final Logger log = LoggerFactory.getLogger(ChangeHookRunner.class);
@@ -110,7 +104,6 @@
protected void configure() {
bind(ChangeHookRunner.class);
bind(ChangeHooks.class).to(ChangeHookRunner.class);
- bind(EventDispatcher.class).to(ChangeHookRunner.class);
DynamicSet.bind(binder(), NewProjectCreatedListener.class).to(ChangeHookRunner.class);
listener().to(ChangeHookRunner.class);
}
@@ -164,13 +157,6 @@
}
}
- /** Listeners to receive changes as they happen (limited by visibility
- * of user). */
- private final DynamicSet<UserScopedEventListener> listeners;
-
- /** Listeners to receive all changes as they happen. */
- private final DynamicSet<EventListener> unrestrictedListeners;
-
/** Path of the new patchset hook. */
private final Optional<Path> patchsetCreatedHook;
@@ -235,7 +221,7 @@
/** Timeout value for synchronous hooks */
private final int syncHookTimeout;
- private final ChangeNotes.Factory notesFactory;
+ private DynamicItem<EventDispatcher> dispatcher;
/**
* Create a new ChangeHookRunner.
@@ -255,9 +241,7 @@
ProjectCache projectCache,
AccountCache accountCache,
EventFactory eventFactory,
- DynamicSet<UserScopedEventListener> listeners,
- DynamicSet<EventListener> unrestrictedListeners,
- ChangeNotes.Factory notesFactory) {
+ DynamicItem<EventDispatcher> dispatcher) {
this.anonymousCowardName = anonymousCowardName;
this.repoManager = repoManager;
this.hookQueue = queue.createQueue(1, "hook");
@@ -265,9 +249,7 @@
this.accountCache = accountCache;
this.eventFactory = eventFactory;
this.sitePaths = sitePath;
- this.listeners = listeners;
- this.unrestrictedListeners = unrestrictedListeners;
- this.notesFactory = notesFactory;
+ this.dispatcher = dispatcher;
Path hooksPath;
String hooksPathConfig = config.getString("hooks", null, "path");
@@ -353,7 +335,7 @@
event.projectName = project.get();
event.headName = headName;
- fireEvent(project, event);
+ dispatcher.get().postEvent(project, event);
if (!projectCreatedHook.isPresent()) {
return;
@@ -378,7 +360,7 @@
event.patchSet = patchSetAttributeSupplier(change, patchSet);
event.uploader = accountAttributeSupplier(uploader);
- fireEvent(change, event, db);
+ dispatcher.get().postEvent(change, event, db);
if (!patchsetCreatedHook.isPresent()) {
return;
@@ -415,7 +397,7 @@
event.patchSet = patchSetAttributeSupplier(change, patchSet);
event.uploader = accountAttributeSupplier(uploader);
- fireEvent(change, event, db);
+ dispatcher.get().postEvent(change, event, db);
if (!draftPublishedHook.isPresent()) {
return;
@@ -467,7 +449,7 @@
}
});
- fireEvent(change, event, db);
+ dispatcher.get().postEvent(change, event, db);
if (!commentAddedHook.isPresent()) {
return;
@@ -511,7 +493,7 @@
event.patchSet = patchSetAttributeSupplier(change, patchSet);
event.newRev = mergeResultRev;
- fireEvent(change, event, db);
+ dispatcher.get().postEvent(change, event, db);
if (!changeMergedHook.isPresent()) {
return;
@@ -546,7 +528,7 @@
event.patchSet = patchSetAttributeSupplier(change, patchSet);
event.reason = reason;
- fireEvent(change, event, db);
+ dispatcher.get().postEvent(change, event, db);
if (!mergeFailedHook.isPresent()) {
return;
@@ -581,7 +563,7 @@
event.patchSet = patchSetAttributeSupplier(change, patchSet);
event.reason = reason;
- fireEvent(change, event, db);
+ dispatcher.get().postEvent(change, event, db);
if (!changeAbandonedHook.isPresent()) {
return;
@@ -616,7 +598,7 @@
event.patchSet = patchSetAttributeSupplier(change, patchSet);
event.reason = reason;
- fireEvent(change, event, db);
+ dispatcher.get().postEvent(change, event, db);
if (!changeRestoredHook.isPresent()) {
return;
@@ -662,7 +644,7 @@
}
});
- fireEvent(refName, event);
+ dispatcher.get().postEvent(refName, event);
if (!refUpdatedHook.isPresent()) {
return;
@@ -691,7 +673,7 @@
event.patchSet = patchSetAttributeSupplier(change, patchSet);
event.reviewer = accountAttributeSupplier(account);
- fireEvent(change, event, db);
+ dispatcher.get().postEvent(change, event, db);
if (!reviewerAddedHook.isPresent()) {
return;
@@ -721,7 +703,7 @@
event.changer = accountAttributeSupplier(account);
event.oldTopic = oldTopic;
- fireEvent(change, event, db);
+ dispatcher.get().postEvent(change, event, db);
if (!topicChangedHook.isPresent()) {
return;
@@ -762,7 +744,7 @@
event.added = hashtagArray(added);
event.removed = hashtagArray(removed);
- fireEvent(change, event, db);
+ dispatcher.get().postEvent(change, event, db);
if (!hashtagsChangedHook.isPresent()) {
return;
@@ -810,28 +792,6 @@
}
}
- @Override
- public void postEvent(Change change, ChangeEvent event, ReviewDb db)
- throws OrmException {
- fireEvent(change, event, db);
- }
-
- @Override
- public void postEvent(Branch.NameKey branchName, RefEvent event) {
- fireEvent(branchName, event);
- }
-
- @Override
- public void postEvent(Project.NameKey projectName, ProjectEvent event) {
- fireEvent(projectName, event);
- }
-
- @Override
- public void postEvent(com.google.gerrit.server.events.Event event,
- ReviewDb db) throws OrmException {
- fireEvent(event, db);
- }
-
private Supplier<AccountState> getAccountSupplier(
final Account.Id account) {
return Suppliers.memoize(
@@ -894,100 +854,6 @@
});
}
- private void fireEventForUnrestrictedListeners(com.google.gerrit.server.events.Event event) {
- for (EventListener listener : unrestrictedListeners) {
- listener.onEvent(event);
- }
- }
-
- private void fireEvent(Change change, ChangeEvent event, ReviewDb db)
- throws OrmException {
- for (UserScopedEventListener listener : listeners) {
- if (isVisibleTo(change, listener.getUser(), db)) {
- listener.onEvent(event);
- }
- }
-
- fireEventForUnrestrictedListeners( event );
- }
-
- private void fireEvent(Project.NameKey project, ProjectEvent event) {
- for (UserScopedEventListener listener : listeners) {
- if (isVisibleTo(project, listener.getUser())) {
- listener.onEvent(event);
- }
- }
-
- fireEventForUnrestrictedListeners(event);
- }
-
- private void fireEvent(Branch.NameKey branchName, RefEvent event) {
- for (UserScopedEventListener listener : listeners) {
- if (isVisibleTo(branchName, listener.getUser())) {
- listener.onEvent(event);
- }
- }
-
- fireEventForUnrestrictedListeners(event);
- }
-
- private void fireEvent(com.google.gerrit.server.events.Event event,
- ReviewDb db) throws OrmException {
- for (UserScopedEventListener listener : listeners) {
- if (isVisibleTo(event, listener.getUser(), db)) {
- listener.onEvent(event);
- }
- }
-
- fireEventForUnrestrictedListeners(event);
- }
-
- private boolean isVisibleTo(Project.NameKey project, CurrentUser user) {
- ProjectState pe = projectCache.get(project);
- if (pe == null) {
- return false;
- }
- return pe.controlFor(user).isVisible();
- }
-
- private boolean isVisibleTo(Change change, CurrentUser user, ReviewDb db)
- throws OrmException {
- if (change == null) {
- return false;
- }
- ProjectState pe = projectCache.get(change.getProject());
- if (pe == null) {
- return false;
- }
- ProjectControl pc = pe.controlFor(user);
- return pc.controlFor(db, change).isVisible(db);
- }
-
- private boolean isVisibleTo(Branch.NameKey branchName, CurrentUser user) {
- ProjectState pe = projectCache.get(branchName.getParentKey());
- if (pe == null) {
- return false;
- }
- ProjectControl pc = pe.controlFor(user);
- return pc.controlForRef(branchName).isVisible();
- }
-
- private boolean isVisibleTo(com.google.gerrit.server.events.Event event,
- CurrentUser user, ReviewDb db) throws OrmException {
- if (event instanceof RefEvent) {
- RefEvent refEvent = (RefEvent) event;
- String ref = refEvent.getRefName();
- if (PatchSet.isChangeRef(ref)) {
- Change.Id cid = PatchSet.Id.fromRef(ref).getParentKey();
- Change change = notesFactory
- .create(db, refEvent.getProjectNameKey(), cid).getChange();
- return isVisibleTo(change, user, db);
- }
- return isVisibleTo(refEvent.getBranchNameKey(), user);
- }
- return true;
- }
-
/**
* Create an ApprovalAttribute for the given approval suitable for serialization to JSON.
* @param approval
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/EventBroker.java b/gerrit-server/src/main/java/com/google/gerrit/common/EventBroker.java
new file mode 100644
index 0000000..97bc2e5
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/common/EventBroker.java
@@ -0,0 +1,185 @@
+// Copyright (C) 2016 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.common;
+
+import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.lifecycle.LifecycleModule;
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.events.ChangeEvent;
+import com.google.gerrit.server.events.Event;
+import com.google.gerrit.server.events.ProjectEvent;
+import com.google.gerrit.server.events.RefEvent;
+import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.project.ProjectState;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+/** Distributes Events to listeners if they are allowed to see them */
+@Singleton
+public class EventBroker implements EventDispatcher {
+
+ public static class Module extends LifecycleModule {
+ @Override
+ protected void configure() {
+ DynamicItem.itemOf(binder(), EventDispatcher.class);
+ DynamicItem.bind(binder(), EventDispatcher.class).to(EventBroker.class);
+ }
+ }
+
+ /**
+ * Listeners to receive changes as they happen (limited by visibility of
+ * user).
+ */
+ protected final DynamicSet<UserScopedEventListener> listeners;
+
+ /** Listeners to receive all changes as they happen. */
+ protected final DynamicSet<EventListener> unrestrictedListeners;
+
+ protected final ProjectCache projectCache;
+
+ protected final ChangeNotes.Factory notesFactory;
+
+ @Inject
+ public EventBroker(DynamicSet<UserScopedEventListener> listeners,
+ DynamicSet<EventListener> unrestrictedListeners,
+ ProjectCache projectCache,
+ ChangeNotes.Factory notesFactory) {
+ this.listeners = listeners;
+ this.unrestrictedListeners = unrestrictedListeners;
+ this.projectCache = projectCache;
+ this.notesFactory = notesFactory;
+ }
+
+ @Override
+ public void postEvent(Change change, ChangeEvent event, ReviewDb db)
+ throws OrmException {
+ fireEvent(change, event, db);
+ }
+
+ @Override
+ public void postEvent(Branch.NameKey branchName, RefEvent event) {
+ fireEvent(branchName, event);
+ }
+
+ @Override
+ public void postEvent(Project.NameKey projectName, ProjectEvent event) {
+ fireEvent(projectName, event);
+ }
+
+ @Override
+ public void postEvent(Event event, ReviewDb db) throws OrmException {
+ fireEvent(event, db);
+ }
+
+ protected void fireEventForUnrestrictedListeners(Event event) {
+ for (EventListener listener : unrestrictedListeners) {
+ listener.onEvent(event);
+ }
+ }
+
+ protected void fireEvent(Change change, ChangeEvent event, ReviewDb db)
+ throws OrmException {
+ for (UserScopedEventListener listener : listeners) {
+ if (isVisibleTo(change, listener.getUser(), db)) {
+ listener.onEvent(event);
+ }
+ }
+ fireEventForUnrestrictedListeners(event);
+ }
+
+ protected void fireEvent(Project.NameKey project, ProjectEvent event) {
+ for (UserScopedEventListener listener : listeners) {
+ if (isVisibleTo(project, listener.getUser())) {
+ listener.onEvent(event);
+ }
+ }
+ fireEventForUnrestrictedListeners(event);
+ }
+
+ protected void fireEvent(Branch.NameKey branchName, RefEvent event) {
+ for (UserScopedEventListener listener : listeners) {
+ if (isVisibleTo(branchName, listener.getUser())) {
+ listener.onEvent(event);
+ }
+ }
+ fireEventForUnrestrictedListeners(event);
+ }
+
+ protected void fireEvent(Event event, ReviewDb db) throws OrmException {
+ for (UserScopedEventListener listener : listeners) {
+ if (isVisibleTo(event, listener.getUser(), db)) {
+ listener.onEvent(event);
+ }
+ }
+ fireEventForUnrestrictedListeners(event);
+ }
+
+ protected boolean isVisibleTo(Project.NameKey project, CurrentUser user) {
+ ProjectState pe = projectCache.get(project);
+ if (pe == null) {
+ return false;
+ }
+ return pe.controlFor(user).isVisible();
+ }
+
+ protected boolean isVisibleTo(Change change, CurrentUser user, ReviewDb db)
+ throws OrmException {
+ if (change == null) {
+ return false;
+ }
+ ProjectState pe = projectCache.get(change.getProject());
+ if (pe == null) {
+ return false;
+ }
+ ProjectControl pc = pe.controlFor(user);
+ return pc.controlFor(db, change).isVisible(db);
+ }
+
+ protected boolean isVisibleTo(Branch.NameKey branchName, CurrentUser user) {
+ ProjectState pe = projectCache.get(branchName.getParentKey());
+ if (pe == null) {
+ return false;
+ }
+ ProjectControl pc = pe.controlFor(user);
+ return pc.controlForRef(branchName).isVisible();
+ }
+
+ protected boolean isVisibleTo(Event event, CurrentUser user, ReviewDb db)
+ throws OrmException {
+ if (event instanceof RefEvent) {
+ RefEvent refEvent = (RefEvent) event;
+ String ref = refEvent.getRefName();
+ if (PatchSet.isChangeRef(ref)) {
+ Change.Id cid = PatchSet.Id.fromRef(ref).getParentKey();
+ Change change = notesFactory
+ .create(db, refEvent.getProjectNameKey(), cid).getChange();
+ return isVisibleTo(change, user, db);
+ }
+ return isVisibleTo(refEvent.getBranchNameKey(), user);
+ } else if (event instanceof ProjectEvent) {
+ return isVisibleTo(((ProjectEvent) event).getProjectNameKey(), user);
+ }
+ return true;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ReviewersUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ReviewersUtil.java
new file mode 100644
index 0000000..e38f88c
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ReviewersUtil.java
@@ -0,0 +1,286 @@
+// Copyright (C) 2016 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;
+
+import com.google.common.base.Function;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Strings;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Ordering;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.common.errors.NoSuchGroupException;
+import com.google.gerrit.extensions.common.AccountInfo;
+import com.google.gerrit.extensions.common.GroupBaseInfo;
+import com.google.gerrit.extensions.common.SuggestedReviewerInfo;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.Url;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.AccountExternalId;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.account.AccountControl;
+import com.google.gerrit.server.account.AccountLoader;
+import com.google.gerrit.server.account.GroupBackend;
+import com.google.gerrit.server.account.GroupMembers;
+import com.google.gerrit.server.change.PostReviewers;
+import com.google.gerrit.server.change.ReviewerSuggestionCache;
+import com.google.gerrit.server.change.SuggestReviewers;
+import com.google.gerrit.server.project.NoSuchProjectException;
+import com.google.gerrit.server.project.ProjectControl;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class ReviewersUtil {
+ private static final String MAX_SUFFIX = "\u9fa5";
+ private static final Ordering<SuggestedReviewerInfo> ORDERING =
+ Ordering.natural().onResultOf(new Function<SuggestedReviewerInfo, String>() {
+ @Nullable
+ @Override
+ public String apply(@Nullable SuggestedReviewerInfo suggestedReviewerInfo) {
+ if (suggestedReviewerInfo == null) {
+ return null;
+ }
+ return suggestedReviewerInfo.account != null
+ ? MoreObjects.firstNonNull(suggestedReviewerInfo.account.email,
+ Strings.nullToEmpty(suggestedReviewerInfo.account.name))
+ : Strings.nullToEmpty(suggestedReviewerInfo.group.name);
+ }
+ });
+ private final AccountLoader accountLoader;
+ private final AccountCache accountCache;
+ private final ReviewerSuggestionCache reviewerSuggestionCache;
+ private final AccountControl accountControl;
+ private final Provider<ReviewDb> dbProvider;
+ private final GroupBackend groupBackend;
+ private final GroupMembers.Factory groupMembersFactory;
+ private final Provider<CurrentUser> currentUser;
+
+ @Inject
+ ReviewersUtil(AccountLoader.Factory accountLoaderFactory,
+ AccountCache accountCache,
+ ReviewerSuggestionCache reviewerSuggestionCache,
+ AccountControl.Factory accountControlFactory,
+ Provider<ReviewDb> dbProvider,
+ GroupBackend groupBackend,
+ GroupMembers.Factory groupMembersFactory,
+ Provider<CurrentUser> currentUser) {
+ this.accountLoader = accountLoaderFactory.create(true);
+ this.accountCache = accountCache;
+ this.reviewerSuggestionCache = reviewerSuggestionCache;
+ this.accountControl = accountControlFactory.get();
+ this.dbProvider = dbProvider;
+ this.groupBackend = groupBackend;
+ this.groupMembersFactory = groupMembersFactory;
+ this.currentUser = currentUser;
+ }
+
+ public interface VisibilityControl {
+ boolean isVisibleTo(Account.Id account) throws OrmException;
+ }
+
+ public List<SuggestedReviewerInfo> suggestReviewers(
+ SuggestReviewers suggestReviewers, ProjectControl projectControl,
+ VisibilityControl visibilityControl)
+ throws IOException, OrmException, BadRequestException {
+ String query = suggestReviewers.getQuery();
+ boolean suggestAccounts = suggestReviewers.getSuggestAccounts();
+ int suggestFrom = suggestReviewers.getSuggestFrom();
+ boolean useFullTextSearch = suggestReviewers.getUseFullTextSearch();
+ int limit = suggestReviewers.getLimit();
+
+ if (Strings.isNullOrEmpty(query)) {
+ throw new BadRequestException("missing query field");
+ }
+
+ if (!suggestAccounts || query.length() < suggestFrom) {
+ return Collections.emptyList();
+ }
+
+ List<AccountInfo> suggestedAccounts;
+ if (useFullTextSearch) {
+ suggestedAccounts = suggestAccountFullTextSearch(suggestReviewers, visibilityControl);
+ } else {
+ suggestedAccounts = suggestAccount(suggestReviewers, visibilityControl);
+ }
+
+ List<SuggestedReviewerInfo> reviewer = Lists.newArrayList();
+ for (AccountInfo a : suggestedAccounts) {
+ SuggestedReviewerInfo info = new SuggestedReviewerInfo();
+ info.account = a;
+ reviewer.add(info);
+ }
+
+ for (GroupReference g : suggestAccountGroup(suggestReviewers, projectControl)) {
+ if (suggestGroupAsReviewer(suggestReviewers, projectControl.getProject(),
+ g, visibilityControl)) {
+ GroupBaseInfo info = new GroupBaseInfo();
+ info.id = Url.encode(g.getUUID().get());
+ info.name = g.getName();
+ SuggestedReviewerInfo suggestedReviewerInfo = new SuggestedReviewerInfo();
+ suggestedReviewerInfo.group = info;
+ reviewer.add(suggestedReviewerInfo);
+ }
+ }
+
+ reviewer = ORDERING.immutableSortedCopy(reviewer);
+ if (reviewer.size() <= limit) {
+ return reviewer;
+ } else {
+ return reviewer.subList(0, limit);
+ }
+ }
+
+ private List<AccountInfo> suggestAccountFullTextSearch(
+ SuggestReviewers suggestReviewers, VisibilityControl visibilityControl)
+ throws IOException, OrmException {
+ List<AccountInfo> results = reviewerSuggestionCache.search(
+ suggestReviewers.getQuery(), suggestReviewers.getFullTextMaxMatches());
+
+ Iterator<AccountInfo> it = results.iterator();
+ while (it.hasNext()) {
+ Account.Id accountId = new Account.Id(it.next()._accountId);
+ if (!(visibilityControl.isVisibleTo(accountId)
+ && accountControl.canSee(accountId))) {
+ it.remove();
+ }
+ }
+
+ return results;
+ }
+
+ private List<AccountInfo> suggestAccount(SuggestReviewers suggestReviewers,
+ VisibilityControl visibilityControl)
+ throws OrmException {
+ String query = suggestReviewers.getQuery();
+ int limit = suggestReviewers.getLimit();
+
+ String a = query;
+ String b = a + MAX_SUFFIX;
+
+ Map<Account.Id, AccountInfo> r = new LinkedHashMap<>();
+ Map<Account.Id, String> queryEmail = new HashMap<>();
+
+ for (Account p : dbProvider.get().accounts()
+ .suggestByFullName(a, b, limit)) {
+ if (p.isActive()) {
+ addSuggestion(r, p.getId(), visibilityControl);
+ }
+ }
+
+ if (r.size() < limit) {
+ for (Account p : dbProvider.get().accounts()
+ .suggestByPreferredEmail(a, b, limit - r.size())) {
+ if (p.isActive()) {
+ addSuggestion(r, p.getId(), visibilityControl);
+ }
+ }
+ }
+
+ if (r.size() < limit) {
+ for (AccountExternalId e : dbProvider.get().accountExternalIds()
+ .suggestByEmailAddress(a, b, limit - r.size())) {
+ if (!r.containsKey(e.getAccountId())) {
+ Account p = accountCache.get(e.getAccountId()).getAccount();
+ if (p.isActive()) {
+ if (addSuggestion(r, p.getId(), visibilityControl)) {
+ queryEmail.put(e.getAccountId(), e.getEmailAddress());
+ }
+ }
+ }
+ }
+ }
+
+ accountLoader.fill();
+ for (Map.Entry<Account.Id, String> p : queryEmail.entrySet()) {
+ AccountInfo info = r.get(p.getKey());
+ if (info != null) {
+ info.email = p.getValue();
+ }
+ }
+ return new ArrayList<>(r.values());
+ }
+
+ private boolean addSuggestion(Map<Account.Id, AccountInfo> map,
+ Account.Id account, VisibilityControl visibilityControl)
+ throws OrmException {
+ if (!map.containsKey(account)
+ // Can the suggestion see the change?
+ && visibilityControl.isVisibleTo(account)
+ // Can the account see the current user?
+ && accountControl.canSee(account)) {
+ map.put(account, accountLoader.get(account));
+ return true;
+ }
+ return false;
+ }
+
+ private List<GroupReference> suggestAccountGroup(
+ SuggestReviewers suggestReviewers, ProjectControl ctl) {
+ return Lists.newArrayList(
+ Iterables.limit(groupBackend.suggest(suggestReviewers.getQuery(), ctl),
+ suggestReviewers.getLimit()));
+ }
+
+ private boolean suggestGroupAsReviewer(SuggestReviewers suggestReviewers,
+ Project project, GroupReference group,
+ VisibilityControl visibilityControl) throws OrmException, IOException {
+ int maxAllowed = suggestReviewers.getMaxAllowed();
+
+ if (!PostReviewers.isLegalReviewerGroup(group.getUUID())) {
+ return false;
+ }
+
+ try {
+ Set<Account> members = groupMembersFactory
+ .create(currentUser.get())
+ .listAccounts(group.getUUID(), project.getNameKey());
+
+ if (members.isEmpty()) {
+ return false;
+ }
+
+ if (maxAllowed > 0 && members.size() > maxAllowed) {
+ return false;
+ }
+
+ // require that at least one member in the group can see the change
+ for (Account account : members) {
+ if (visibilityControl.isVisibleTo(account.getId())) {
+ return true;
+ }
+ }
+ } catch (NoSuchGroupException e) {
+ return false;
+ } catch (NoSuchProjectException e) {
+ return false;
+ }
+
+ return false;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
index 446c8ed..a9bf220 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
@@ -52,7 +52,7 @@
import com.google.gerrit.server.change.Reviewers;
import com.google.gerrit.server.change.Revisions;
import com.google.gerrit.server.change.SubmittedTogether;
-import com.google.gerrit.server.change.SuggestReviewers;
+import com.google.gerrit.server.change.SuggestChangeReviewers;
import com.google.gerrit.server.git.UpdateException;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gwtorm.server.OrmException;
@@ -77,7 +77,7 @@
private final Revisions revisions;
private final ReviewerApiImpl.Factory reviewerApi;
private final RevisionApiImpl.Factory revisionApi;
- private final Provider<SuggestReviewers> suggestReviewers;
+ private final Provider<SuggestChangeReviewers> suggestReviewers;
private final ChangeResource change;
private final Abandon abandon;
private final Revert revert;
@@ -104,7 +104,7 @@
Revisions revisions,
ReviewerApiImpl.Factory reviewerApi,
RevisionApiImpl.Factory revisionApi,
- Provider<SuggestReviewers> suggestReviewers,
+ Provider<SuggestChangeReviewers> suggestReviewers,
Abandon abandon,
Revert revert,
Restore restore,
@@ -304,7 +304,7 @@
private List<SuggestedReviewerInfo> suggestReviewers(SuggestedReviewersRequest r)
throws RestApiException {
try {
- SuggestReviewers mySuggestReviewers = suggestReviewers.get();
+ SuggestChangeReviewers mySuggestReviewers = suggestReviewers.get();
mySuggestReviewers.setQuery(r.getQuery());
mySuggestReviewers.setLimit(r.getLimit());
return mySuggestReviewers.apply(change);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
index 0ef8b51..37f15bd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
@@ -72,7 +72,7 @@
post(CHANGE_KIND, "index").to(Index.class);
post(CHANGE_KIND, "reviewers").to(PostReviewers.class);
- get(CHANGE_KIND, "suggest_reviewers").to(SuggestReviewers.class);
+ get(CHANGE_KIND, "suggest_reviewers").to(SuggestChangeReviewers.class);
child(CHANGE_KIND, "reviewers").to(Reviewers.class);
get(REVIEWER_KIND).to(GetReviewer.class);
delete(REVIEWER_KIND).to(DeleteReviewer.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseUtil.java
index dd9a0b9..ae2672b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseUtil.java
@@ -22,9 +22,9 @@
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Change.Status;
-import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.RevId;
+import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.project.ChangeControl;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ReviewerSuggestionCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ReviewerSuggestionCache.java
index ffb63f6..20078cc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ReviewerSuggestionCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ReviewerSuggestionCache.java
@@ -102,7 +102,7 @@
});
}
- List<AccountInfo> search(String query, int n) throws IOException {
+ public List<AccountInfo> search(String query, int n) throws IOException {
IndexSearcher searcher = get();
if (searcher == null) {
return Collections.emptyList();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
index 4e96704..fb69f87 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
@@ -15,11 +15,14 @@
package com.google.gerrit.server.change;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
import com.google.common.base.MoreObjects;
import com.google.common.base.Predicate;
import com.google.common.base.Strings;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.gerrit.common.data.ParameterizedString;
@@ -71,6 +74,7 @@
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -96,7 +100,7 @@
private static final String CLICK_FAILURE_TOOLTIP =
"Clicking the button would fail";
private static final String CHANGES_NOT_MERGEABLE =
- "See the \"Submitted Together\" tab for problems";
+ "See the \"Submitted Together\" tab for problems, specially see: ";
public static class Output {
transient Change change;
@@ -114,10 +118,8 @@
public static class TestSubmitInput extends SubmitInput {
public final boolean failAfterRefUpdates;
- @SuppressWarnings("deprecation")
public TestSubmitInput(SubmitInput base, boolean failAfterRefUpdates) {
this.onBehalfOf = base.onBehalfOf;
- this.waitForMerge = base.waitForMerge;
this.failAfterRefUpdates = failAfterRefUpdates;
}
}
@@ -263,11 +265,18 @@
MergeOp.checkSubmitRule(c);
}
- Boolean csIsMergeable = isPatchSetMergeable(cs);
- if (csIsMergeable == null) {
+ Collection<ChangeData> unmergeable = unmergeableChanges(cs);
+ if (unmergeable == null) {
return CLICK_FAILURE_TOOLTIP;
- } else if (!csIsMergeable) {
- return CHANGES_NOT_MERGEABLE;
+ } else if (!unmergeable.isEmpty()) {
+ return CHANGES_NOT_MERGEABLE + Joiner.on(", ").join(
+ Iterables.transform(unmergeable,
+ new Function<ChangeData, String>() {
+ @Override
+ public String apply(ChangeData cd) {
+ return String.valueOf(cd.getId().get());
+ }
+ }));
}
} catch (ResourceConflictException e) {
return BLOCKED_SUBMIT_TOOLTIP;
@@ -407,11 +416,11 @@
return change != null ? change.getStatus().name().toLowerCase() : "deleted";
}
- public Boolean isPatchSetMergeable(ChangeSet cs)
+ public Collection<ChangeData> unmergeableChanges(ChangeSet cs)
throws OrmException, IOException {
- Map<ChangeData, Boolean> mergeabilityMap = new HashMap<>();
+ Set<ChangeData> mergeabilityMap = new HashSet<>();
for (ChangeData change : cs.changes()) {
- mergeabilityMap.put(change, false);
+ mergeabilityMap.add(change);
}
Multimap<Branch.NameKey, ChangeData> cbb = cs.changesByBranch();
@@ -442,17 +451,19 @@
// Skip whole check, cannot determine if mergeable
return null;
}
- mergeabilityMap.put(change, mergeable);
+ if (mergeable) {
+ mergeabilityMap.remove(change);
+ }
if (isLastInChain && isMergeCommit && mergeable) {
for (ChangeData c : targetBranch) {
- mergeabilityMap.put(c, true);
+ mergeabilityMap.remove(c);
}
break;
}
}
}
- return !mergeabilityMap.values().contains(Boolean.FALSE);
+ return mergeabilityMap;
}
private HashMap<Change.Id, RevCommit> findCommits(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestChangeReviewers.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestChangeReviewers.java
new file mode 100644
index 0000000..46fbe67
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestChangeReviewers.java
@@ -0,0 +1,76 @@
+// Copyright (C) 2016 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.change;
+
+import com.google.gerrit.extensions.common.SuggestedReviewerInfo;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.IdentifiedUser.GenericFactory;
+import com.google.gerrit.server.ReviewersUtil;
+import com.google.gerrit.server.ReviewersUtil.VisibilityControl;
+import com.google.gerrit.server.account.AccountVisibility;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.eclipse.jgit.lib.Config;
+
+import java.io.IOException;
+import java.util.List;
+
+public class SuggestChangeReviewers extends SuggestReviewers
+ implements RestReadView<ChangeResource> {
+ @Inject
+ SuggestChangeReviewers(AccountVisibility av,
+ GenericFactory identifiedUserFactory,
+ Provider<ReviewDb> dbProvider,
+ @GerritServerConfig Config cfg,
+ ReviewersUtil reviewersUtil) {
+ super(av, identifiedUserFactory, dbProvider, cfg, reviewersUtil);
+ }
+
+ @Override
+ public List<SuggestedReviewerInfo> apply(ChangeResource rsrc)
+ throws BadRequestException, OrmException, IOException {
+ return reviewersUtil.suggestReviewers(this,
+ rsrc.getControl().getProjectControl(), getVisibility(rsrc));
+ }
+
+ private VisibilityControl getVisibility(final ChangeResource rsrc) {
+ if (rsrc.getControl().getRefControl().isVisibleByRegisteredUsers()) {
+ return new VisibilityControl() {
+ @Override
+ public boolean isVisibleTo(Account.Id account) throws OrmException {
+ return true;
+ }
+ };
+ } else {
+ return new VisibilityControl() {
+ @Override
+ public boolean isVisibleTo(Account.Id account) throws OrmException {
+ IdentifiedUser who =
+ identifiedUserFactory.create(dbProvider, account);
+ // we can't use changeControl directly as it won't suggest reviewers
+ // to drafts
+ return rsrc.getControl().forUser(who).isRefVisible();
+ }
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestReviewers.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestReviewers.java
index 4561ae4..3b61033 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestReviewers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestReviewers.java
@@ -14,89 +14,33 @@
package com.google.gerrit.server.change;
-import com.google.common.base.Function;
-import com.google.common.base.MoreObjects;
-import com.google.common.base.Strings;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Ordering;
-import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.data.GroupReference;
-import com.google.gerrit.common.errors.NoSuchGroupException;
-import com.google.gerrit.extensions.common.AccountInfo;
-import com.google.gerrit.extensions.common.GroupBaseInfo;
-import com.google.gerrit.extensions.common.SuggestedReviewerInfo;
-import com.google.gerrit.extensions.restapi.BadRequestException;
-import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.extensions.restapi.Url;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.AccountExternalId;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.account.AccountCache;
-import com.google.gerrit.server.account.AccountControl;
-import com.google.gerrit.server.account.AccountLoader;
+import com.google.gerrit.server.ReviewersUtil;
import com.google.gerrit.server.account.AccountVisibility;
-import com.google.gerrit.server.account.GroupBackend;
-import com.google.gerrit.server.account.GroupMembers;
import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.project.NoSuchProjectException;
-import com.google.gerrit.server.project.ProjectControl;
-import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import org.eclipse.jgit.lib.Config;
import org.kohsuke.args4j.Option;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-public class SuggestReviewers implements RestReadView<ChangeResource> {
- private static final String MAX_SUFFIX = "\u9fa5";
+public class SuggestReviewers {
private static final int DEFAULT_MAX_SUGGESTED = 10;
private static final int DEFAULT_MAX_MATCHES = 100;
- private static final Ordering<SuggestedReviewerInfo> ORDERING =
- Ordering.natural().onResultOf(new Function<SuggestedReviewerInfo, String>() {
- @Nullable
- @Override
- public String apply(@Nullable SuggestedReviewerInfo suggestedReviewerInfo) {
- if (suggestedReviewerInfo == null) {
- return null;
- }
- return suggestedReviewerInfo.account != null
- ? MoreObjects.firstNonNull(suggestedReviewerInfo.account.email,
- Strings.nullToEmpty(suggestedReviewerInfo.account.name))
- : Strings.nullToEmpty(suggestedReviewerInfo.group.name);
- }
- });
- private final AccountLoader accountLoader;
- private final AccountControl accountControl;
- private final GroupMembers.Factory groupMembersFactory;
- private final AccountCache accountCache;
- private final Provider<ReviewDb> dbProvider;
- private final Provider<CurrentUser> currentUser;
- private final IdentifiedUser.GenericFactory identifiedUserFactory;
- private final GroupBackend groupBackend;
+ protected final Provider<ReviewDb> dbProvider;
+ protected final IdentifiedUser.GenericFactory identifiedUserFactory;
+ protected final ReviewersUtil reviewersUtil;
+
private final boolean suggestAccounts;
private final int suggestFrom;
private final int maxAllowed;
- private int limit;
- private String query;
+ protected int limit;
+ protected String query;
private boolean useFullTextSearch;
private final int fullTextMaxMatches;
- private final int maxSuggestedReviewers;
- private final ReviewerSuggestionCache reviewerSuggestionCache;
+ protected final int maxSuggestedReviewers;
@Option(name = "--limit", aliases = {"-n"}, metaVar = "CNT",
usage = "maximum number of reviewers to list")
@@ -112,27 +56,43 @@
this.query = q;
}
+ public String getQuery() {
+ return query;
+ }
+
+ public boolean getSuggestAccounts() {
+ return suggestAccounts;
+ }
+
+ public int getSuggestFrom() {
+ return suggestFrom;
+ }
+
+ public boolean getUseFullTextSearch() {
+ return useFullTextSearch;
+ }
+
+ public int getFullTextMaxMatches() {
+ return fullTextMaxMatches;
+ }
+
+ public int getLimit() {
+ return limit;
+ }
+
+ public int getMaxAllowed() {
+ return maxAllowed;
+ }
+
@Inject
- SuggestReviewers(AccountVisibility av,
- AccountLoader.Factory accountLoaderFactory,
- AccountControl.Factory accountControlFactory,
- AccountCache accountCache,
- GroupMembers.Factory groupMembersFactory,
+ public SuggestReviewers(AccountVisibility av,
IdentifiedUser.GenericFactory identifiedUserFactory,
- Provider<CurrentUser> currentUser,
Provider<ReviewDb> dbProvider,
@GerritServerConfig Config cfg,
- GroupBackend groupBackend,
- ReviewerSuggestionCache reviewerSuggestionCache) {
- this.accountLoader = accountLoaderFactory.create(true);
- this.accountControl = accountControlFactory.get();
- this.accountCache = accountCache;
- this.groupMembersFactory = groupMembersFactory;
+ ReviewersUtil reviewersUtil) {
this.dbProvider = dbProvider;
this.identifiedUserFactory = identifiedUserFactory;
- this.currentUser = currentUser;
- this.groupBackend = groupBackend;
- this.reviewerSuggestionCache = reviewerSuggestionCache;
+ this.reviewersUtil = reviewersUtil;
this.maxSuggestedReviewers =
cfg.getInt("suggest", "maxSuggestedReviewers", DEFAULT_MAX_SUGGESTED);
this.limit = this.maxSuggestedReviewers;
@@ -152,196 +112,4 @@
this.maxAllowed = cfg.getInt("addreviewer", "maxAllowed",
PostReviewers.DEFAULT_MAX_REVIEWERS);
}
-
- private interface VisibilityControl {
- boolean isVisibleTo(Account.Id account) throws OrmException;
- }
-
- @Override
- public List<SuggestedReviewerInfo> apply(ChangeResource rsrc)
- throws BadRequestException, OrmException, IOException {
- if (Strings.isNullOrEmpty(query)) {
- throw new BadRequestException("missing query field");
- }
-
- if (!suggestAccounts || query.length() < suggestFrom) {
- return Collections.emptyList();
- }
-
- VisibilityControl visibilityControl = getVisibility(rsrc);
- List<AccountInfo> suggestedAccounts;
- if (useFullTextSearch) {
- suggestedAccounts = suggestAccountFullTextSearch(visibilityControl);
- } else {
- suggestedAccounts = suggestAccount(visibilityControl);
- }
-
- List<SuggestedReviewerInfo> reviewer = Lists.newArrayList();
- for (AccountInfo a : suggestedAccounts) {
- SuggestedReviewerInfo info = new SuggestedReviewerInfo();
- info.account = a;
- reviewer.add(info);
- }
-
- Project p = rsrc.getControl().getProject();
- for (GroupReference g : suggestAccountGroup(
- rsrc.getControl().getProjectControl())) {
- if (suggestGroupAsReviewer(p, g, visibilityControl)) {
- GroupBaseInfo info = new GroupBaseInfo();
- info.id = Url.encode(g.getUUID().get());
- info.name = g.getName();
- SuggestedReviewerInfo suggestedReviewerInfo = new SuggestedReviewerInfo();
- suggestedReviewerInfo.group = info;
- reviewer.add(suggestedReviewerInfo);
- }
- }
-
- reviewer = ORDERING.immutableSortedCopy(reviewer);
- if (reviewer.size() <= limit) {
- return reviewer;
- } else {
- return reviewer.subList(0, limit);
- }
- }
-
- private VisibilityControl getVisibility(final ChangeResource rsrc) {
- if (rsrc.getControl().getRefControl().isVisibleByRegisteredUsers()) {
- return new VisibilityControl() {
- @Override
- public boolean isVisibleTo(Account.Id account) throws OrmException {
- return true;
- }
- };
- } else {
- return new VisibilityControl() {
- @Override
- public boolean isVisibleTo(Account.Id account) throws OrmException {
- IdentifiedUser who =
- identifiedUserFactory.create(dbProvider, account);
- // we can't use changeControl directly as it won't suggest reviewers
- // to drafts
- return rsrc.getControl().forUser(who).isRefVisible();
- }
- };
- }
- }
-
- private List<GroupReference> suggestAccountGroup(ProjectControl ctl) {
- return Lists.newArrayList(
- Iterables.limit(groupBackend.suggest(query, ctl), limit));
- }
-
- private List<AccountInfo> suggestAccount(VisibilityControl visibilityControl)
- throws OrmException {
- String a = query;
- String b = a + MAX_SUFFIX;
-
- Map<Account.Id, AccountInfo> r = new LinkedHashMap<>();
- Map<Account.Id, String> queryEmail = new HashMap<>();
-
- for (Account p : dbProvider.get().accounts()
- .suggestByFullName(a, b, limit)) {
- if (p.isActive()) {
- addSuggestion(r, p.getId(), visibilityControl);
- }
- }
-
- if (r.size() < limit) {
- for (Account p : dbProvider.get().accounts()
- .suggestByPreferredEmail(a, b, limit - r.size())) {
- if (p.isActive()) {
- addSuggestion(r, p.getId(), visibilityControl);
- }
- }
- }
-
- if (r.size() < limit) {
- for (AccountExternalId e : dbProvider.get().accountExternalIds()
- .suggestByEmailAddress(a, b, limit - r.size())) {
- if (!r.containsKey(e.getAccountId())) {
- Account p = accountCache.get(e.getAccountId()).getAccount();
- if (p.isActive()) {
- if (addSuggestion(r, p.getId(), visibilityControl)) {
- queryEmail.put(e.getAccountId(), e.getEmailAddress());
- }
- }
- }
- }
- }
-
- accountLoader.fill();
- for (Map.Entry<Account.Id, String> p : queryEmail.entrySet()) {
- AccountInfo info = r.get(p.getKey());
- if (info != null) {
- info.email = p.getValue();
- }
- }
- return new ArrayList<>(r.values());
- }
-
- private List<AccountInfo> suggestAccountFullTextSearch(
- VisibilityControl visibilityControl) throws IOException, OrmException {
- List<AccountInfo> results = reviewerSuggestionCache.search(
- query, fullTextMaxMatches);
-
- Iterator<AccountInfo> it = results.iterator();
- while (it.hasNext()) {
- Account.Id accountId = new Account.Id(it.next()._accountId);
- if (!(visibilityControl.isVisibleTo(accountId)
- && accountControl.canSee(accountId))) {
- it.remove();
- }
- }
-
- return results;
- }
-
- private boolean addSuggestion(Map<Account.Id, AccountInfo> map,
- Account.Id account, VisibilityControl visibilityControl)
- throws OrmException {
- if (!map.containsKey(account)
- // Can the suggestion see the change?
- && visibilityControl.isVisibleTo(account)
- // Can the account see the current user?
- && accountControl.canSee(account)) {
- map.put(account, accountLoader.get(account));
- return true;
- }
- return false;
- }
-
- private boolean suggestGroupAsReviewer(Project project,
- GroupReference group, VisibilityControl visibilityControl)
- throws OrmException, IOException {
- if (!PostReviewers.isLegalReviewerGroup(group.getUUID())) {
- return false;
- }
-
- try {
- Set<Account> members = groupMembersFactory
- .create(currentUser.get())
- .listAccounts(group.getUUID(), project.getNameKey());
-
- if (members.isEmpty()) {
- return false;
- }
-
- if (maxAllowed > 0 && members.size() > maxAllowed) {
- return false;
- }
-
- // require that at least one member in the group can see the change
- for (Account account : members) {
- if (visibilityControl.isVisibleTo(account.getId())) {
- return true;
- }
- }
- } catch (NoSuchGroupException e) {
- return false;
- } catch (NoSuchProjectException e) {
- return false;
- }
-
- return false;
- }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
index 7d9bd15..dee43ac 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -79,6 +79,7 @@
import com.google.gerrit.server.change.MergeabilityCacheImpl;
import com.google.gerrit.server.events.EventFactory;
import com.google.gerrit.server.events.EventsMetrics;
+import com.google.gerrit.server.extensions.events.ChangeIndexedListener;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.BatchUpdate;
import com.google.gerrit.server.git.EmailMerge;
@@ -272,6 +273,7 @@
DynamicSet.setOf(binder(), ReceivePackInitializer.class);
DynamicSet.setOf(binder(), PostReceiveHook.class);
DynamicSet.setOf(binder(), PreUploadHook.class);
+ DynamicSet.setOf(binder(), ChangeIndexedListener.class);
DynamicSet.setOf(binder(), NewProjectCreatedListener.class);
DynamicSet.setOf(binder(), ProjectDeletedListener.class);
DynamicSet.setOf(binder(), GarbageCollectorListener.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ChangeIndexedListener.java b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ChangeIndexedListener.java
new file mode 100644
index 0000000..f996724
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ChangeIndexedListener.java
@@ -0,0 +1,29 @@
+// Copyright (C) 2015 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.extensions.events;
+
+import com.google.gerrit.extensions.annotations.ExtensionPoint;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.server.query.change.ChangeData;
+
+/** Notified whenever a change is indexed or deleted from the index. */
+@ExtensionPoint
+public interface ChangeIndexedListener {
+ /** Invoked when a change is indexed. */
+ void onChangeIndexed(ChangeData change);
+
+ /** Invoked when a change is deleted from the index. */
+ void onChangeDeleted(Change.Id id);
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
index 72517dc..775ecae 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
@@ -1775,6 +1775,7 @@
.setExtraCC(recipients.getCcOnly())
.setApprovals(approvals)
.setMessage(msg)
+ .setNotify(magicBranch.notify)
.setRequestScopePropagator(requestScopePropagator)
.setSendMail(true)
.setUpdateRef(true));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyOp.java
index bdf9f052..b32d668 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyOp.java
@@ -361,8 +361,8 @@
PatchSet.Id psId = update.getPatchSetId();
ctx.getDb().patchSetApprovals().upsert(
convertPatchSet(normalized.getNormalized(), psId));
- ctx.getDb().patchSetApprovals().delete(
- convertPatchSet(normalized.deleted(), psId));
+ ctx.getDb().patchSetApprovals().update(
+ zero(convertPatchSet(normalized.deleted(), psId)));
for (PatchSetApproval psa : normalized.updated()) {
update.putApprovalFor(psa.getAccountId(), psa.getLabel(), psa.getValue());
}
@@ -400,6 +400,19 @@
return Iterables.transform(approvals, convertPatchSet(psId));
}
+ private static Iterable<PatchSetApproval> zero(
+ Iterable<PatchSetApproval> approvals) {
+ return Iterables.transform(approvals,
+ new Function<PatchSetApproval, PatchSetApproval>() {
+ @Override
+ public PatchSetApproval apply(PatchSetApproval in) {
+ PatchSetApproval copy = new PatchSetApproval(in.getPatchSetId(), in);
+ copy.setValue((short) 0);
+ return copy;
+ }
+ });
+ }
+
private String getByAccountName() {
checkNotNull(submitter,
"getByAccountName called before submitter populated");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndexer.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndexer.java
index a0860c7..0f9b459 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndexer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeIndexer.java
@@ -20,10 +20,12 @@
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.extensions.events.ChangeIndexedListener;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.util.RequestContext;
import com.google.gerrit.server.util.ThreadLocalRequestContext;
@@ -94,11 +96,13 @@
private final ChangeData.Factory changeDataFactory;
private final ThreadLocalRequestContext context;
private final ListeningExecutorService executor;
+ private final DynamicSet<ChangeIndexedListener> indexedListener;
@AssistedInject
ChangeIndexer(SchemaFactory<ReviewDb> schemaFactory,
ChangeData.Factory changeDataFactory,
ThreadLocalRequestContext context,
+ DynamicSet<ChangeIndexedListener> indexedListener,
@Assisted ListeningExecutorService executor,
@Assisted ChangeIndex index) {
this.executor = executor;
@@ -107,12 +111,14 @@
this.context = context;
this.index = index;
this.indexes = null;
+ this.indexedListener = indexedListener;
}
@AssistedInject
ChangeIndexer(SchemaFactory<ReviewDb> schemaFactory,
ChangeData.Factory changeDataFactory,
ThreadLocalRequestContext context,
+ DynamicSet<ChangeIndexedListener> indexedListener,
@Assisted ListeningExecutorService executor,
@Assisted IndexCollection indexes) {
this.executor = executor;
@@ -121,6 +127,7 @@
this.context = context;
this.index = null;
this.indexes = indexes;
+ this.indexedListener = indexedListener;
}
/**
@@ -160,6 +167,19 @@
for (ChangeIndex i : getWriteIndexes()) {
i.replace(cd);
}
+ fireChangeIndexedEvent(cd);
+ }
+
+ private void fireChangeIndexedEvent(ChangeData change) {
+ for (ChangeIndexedListener listener : indexedListener) {
+ listener.onChangeIndexed(change);
+ }
+ }
+
+ private void fireChangeDeletedFromIndexEvent(Change.Id id) {
+ for (ChangeIndexedListener listener : indexedListener) {
+ listener.onChangeDeleted(id);
+ }
}
/**
@@ -280,6 +300,7 @@
for (ChangeIndex i : getWriteIndexes()) {
i.delete(id);
}
+ fireChangeDeletedFromIndexEvent(id);
return null;
}
}
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 da1e7b5..19a0535 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
@@ -97,14 +97,13 @@
public PatchList get(Change change, PatchSet patchSet)
throws PatchListNotAvailableException {
Project.NameKey project = change.getProject();
- ObjectId a = null;
if (patchSet.getRevision() == null) {
throw new PatchListNotAvailableException(
"revision is null for " + patchSet.getId());
}
ObjectId b = ObjectId.fromString(patchSet.getRevision().get());
Whitespace ws = Whitespace.IGNORE_NONE;
- return get(new PatchListKey(a, b, ws), project);
+ return get(new PatchListKey(null, b, ws), project);
}
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java
index 1d717ef..0146f84 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java
@@ -40,10 +40,6 @@
public class ListPlugins implements RestReadView<TopLevelResource> {
private final PluginLoader pluginLoader;
- @Deprecated
- @Option(name = "--format", usage = "(deprecated) output format")
- private OutputFormat format = OutputFormat.TEXT;
-
@Option(name = "--all", aliases = {"-a"}, usage = "List all plugins, including disabled plugins")
private boolean all;
@@ -52,23 +48,12 @@
this.pluginLoader = pluginLoader;
}
- public OutputFormat getFormat() {
- return format;
- }
-
- public ListPlugins setFormat(OutputFormat fmt) {
- this.format = fmt;
- return this;
- }
-
@Override
public Object apply(TopLevelResource resource) {
- format = OutputFormat.JSON;
return display(null);
}
public JsonElement display(PrintWriter stdout) {
- Map<String, PluginInfo> output = Maps.newTreeMap();
List<Plugin> plugins = Lists.newArrayList(pluginLoader.getPlugins(all));
Collections.sort(plugins, new Comparator<Plugin>() {
@Override
@@ -77,33 +62,28 @@
}
});
- if (!format.isJson()) {
+ if (stdout == null) {
+ Map<String, PluginInfo> output = Maps.newTreeMap();
+ for (Plugin p : plugins) {
+ PluginInfo info = new PluginInfo(p);
+ output.put(p.getName(), info);
+ }
+ return OutputFormat.JSON.newGson().toJsonTree(
+ output,
+ new TypeToken<Map<String, Object>>() {}.getType());
+ } else {
stdout.format("%-30s %-10s %-8s %s\n", "Name", "Version", "Status", "File");
stdout.print("-------------------------------------------------------------------------------\n");
- }
-
- for (Plugin p : plugins) {
- PluginInfo info = new PluginInfo(p);
- if (format.isJson()) {
- output.put(p.getName(), info);
- } else {
+ for (Plugin p : plugins) {
+ PluginInfo info = new PluginInfo(p);
stdout.format("%-30s %-10s %-8s %s\n", p.getName(),
Strings.nullToEmpty(info.version),
p.isDisabled() ? "DISABLED" : "ENABLED",
p.getSrcFile().getFileName());
+ stdout.print('\n');
}
+ stdout.flush();
}
-
- if (stdout == null) {
- return OutputFormat.JSON.newGson().toJsonTree(
- output,
- new TypeToken<Map<String, Object>>() {}.getType());
- } else if (format.isJson()) {
- format.newGson().toJson(output,
- new TypeToken<Map<String, PluginInfo>>() {}.getType(), stdout);
- stdout.print('\n');
- }
- stdout.flush();
return null;
}
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 312ac89..53a7c3b 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
@@ -96,8 +96,8 @@
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
-import java.util.concurrent.TimeUnit;
import java.util.Map;
+import java.util.concurrent.TimeUnit;
@Ignore
public abstract class AbstractQueryChangesTest extends GerritServerTests {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeEmailSender.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeEmailSender.java
index 7adf721..f2d563e 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeEmailSender.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeEmailSender.java
@@ -97,6 +97,13 @@
messages.add(Message.create(from, rcpt, headers, body));
}
+ public void clear() {
+ waitForEmails();
+ synchronized (messages) {
+ messages.clear();
+ }
+ }
+
public ImmutableList<Message> getMessages() {
waitForEmails();
synchronized (messages) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java
index cec3249..fed8226 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshLog.java
@@ -157,6 +157,9 @@
}
private Multimap<String, ?> extractParameters(DispatchCommand dcmd) {
+ if (dcmd == null) {
+ return ArrayListMultimap.create(0, 0);
+ }
String[] cmdArgs = dcmd.getArguments();
String paramName = null;
int argPos = 0;
@@ -274,6 +277,9 @@
}
private String extractWhat(DispatchCommand dcmd) {
+ if (dcmd == null) {
+ return "Command was already destroyed";
+ }
StringBuilder commandName = new StringBuilder(dcmd.getCommandName());
String[] args = dcmd.getArguments();
for (int i = 1; i < args.length; i++) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
index ce969da..00cf53f 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
@@ -262,9 +262,6 @@
}
private void reviewPatchSet(final PatchSet patchSet) throws Exception {
- if (changeComment == null) {
- changeComment = "";
- }
if (notify == null) {
notify = NotifyHandling.ALL;
}
@@ -283,22 +280,20 @@
}
review.labels.putAll(customLabels);
- // If review labels are being applied, the comment will be included
- // on the review note. We don't need to add it again on the abandon
- // or restore comment.
- if (!review.labels.isEmpty() && (abandonChange || restoreChange)) {
- changeComment = null;
+ // We don't need to add the review comment when abandoning/restoring.
+ if (abandonChange || restoreChange) {
+ review.message = null;
}
try {
if (abandonChange) {
AbandonInput input = new AbandonInput();
- input.message = changeComment;
+ input.message = Strings.emptyToNull(changeComment);
applyReview(patchSet, review);
changeApi(patchSet).abandon(input);
} else if (restoreChange) {
RestoreInput input = new RestoreInput();
- input.message = changeComment;
+ input.message = Strings.emptyToNull(changeComment);
changeApi(patchSet).restore(input);
applyReview(patchSet, review);
} else {
diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
index cf3e76c..20c3c2a 100644
--- a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
+++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
@@ -19,6 +19,7 @@
import com.google.common.base.Splitter;
import com.google.gerrit.common.ChangeHookRunner;
+import com.google.gerrit.common.EventBroker;
import com.google.gerrit.gpg.GpgModule;
import com.google.gerrit.httpd.auth.oauth.OAuthModule;
import com.google.gerrit.httpd.auth.openid.OpenIdModule;
@@ -294,6 +295,7 @@
private Injector createSysInjector() {
final List<Module> modules = new ArrayList<>();
modules.add(new DropWizardMetricMaker.RestModule());
+ modules.add(new EventBroker.Module());
modules.add(new ChangeHookRunner.Module());
modules.add(new ReceiveCommitsExecutorModule());
modules.add(new DiffExecutorModule());
diff --git a/lib/commons/BUCK b/lib/commons/BUCK
index 2ed62f6..cc503a3 100644
--- a/lib/commons/BUCK
+++ b/lib/commons/BUCK
@@ -10,8 +10,8 @@
maven_jar(
name = 'collections',
- id = 'commons-collections:commons-collections:3.2.1',
- sha1 = '761ea405b9b37ced573d2df0d1e3a4e0f9edc668',
+ id = 'commons-collections:commons-collections:3.2.2',
+ sha1 = '8ad72fe39fa8c91eaaf12aadb21e0c3661fe26d5',
license = 'Apache2.0',
exclude = ['META-INF/LICENSE.txt', 'META-INF/NOTICE.txt'],
attach_source = False,
diff --git a/lib/js/BUCK b/lib/js/BUCK
index 56d09b6..948f9b4 100644
--- a/lib/js/BUCK
+++ b/lib/js/BUCK
@@ -94,28 +94,28 @@
bower_component(
name = 'iron-a11y-keys-behavior',
package = 'polymerelements/iron-a11y-keys-behavior',
- version = '1.1.0',
+ version = '1.1.1',
deps = [':polymer'],
license = 'polymer',
- sha1 = '0b7962ed8409336652da4b4e83d052dbe53d4e1a',
+ sha1 = '6bb52b967a4fb242897520dad6c366135e3813ce',
)
bower_component(
name = 'iron-ajax',
package = 'polymerelements/iron-ajax',
- version = '1.1.0',
+ version = '1.2.0',
deps = [
':polymer',
':promise-polyfill',
],
license = 'polymer',
- sha1 = 'f94a3a3d847842c49def41e27da42c7c94f8d7c7',
+ sha1 = 'f195d0d0ddef73a20573b0a02ce6a505cc1d7014',
)
bower_component(
name = 'iron-autogrow-textarea',
package = 'polymerelements/iron-autogrow-textarea',
- version = '1.0.10',
+ version = '1.0.12',
deps = [
':iron-behaviors',
':iron-flex-layout',
@@ -124,25 +124,25 @@
':polymer',
],
license = 'polymer',
- sha1 = 'd368240e60a4b02ffc731ad8f45f3c8bbf47e9bd',
+ sha1 = 'b9b6874c9a2b5be435557a827ff8bd6661672ee3',
)
bower_component(
name = 'iron-behaviors',
package = 'polymerelements/iron-behaviors',
- version = '1.0.11',
+ version = '1.0.13',
deps = [
':iron-a11y-keys-behavior',
':polymer',
],
license = 'polymer',
- sha1 = 'e0fcfcd8696381fc78ff62261ba333e5e133f39d',
+ sha1 = 'e9bcdac5414cb8282b5f75eeb51c9154380045af',
)
bower_component(
name = 'iron-dropdown',
package = 'polymerelements/iron-dropdown',
- version = '1.0.6',
+ version = '1.2.0',
deps = [
':iron-a11y-keys-behavior',
':iron-behaviors',
@@ -152,25 +152,25 @@
':polymer',
],
license = 'polymer',
- sha1 = 'b54ff404ce5535919979bb4488e4b6ae9146fc5a',
+ sha1 = 'ca97cbfe5873324ba8af80dbdf79af9e72b6f0b8',
)
bower_component(
name = 'iron-fit-behavior',
package = 'polymerelements/iron-fit-behavior',
- version = '1.0.5',
+ version = '1.0.6',
deps = [':polymer'],
license = 'polymer',
- sha1 = 'c0273d22531451a1e64f447971ad16b357a7f7e0',
+ sha1 = '28df0349d3cb20ac5e4aeb40651ef7d84de75fb0',
)
bower_component(
name = 'iron-flex-layout',
package = 'polymerelements/iron-flex-layout',
- version = '1.2.2',
+ version = '1.3.1',
deps = [':polymer'],
license = 'polymer',
- sha1 = '3ca2fbbf3b56d95677663f78304262dee68753c3',
+ sha1 = 'ba696394abff5e799fc06eb11bff4720129a1b52',
)
bower_component(
@@ -185,13 +185,13 @@
bower_component(
name = 'iron-input',
package = 'polymerelements/iron-input',
- version = '1.0.6',
+ version = '1.0.8',
deps = [
':iron-validatable-behavior',
':polymer',
],
license = 'polymer',
- sha1 = '2d3eedf0a26046c0e828b1ce3d5b102ee1d0ab19',
+ sha1 = '568c407ffbb524fe2c9ad8230eb895d76c9a8671',
)
bower_component(
@@ -206,32 +206,33 @@
bower_component(
name = 'iron-overlay-behavior',
package = 'polymerelements/iron-overlay-behavior',
- version = '1.1.1',
+ version = '1.4.2',
deps = [
+ ':iron-a11y-keys-behavior',
':iron-fit-behavior',
':iron-resizable-behavior',
':polymer',
],
license = 'polymer',
- sha1 = '98d80ea1cbee2631553d4fbc98da6cbb25748a4f',
+ sha1 = 'babdd95d7efd63bf3f2969a8f1036e8f324979a9',
)
bower_component(
name = 'iron-resizable-behavior',
package = 'polymerelements/iron-resizable-behavior',
- version = '1.0.2',
+ version = '1.0.3',
deps = [':polymer'],
license = 'polymer',
- sha1 = '954e82c70b5412d20e7b4d65195a844bb6dc9a07',
+ sha1 = '5982a3e19af7ed3e3de276a9b7bd266b3a144002',
)
bower_component(
name = 'iron-selector',
package = 'polymerelements/iron-selector',
- version = '1.0.8',
+ version = '1.2.5',
deps = [':polymer'],
license = 'polymer',
- sha1 = '7559560733882656bf479b620669a1d60c3bda21',
+ sha1 = '7728750bc9dfa858915dfd25397709bdbdaee2b1',
)
bower_component(
@@ -258,7 +259,7 @@
bower_component(
name = 'neon-animation',
package = 'polymerelements/neon-animation',
- version = '1.0.8',
+ version = '1.1.1',
deps = [
':iron-meta',
':iron-resizable-behavior',
@@ -268,7 +269,7 @@
':web-animations-js',
],
license = 'polymer',
- sha1 = 'c5f3700e9259554db14f9dfddb290a42c099d88a',
+ sha1 = 'd6e1b45e5a936d0ec0b66b3520e230e9d8605642',
)
bower_component(
@@ -282,23 +283,23 @@
bower_component(
name = 'paper-styles',
package = 'polymerelements/paper-styles',
- version = '1.0.13',
+ version = '1.1.4',
deps = [
':font-roboto',
':iron-flex-layout',
':polymer',
],
license = 'polymer',
- sha1 = 'e0bfdadfe10e070f39c16aa784de16734eed25a6',
+ sha1 = '89276c5ec18b8927a704dda2bf14ff35c310401a',
)
bower_component(
name = 'polymer',
package = 'polymer/polymer',
- version = '1.2.4',
+ version = '1.3.1',
deps = [':webcomponentsjs'],
license = 'polymer',
- sha1 = 'bcc1356d8e7da1a2e339b953e4e056cc13b58bd4',
+ sha1 = '5f54c14f7b8cecdb356e446a84dabb4ba349d278',
)
bower_component(
@@ -321,15 +322,15 @@
bower_component(
name = 'web-animations-js',
package = 'web-animations/web-animations-js',
- version = '2.1.2',
+ version = '2.1.4',
license = 'Apache2.0',
- sha1 = '3e2f4648b770183f577cb5171785cfedcb3a960b',
+ sha1 = '92f06d8417a51f1f75c94b7a19616e19695cc6db',
)
bower_component(
name = 'webcomponentsjs',
package = 'webcomponentsjs',
- version = '0.7.20',
+ version = '0.7.21',
license = 'polymer',
- sha1 = '95bb33be5aee6af2f49b5d8ca5cfcb4989e89ba8',
+ sha1 = 'ceb96b01c8a86b17831a25d6ab9eca95226c408e',
)
diff --git a/plugins/replication b/plugins/replication
index 2044446..f74f8f5 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit 204444637abfb38254667f73b6cd1242daf19e24
+Subproject commit f74f8f500ee8ea18dca64bada77c8e39e3d1f742
diff --git a/polygerrit-ui/app/BUCK b/polygerrit-ui/app/BUCK
index 93cf614..391aa97 100644
--- a/polygerrit-ui/app/BUCK
+++ b/polygerrit-ui/app/BUCK
@@ -1,6 +1,9 @@
include_defs('//lib/js.defs')
-WCT_TEST_PATTERNS = ['test/**']
+WCT_TEST_PATTERNS = [
+ 'test/**',
+ '**/*_test.html',
+]
PY_TEST_PATTERNS = ['polygerrit_wct_tests.py']
APP_SRCS = glob(
['**'],
diff --git a/polygerrit-ui/app/behaviors/rest-client-behavior.html b/polygerrit-ui/app/behaviors/rest-client-behavior.html
index 55cd2b1..f6897cd 100644
--- a/polygerrit-ui/app/behaviors/rest-client-behavior.html
+++ b/polygerrit-ui/app/behaviors/rest-client-behavior.html
@@ -98,6 +98,10 @@
}
return v;
},
+
+ changePath: function(changeNum) {
+ return '/c/' + changeNum;
+ },
};
window.Gerrit = window.Gerrit || {};
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html
index 0a4aec4..39dda7f 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html
@@ -20,7 +20,7 @@
<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
<script src="../../../bower_components/web-component-tester/browser.js"></script>
-<script src="../../../scripts/fake-app.js"></script>
+<script src="../../../test/fake-app.js"></script>
<script src="../../../scripts/util.js"></script>
<link rel="import" href="gr-change-list-item.html">
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html
index acb8789..ca9da5b 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html
@@ -21,7 +21,7 @@
<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
<script src="../../../bower_components/web-component-tester/browser.js"></script>
<script src="../../../bower_components/page/page.js"></script>
-<script src="../../../scripts/fake-app.js"></script>
+<script src="../../../test/fake-app.js"></script>
<script src="../../../scripts/util.js"></script>
<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
index 1593fab..582a28a 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
@@ -23,6 +23,7 @@
<link rel="import" href="../../shared/gr-overlay/gr-overlay.html">
<link rel="import" href="../../shared/gr-request/gr-request.html">
+<link rel="import" href="../gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.html">
<link rel="import" href="../gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.html">
<dom-module id="gr-change-actions">
@@ -78,6 +79,12 @@
on-confirm="_handleRebaseConfirm"
on-cancel="_handleConfirmDialogCancel"
hidden></gr-confirm-rebase-dialog>
+ <gr-confirm-cherrypick-dialog id="confirmCherrypick"
+ class="confirmDialog"
+ message="[[commitMessage]]"
+ on-confirm="_handleCherrypickConfirm"
+ on-cancel="_handleConfirmDialogCancel"
+ hidden></gr-confirm-cherrypick-dialog>
</gr-overlay>
</template>
<script src="gr-change-actions.js"></script>
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
index a89c0aa..04b9bb5 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
@@ -23,6 +23,7 @@
// TODO(andybons): Add the rest of the revision actions.
var RevisionActions = {
+ CHERRYPICK: 'cherrypick',
DELETE: '/',
PUBLISH: 'publish',
REBASE: 'rebase',
@@ -44,6 +45,7 @@
},
changeNum: String,
patchNum: String,
+ commitMessage: String,
_loading: {
type: Boolean,
value: true,
@@ -67,12 +69,7 @@
},
_actionsChanged: function(actions, revisionActions) {
- this.hidden =
- revisionActions.rebase == null &&
- revisionActions.submit == null &&
- revisionActions.publish == null &&
- actions.abandon == null &&
- actions.restore == null;
+ this.hidden = actions.length == 0 && revisionActions.length == 0;
},
_computeRevisionActionsPath: function(changeNum, patchNum) {
@@ -100,6 +97,7 @@
_computeLoadingLabel: function(action) {
return {
+ 'cherrypick': 'Cherry-Picking...',
'rebase': 'Rebasing...',
'submit': 'Submitting...',
}[action];
@@ -124,7 +122,10 @@
var type = el.getAttribute('data-action-type');
if (type == 'revision') {
if (key == RevisionActions.REBASE) {
- this._showRebaseDialog();
+ this._showActionDialog(this.$.confirmRebase);
+ return;
+ } else if (key == RevisionActions.CHERRYPICK) {
+ this._showActionDialog(this.$.confirmCherrypick);
return;
}
this._fireRevisionAction(this._prependSlash(key),
@@ -167,6 +168,28 @@
payload);
},
+ _handleCherrypickConfirm: function() {
+ var el = this.$.confirmCherrypick;
+ if (!el.branch) {
+ // TODO(davido): Fix error handling
+ alert('The destination branch can’t be empty.');
+ return;
+ }
+ if (!el.message) {
+ alert('The commit message can’t be empty.');
+ return;
+ }
+ this.$.overlay.close();
+ el.hidden = false;
+ this._fireRevisionAction('/cherrypick',
+ this._revisionActions.cherrypick,
+ {
+ destination: el.branch,
+ message: el.message,
+ }
+ );
+ },
+
_fireChangeAction: function(endpoint, action) {
this._send(action.method, {}, endpoint).then(
function() {
@@ -193,8 +216,12 @@
}
this._send(action.method, opt_payload, endpoint, true).then(
- function() {
- this.fire('reload-change', null, {bubbles: false});
+ function(req) {
+ if (action.__key == RevisionActions.CHERRYPICK) {
+ page.show(this.changePath(req.response._number));
+ } else {
+ this.fire('reload-change', null, {bubbles: false});
+ }
enableButton();
}.bind(this)).catch(function(err) {
// TODO(andybons): Handle merge conflict (409 status);
@@ -205,8 +232,8 @@
});
},
- _showRebaseDialog: function() {
- this.$.confirmRebase.hidden = false;
+ _showActionDialog: function(dialog) {
+ dialog.hidden = false;
this.$.overlay.open();
},
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
index 1c5eb9c..cc94404 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
@@ -36,7 +36,7 @@
var element;
var server;
- setup(function() {
+ setup(function(done) {
element = fixture('basic');
server = sinon.fakeServer.create();
@@ -91,15 +91,17 @@
element.changeNum = '42';
element.patchNum = '2';
- element.reload();
+ element.reload().then(function() {
+ done();
+ });
server.respond();
});
- test('submit and rebase buttons show', function(done) {
+ test('submit, rebase, and cherry-pick buttons show', function(done) {
flush(function() {
var buttonEls = Polymer.dom(element.root).querySelectorAll('gr-button');
- assert.equal(buttonEls.length, 2);
+ assert.equal(buttonEls.length, 3);
assert.isFalse(element.hidden);
done();
});
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
index 7b1a2f1..22cf176 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
@@ -20,8 +20,6 @@
<link rel="import" href="../../shared/gr-date-formatter/gr-date-formatter.html">
<link rel="import" href="../gr-reviewer-list/gr-reviewer-list.html">
-<script src="../../../scripts/fake-app.js"></script>
-
<dom-module id="gr-change-metadata">
<template>
<style>
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
index 6c97b5a..f437dbc 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
@@ -24,6 +24,7 @@
<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
<link rel="import" href="gr-change-metadata.html">
+<script src="../../../test/fake-app.js"></script>
<script src="../../../scripts/util.js"></script>
<test-fixture id="basic">
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
index 1dbdbfb..a394b2b 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
@@ -21,9 +21,10 @@
<link rel="import" href="../../shared/gr-ajax/gr-ajax.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../../shared/gr-change-star/gr-change-star.html">
-<link rel="import" href="../../shared/gr-overlay/gr-overlay.html">
<link rel="import" href="../../shared/gr-date-formatter/gr-date-formatter.html">
<link rel="import" href="../../shared/gr-linked-text/gr-linked-text.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-change-actions/gr-change-actions.html">
<link rel="import" href="../gr-change-metadata/gr-change-metadata.html">
@@ -79,7 +80,7 @@
}
.download,
.patchSelectLabel {
- margin-left: var(--default-horizontal-margin);
+ margin-left: 1em;
}
.header select {
margin-left: .5em;
@@ -88,8 +89,7 @@
margin-left: var(--default-horizontal-margin);
}
gr-reply-dialog {
- min-width: 30em;
- max-width: 50em;
+ width: 50em;
}
.changeStatus {
color: #999;
@@ -243,8 +243,12 @@
<span class="changeStatus">[[_computeChangeStatus(_change, _patchNum)]]</span>
</span>
<span class="header-actions">
- <gr-button class="reply" hidden$="[[!_loggedIn]]" hidden on-tap="_handleReplyTap">Reply</gr-button>
- <gr-button link class="download" on-tap="_handleDownloadTap">Download</gr-button>
+ <gr-button hidden
+ class="reply"
+ primary$="[[_computeReplyButtonHighlighted(_diffDrafts)]]"
+ hidden$="[[!_loggedIn]]"
+ on-tap="_handleReplyTap">[[_replyButtonLabel]]</gr-button>
+ <gr-button class="download" on-tap="_handleDownloadTap">Download</gr-button>
<span>
<label class="patchSelectLabel" for="patchSetSelect">Patch set</label>
<select id="patchSetSelect" on-change="_handlePatchChange">
@@ -269,6 +273,7 @@
actions="[[_change.actions]]"
change-num="[[_changeNum]]"
patch-num="[[_patchNum]]"
+ commit-message="[[_commitInfo.message]]"
on-reload-change="_handleReloadChange"></gr-change-actions>
</div>
<div class="changeInfo-column commitAndRelated">
@@ -290,6 +295,7 @@
change-num="[[_changeNum]]"
patch-num="[[_patchNum]]"
comments="[[_comments]]"
+ drafts="[[_diffDrafts]]"
selected-index="{{viewState.selectedFileIndex}}"></gr-file-list>
<gr-messages-list id="messageList"
change-num="[[_changeNum]]"
@@ -314,10 +320,12 @@
patch-num="[[_patchNum]]"
labels="[[_change.labels]]"
permitted-labels="[[_change.permitted_labels]]"
+ diff-drafts="[[_diffDrafts]]"
on-send="_handleReplySent"
on-cancel="_handleReplyCancel"
hidden$="[[!_loggedIn]]">Reply</gr-reply-dialog>
</gr-overlay>
+ <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
</template>
<script src="gr-change-view.js"></script>
</dom-module>
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
index a42a379..9af6aa9 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
@@ -49,6 +49,7 @@
},
_commitInfo: Object,
_changeNum: String,
+ _diffDrafts: Object,
_patchNum: String,
_allPatchSets: {
type: Array,
@@ -66,6 +67,11 @@
type: Function,
value: function() { return this._handleBodyScroll.bind(this); },
},
+ _replyButtonLabel: {
+ type: String,
+ value: 'Reply',
+ computed: '_computeReplyButtonLabel(_diffDrafts)',
+ },
},
behaviors: [
@@ -74,13 +80,14 @@
],
ready: function() {
- app.accountReady.then(function() {
- this._loggedIn = app.loggedIn;
- }.bind(this));
this._headerEl = this.$$('.header');
},
attached: function() {
+ this._getLoggedIn().then(function(loggedIn) {
+ this._loggedIn = loggedIn;
+ }.bind(this));
+
window.addEventListener('scroll', this._boundScrollHandler);
},
@@ -116,10 +123,10 @@
var currentPatchNum =
this._change.revisions[this._change.current_revision]._number;
if (patchNum == currentPatchNum) {
- page.show(this._computeChangePath(this._changeNum));
+ page.show(this.changePath(this._changeNum));
return;
}
- page.show(this._computeChangePath(this._changeNum) + '/' + patchNum);
+ page.show(this.changePath(this._changeNum) + '/' + patchNum);
},
_handleReplyTap: function(e) {
@@ -145,9 +152,6 @@
},
_handleReplyOverlayOpen: function(e) {
- this.$.replyDialog.reload().then(function() {
- this.async(function() { this.$.replyOverlay.center() }, 1);
- }.bind(this));
this.$.replyDialog.focus();
},
@@ -186,8 +190,8 @@
}
}.bind(this), 1);
- app.accountReady.then(function() {
- if (!this._loggedIn) { return; }
+ this._getLoggedIn().then(function(loggedIn) {
+ if (!loggedIn) { return; }
if (this.viewState.showReplyDialog) {
this.$.replyOverlay.open();
@@ -206,10 +210,6 @@
this.fire('title-change', {title: title});
},
- _computeChangePath: function(changeNum) {
- return '/c/' + changeNum;
- },
-
_computeChangePermalink: function(changeNum) {
return '/' + changeNum;
},
@@ -305,11 +305,30 @@
return result;
},
+ _computeReplyButtonHighlighted: function(drafts) {
+ return Object.keys(drafts || {}).length > 0;
+ },
+
+ _computeReplyButtonLabel: function(drafts) {
+ drafts = drafts || {};
+ var draftCount = Object.keys(drafts).reduce(function(count, file) {
+ return count + drafts[file].length;
+ }, 0);
+
+ var label = 'Reply';
+ if (draftCount > 0) {
+ label += ' (' + draftCount + ')';
+ }
+ return label;
+ },
+
_handleKey: function(e) {
if (this.shouldSupressKeyboardShortcut(e)) { return; }
switch (e.keyCode) {
case 65: // 'a'
+ if (!this._loggedIn) { return; }
+
e.preventDefault();
this.$.replyOverlay.open();
break;
@@ -321,12 +340,37 @@
},
_handleReloadChange: function() {
- page.show(this._computeChangePath(this._changeNum));
+ page.show(this.changePath(this._changeNum));
+ },
+
+ _getDiffDrafts: function() {
+ return this.$.restAPI.getDiffDrafts(this._changeNum).then(
+ function(drafts) { return this._diffDrafts = drafts; }.bind(this));
+ },
+
+ _getLoggedIn: function() {
+ return this.$.restAPI.getLoggedIn();
+ },
+
+ _reloadDiffDrafts: function() {
+ this._diffDrafts = {};
+ this._getDiffDrafts().then(function() {
+ if (this.$.replyOverlay.opened) {
+ this.async(function() { this.$.replyOverlay.center(); }, 1);
+ }
+ }.bind(this));
},
_reload: function() {
+ this._getLoggedIn().then(function(loggedIn) {
+ if (!loggedIn) { return; }
+
+ this._reloadDiffDrafts();
+ }.bind(this));
+
var detailCompletes = this.$.detailXHR.generateRequest().completes;
this.$.commentsXHR.generateRequest();
+
var reloadPatchNumDependentResources = function() {
return Promise.all([
this.$.commitInfoXHR.generateRequest().completes,
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
index ed9d28d..af14e06 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
@@ -21,7 +21,7 @@
<script src="../../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
<script src="../../../bower_components/web-component-tester/browser.js"></script>
<script src="../../../bower_components/page/page.js"></script>
-<script src="../../../scripts/fake-app.js"></script>
+<script src="../../../test/fake-app.js"></script>
<script src="../../../scripts/util.js"></script>
<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
@@ -67,13 +67,37 @@
'Should navigate to /');
showStub.restore();
+
MockInteractions.pressAndReleaseKeyOn(element, 65); // 'a'
var overlayEl = element.$.replyOverlay;
+ assert.isFalse(overlayEl.opened);
+ element._loggedIn = true;
+
+ MockInteractions.pressAndReleaseKeyOn(element, 65); // 'a'
assert.isTrue(overlayEl.opened);
overlayEl.close();
assert.isFalse(overlayEl.opened);
});
+ test('reply button is highlighted when there are drafts', function() {
+ var replyButton = element.$$('gr-button.reply');
+ assert.ok(replyButton);
+ assert.isFalse(replyButton.hasAttribute('primary'));
+
+ element._diffDrafts = null;
+ assert.isFalse(replyButton.hasAttribute('primary'));
+
+ element._diffDrafts = {};
+ assert.isFalse(replyButton.hasAttribute('primary'));
+
+ element._diffDrafts = {
+ 'file1.txt': [{}],
+ 'file2.txt': [{}, {}],
+ };
+ assert.isTrue(replyButton.hasAttribute('primary'));
+ assert.equal(replyButton.textContent, 'Reply (3)');
+ });
+
test('patch num change', function(done) {
element._changeNum = '42';
element._patchNum = 2;
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.html b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.html
new file mode 100644
index 0000000..b21575b
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.html
@@ -0,0 +1,74 @@
+<!--
+Copyright (C) 2016 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/iron-autogrow-textarea/iron-autogrow-textarea.html">
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../shared/gr-confirm-dialog/gr-confirm-dialog.html">
+
+<dom-module id="gr-confirm-cherrypick-dialog">
+ <template>
+ <style>
+ :host {
+ display: block;
+ width: 30em;
+ }
+ :host([disabled]) {
+ opacity: .5;
+ pointer-events: none;
+ }
+ label {
+ cursor: pointer;
+ }
+ iron-autogrow-textarea {
+ padding: 0;
+ }
+ .main label,
+ .main input[type="text"] {
+ display: block;
+ font: inherit;
+ width: 100%;
+ }
+ .main .message {
+ border: groove;
+ width: 100%;
+ }
+ </style>
+ <gr-confirm-dialog
+ confirm-label="Cherry Pick"
+ on-confirm="_handleConfirmTap"
+ on-cancel="_handleCancelTap">
+ <div class="header">Cherry Pick Change to Another Branch</div>
+ <div class="main">
+ <label for="branchInput">
+ Cherry Pick to branch
+ </label>
+ <input is="iron-input"
+ type="text"
+ id="branchInput"
+ bind-value="{{branch}}"
+ placeholder="Destination branch">
+ <label for="messageInput">
+ Cherry Pick Commit Message
+ </label>
+ <iron-autogrow-textarea
+ id="messageInput"
+ class="message"
+ bind-value="{{message}}"></iron-autogrow-textarea>
+ </div>
+ </gr-confirm-dialog>
+ </template>
+ <script src="gr-confirm-cherrypick-dialog.js"></script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.js b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.js
new file mode 100644
index 0000000..f27e4e2
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.js
@@ -0,0 +1,47 @@
+// Copyright (C) 2016 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-confirm-cherrypick-dialog',
+
+ /**
+ * Fired when the confirm button is pressed.
+ *
+ * @event confirm
+ */
+
+ /**
+ * Fired when the cancel button is pressed.
+ *
+ * @event cancel
+ */
+
+ properties: {
+ branch: String,
+ message: String,
+ },
+
+ _handleConfirmTap: function(e) {
+ e.preventDefault();
+ this.fire('confirm', null, {bubbles: false});
+ },
+
+ _handleCancelTap: function(e) {
+ e.preventDefault();
+ this.fire('cancel', null, {bubbles: false});
+ },
+ });
+})();
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 e010468..d94a828 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
@@ -16,9 +16,8 @@
<link rel="import" href="../../../bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/keyboard-shortcut-behavior.html">
-<link rel="import" href="../../../behaviors/rest-client-behavior.html">
-<link rel="import" href="../../shared/gr-ajax/gr-ajax.html">
<link rel="import" href="../../shared/gr-request/gr-request.html">
+<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<dom-module id="gr-file-list">
<template>
@@ -49,6 +48,9 @@
visibility: hidden;
width: 1.25em;
}
+ .row:not(.header):hover {
+ background-color: #f5fafd;
+ }
.row[selected] {
background-color: #ebf5fb;
}
@@ -112,17 +114,6 @@
}
}
</style>
- <gr-ajax id="filesXHR"
- url="[[_computeFilesURL(changeNum, patchNum)]]"
- on-response="_handleResponse"></gr-ajax>
- <gr-ajax id="draftsXHR"
- url="[[_computeDraftsURL(changeNum, patchNum)]]"
- last-response="{{_drafts}}"></gr-ajax>
- <gr-ajax id="reviewedXHR"
- url="[[_computeReviewedURL(changeNum, patchNum)]]"
- last-response="{{_reviewed}}"></gr-ajax>
- </gr-ajax>
-
<div class="row header">
<div class="positionIndicator"></div>
<div class="reviewed" hidden$="[[!_loggedIn]]" hidden></div>
@@ -131,7 +122,7 @@
<div class="comments">Comments</div>
<div class="stats">Stats</div>
</div>
- <template is="dom-repeat" items="{{files}}" as="file">
+ <template is="dom-repeat" items="{{_files}}" as="file">
<div class="row" selected$="[[_computeFileSelected(index, selectedIndex)]]">
<div class="positionIndicator">▶</div>
<div class="reviewed" hidden$="[[!_loggedIn]]" hidden>
@@ -145,7 +136,7 @@
[[_computeFileDisplayName(file.__path)]]
</a>
<div class="comments">
- <span class="drafts">[[_computeDraftsString(_drafts, file.__path)]]</span>
+ <span class="drafts">[[_computeDraftsString(drafts, patchNum, file.__path)]]</span>
[[_computeCommentsString(comments, patchNum, file.__path)]]
</div>
<div class$="[[_computeClass('stats', file.__path)]]">
@@ -154,6 +145,7 @@
</div>
</div>
</template>
+ <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
</template>
<script src="gr-file-list.js"></script>
</dom-module>
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 3c66289..39e50a9 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
@@ -23,7 +23,7 @@
patchNum: String,
changeNum: String,
comments: Object,
- files: Array,
+ drafts: Object,
selectedIndex: {
type: Number,
notify: true,
@@ -33,107 +33,114 @@
value: function() { return document.body; },
},
+ _files: Array,
_loggedIn: {
type: Boolean,
value: false,
},
- _drafts: Object,
_reviewed: {
type: Array,
value: function() { return []; },
},
- _filesRequestPromise: Object, // Used for testing.
- _reviewedRequestPromise: Object, // Used for testing.
- _xhrPromise: Object, // Used for testing.
},
behaviors: [
Gerrit.KeyboardShortcutBehavior,
- Gerrit.RESTClientBehavior,
],
reload: function() {
if (!this.changeNum || !this.patchNum) {
return Promise.resolve();
}
- return Promise.all([
- this._filesRequestPromise =
- this.$.filesXHR.generateRequest().completes,
- app.accountReady.then(function() {
- this._loggedIn = app.loggedIn;
- if (!app.loggedIn) { return; }
- this.$.draftsXHR.generateRequest();
- this._reviewedRequestPromise =
- this.$.reviewedXHR.generateRequest().completes;
- }.bind(this)),
- ]);
- },
- _computeFilesURL: function(changeNum, patchNum) {
- return this.changeBaseURL(changeNum, patchNum) + '/files';
+ var promises = [];
+ var _this = this;
+
+ promises.push(this._getFiles().then(function(files) {
+ _this._files = files;
+ }));
+ promises.push(this._getLoggedIn().then(function(loggedIn) {
+ return _this._loggedIn = loggedIn;
+ }).then(function(loggedIn) {
+ if (!loggedIn) { return; }
+
+ return _this._getReviewedFiles().then(function(reviewed) {
+ _this._reviewed = reviewed;
+ });
+ }));
+
+ return Promise.all(promises);
},
_computeCommentsString: function(comments, patchNum, path) {
- var patchComments = (comments[path] || []).filter(function(c) {
- return c.patch_set == patchNum;
- });
- var num = patchComments.length;
- if (num == 0) { return ''; }
- if (num == 1) { return '1 comment'; }
- if (num > 1) { return num + ' comments'; }
+ return this._computeCountString(comments, patchNum, path, 'comment');
},
- _computeReviewedURL: function(changeNum, patchNum) {
- return this.changeBaseURL(changeNum, patchNum) + '/files?reviewed';
+ _computeDraftsString: function(drafts, patchNum, path) {
+ return this._computeCountString(drafts, patchNum, path, 'draft');
+ },
+
+ _computeCountString: function(comments, patchNum, path, noun) {
+ if (!comments) { return ''; }
+
+ var patchComments = (comments[path] || []).filter(function(c) {
+ return parseInt(c.patch_set, 10) === parseInt(patchNum, 10);
+ });
+ var num = patchComments.length;
+ if (num === 0) { return ''; }
+ return num + ' ' + noun + (num > 1 ? 's' : '');
},
_computeReviewed: function(file, _reviewed) {
- return _reviewed.indexOf(file.__path) != -1;
+ return _reviewed.indexOf(file.__path) !== -1;
},
_handleReviewedChange: function(e) {
var path = Polymer.dom(e).rootTarget.getAttribute('data-path');
var index = this._reviewed.indexOf(path);
- var reviewed = index != -1;
+ var reviewed = index !== -1;
if (reviewed) {
this.splice('_reviewed', index, 1);
} else {
this.push('_reviewed', path);
}
- var method = reviewed ? 'DELETE' : 'PUT';
- var url = this.changeBaseURL(this.changeNum, this.patchNum) +
- '/files/' + encodeURIComponent(path) + '/reviewed';
- this._send(method, url).catch(function(err) {
+ this._saveReviewedState(path, !reviewed).catch(function(err) {
alert('Couldn’t change file review status. Check the console ' +
'and contact the PolyGerrit team for assistance.');
throw err;
}.bind(this));
},
- _computeDraftsURL: function(changeNum, patchNum) {
- return this.changeBaseURL(changeNum, patchNum) + '/drafts';
+ _saveReviewedState: function(path, reviewed) {
+ return this.$.restAPI.saveFileReviewed(this.changeNum, this.patchNum,
+ path, reviewed);
},
- _computeDraftsString: function(drafts, path) {
- var num = (drafts[path] || []).length;
- if (num == 0) { return ''; }
- if (num == 1) { return '1 draft'; }
- if (num > 1) { return num + ' drafts'; }
+ _getLoggedIn: function() {
+ return this.$.restAPI.getLoggedIn();
},
- _handleResponse: function(e, req) {
- var result = e.detail.response;
- var paths = Object.keys(result).sort();
+ _getReviewedFiles: function() {
+ return this.$.restAPI.getReviewedFiles(this.changeNum, this.patchNum);
+ },
+
+ _getFiles: function() {
+ return this.$.restAPI.getChangeFiles(this.changeNum, this.patchNum).then(
+ this._normalizeFilesResponse.bind(this));
+ },
+
+ _normalizeFilesResponse: function(response) {
+ var paths = Object.keys(response).sort();
var files = [];
for (var i = 0; i < paths.length; i++) {
- var info = result[paths[i]];
+ var info = response[paths[i]];
info.__path = paths[i];
info.lines_inserted = info.lines_inserted || 0;
info.lines_deleted = info.lines_deleted || 0;
files.push(info);
}
- this.files = files;
+ return files;
},
_handleKey: function(e) {
@@ -143,7 +150,7 @@
case 74: // 'j'
e.preventDefault();
this.selectedIndex =
- Math.min(this.files.length - 1, this.selectedIndex + 1);
+ Math.min(this._files.length - 1, this.selectedIndex + 1);
break;
case 75: // 'k'
e.preventDefault();
@@ -151,7 +158,7 @@
break;
case 219: // '['
e.preventDefault();
- this._openSelectedFile(this.files.length - 1);
+ this._openSelectedFile(this._files.length - 1);
break;
case 221: // ']'
e.preventDefault();
@@ -170,11 +177,11 @@
this.selectedIndex = opt_index;
}
page.show(this._computeDiffURL(this.changeNum, this.patchNum,
- this.files[this.selectedIndex].__path));
+ this._files[this.selectedIndex].__path));
},
_computeFileSelected: function(index, selectedIndex) {
- return index == selectedIndex;
+ return index === selectedIndex;
},
_computeFileStatus: function(status) {
@@ -186,24 +193,15 @@
},
_computeFileDisplayName: function(path) {
- return path == COMMIT_MESSAGE_PATH ? 'Commit message' : path;
+ return path === COMMIT_MESSAGE_PATH ? 'Commit message' : path;
},
_computeClass: function(baseClass, path) {
var classes = [baseClass];
- if (path == COMMIT_MESSAGE_PATH) {
+ if (path === COMMIT_MESSAGE_PATH) {
classes.push('invisible');
}
return classes.join(' ');
},
-
- _send: function(method, url) {
- var xhr = document.createElement('gr-request');
- this._xhrPromise = xhr.send({
- method: method,
- url: url,
- });
- return this._xhrPromise;
- },
});
})();
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 a1140f5..21263c0 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
@@ -21,7 +21,7 @@
<script src="../../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
<script src="../../../bower_components/web-component-tester/browser.js"></script>
<script src="../../../bower_components/page/page.js"></script>
-<script src="../../../scripts/fake-app.js"></script>
+<script src="../../../test/fake-app.js"></script>
<script src="../../../scripts/util.js"></script>
<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
@@ -36,204 +36,93 @@
<script>
suite('gr-file-list tests', function() {
var element;
- var server;
+ var getLoggedInStub;
setup(function() {
element = fixture('basic');
- server = sinon.fakeServer.create();
- server.respondWith(
- 'GET',
- '/changes/42/revisions/1/files',
- [
- 200,
- {'Content-Type': 'application/json'},
- ')]}\'\n' +
- JSON.stringify({
- '/COMMIT_MSG': {
- status: 'A',
- lines_inserted: 9,
- size_delta: 317,
- size: 317
- },
- 'myfile.txt': {
- lines_inserted: 35,
- size_delta: 1146,
- size: 1167
- }
- }),
- ]
- );
- server.respondWith(
- 'GET',
- '/changes/42/revisions/2/files',
- [
- 200,
- {'Content-Type': 'application/json'},
- ')]}\'\n' +
- JSON.stringify({
- '/COMMIT_MSG': {
- status: 'A',
- lines_inserted: 9,
- size_delta: 317,
- size: 317
- },
- 'myfile.txt': {
- lines_inserted: 35,
- size_delta: 1146,
- size: 1167
- },
- 'file_added_in_rev2.txt': {
- lines_inserted: 98,
- size_delta: 234,
- size: 136
- }
- }),
- ]
- );
- server.respondWith(
- 'GET',
- '/changes/42/revisions/1/drafts',
- [
- 200,
- {'Content-Type': 'application/json'},
- ')]}\'\n' +
- '{}',
- ]
- );
- server.respondWith(
- 'GET',
- '/changes/42/revisions/2/drafts',
- [
- 200,
- {'Content-Type': 'application/json'},
- ')]}\'\n' +
- '{}',
- ]
- );
- server.respondWith(
- 'GET',
- '/changes/42/revisions/1/files?reviewed',
- [
- 200,
- {'Content-Type': 'application/json'},
- ')]}\'\n' +
- '["/COMMIT_MSG"]',
- ]
- );
- server.respondWith(
- 'GET',
- '/changes/42/revisions/2/files?reviewed',
- [
- 200,
- {'Content-Type': 'application/json'},
- ')]}\'\n' +
- '["/COMMIT_MSG","myfile.txt"]',
- ]
- );
- server.respondWith(
- 'PUT',
- '/changes/42/revisions/2/files/%2FCOMMIT_MSG/reviewed',
- [
- 201,
- {'Content-Type': 'application/json'},
- ')]}\'\n' +
- '""',
- ]
- );
- server.respondWith(
- 'DELETE',
- '/changes/42/revisions/2/files/%2FCOMMIT_MSG/reviewed',
- [
- 204,
- {'Content-Type': 'application/json'},
- '',
- ]
- );
-
- app.loggedIn = true;
+ getLoggedInStub = sinon.stub(element, '_getLoggedIn', function() {
+ return Promise.resolve(true);
+ });
});
teardown(function() {
- server.restore();
+ getLoggedInStub.restore();
});
- test('requests', function(done) {
- element.changeNum = '42';
- element.patchNum = '1';
- element.reload();
- server.respond();
-
- Promise.all([
- element._filesRequestPromise,
- element._reviewedRequestPromise,
- ]).then(function() {
- flushAsynchronousOperations();
- var filenames = element.files.map(function(f) {
- return f.__path;
- });
- assert.deepEqual(filenames, ['/COMMIT_MSG', 'myfile.txt']);
- assert.deepEqual(element._reviewed, ['/COMMIT_MSG']);
-
- element.patchNum = '2';
- element.reload();
- server.respond();
- Promise.all([
- element._filesRequestPromise,
- element._reviewedRequestPromise,
- ]).then(function() {
- flushAsynchronousOperations();
- filenames = element.files.map(function(f) {
- return f.__path;
+ test('get file list', function(done) {
+ var getChangeFilesStub = sinon.stub(element.$.restAPI, 'getChangeFiles',
+ function() {
+ return Promise.resolve({
+ '/COMMIT_MSG': {lines_inserted: 9},
+ 'tags.html': {lines_deleted: 123},
+ 'about.txt': {},
+ });
});
- assert.deepEqual(filenames,
- ['/COMMIT_MSG', 'file_added_in_rev2.txt', 'myfile.txt']);
- assert.deepEqual(element._reviewed, ['/COMMIT_MSG', 'myfile.txt']);
- done();
+
+ element._getFiles().then(function(files) {
+ var filenames = files.map(function(f) { return f.__path; });
+ assert.deepEqual(filenames, ['/COMMIT_MSG', 'about.txt', 'tags.html']);
+ assert.deepEqual(files[0], {
+ lines_inserted: 9,
+ lines_deleted: 0,
+ __path: '/COMMIT_MSG',
});
- });
- });
+ assert.deepEqual(files[1], {
+ lines_inserted: 0,
+ lines_deleted: 0,
+ __path: 'about.txt',
+ });
+ assert.deepEqual(files[2], {
+ lines_inserted: 0,
+ lines_deleted: 123,
+ __path: 'tags.html',
+ });
- test('keyboard shortcuts', function(done) {
- element.changeNum = '42';
- element.patchNum = '2';
- element.selectedIndex = 0;
- element.reload();
- server.respond();
-
- element._filesRequestPromise.then(function() {
- flushAsynchronousOperations();
- var elementItems = Polymer.dom(element.root).querySelectorAll(
- '.row:not(.header)');
- assert.equal(elementItems.length, 3);
- assert.isTrue(elementItems[0].hasAttribute('selected'));
- assert.isFalse(elementItems[1].hasAttribute('selected'));
- assert.isFalse(elementItems[2].hasAttribute('selected'));
- MockInteractions.pressAndReleaseKeyOn(element, 74); // 'j'
- assert.equal(element.selectedIndex, 1);
- MockInteractions.pressAndReleaseKeyOn(element, 74); // 'j'
-
- var showStub = sinon.stub(page, 'show');
- assert.equal(element.selectedIndex, 2);
- MockInteractions.pressAndReleaseKeyOn(element, 13); // 'enter'
- assert(showStub.lastCall.calledWith('/c/42/2/myfile.txt'),
- 'Should navigate to /c/42/2/myfile.txt');
-
- MockInteractions.pressAndReleaseKeyOn(element, 75); // 'k'
- assert.equal(element.selectedIndex, 1);
- MockInteractions.pressAndReleaseKeyOn(element, 79); // 'o'
- assert(showStub.lastCall.calledWith('/c/42/2/file_added_in_rev2.txt'),
- 'Should navigate to /c/42/2/file_added_in_rev2.txt');
-
- MockInteractions.pressAndReleaseKeyOn(element, 75); // 'k'
- MockInteractions.pressAndReleaseKeyOn(element, 75); // 'k'
- MockInteractions.pressAndReleaseKeyOn(element, 75); // 'k'
- assert.equal(element.selectedIndex, 0);
-
- showStub.restore();
done();
});
});
+ test('keyboard shortcuts', function() {
+ element._files = [
+ {__path: '/COMMIT_MSG'},
+ {__path: 'file_added_in_rev2.txt'},
+ {__path: 'myfile.txt'},
+ ];
+ element.changeNum = '42',
+ element.patchNum = '2';
+ element.selectedIndex = 0;
+
+ flushAsynchronousOperations();
+ var elementItems = Polymer.dom(element.root).querySelectorAll(
+ '.row:not(.header)');
+ assert.equal(elementItems.length, 3);
+ assert.isTrue(elementItems[0].hasAttribute('selected'));
+ assert.isFalse(elementItems[1].hasAttribute('selected'));
+ assert.isFalse(elementItems[2].hasAttribute('selected'));
+ MockInteractions.pressAndReleaseKeyOn(element, 74); // 'j'
+ assert.equal(element.selectedIndex, 1);
+ MockInteractions.pressAndReleaseKeyOn(element, 74); // 'j'
+
+ var showStub = sinon.stub(page, 'show');
+ assert.equal(element.selectedIndex, 2);
+ MockInteractions.pressAndReleaseKeyOn(element, 13); // 'enter'
+ assert(showStub.lastCall.calledWith('/c/42/2/myfile.txt'),
+ 'Should navigate to /c/42/2/myfile.txt');
+
+ MockInteractions.pressAndReleaseKeyOn(element, 75); // 'k'
+ assert.equal(element.selectedIndex, 1);
+ MockInteractions.pressAndReleaseKeyOn(element, 79); // 'o'
+ assert(showStub.lastCall.calledWith('/c/42/2/file_added_in_rev2.txt'),
+ 'Should navigate to /c/42/2/file_added_in_rev2.txt');
+
+ MockInteractions.pressAndReleaseKeyOn(element, 75); // 'k'
+ MockInteractions.pressAndReleaseKeyOn(element, 75); // 'k'
+ MockInteractions.pressAndReleaseKeyOn(element, 75); // 'k'
+ assert.equal(element.selectedIndex, 0);
+
+ showStub.restore();
+ });
+
test('comment filtering', function() {
var comments = {
'/COMMIT_MSG': [
@@ -248,24 +137,24 @@
],
};
assert.equal(
- element._computeCommentsString(comments, '1', '/COMMIT_MSG'),
+ element._computeCountString(comments, '1', '/COMMIT_MSG', 'comment'),
'2 comments');
assert.equal(
- element._computeCommentsString(comments, '1', 'myfile.txt'),
+ element._computeCountString(comments, '1', 'myfile.txt', 'comment'),
'1 comment');
assert.equal(
- element._computeCommentsString(comments, '1',
- 'file_added_in_rev2.txt'),
+ element._computeCountString(comments, '1',
+ 'file_added_in_rev2.txt', 'comment'),
'');
assert.equal(
- element._computeCommentsString(comments, '2', '/COMMIT_MSG'),
+ element._computeCountString(comments, '2', '/COMMIT_MSG', 'comment'),
'1 comment');
assert.equal(
- element._computeCommentsString(comments, '2', 'myfile.txt'),
+ element._computeCountString(comments, '2', 'myfile.txt', 'comment'),
'2 comments');
assert.equal(
- element._computeCommentsString(comments, '2',
- 'file_added_in_rev2.txt'),
+ element._computeCountString(comments, '2',
+ 'file_added_in_rev2.txt', 'comment'),
'');
});
@@ -284,50 +173,37 @@
'clazz invisible');
});
- test('file review status', function(done) {
+ test('file review status', function() {
+ element._files = [
+ {__path: '/COMMIT_MSG'},
+ {__path: 'file_added_in_rev2.txt'},
+ {__path: 'myfile.txt'},
+ ];
+ element._reviewed = ['/COMMIT_MSG', 'myfile.txt'];
element.changeNum = '42';
element.patchNum = '2';
- element.reload();
- server.respond();
+ element.selectedIndex = 0;
- Promise.all([
- element._filesRequestPromise,
- element._reviewedRequestPromise,
- ]).then(function() {
- flushAsynchronousOperations();
- var fileRows =
- Polymer.dom(element.root).querySelectorAll('.row:not(.header)');
- var commitMsg = fileRows[0].querySelector('input[type="checkbox"]');
- var fileAdded = fileRows[1].querySelector('input[type="checkbox"]');
- var myFile = fileRows[2].querySelector('input[type="checkbox"]');
+ flushAsynchronousOperations();
+ var fileRows =
+ Polymer.dom(element.root).querySelectorAll('.row:not(.header)');
+ var commitMsg = fileRows[0].querySelector('input[type="checkbox"]');
+ var fileAdded = fileRows[1].querySelector('input[type="checkbox"]');
+ var myFile = fileRows[2].querySelector('input[type="checkbox"]');
- assert.isTrue(commitMsg.checked);
- assert.isFalse(fileAdded.checked);
- assert.isTrue(myFile.checked);
+ assert.isTrue(commitMsg.checked);
+ assert.isFalse(fileAdded.checked);
+ assert.isTrue(myFile.checked);
- assert.equal(element._reviewed.length, 2);
+ var saveStub = sinon.stub(element, '_saveReviewedState',
+ function() { return Promise.resolve(); });
- MockInteractions.tap(commitMsg);
- server.respond();
- element._xhrPromise.then(function(req) {
- assert.equal(element._reviewed.length, 1);
- assert.equal(req.status, 204);
- assert.equal(req.url,
- '/changes/42/revisions/2/files/%2FCOMMIT_MSG/reviewed');
+ MockInteractions.tap(commitMsg);
+ assert.isTrue(saveStub.lastCall.calledWithExactly('/COMMIT_MSG', false));
+ MockInteractions.tap(commitMsg);
+ assert.isTrue(saveStub.lastCall.calledWithExactly('/COMMIT_MSG', true));
- MockInteractions.tap(commitMsg);
- server.respond();
- }).then(function() {
- element._xhrPromise.then(function(req) {
- assert.equal(element._reviewed.length, 2);
- assert.equal(req.status, 201);
- assert.equal(req.url,
- '/changes/42/revisions/2/files/%2FCOMMIT_MSG/reviewed');
-
- done();
- });
- });
- });
+ saveStub.restore();
});
});
</script>
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message.html b/polygerrit-ui/app/elements/change/gr-message/gr-message.html
index 5733acd..7c287db 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message.html
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message.html
@@ -19,6 +19,7 @@
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../../shared/gr-date-formatter/gr-date-formatter.html">
<link rel="import" href="../../shared/gr-linked-text/gr-linked-text.html">
+<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../gr-comment-list/gr-comment-list.html">
@@ -120,6 +121,7 @@
<gr-button small on-tap="_handleReplyTap">Reply</gr-button>
</div>
</div>
+ <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
</template>
<script src="gr-message.js"></script>
</dom-module>
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message.js b/polygerrit-ui/app/elements/change/gr-message/gr-message.js
index 1ab5e6c..26b9fb9 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message.js
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message.js
@@ -57,7 +57,7 @@
},
ready: function() {
- app.configReady.then(function(cfg) {
+ this.$.restAPI.getConfig().then(function(cfg) {
this.showAvatar = !!(cfg && cfg.plugin && cfg.plugin.has_avatars) &&
this.message && this.message.author;
}.bind(this));
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message_test.html b/polygerrit-ui/app/elements/change/gr-message/gr-message_test.html
index 0f09b70..dc4464b 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message_test.html
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message_test.html
@@ -20,7 +20,7 @@
<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
<script src="../../../bower_components/web-component-tester/browser.js"></script>
-<script src="../../../scripts/fake-app.js"></script>
+<script src="../../../test/fake-app.js"></script>
<script src="../../../scripts/util.js"></script>
<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.html b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.html
index 5a562ba..4f18439 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.html
@@ -20,7 +20,7 @@
<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
<script src="../../../bower_components/web-component-tester/browser.js"></script>
-<script src="../../../scripts/fake-app.js"></script>
+<script src="../../../test/fake-app.js"></script>
<script src="../../../scripts/util.js"></script>
<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
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 ab21e6c..1a2fbab 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
@@ -18,7 +18,6 @@
<link rel="import" href="../../../bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
<link rel="import" href="../../../bower_components/iron-selector/iron-selector.html">
<link rel="import" href="../../../behaviors/rest-client-behavior.html">
-<link rel="import" href="../../shared/gr-ajax/gr-ajax.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../../shared/gr-request/gr-request.html">
@@ -43,13 +42,13 @@
border-top: 1px solid #ddd;
padding: .5em .75em;
}
- .textareaContainer,
.labelsContainer,
.actionsContainer {
flex-shrink: 0;
}
.textareaContainer {
position: relative;
+ display: flex;
}
iron-autogrow-textarea {
padding: 0;
@@ -101,9 +100,6 @@
}
</style>
<template>
- <gr-ajax id="draftsXHR"
- url="[[_computeDraftsURL(changeNum)]]"
- last-response="{{_drafts}}"></gr-ajax>
<div class="container">
<section class="textareaContainer">
<iron-autogrow-textarea
@@ -131,10 +127,10 @@
</div>
</template>
</section>
- <section class="draftsContainer" hidden$="[[_computeHideDraftList(_drafts)]]">
- <h3>[[_computeDraftsTitle(_drafts)]]</h3>
+ <section class="draftsContainer" hidden$="[[_computeHideDraftList(diffDrafts)]]">
+ <h3>[[_computeDraftsTitle(diffDrafts)]]</h3>
<gr-comment-list
- comments="[[_drafts]]"
+ comments="[[diffDrafts]]"
change-num="[[changeNum]]"
patch-num="[[patchNum]]"></gr-comment-list>
</section>
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
index 3cd6e12..6ec4d87 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
@@ -41,11 +41,11 @@
type: String,
value: '',
},
+ diffDrafts: Object,
labels: Object,
permittedLabels: Object,
_account: Object,
- _drafts: Object,
_xhrPromise: Object, // Used for testing.
},
@@ -54,25 +54,17 @@
],
ready: function() {
- app.accountReady.then(function() {
- this._account = app.account;
+ app.accountReady.then(function(account) {
+ this._account = account;
}.bind(this));
},
- reload: function() {
- return this.$.draftsXHR.generateRequest().completes;
- },
-
focus: function() {
this.async(function() {
this.$.textarea.textarea.focus();
}.bind(this));
},
- _computeDraftsURL: function(changeNum) {
- return '/changes/' + changeNum + '/drafts';
- },
-
_computeHideDraftList: function(drafts) {
return Object.keys(drafts || {}).length == 0;
},
@@ -125,7 +117,6 @@
_cancelTapHandler: function(e) {
e.preventDefault();
- this._drafts = null;
this.fire('cancel', null, {bubbles: false});
},
@@ -149,7 +140,6 @@
this.fire('send', null, {bubbles: false});
this.draft = '';
this.disabled = false;
- this._drafts = null;
}.bind(this)).catch(function(err) {
alert('Oops. Something went wrong. Check the console and bug the ' +
'PolyGerrit team for assistance.');
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
index 3cde22a..d3072ac 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
@@ -20,7 +20,7 @@
<script src="../../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
<script src="../../../bower_components/web-component-tester/browser.js"></script>
-<script src="../../../scripts/fake-app.js"></script>
+<script src="../../../test/fake-app.js"></script>
<script src="../../../scripts/util.js"></script>
<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
diff --git a/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.html b/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.html
index 0d549d9..814e206 100644
--- a/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.html
+++ b/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.html
@@ -17,6 +17,7 @@
<link rel="import" href="../../../bower_components/polymer/polymer.html">
<link rel="import" href="../../../bower_components/iron-dropdown/iron-dropdown.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
+<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<dom-module id="gr-account-dropdown">
<style>
@@ -36,6 +37,11 @@
font: inherit;
padding: .3em 0;
}
+ gr-avatar {
+ height: 2em;
+ width: 2em;
+ vertical-align: -.25em;
+ }
ul {
list-style: none;
}
@@ -59,7 +65,11 @@
</style>
<template>
<gr-button link class="dropdown-trigger" id="trigger"
- on-tap="_showDropdownTapHandler">[[account.name]]</gr-button>
+ on-tap="_showDropdownTapHandler">
+ <span hidden$="[[_hasAvatars]]" hidden>[[account.name]]</span>
+ <gr-avatar account="[[account]]" hidden$="[[!_hasAvatars]]" hidden
+ image-size="56"></gr-avatar>
+ </gr-button>
<iron-dropdown id="dropdown"
vertical-align="top"
vertical-offset="25"
@@ -77,6 +87,7 @@
</ul>
</div>
</iron-dropdown>
+ <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
</template>
<script src="gr-account-dropdown.js"></script>
</dom-module>
diff --git a/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.js b/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.js
index 09de6c1..62212a3 100644
--- a/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.js
+++ b/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.js
@@ -19,6 +19,13 @@
properties: {
account: Object,
+ _hasAvatars: Boolean,
+ },
+
+ attached: function() {
+ this.$.restAPI.getConfig().then(function(cfg) {
+ this._hasAvatars = !!(cfg && cfg.plugin && cfg.plugin.has_avatars);
+ }.bind(this));
},
_showDropdownTapHandler: function(e) {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
index 847a641..557c213 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
@@ -122,6 +122,7 @@
this._navToFile(this._fileList, 1);
break;
case 78: // 'n'
+ e.preventDefault();
if (e.shiftKey) {
this.$.diff.scrollToNextCommentThread();
} else {
@@ -129,6 +130,7 @@
}
break;
case 80: // 'p'
+ e.preventDefault();
if (e.shiftKey) {
this.$.diff.scrollToPreviousCommentThread();
} else {
@@ -150,6 +152,7 @@
}
break;
case 188: // ','
+ e.preventDefault();
this.$.diff.showDiffPreferences();
break;
}
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
index 4d0c6e5..188bc5d 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
@@ -21,7 +21,7 @@
<script src="../../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
<script src="../../../bower_components/web-component-tester/browser.js"></script>
<script src="../../../bower_components/page/page.js"></script>
-<script src="../../../scripts/fake-app.js"></script>
+<script src="../../../test/fake-app.js"></script>
<script src="../../../scripts/util.js"></script>
<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
index 71a04b8..cac1f3e 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
@@ -87,7 +87,6 @@
},
_savedPrefs: Object,
- _diffRequestsPromise: Object, // Used for testing.
_diffPreferencesPromise: Object, // Used for testing.
},
@@ -157,7 +156,7 @@
}.bind(this)));
}
- this._diffRequestsPromise = Promise.all(promises).then(function() {
+ return Promise.all(promises).then(function() {
this._render();
this._loading = false;
}.bind(this)).catch(function(err) {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
index 921d766..91f5f3b 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
@@ -20,7 +20,7 @@
<script src="../../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
<script src="../../../bower_components/web-component-tester/browser.js"></script>
-<script src="../../../scripts/fake-app.js"></script>
+<script src="../../../test/fake-app.js"></script>
<script src="../../../scripts/util.js"></script>
<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
@@ -162,86 +162,85 @@
patchNum: 2,
};
- element.reload();
- server.respond();
-
- // Allow events to fire and the threads to render.
- flush(function() {
- var leftThreadEls =
- Polymer.dom(element.$.leftDiff.root).querySelectorAll(
- 'gr-diff-comment-thread');
- assert.equal(leftThreadEls.length, 1);
- assert.equal(leftThreadEls[0].comments.length, 1);
-
- var rightThreadEls =
- Polymer.dom(element.$.rightDiff.root).querySelectorAll(
- 'gr-diff-comment-thread');
- assert.equal(rightThreadEls.length, 1);
- assert.equal(rightThreadEls[0].comments.length, 2);
-
- var index = leftThreadEls[0].getAttribute('data-index');
- var leftFillerEls =
- Polymer.dom(element.$.leftDiff.root).querySelectorAll(
- '.commentThread.filler[data-index="' + index + '"]');
- assert.equal(leftFillerEls.length, 1);
- var rightFillerEls =
- Polymer.dom(element.$.rightDiff.root).querySelectorAll(
- '[data-index="' + index + '"]');
- assert.equal(rightFillerEls.length, 2);
-
- for (var i = 0; i < rightFillerEls.length; i++) {
- assert.isTrue(rightFillerEls[i].classList.contains('filler'));
- }
- var originalHeight = rightFillerEls[0].offsetHeight;
- assert.equal(rightFillerEls[1].offsetHeight, originalHeight);
- assert.equal(leftThreadEls[0].offsetHeight, originalHeight);
- assert.equal(leftFillerEls[0].offsetHeight, originalHeight);
-
- // Create a comment on the opposite side of the first comment.
- var rightLineEL = element.$.rightDiff.$$(
- '.lineNum[data-index="' + (index - 1) + '"]');
- assert.ok(rightLineEL);
- MockInteractions.tap(rightLineEL);
+ element.reload().then(function() {
flush(function() {
- var newThreadEls =
- Polymer.dom(element.$.rightDiff.root).querySelectorAll(
- '[data-index="' + index + '"]');
- assert.equal(newThreadEls.length, 2);
- for (var i = 0; i < newThreadEls.length; i++) {
- assert.isTrue(
- newThreadEls[i].classList.contains('commentThread') ||
- newThreadEls[i].tagName == 'GR-DIFF-COMMENT-THREAD');
+ var leftThreadEls =
+ Polymer.dom(element.$.leftDiff.root).querySelectorAll(
+ 'gr-diff-comment-thread');
+ assert.equal(leftThreadEls.length, 1);
+ assert.equal(leftThreadEls[0].comments.length, 1);
+
+ var rightThreadEls =
+ Polymer.dom(element.$.rightDiff.root).querySelectorAll(
+ 'gr-diff-comment-thread');
+ assert.equal(rightThreadEls.length, 1);
+ assert.equal(rightThreadEls[0].comments.length, 2);
+
+ var index = leftThreadEls[0].getAttribute('data-index');
+ var leftFillerEls =
+ Polymer.dom(element.$.leftDiff.root).querySelectorAll(
+ '.commentThread.filler[data-index="' + index + '"]');
+ assert.equal(leftFillerEls.length, 1);
+ var rightFillerEls =
+ Polymer.dom(element.$.rightDiff.root).querySelectorAll(
+ '[data-index="' + index + '"]');
+ assert.equal(rightFillerEls.length, 2);
+
+ for (var i = 0; i < rightFillerEls.length; i++) {
+ assert.isTrue(rightFillerEls[i].classList.contains('filler'));
}
- var newHeight = newThreadEls[0].offsetHeight;
- assert.equal(newThreadEls[1].offsetHeight, newHeight);
- assert.equal(leftFillerEls[0].offsetHeight, newHeight);
- assert.equal(leftThreadEls[0].offsetHeight, newHeight);
+ var originalHeight = rightFillerEls[0].offsetHeight;
+ assert.equal(rightFillerEls[1].offsetHeight, originalHeight);
+ assert.equal(leftThreadEls[0].offsetHeight, originalHeight);
+ assert.equal(leftFillerEls[0].offsetHeight, originalHeight);
- // The editing mode height of the right comment will be greater than
- // the non-editing mode height of the left comment.
- assert.isAbove(newHeight, originalHeight);
-
- // Discard the right thread and ensure the left comment heights are
- // back to their original values.
- newThreadEls[1].addEventListener('discard', function() {
- rightFillerEls =
- Polymer.dom(element.$.rightDiff.root).querySelectorAll(
- '[data-index="' + index + '"]');
- assert.equal(rightFillerEls.length, 2);
-
- for (var i = 0; i < rightFillerEls.length; i++) {
- assert.isTrue(rightFillerEls[i].classList.contains('filler'));
+ // Create a comment on the opposite side of the first comment.
+ var rightLineEL = element.$.rightDiff.$$(
+ '.lineNum[data-index="' + (index - 1) + '"]');
+ assert.ok(rightLineEL);
+ MockInteractions.tap(rightLineEL);
+ flush(function() {
+ var newThreadEls =
+ Polymer.dom(element.$.rightDiff.root).querySelectorAll(
+ '[data-index="' + index + '"]');
+ assert.equal(newThreadEls.length, 2);
+ for (var i = 0; i < newThreadEls.length; i++) {
+ assert.isTrue(
+ newThreadEls[i].classList.contains('commentThread') ||
+ newThreadEls[i].tagName == 'GR-DIFF-COMMENT-THREAD');
}
- var originalHeight = rightFillerEls[0].offsetHeight;
- assert.equal(rightFillerEls[1].offsetHeight, originalHeight);
- assert.equal(leftThreadEls[0].offsetHeight, originalHeight);
- assert.equal(leftFillerEls[0].offsetHeight, originalHeight);
- done();
+ var newHeight = newThreadEls[0].offsetHeight;
+ assert.equal(newThreadEls[1].offsetHeight, newHeight);
+ assert.equal(leftFillerEls[0].offsetHeight, newHeight);
+ assert.equal(leftThreadEls[0].offsetHeight, newHeight);
+
+ // The editing mode height of the right comment will be greater than
+ // the non-editing mode height of the left comment.
+ assert.isAbove(newHeight, originalHeight);
+
+ // Discard the right thread and ensure the left comment heights are
+ // back to their original values.
+ newThreadEls[1].addEventListener('discard', function() {
+ rightFillerEls =
+ Polymer.dom(element.$.rightDiff.root).querySelectorAll(
+ '[data-index="' + index + '"]');
+ assert.equal(rightFillerEls.length, 2);
+
+ for (var i = 0; i < rightFillerEls.length; i++) {
+ assert.isTrue(rightFillerEls[i].classList.contains('filler'));
+ }
+ var originalHeight = rightFillerEls[0].offsetHeight;
+ assert.equal(rightFillerEls[1].offsetHeight, originalHeight);
+ assert.equal(leftThreadEls[0].offsetHeight, originalHeight);
+ assert.equal(leftFillerEls[0].offsetHeight, originalHeight);
+ done();
+ });
+ var commentEl = newThreadEls[1].$$('gr-diff-comment');
+ commentEl.fire('discard', null, {bubbles: false});
});
- var commentEl = newThreadEls[1].$$('gr-diff-comment');
- commentEl.fire('discard', null, {bubbles: false});
});
});
+ server.respond();
});
test('intraline normalization', function() {
diff --git a/polygerrit-ui/app/elements/gr-app.html b/polygerrit-ui/app/elements/gr-app.html
index 67ab50f..b1c084e 100644
--- a/polygerrit-ui/app/elements/gr-app.html
+++ b/polygerrit-ui/app/elements/gr-app.html
@@ -26,7 +26,6 @@
<link rel="import" href="./change/gr-change-view/gr-change-view.html">
<link rel="import" href="./diff/gr-diff-view/gr-diff-view.html">
-<link rel="import" href="./shared/gr-ajax/gr-ajax.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">
@@ -56,11 +55,6 @@
color: #b71c1c;
}
</style>
- <gr-ajax auto url="/config/server/info" last-response="{{config}}"></gr-ajax>
- <gr-ajax auto url="/config/server/version" last-response="{{version}}"></gr-ajax>
- <gr-ajax id="diffPreferencesXHR"
- url="/accounts/self/preferences.diff"
- last-response="{{_diffPreferences}}"></gr-ajax>
<gr-main-header search-query="{{params.query}}"></gr-main-header>
<main>
<template is="dom-if" if="{{_showChangeListView}}" restamp="true">
@@ -68,18 +62,18 @@
params="[[params]]"
view-state="{{_viewState.changeListView}}"
changes-per-page="[[_preferences.changes_per_page]]"
- logged-in="[[_computeLoggedIn(account)]]"></gr-change-list-view>
+ logged-in="[[_computeLoggedIn(_account)]]"></gr-change-list-view>
</template>
<template is="dom-if" if="{{_showDashboardView}}" restamp="true">
<gr-dashboard-view
- account="[[account]]"
+ account="[[_account]]"
params="[[params]]"
view-state="{{_viewState.dashboardView}}"></gr-dashboard-view>
</template>
<template is="dom-if" if="{{_showChangeView}}" restamp="true">
<gr-change-view
params="[[params]]"
- server-config="[[config]]"
+ server-config="[[_serverConfig]]"
view-state="{{_viewState.changeView}}"></gr-change-view>
</template>
<template is="dom-if" if="{{_showDiffView}}" restamp="true">
@@ -91,14 +85,14 @@
</main>
<footer role="contentinfo">
Powered by <a href="https://www.gerritcodereview.com/" target="_blank">Gerrit Code Review</a>
- ([[version]])
- <span hidden$="[[!config.gerrit.report_bug_url]]">
+ ([[_version]])
+ <span hidden$="[[!_serverConfig.gerrit.report_bug_url]]">
|
- <a href$="[[config.gerrit.report_bug_url]]" target="_blank">
- <span hidden$="[[!config.gerrit.report_bug_text]]">
- [[config.gerrit.report_bug_text]]
+ <a href$="[[_serverConfig.gerrit.report_bug_url]]" target="_blank">
+ <span hidden$="[[!_serverConfig.gerrit.report_bug_text]]">
+ [[_serverConfig.gerrit.report_bug_text]]
</span>
- <span hidden$="[[config.gerrit.report_bug_text]]">Report Bug</span>
+ <span hidden$="[[_serverConfig.gerrit.report_bug_text]]">Report Bug</span>
</a>
</span>
|
diff --git a/polygerrit-ui/app/elements/gr-app.js b/polygerrit-ui/app/elements/gr-app.js
index a40e61b..55d3920 100644
--- a/polygerrit-ui/app/elements/gr-app.js
+++ b/polygerrit-ui/app/elements/gr-app.js
@@ -18,10 +18,7 @@
is: 'gr-app',
properties: {
- account: {
- type: Object,
- observer: '_accountChanged',
- },
+ params: Object,
accountReady: {
type: Object,
readOnly: true,
@@ -32,29 +29,20 @@
}.bind(this));
},
},
- config: {
- type: Object,
- observer: '_configChanged',
- },
- configReady: {
- type: Object,
- readOnly: true,
- notify: true,
- value: function() {
- return new Promise(function(resolve) {
- this._resolveConfigReady = resolve;
- }.bind(this));
- },
- },
- version: String,
- params: Object,
keyEventTarget: {
type: Object,
value: function() { return document.body; },
},
+ _account: {
+ type: Object,
+ observer: '_accountChanged',
+ },
+ _serverConfig: Object,
+ _version: String,
_diffPreferences: Object,
_preferences: Object,
+ _resolveAccountReady: Function,
_showChangeListView: Boolean,
_showDashboardView: Boolean,
_showChangeView: Boolean,
@@ -75,12 +63,18 @@
],
get loggedIn() {
- return !!(this.account && Object.keys(this.account).length > 0);
+ return !!(this._account && Object.keys(this._account).length > 0);
},
attached: function() {
this.$.restAPI.getAccount().then(function(account) {
- this.account = account;
+ this._account = account;
+ }.bind(this));
+ this.$.restAPI.getConfig().then(function(config) {
+ this._serverConfig = config;
+ }.bind(this));
+ this.$.restAPI.getVersion().then(function(version) {
+ this._version = version;
}.bind(this));
},
@@ -103,14 +97,16 @@
};
},
- _accountChanged: function() {
- this._resolveAccountReady();
- if (this.loggedIn) {
- this.$.diffPreferencesXHR.generateRequest();
+ _accountChanged: function(account) {
+ this._resolveAccountReady(account);
+ if (this.loggedIn) {
this.$.restAPI.getPreferences().then(function(preferences) {
this._preferences = preferences;
}.bind(this));
+ this.$.restAPI.getDiffPreferences().then(function(prefs) {
+ this._diffPreferences = prefs;
+ }.bind(this));
} else {
// These defaults should match the defaults in
// gerrit-extension-api/src/main/jcg/gerrit/extensions/client/DiffPreferencesInfo.java
@@ -137,10 +133,6 @@
}
},
- _configChanged: function(config) {
- this._resolveConfigReady(config);
- },
-
_viewChanged: function(view) {
this.set('_showChangeListView', view == 'gr-change-list-view');
this.set('_showDashboardView', view == 'gr-dashboard-view');
diff --git a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.html b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.html
index c39f288..af65bfd 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.html
@@ -20,7 +20,7 @@
<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
<script src="../../../bower_components/web-component-tester/browser.js"></script>
-<script src="../../../scripts/fake-app.js"></script>
+<script src="../../../test/fake-app.js"></script>
<script src="../../../scripts/util.js"></script>
<link rel="import" href="gr-account-label.html">
diff --git a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link_test.html b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link_test.html
index e1ef862..869d812 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-account-link/gr-account-link_test.html
@@ -20,7 +20,7 @@
<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
<script src="../../../bower_components/web-component-tester/browser.js"></script>
-<script src="../../../scripts/fake-app.js"></script>
+<script src="../../../test/fake-app.js"></script>
<script src="../../../scripts/util.js"></script>
<link rel="import" href="gr-account-link.html">
diff --git a/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar.html b/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar.html
index 3491443..55655c0 100644
--- a/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar.html
+++ b/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar.html
@@ -15,6 +15,7 @@
-->
<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../gr-rest-api-interface/gr-rest-api-interface.html">
<dom-module id="gr-avatar">
<template>
@@ -26,6 +27,7 @@
background-color: var(--background-color, #f1f2f3);
}
</style>
+ <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
</template>
<script src="gr-avatar.js"></script>
</dom-module>
diff --git a/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar.js b/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar.js
index 8f289ca..3655975 100644
--- a/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar.js
+++ b/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar.js
@@ -32,8 +32,8 @@
this.hidden = true;
},
- ready: function() {
- app.configReady.then(function(cfg) {
+ attached: function() {
+ this.$.restAPI.getConfig().then(function(cfg) {
var hasAvatars = !!(cfg && cfg.plugin && cfg.plugin.has_avatars);
if (hasAvatars) {
this.hidden = false;
diff --git a/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar_test.html b/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar_test.html
index 7e3c25c..f065290 100644
--- a/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar_test.html
@@ -20,7 +20,7 @@
<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
<script src="../../../bower_components/web-component-tester/browser.js"></script>
-<script src="../../../scripts/fake-app.js"></script>
+<script src="../../../test/fake-app.js"></script>
<link rel="import" href="gr-avatar.html">
diff --git a/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star_test.html b/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star_test.html
index 86ee947..03b8e13 100644
--- a/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star_test.html
@@ -21,7 +21,7 @@
<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
<script src="../../../bower_components/web-component-tester/browser.js"></script>
<script src="../../../bower_components/page/page.js"></script>
-<script src="../../../scripts/fake-app.js"></script>
+<script src="../../../test/fake-app.js"></script>
<script src="../../../scripts/util.js"></script>
<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
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 09b6d35..85d26e6 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
@@ -82,10 +82,28 @@
}.bind(this));
},
+ getConfig: function() {
+ return this._fetchSharedCacheURL('/config/server/info');
+ },
+
+ getVersion: function() {
+ return this._fetchSharedCacheURL('/config/server/version');
+ },
+
+ getDiffPreferences: function() {
+ return this._fetchSharedCacheURL('/accounts/self/preferences.diff');
+ },
+
getAccount: function() {
return this._fetchSharedCacheURL('/accounts/self/detail');
},
+ getLoggedIn: function() {
+ return this.getAccount().then(function(account) {
+ return account != null;
+ });
+ },
+
getPreferences: function() {
return this._fetchSharedCacheURL('/accounts/self/preferences');
},
@@ -112,6 +130,48 @@
return this._sharedFetchPromises[url];
},
+ getChangeFiles: function(changeNum, patchNum) {
+ return this.fetchJSON(
+ this._changeBaseURL(changeNum, patchNum) + '/files');
+ },
+
+ getReviewedFiles: function(changeNum, patchNum) {
+ return this.fetchJSON(
+ this._changeBaseURL(changeNum, patchNum) + '/files?reviewed');
+ },
+
+ saveFileReviewed: function(changeNum, patchNum, path, reviewed, opt_errFn,
+ opt_ctx) {
+ var method = reviewed ? 'PUT' : 'DELETE';
+ var url = this._changeBaseURL(changeNum, patchNum) + '/files/' +
+ encodeURIComponent(path) + '/reviewed';
+
+ return this._save(method, url, null, opt_errFn, opt_ctx);
+ },
+
+ _save: function(method, url, opt_body, opt_errFn, opt_ctx) {
+ var headers = new Headers({
+ 'X-Gerrit-Auth': this._getCookie('XSRF_TOKEN'),
+ });
+
+ if (opt_body) {
+ headers.append('Content-Type', 'application/json');
+ options.body = body;
+ }
+ var options = {
+ method: method,
+ headers: headers,
+ credentials: 'same-origin',
+ };
+ return fetch(url, options).catch(function(err) {
+ if (opt_errFn) {
+ opt_errFn.call(opt_ctx || this);
+ } else {
+ throw err;
+ }
+ });
+ },
+
getDiff: function(changeNum, basePatchNum, patchNum, path,
opt_cancelCondition) {
var url = this._getDiffFetchURL(changeNum, patchNum, path);
@@ -131,38 +191,46 @@
encodeURIComponent(path) + '/diff';
},
- getDiffComments: function(changeNum, basePatchNum, patchNum, path) {
- return this._getDiffComments(changeNum, basePatchNum, patchNum, path,
- '/comments');
+ getDiffComments: function(changeNum, opt_basePatchNum, opt_patchNum,
+ opt_path) {
+ return this._getDiffComments(changeNum, '/comments', opt_basePatchNum,
+ opt_patchNum, opt_path);
},
- getDiffDrafts: function(changeNum, basePatchNum, patchNum, path) {
- return this._getDiffComments(changeNum, basePatchNum, patchNum, path,
- '/drafts');
+ getDiffDrafts: function(changeNum, opt_basePatchNum, opt_patchNum,
+ opt_path) {
+ return this._getDiffComments(changeNum, '/drafts', opt_basePatchNum,
+ opt_patchNum, opt_path);
},
- _getDiffComments: function(changeNum, basePatchNum, patchNum, path,
- endpoint) {
+ _getDiffComments: function(changeNum, endpoint, opt_basePatchNum,
+ opt_patchNum, opt_path) {
+ if (!opt_basePatchNum && !opt_patchNum && !opt_path) {
+ return this.fetchJSON(
+ this._getDiffCommentsFetchURL(changeNum, '/drafts'));
+ }
+
function onlyParent(c) { return c.side == PARENT_PATCH_NUM; }
function withoutParent(c) { return c.side != PARENT_PATCH_NUM; }
var promises = [];
var comments;
var baseComments;
- var url = this._getDiffCommentsFetchURL(changeNum, patchNum, endpoint);
+ var url =
+ this._getDiffCommentsFetchURL(changeNum, endpoint, opt_patchNum);
promises.push(this.fetchJSON(url).then(function(response) {
- comments = response[path] || [];
- if (basePatchNum == PARENT_PATCH_NUM) {
+ comments = response[opt_path] || [];
+ if (opt_basePatchNum == PARENT_PATCH_NUM) {
baseComments = comments.filter(onlyParent);
}
comments = comments.filter(withoutParent);
}.bind(this)));
- if (basePatchNum != PARENT_PATCH_NUM) {
- var baseURL = this._getDiffCommentsFetchURL(changeNum, basePatchNum,
- endpoint);
+ if (opt_basePatchNum != PARENT_PATCH_NUM) {
+ var baseURL = this._getDiffCommentsFetchURL(changeNum, endpoint,
+ opt_basePatchNum);
promises.push(this.fetchJSON(baseURL).then(function(response) {
- baseComments = (response[path] || []).filter(withoutParent);
+ baseComments = (response[opt_path] || []).filter(withoutParent);
}));
}
@@ -174,17 +242,32 @@
});
},
- _getDiffCommentsFetchURL: function(changeNum, patchNum, endpoint) {
- return this._changeBaseURL(changeNum, patchNum) + endpoint;
+ _getDiffCommentsFetchURL: function(changeNum, endpoint, opt_patchNum) {
+ return this._changeBaseURL(changeNum, opt_patchNum) + endpoint;
},
- _changeBaseURL: function(changeNum, patchNum) {
+ _changeBaseURL: function(changeNum, opt_patchNum) {
var v = '/changes/' + changeNum;
- if (patchNum) {
- v += '/revisions/' + patchNum;
+ if (opt_patchNum) {
+ v += '/revisions/' + opt_patchNum;
}
return v;
},
+ _getCookie: function(name) {
+ var key = name + '=';
+ var cookies = document.cookie.split(';');
+ for (var i = 0; i < cookies.length; i++) {
+ var c = cookies[i];
+ while (c.charAt(0) == ' ') {
+ c = c.substring(1);
+ }
+ if (c.indexOf(key) == 0) {
+ return c.substring(key.length, c.length);
+ }
+ }
+ return '';
+ },
+
});
})();
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
index b0a6649..44e49cb 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
@@ -42,7 +42,7 @@
var testJSON = ')]}\'\n{"hello": "bonjour"}';
var fetchStub = sinon.stub(window, 'fetch', function() {
- return Promise.resolve({ text: function() {
+ return Promise.resolve({text: function() {
return Promise.resolve(testJSON);
}});
});
@@ -65,7 +65,7 @@
Promise.all(promises).then(function(results) {
assert.deepEqual(results, [1, 1, 1]);
- element._fetchSharedCacheURL('/foo').then(function(foo) {
+ element._fetchSharedCacheURL('/foo').then(function(foo) {
assert.equal(foo, 1);
fetchJSONStub.restore();
done();
@@ -84,7 +84,7 @@
test('params are properly encoded', function() {
var fetchStub = sinon.stub(window, 'fetch', function() {
- return Promise.resolve({ text: function() {
+ return Promise.resolve({text: function() {
return Promise.resolve(')]}\'\n{}');
}});
});
@@ -101,7 +101,7 @@
test('request callbacks can be canceled', function(done) {
var cancelCalled = false;
var fetchStub = sinon.stub(window, 'fetch', function() {
- return Promise.resolve({ body: {
+ return Promise.resolve({body: {
cancel: function() { cancelCalled = true; }
}});
});
@@ -129,7 +129,7 @@
],
});
});
- element._getDiffComments('42', 'PARENT', 1, 'sieve.go', '').then(
+ element._getDiffComments('42', '', 'PARENT', 1, 'sieve.go').then(
function(obj) {
assert.equal(obj.baseComments.length, 1);
assert.deepEqual(obj.baseComments[0], {
@@ -140,6 +140,7 @@
assert.deepEqual(obj.comments[0], {
message: 'this isn’t quite right',
});
+ fetchJSONStub.restore();
done();
});
});
@@ -177,7 +178,7 @@
});
}
});
- element._getDiffComments('42', 1, 2, 'sieve.go', '').then(
+ element._getDiffComments('42', '', 1, 2, 'sieve.go').then(
function(obj) {
assert.equal(obj.baseComments.length, 1);
assert.deepEqual(obj.baseComments[0], {
@@ -190,6 +191,7 @@
assert.deepEqual(obj.comments[1], {
message: '¯\\_(ツ)_/¯',
});
+ fetchJSONStub.restore();
done();
});
});
diff --git a/polygerrit-ui/app/scripts/fake-app.js b/polygerrit-ui/app/test/fake-app.js
similarity index 89%
rename from polygerrit-ui/app/scripts/fake-app.js
rename to polygerrit-ui/app/test/fake-app.js
index 87e2d04..5ee6915 100644
--- a/polygerrit-ui/app/scripts/fake-app.js
+++ b/polygerrit-ui/app/test/fake-app.js
@@ -19,10 +19,7 @@
*/
var app = {
accountReady: {
- then: function(cb) { cb(); },
- },
- configReady: {
- then: function(cb) { cb(); },
+ then: function(cb) { return cb(); },
},
loggedIn: false,
};
diff --git a/tools/maven/mvn.py b/tools/maven/mvn.py
index 7017406..83a33e8 100755
--- a/tools/maven/mvn.py
+++ b/tools/maven/mvn.py
@@ -65,7 +65,8 @@
print(' '.join(exe), file=stderr)
check_output(exe)
except Exception as e:
- print('%s command failed: %s' % (args.a, e), file=stderr)
+ print('%s command failed: %s\n%s' % (args.a, ' '.join(exe), e),
+ file=stderr)
exit(1)
with open(args.o, 'w') as fd:
diff --git a/tools/plugin_archetype_deploy.sh b/tools/plugin_archetype_deploy.sh
index 4ad8b70..b16ce95 100755
--- a/tools/plugin_archetype_deploy.sh
+++ b/tools/plugin_archetype_deploy.sh
@@ -63,21 +63,9 @@
-Dfile=target/$module-$ver.jar
}
-function confirm
-{
- read -n1 -p "Are you sure you want to deploy? [N/y]: " ready
- if [[ ! $ready == [Yy] ]]; then
- if [[ $ready == [Nn] || -z $ready ]]; then
- echo; exit
- else
- echo; confirm
- fi
- fi
-}
-
function run
{
- test ${dryRun:-'false'} == 'false' && confirm
+ test ${dryRun:-'false'} == 'false'
root=$(instroot)
cd "$root"
ver=$(getver GERRIT_VERSION)