Merge "Fix emailStrategy ListBox not displayed on MyPreferencesScreen"
diff --git a/Documentation/cmd-index.txt b/Documentation/cmd-index.txt
index ef653cc..90212fb 100644
--- a/Documentation/cmd-index.txt
+++ b/Documentation/cmd-index.txt
@@ -120,7 +120,7 @@
link:cmd-gsql.html[gerrit gsql]::
Administrative interface to active database.
-link:cmd-index-index.html[gerrit index activate]::
+link:cmd-index-activate.html[gerrit index activate]::
Activate the latest index version available.
link:cmd-index-start.html[gerrit index start]::
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index ce9b1b3..a38f9fc 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -1394,21 +1394,37 @@
used to automatically create correct database.driver and database.url
values to open the connection.
+
-* `POSTGRESQL`
+* `DB2`
+
-Connect to a PostgreSQL database server.
+Connect to a DB2 database server.
++
+* `DERBY`
++
+Connect to an Apache Derby database server.
+
* `H2`
+
Connect to a local embedded H2 database.
+
+* `JDBC`
++
+Connect using a JDBC driver class name and URL.
++
+* `MAXDB`
++
+Connect to an SAP MaxDb database server.
++
* `MYSQL`
+
Connect to a MySQL database server.
+
-* `JDBC`
+* `ORACLE`
+
-Connect using a JDBC driver class name and URL.
+Connect to an Oracle database server.
++
+* `POSTGRESQL`
++
+Connect to a PostgreSQL database server.
+
If not specified, database.driver and database.url are used as-is,
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index e32e30d..6b26336 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -433,6 +433,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/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
new file mode 100644
index 0000000..f49de7d
--- /dev/null
+++ b/ReleaseNotes/ReleaseNotes-2.12.1.txt
@@ -0,0 +1,236 @@
+Release notes for Gerrit 2.12.1
+===============================
+
+Gerrit 2.12.1 is now available:
+
+link:https://gerrit-releases.storage.googleapis.com/gerrit-2.12.1.war[
+https://gerrit-releases.storage.googleapis.com/gerrit-2.12.1.war]
+
+Gerrit 2.12.1 includes the bug fixes done with
+link:ReleaseNotes-2.11.6.html[Gerrit 2.11.6] and
+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.
+
+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;
+----
+
+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.
+
+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.
++
+The column type `VARCHAR(255)` was too small, preventing some PGP push
+certificates from being stored.
+
+* Add the `DRAFT_COMMENTS` option to the list changes REST API endpoint
+and mark it as deprecated.
++
+It was removed in version 2.12 because it's not needed any more by the UI,
+but this caused failures for clients that still use it.
++
+Now it is added back, although it does not do anything and is marked as
+deprecated.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=3669[Issue 3669]:
+Fix schema migration when migrating to 2.12.x directly from a version
+earlier than 2.11.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=3733[Issue 3733]:
+Correctly detect symlinked log directory on startup.
++
+If `$site_path/logs` was a symlink, the server would not start.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=3871[Issue 3871]:
+Throw an explicit exception when failing to load a change from the database.
++
+If a change could not be loaded from the database, for example if it was
+manually removed from the changes table but references to it were remaining
+in other tables, a null change was returned which would then lead to an
+'Internal Server Error' that was difficult to track down. Now an error is
+raised earlier which will help administrators to find the root cause.
+
+* 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.
++
+When submitting a change that required rebase, the committer was being
+set to 'Gerrit Code Review' instead of the name of the submitter.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=3758[Issue 3758]:
+Fix serving of static resources when deployed in application container.
++
+When deployed in a container, for example Tomcat, it was not possible to
+load the UI because static content could not be loaded from the WAR file.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=3790[Issue 3790]:
+When deployed in a container, for example Tomcat, the 'Documentation' menu
+was missing.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=3786[Issue 3786]:
+Fix SQL statement syntax in schema migration.
++
+An extra semicolon was preventing migration from 2.11.x to 2.12 when using
+an Oracle database.
+
+* Send email using email queue instead of the default queue.
++
+Some emails sent asynchronously were already being sent using that queue
+but some were not. This was confusing for a gerrit administrator because
+if there is a build up of `send-email` tasks in the queue, he would
+think that increasing `sendemail.threadPoolSize` would help but it did not
+because some of the email were sent using the default queue which is
+configurable using `execution.defaultThreadPoolSize`.
+
+* Fix XSRF token cookie to honor `auth.cookieSecure` setting.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=3767[Issue 3767]:
+Fix replication of first patch set for new changes.
++
+When new changes were pushed from the command line, the first patch
+set did not get replicated to destinations.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=3771[Issue 3771]:
+Remove `index.defaultMaxClauseCount` configuration option.
++
+When `index.maxTerms` was either not set (thus no limit) or set to a value
+higher than `index.defaultMaxClauseCount` it was possible that viewing the
+related changes tab could cause a 'Too many clauses' error for changes that
+have a lot of related changes.
++
+The `index.defaultMaxClauseCount` configuration option is removed, and the
+existing `index.maxTerms` is reused. The default value of `index.maxTerms`
+is reduced from 'no limit' to 1024.
+
+* 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.
+
+* 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.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=3718[Issue 3718]:
+Hide avatar images when no avatars are available.
++
+The UI was showing a transparent empty image with a border.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=3731[Issue 3731]:
+Fix syntax higlighting of tcl files.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=3863[Issue 3863]:
+Fix display of active row marker in tag list.
++
+Clicking on one of the rows would cause the tag name to disappear.
+
+* 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.
+
+* 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
+~~~~~~~
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=3821[Issue 3821]:
+Fix repeated reloading of plugins when running on OpenJDK 8.
++
+OpenJDK 8 uses nanotime precision for file modification time on systems that
+are POSIX 2008 compatible. This leads to precision incompatibility when
+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
+--------
+
+* Upgrade JGit to 4.1.2.201602141800-r.
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 fa57dab..4cab151 100644
--- a/ReleaseNotes/index.txt
+++ b/ReleaseNotes/index.txt
@@ -9,11 +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/PushOneCommit.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/PushOneCommit.java
index 35e86c5..291b953 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/PushOneCommit.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/PushOneCommit.java
@@ -43,6 +43,8 @@
import org.eclipse.jgit.transport.RemoteRefUpdate;
import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
+import java.util.List;
+
public class PushOneCommit {
public static final String SUBJECT = "test commit";
public static final String FILE_NAME = "a.txt";
@@ -179,6 +181,13 @@
.committer(new PersonIdent(i, testRepo.getDate()));
}
+ public void setParents(List<RevCommit> parents) throws Exception {
+ commitBuilder.noParents();
+ for (RevCommit p : parents) {
+ commitBuilder.parent(p);
+ }
+ }
+
public Result to(String ref) throws Exception {
commitBuilder.add(fileName, content);
return execute(ref);
@@ -189,7 +198,7 @@
return execute(ref);
}
- private Result execute(String ref) throws Exception {
+ public Result execute(String ref) throws Exception {
RevCommit c = commitBuilder.create();
if (changeId == null) {
changeId = GitUtil.getChangeId(testRepo, c).get();
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmitOnPushIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmitOnPushIT.java
index 7e107cf..431cfaa 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmitOnPushIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmitOnPushIT.java
@@ -189,7 +189,7 @@
.setRefSpecs(new RefSpec(r.getCommit().name() + ":refs/heads/master"))
.call();
assertCommit(project, "refs/heads/master");
- assertThat(getSubmitter(r.getPatchSetId())).isNull();
+ assertSubmitApproval(r.getPatchSetId());
ChangeInfo c =
gApi.changes().id(r.getPatchSetId().getParentKey().get()).get();
assertThat(c.status).isEqualTo(ChangeStatus.MERGED);
@@ -209,7 +209,7 @@
r.assertOkStatus();
assertCommit(project, "refs/heads/master");
- assertThat(getSubmitter(r.getPatchSetId())).isNull();
+ assertSubmitApproval(r.getPatchSetId());
ChangeInfo c =
gApi.changes().id(r.getPatchSetId().getParentKey().get()).get();
assertThat(c.status).isEqualTo(ChangeStatus.MERGED);
@@ -225,7 +225,7 @@
private void assertSubmitApproval(PatchSet.Id patchSetId) throws Exception {
PatchSetApproval a = getSubmitter(patchSetId);
- assertThat(a.isSubmit()).isTrue();
+ assertThat(a.isLegacySubmit()).isTrue();
assertThat(a.getValue()).isEqualTo((short) 1);
assertThat(a.getAccountId()).isEqualTo(admin.id);
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
index 2b7f930..28a01fb 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
@@ -356,7 +356,7 @@
PatchSetApproval submitter = approvalsUtil.getSubmitter(
db, cn, new PatchSet.Id(cn.getChangeId(), psId));
assertThat(submitter).isNotNull();
- assertThat(submitter.isSubmit()).isTrue();
+ assertThat(submitter.isLegacySubmit()).isTrue();
assertThat(submitter.getAccountId()).isEqualTo(admin.getId());
}
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 d6c8dac..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(
- "Clicking the button would fail for other changes");
+ "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
new file mode 100644
index 0000000..5a6c36a
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitResolvingMergeCommitIT.java
@@ -0,0 +1,363 @@
+// 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.common.truth.TruthJUnit.assume;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.extensions.client.ChangeStatus;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.change.Submit;
+import com.google.gerrit.server.git.ChangeSet;
+import com.google.gerrit.server.git.MergeSuperSet;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.testutil.ConfigSuite;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+@NoHttpd
+public class SubmitResolvingMergeCommitIT extends AbstractDaemonTest {
+ @Inject
+ private MergeSuperSet mergeSuperSet;
+
+ @Inject
+ private Submit submit;
+
+ @ConfigSuite.Default
+ public static Config submitWholeTopicEnabled() {
+ return submitWholeTopicEnabledConfig();
+ }
+
+ @Test
+ public void resolvingMergeCommitAtEndOfChain() throws Exception {
+ /*
+ A <- B <- C <------- D
+ ^ ^
+ | |
+ E <- F <- G <- H <-- M*
+
+ G has a conflict with C and is resolved in M which is a merge
+ commit of H and D.
+ */
+
+ PushOneCommit.Result a = createChange("A");
+ PushOneCommit.Result b = createChange("B", "new.txt", "No conflict line",
+ ImmutableList.of(a.getCommit()));
+ PushOneCommit.Result c = createChange("C", ImmutableList.of(b.getCommit()));
+ PushOneCommit.Result d = createChange("D", ImmutableList.of(c.getCommit()));
+
+ PushOneCommit.Result e = createChange("E", ImmutableList.of(a.getCommit()));
+ PushOneCommit.Result f = createChange("F", ImmutableList.of(e.getCommit()));
+ PushOneCommit.Result g = createChange("G", "new.txt", "Conflicting line",
+ ImmutableList.of(f.getCommit()));
+ PushOneCommit.Result h = createChange("H", ImmutableList.of(g.getCommit()));
+
+ approve(a.getChangeId());
+ approve(b.getChangeId());
+ approve(c.getChangeId());
+ approve(d.getChangeId());
+ submit(d.getChangeId());
+
+ approve(e.getChangeId());
+ approve(f.getChangeId());
+ approve(g.getChangeId());
+ approve(h.getChangeId());
+
+ assertMergeable(e.getChange(), true);
+ assertMergeable(f.getChange(), true);
+ assertMergeable(g.getChange(), false);
+ assertMergeable(h.getChange(), false);
+
+ PushOneCommit.Result m = createChange("M", "new.txt", "Resolved conflict",
+ ImmutableList.of(d.getCommit(), h.getCommit()));
+ approve(m.getChangeId());
+
+ assertChangeSetMergeable(m.getChange(), true);
+
+ assertMergeable(m.getChange(), true);
+ submit(m.getChangeId());
+
+ assertMerged(e.getChangeId());
+ assertMerged(f.getChangeId());
+ assertMerged(g.getChangeId());
+ assertMerged(h.getChangeId());
+ assertMerged(m.getChangeId());
+ }
+
+ @Test
+ public void resolvingMergeCommitComingBeforeConflict() throws Exception {
+ /*
+ A <- B <- C <- D
+ ^ ^
+ | |
+ E <- F* <- G
+
+ F is a merge commit of E and B and resolves any conflict.
+ However G is conflicting with C.
+ */
+
+ PushOneCommit.Result a = createChange("A");
+ PushOneCommit.Result b = createChange("B", "new.txt", "No conflict line",
+ ImmutableList.of(a.getCommit()));
+ PushOneCommit.Result c = createChange("C", "new.txt", "No conflict line #2",
+ ImmutableList.of(b.getCommit()));
+ PushOneCommit.Result d = createChange("D", ImmutableList.of(c.getCommit()));
+ PushOneCommit.Result e = createChange("E", "new.txt", "Conflicting line",
+ ImmutableList.of(a.getCommit()));
+ PushOneCommit.Result f = createChange("F", "new.txt", "Resolved conflict",
+ ImmutableList.of(b.getCommit(), e.getCommit()));
+ PushOneCommit.Result g = createChange("G", "new.txt", "Conflicting line #2",
+ ImmutableList.of(f.getCommit()));
+
+ assertMergeable(e.getChange(), true);
+
+ approve(a.getChangeId());
+ approve(b.getChangeId());
+ submit(b.getChangeId());
+
+ assertMergeable(e.getChange(), false);
+ assertMergeable(f.getChange(), true);
+ assertMergeable(g.getChange(), true);
+
+ approve(c.getChangeId());
+ approve(d.getChangeId());
+ submit(d.getChangeId());
+
+ approve(e.getChangeId());
+ approve(f.getChangeId());
+ approve(g.getChangeId());
+
+ assertMergeable(g.getChange(), false);
+ assertChangeSetMergeable(g.getChange(), false);
+ }
+
+ @Test
+ public void resolvingMergeCommitWithTopics() throws Exception {
+ /*
+ Project1:
+ A <- B <-- C <---
+ ^ ^ |
+ | | |
+ E <- F* <- G <- L*
+
+ G clashes with C, and F resolves the clashes between E and B.
+ Later, L resolves the clashes between C and G.
+
+ Project2:
+ H <- I
+ ^ ^
+ | |
+ J <- K*
+
+ J clashes with I, and K resolves all problems.
+ G, K and L are in the same topic.
+ */
+ assume().that(isSubmitWholeTopicEnabled()).isTrue();
+
+ String project1Name = name("Project1");
+ String project2Name = name("Project2");
+ gApi.projects().create(project1Name);
+ gApi.projects().create(project2Name);
+ TestRepository<InMemoryRepository> project1 =
+ cloneProject(new Project.NameKey(project1Name));
+ TestRepository<InMemoryRepository> project2 =
+ cloneProject(new Project.NameKey(project2Name));
+
+ PushOneCommit.Result a = createChange(project1, "A");
+ PushOneCommit.Result b = createChange(project1, "B", "new.txt",
+ "No conflict line", ImmutableList.of(a.getCommit()));
+ PushOneCommit.Result c = createChange(project1, "C", "new.txt",
+ "No conflict line #2", ImmutableList.of(b.getCommit()));
+
+ approve(a.getChangeId());
+ approve(b.getChangeId());
+ approve(c.getChangeId());
+ submit(c.getChangeId());
+
+ PushOneCommit.Result e = createChange(project1, "E", "new.txt",
+ "Conflicting line", ImmutableList.of(a.getCommit()));
+ PushOneCommit.Result f = createChange(project1, "F", "new.txt",
+ "Resolved conflict", ImmutableList.of(b.getCommit(), e.getCommit()));
+ PushOneCommit.Result g = createChange(project1, "G", "new.txt",
+ "Conflicting line #2", ImmutableList.of(f.getCommit()),
+ "refs/for/master/" + name("topic1"));
+
+ PushOneCommit.Result h = createChange(project2, "H");
+ PushOneCommit.Result i = createChange(project2, "I", "new.txt",
+ "No conflict line", ImmutableList.of(h.getCommit()));
+ PushOneCommit.Result j = createChange(project2, "J", "new.txt",
+ "Conflicting line", ImmutableList.of(h.getCommit()));
+ PushOneCommit.Result k =
+ createChange(project2, "K", "new.txt", "Sadly conflicting topic-wise",
+ ImmutableList.of(i.getCommit(), j.getCommit()),
+ "refs/for/master/" + name("topic1"));
+
+ approve(h.getChangeId());
+ approve(i.getChangeId());
+ submit(i.getChangeId());
+
+ approve(e.getChangeId());
+ approve(f.getChangeId());
+ approve(g.getChangeId());
+ approve(j.getChangeId());
+ approve(k.getChangeId());
+
+ assertChangeSetMergeable(g.getChange(), false);
+ assertChangeSetMergeable(k.getChange(), false);
+
+ PushOneCommit.Result l =
+ createChange(project1, "L", "new.txt", "Resolving conflicts again",
+ ImmutableList.of(c.getCommit(), g.getCommit()),
+ "refs/for/master/" + name("topic1"));
+
+ approve(l.getChangeId());
+ assertChangeSetMergeable(l.getChange(), true);
+
+ submit(l.getChangeId());
+ assertMerged(c.getChangeId());
+ assertMerged(g.getChangeId());
+ assertMerged(k.getChangeId());
+ }
+
+ @Test
+ public void resolvingMergeCommitAtEndOfChainAndNotUpToDate() throws Exception {
+ /*
+ A <-- B
+ \
+ C <- D
+ \ /
+ E
+
+ B is the target branch, and D should be merged with B, but one
+ of C conflicts with B
+ */
+
+ PushOneCommit.Result a = createChange("A");
+ PushOneCommit.Result b = createChange("B", "new.txt", "No conflict line",
+ ImmutableList.of(a.getCommit()));
+
+ approve(a.getChangeId());
+ approve(b.getChangeId());
+ submit(b.getChangeId());
+
+ PushOneCommit.Result c = createChange("C", "new.txt", "Create conflicts",
+ ImmutableList.of(a.getCommit()));
+ PushOneCommit.Result e = createChange("E", ImmutableList.of(c.getCommit()));
+ PushOneCommit.Result d = createChange("D", "new.txt", "Resolves conflicts",
+ ImmutableList.of(c.getCommit(), e.getCommit()));
+
+ approve(c.getChangeId());
+ approve(e.getChangeId());
+ approve(d.getChangeId());
+ assertMergeable(d.getChange(), false);
+ assertChangeSetMergeable(d.getChange(), false);
+ }
+
+ private void submit(String changeId) throws Exception {
+ gApi.changes()
+ .id(changeId)
+ .current()
+ .submit();
+ }
+
+ private void assertChangeSetMergeable(ChangeData change, boolean expected)
+ throws MissingObjectException, IncorrectObjectTypeException, IOException,
+ OrmException {
+ ChangeSet cs =
+ mergeSuperSet.completeChangeSet(db, change.change(), user(admin));
+ assertThat(submit.unmergeableChanges(cs).isEmpty()).isEqualTo(expected);
+ }
+
+ private void assertMergeable(ChangeData change, boolean expected)
+ throws Exception {
+ change.setMergeable(null);
+ assertThat(change.isMergeable()).isEqualTo(expected);
+ }
+
+ private void assertMerged(String changeId) throws Exception {
+ assertThat(gApi
+ .changes()
+ .id(changeId)
+ .get()
+ .status).isEqualTo(ChangeStatus.MERGED);
+ }
+
+ private PushOneCommit.Result createChange(TestRepository<?> repo,
+ String subject, String fileName, String content, List<RevCommit> parents,
+ String ref) throws Exception {
+ PushOneCommit push = pushFactory.create(db, admin.getIdent(), repo,
+ subject, fileName, content);
+
+ if (!parents.isEmpty()) {
+ push.setParents(parents);
+ }
+
+ PushOneCommit.Result result;
+ if (fileName.isEmpty()) {
+ result = push.execute(ref);
+ } else {
+ result = push.to(ref);
+ }
+ result.assertOkStatus();
+ return result;
+ }
+
+ private PushOneCommit.Result createChange(TestRepository<?> repo,
+ String subject) throws Exception {
+ return createChange(repo, subject, "x", "x", new ArrayList<RevCommit>(),
+ "refs/for/master");
+ }
+
+ private PushOneCommit.Result createChange(TestRepository<?> repo,
+ String subject, String fileName, String content, List<RevCommit> parents)
+ throws Exception {
+ return createChange(repo, subject, fileName, content, parents,
+ "refs/for/master");
+ }
+
+ @Override
+ protected PushOneCommit.Result createChange(String subject) throws Exception {
+ return createChange(testRepo, subject, "", "",
+ Collections.<RevCommit> emptyList(), "refs/for/master");
+ }
+
+ private PushOneCommit.Result createChange(String subject,
+ List<RevCommit> parents) throws Exception {
+ return createChange(testRepo, subject, "", "", parents, "refs/for/master");
+ }
+
+ private PushOneCommit.Result createChange(String subject, String fileName,
+ String content, List<RevCommit> parents) throws Exception {
+ return createChange(testRepo, subject, fileName, content, parents,
+ "refs/for/master");
+ }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/SetParentIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/SetParentIT.java
index e09c63a..b77fb01 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/SetParentIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/SetParentIT.java
@@ -19,6 +19,7 @@
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.config.AllProjectsNameProvider;
import com.google.gerrit.server.project.SetParent;
import org.junit.Test;
@@ -49,6 +50,19 @@
newGson().fromJson(r.getReader(), String.class);
assertThat(newParent).isEqualTo(parent);
r.consume();
+
+ // When the parent name is not explicitly set, it should be
+ // set to "All-Projects".
+ r = adminSession.put("/projects/" + project.get() + "/parent",
+ newParentInput(null));
+ r.assertOK();
+ r.consume();
+
+ r = adminSession.get("/projects/" + project.get() + "/parent");
+ r.assertOK();
+ newParent = newGson().fromJson(r.getReader(), String.class);
+ assertThat(newParent).isEqualTo(AllProjectsNameProvider.DEFAULT);
+ r.consume();
}
@Test
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..4d368f6 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,5 @@
package com.google.gerrit.extensions.api.changes;
public class SubmitInput {
- @Deprecated
- public boolean waitForMerge;
public String onBehalfOf;
}
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyCommandSet.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyCommandSet.java
index e2fec27..2625222 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyCommandSet.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyCommandSet.java
@@ -136,15 +136,6 @@
if (mask == 0) {
mask = event.getNativeEvent().getKeyCode();
}
- if (event.isAltKeyDown()) {
- mask |= KeyCommand.M_ALT;
- }
- if (event.isControlKeyDown()) {
- mask |= KeyCommand.M_CTRL;
- }
- if (event.isMetaKeyDown()) {
- mask |= KeyCommand.M_META;
- }
return mask;
}
}
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 c652cb2..2996c07 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
@@ -368,8 +368,8 @@
KeyMap keyMap = KeyMap.create()
.on("A", upToChange(true))
.on("U", upToChange(false))
- .on("[", header.navigate(Direction.PREV))
- .on("]", header.navigate(Direction.NEXT))
+ .on("'['", header.navigate(Direction.PREV))
+ .on("']'", header.navigate(Direction.NEXT))
.on("R", header.toggleReviewed())
.on("O", commentManager.toggleOpenBox(cm))
.on("Enter", commentManager.toggleOpenBox(cm))
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 190de6d..5bbb798 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
@@ -47,6 +47,7 @@
import com.google.gerrit.server.notedb.ChangeRebuilder;
import com.google.gerrit.server.notedb.NoteDbModule;
import com.google.gerrit.server.notedb.NotesMigration;
+import com.google.gerrit.server.schema.DisabledChangesReviewDbWrapper;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.AbstractModule;
@@ -221,13 +222,20 @@
Multimap<Project.NameKey, Change.Id> changesByProject =
ArrayListMultimap.create();
try (ReviewDb db = schemaFactory.open()) {
- for (Change c : db.changes().all()) {
+ for (Change c : unwrap(db).changes().all()) {
changesByProject.put(c.getProject(), c.getId());
}
return changesByProject;
}
}
+ private static ReviewDb unwrap(ReviewDb db) {
+ if (db instanceof DisabledChangesReviewDbWrapper) {
+ db = ((DisabledChangesReviewDbWrapper) db).unsafeGetDelegate();
+ }
+ return db;
+ }
+
private static class RebuildListener implements Runnable {
private Change.Id changeId;
private ListenableFuture<?> future;
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/LabelId.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/LabelId.java
index f2af5fa..5239447 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/LabelId.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/LabelId.java
@@ -20,7 +20,11 @@
public class LabelId extends StringKey<com.google.gwtorm.client.Key<?>> {
private static final long serialVersionUID = 1L;
- public static final LabelId SUBMIT = new LabelId("SUBM");
+ static final String LEGACY_SUBMIT_NAME = "SUBM";
+
+ public static LabelId legacySubmit() {
+ return new LabelId(LEGACY_SUBMIT_NAME);
+ }
@Column(id = 1)
public String id;
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSetApproval.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSetApproval.java
index bc7ebc9..c89be30 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSetApproval.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/PatchSetApproval.java
@@ -149,8 +149,8 @@
return getLabelId().get();
}
- public boolean isSubmit() {
- return LabelId.SUBMIT.get().equals(getLabel());
+ public boolean isLegacySubmit() {
+ return LabelId.LEGACY_SUBMIT_NAME.equals(getLabel());
}
@Override
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..5442f07
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/common/EventBroker.java
@@ -0,0 +1,183 @@
+// 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).
+ */
+ private final DynamicSet<UserScopedEventListener> listeners;
+
+ /** Listeners to receive all changes as they happen. */
+ private final DynamicSet<EventListener> unrestrictedListeners;
+
+ private final ProjectCache projectCache;
+
+ private 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);
+ }
+
+ private 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);
+ }
+ return true;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java
index 677f849..78cfb15 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java
@@ -321,7 +321,7 @@
}
PatchSetApproval submitter = null;
for (PatchSetApproval a : approvals) {
- if (a.getPatchSetId().equals(c) && a.getValue() > 0 && a.isSubmit()) {
+ if (a.getPatchSetId().equals(c) && a.getValue() > 0 && a.isLegacySubmit()) {
if (submitter == null
|| a.getGranted().compareTo(submitter.getGranted()) > 0) {
submitter = a;
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..52446fe 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
@@ -26,6 +26,7 @@
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.RestApiModule;
import com.google.gerrit.server.account.AccountLoader;
+import com.google.gerrit.server.change.SuggestChangeReviewers;
import com.google.gerrit.server.change.Reviewed.DeleteReviewed;
import com.google.gerrit.server.change.Reviewed.PutReviewed;
@@ -72,7 +73,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/PostReview.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
index bf40bbb..ef4f6d7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
@@ -611,7 +611,7 @@
for (PatchSetApproval a : approvalsUtil.byPatchSetUser(
ctx.getDb(), ctx.getControl(), psId, user.getAccountId())) {
- if (a.isSubmit()) {
+ if (a.isLegacySubmit()) {
continue;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java
index f07a7ed..84f7307 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java
@@ -14,8 +14,6 @@
package com.google.gerrit.server.change;
-import com.google.auto.value.AutoValue;
-import com.google.common.primitives.Ints;
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.extensions.api.changes.RebaseInput;
@@ -31,16 +29,13 @@
import com.google.gerrit.reviewdb.client.Change.Status;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.PatchSetUtil;
+import com.google.gerrit.server.change.RebaseUtil.Base;
import com.google.gerrit.server.git.BatchUpdate;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.UpdateException;
import com.google.gerrit.server.git.validators.CommitValidators;
-import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
-import com.google.gerrit.server.query.change.ChangeData;
-import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -70,10 +65,7 @@
private final RebaseChangeOp.Factory rebaseFactory;
private final RebaseUtil rebaseUtil;
private final ChangeJson.Factory json;
- private final ChangeNotes.Factory notesFactory;
private final Provider<ReviewDb> dbProvider;
- private final Provider<InternalChangeQuery> queryProvider;
- private final PatchSetUtil psUtil;
@Inject
public Rebase(BatchUpdate.Factory updateFactory,
@@ -81,19 +73,13 @@
RebaseChangeOp.Factory rebaseFactory,
RebaseUtil rebaseUtil,
ChangeJson.Factory json,
- ChangeNotes.Factory notesFactory,
- Provider<ReviewDb> dbProvider,
- Provider<InternalChangeQuery> queryProvider,
- PatchSetUtil psUtil) {
+ Provider<ReviewDb> dbProvider) {
this.updateFactory = updateFactory;
this.repoManager = repoManager;
this.rebaseFactory = rebaseFactory;
this.rebaseUtil = rebaseUtil;
this.json = json;
- this.notesFactory = notesFactory;
this.dbProvider = dbProvider;
- this.queryProvider = queryProvider;
- this.psUtil = psUtil;
}
@Override
@@ -144,7 +130,7 @@
@SuppressWarnings("resource")
ReviewDb db = dbProvider.get();
- Base base = parseBase(rsrc, str);
+ Base base = rebaseUtil.parseBase(rsrc, str);
if (base == null) {
throw new ResourceConflictException("base revision is missing: " + str);
}
@@ -180,72 +166,6 @@
return rw.isMergedInto(rw.parseCommit(baseId), rw.parseCommit(tipId));
}
- @AutoValue
- static abstract class Base {
- private static Base create(ChangeControl ctl, PatchSet ps) {
- if (ctl == null) {
- return null;
- }
- return new AutoValue_Rebase_Base(ctl, ps);
- }
-
- abstract ChangeControl control();
- abstract PatchSet patchSet();
- }
-
- private Base parseBase(RevisionResource rsrc, String base)
- throws OrmException, NoSuchChangeException {
- ReviewDb db = dbProvider.get();
-
- // Try parsing the base as a ref string.
- PatchSet.Id basePatchSetId = PatchSet.Id.fromRef(base);
- if (basePatchSetId != null) {
- Change.Id baseChangeId = basePatchSetId.getParentKey();
- ChangeControl baseCtl = controlFor(rsrc, baseChangeId);
- if (baseCtl != null) {
- return Base.create(
- controlFor(rsrc, basePatchSetId.getParentKey()),
- psUtil.get(db, baseCtl.getNotes(), basePatchSetId));
- }
- }
-
- // Try parsing base as a change number (assume current patch set).
- Integer baseChangeId = Ints.tryParse(base);
- if (baseChangeId != null) {
- ChangeControl baseCtl = controlFor(rsrc, new Change.Id(baseChangeId));
- if (baseCtl != null) {
- return Base.create(baseCtl, psUtil.current(db, baseCtl.getNotes()));
- }
- }
-
- // Try parsing as SHA-1.
- Base ret = null;
- for (ChangeData cd : queryProvider.get()
- .byProjectCommit(rsrc.getProject(), base)) {
- for (PatchSet ps : cd.patchSets()) {
- if (!ps.getRevision().matches(base)) {
- continue;
- }
- if (ret == null || ret.patchSet().getId().get() < ps.getId().get()) {
- ret = Base.create(
- rsrc.getControl().getProjectControl().controlFor(cd.notes()),
- ps);
- }
- }
- }
- return ret;
- }
-
- private ChangeControl controlFor(RevisionResource rsrc, Change.Id id)
- throws OrmException, NoSuchChangeException {
- if (rsrc.getChange().getId().equals(id)) {
- return rsrc.getControl();
- }
- ChangeNotes notes =
- notesFactory.createChecked(dbProvider.get(), rsrc.getProject(), id);
- return rsrc.getControl().getProjectControl().controlFor(notes);
- }
-
private boolean hasOneParent(RevWalk rw, PatchSet ps) throws IOException {
// Prevent rebase of exotic changes (merge commit, no ancestor).
RevCommit c = rw.parseCommit(ObjectId.fromString(ps.getRevision().get()));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeOp.java
index 4366019..c7e11d2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeOp.java
@@ -23,6 +23,7 @@
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.change.RebaseUtil.Base;
import com.google.gerrit.server.git.BatchUpdate;
import com.google.gerrit.server.git.BatchUpdate.ChangeContext;
import com.google.gerrit.server.git.BatchUpdate.Context;
@@ -31,6 +32,7 @@
import com.google.gerrit.server.git.validators.CommitValidators;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.ProjectState;
import com.google.gwtorm.server.OrmException;
import com.google.inject.assistedinject.Assisted;
@@ -114,7 +116,7 @@
@Override
public void updateRepo(RepoContext ctx) throws MergeConflictException,
InvalidChangeOperationException, RestApiException, IOException,
- OrmException {
+ OrmException, NoSuchChangeException {
// Ok that originalPatchSet was not read in a transaction, since we just
// need its revision.
RevId oldRev = originalPatchSet.getRevision();
@@ -133,6 +135,12 @@
rebasedCommit = rebaseCommit(ctx, original, baseCommit);
+ RevId baseRevId = new RevId((baseCommitish != null) ? baseCommitish
+ : ObjectId.toString(baseCommit.getId()));
+ Base base = rebaseUtil.parseBase(
+ new RevisionResource(new ChangeResource(ctl), originalPatchSet),
+ baseRevId.get());
+
rebasedPatchSetId = ChangeUtil.nextPatchSetId(
ctx.getRepository(), ctl.getChange().currentPatchSetId());
patchSetInserter = patchSetInserterFactory
@@ -144,6 +152,10 @@
.setMessage(
"Patch Set " + rebasedPatchSetId.get()
+ ": Patch Set " + originalPatchSet.getId().get() + " was rebased");
+
+ if (base != null) {
+ patchSetInserter.setGroups(base.patchSet().getGroups());
+ }
if (validate != null) {
patchSetInserter.setValidatePolicy(validate);
}
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 b75a659..dd9a0b9 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
@@ -14,14 +14,21 @@
package com.google.gerrit.server.change;
+import com.google.auto.value.AutoValue;
+import com.google.common.primitives.Ints;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
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.server.PatchSetUtil;
+import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gwtorm.server.OrmException;
@@ -43,10 +50,19 @@
private static final Logger log = LoggerFactory.getLogger(RebaseUtil.class);
private final Provider<InternalChangeQuery> queryProvider;
+ private final ChangeNotes.Factory notesFactory;
+ private final Provider<ReviewDb> dbProvider;
+ private final PatchSetUtil psUtil;
@Inject
- RebaseUtil(Provider<InternalChangeQuery> queryProvider) {
+ RebaseUtil(Provider<InternalChangeQuery> queryProvider,
+ ChangeNotes.Factory notesFactory,
+ Provider<ReviewDb> dbProvider,
+ PatchSetUtil psUtil) {
this.queryProvider = queryProvider;
+ this.notesFactory = notesFactory;
+ this.dbProvider = dbProvider;
+ this.psUtil = psUtil;
}
public boolean canRebase(PatchSet patchSet, Branch.NameKey dest,
@@ -64,6 +80,72 @@
}
}
+ @AutoValue
+ static abstract class Base {
+ private static Base create(ChangeControl ctl, PatchSet ps) {
+ if (ctl == null) {
+ return null;
+ }
+ return new AutoValue_RebaseUtil_Base(ctl, ps);
+ }
+
+ abstract ChangeControl control();
+ abstract PatchSet patchSet();
+ }
+
+ Base parseBase(RevisionResource rsrc, String base)
+ throws OrmException, NoSuchChangeException {
+ ReviewDb db = dbProvider.get();
+
+ // Try parsing the base as a ref string.
+ PatchSet.Id basePatchSetId = PatchSet.Id.fromRef(base);
+ if (basePatchSetId != null) {
+ Change.Id baseChangeId = basePatchSetId.getParentKey();
+ ChangeControl baseCtl = controlFor(rsrc, baseChangeId);
+ if (baseCtl != null) {
+ return Base.create(
+ controlFor(rsrc, basePatchSetId.getParentKey()),
+ psUtil.get(db, baseCtl.getNotes(), basePatchSetId));
+ }
+ }
+
+ // Try parsing base as a change number (assume current patch set).
+ Integer baseChangeId = Ints.tryParse(base);
+ if (baseChangeId != null) {
+ ChangeControl baseCtl = controlFor(rsrc, new Change.Id(baseChangeId));
+ if (baseCtl != null) {
+ return Base.create(baseCtl, psUtil.current(db, baseCtl.getNotes()));
+ }
+ }
+
+ // Try parsing as SHA-1.
+ Base ret = null;
+ for (ChangeData cd : queryProvider.get()
+ .byProjectCommit(rsrc.getProject(), base)) {
+ for (PatchSet ps : cd.patchSets()) {
+ if (!ps.getRevision().matches(base)) {
+ continue;
+ }
+ if (ret == null || ret.patchSet().getId().get() < ps.getId().get()) {
+ ret = Base.create(
+ rsrc.getControl().getProjectControl().controlFor(cd.notes()),
+ ps);
+ }
+ }
+ }
+ return ret;
+ }
+
+ private ChangeControl controlFor(RevisionResource rsrc, Change.Id id)
+ throws OrmException, NoSuchChangeException {
+ if (rsrc.getChange().getId().equals(id)) {
+ return rsrc.getControl();
+ }
+ ChangeNotes notes =
+ notesFactory.createChecked(dbProvider.get(), rsrc.getProject(), id);
+ return rsrc.getControl().getProjectControl().controlFor(notes);
+ }
+
/**
* Find the commit onto which a patch set should be rebased.
* <p>
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 154f8d9..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,16 @@
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;
import com.google.gerrit.extensions.api.changes.SubmitInput;
import com.google.gerrit.extensions.common.ChangeInfo;
@@ -29,9 +34,11 @@
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.extensions.webui.UiAction;
+import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ChangeMessagesUtil;
@@ -58,12 +65,19 @@
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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;
@Singleton
public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
@@ -85,8 +99,8 @@
"This change depends on other hidden changes which are not ready";
private static final String CLICK_FAILURE_TOOLTIP =
"Clicking the button would fail";
- private static final String CLICK_FAILURE_OTHER_TOOLTIP =
- "Clicking the button would fail for other changes";
+ private static final String CHANGES_NOT_MERGEABLE =
+ "See the \"Submitted Together\" tab for problems, specially see: ";
public static class Output {
transient Change change;
@@ -104,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;
}
}
@@ -129,6 +141,7 @@
private final ParameterizedString submitTopicTooltip;
private final boolean submitWholeTopic;
private final Provider<InternalChangeQuery> queryProvider;
+ private final PatchSetUtil psUtil;
@Inject
Submit(Provider<ReviewDb> dbProvider,
@@ -141,7 +154,8 @@
AccountsCollection accounts,
ChangesCollection changes,
@GerritServerConfig Config cfg,
- Provider<InternalChangeQuery> queryProvider) {
+ Provider<InternalChangeQuery> queryProvider,
+ PatchSetUtil psUtil) {
this.dbProvider = dbProvider;
this.repoManager = repoManager;
this.changeDataFactory = changeDataFactory;
@@ -173,6 +187,7 @@
cfg.getString("change", null, "submitTopicTooltip"),
DEFAULT_TOPIC_TOOLTIP));
this.queryProvider = queryProvider;
+ this.psUtil = psUtil;
}
@Override
@@ -247,24 +262,25 @@
if (!changeControl.canSubmit()) {
return BLOCKED_SUBMIT_TOOLTIP;
}
- // Recheck mergeability rather than using value stored in the index,
- // which may be stale.
- // TODO(dborowitz): This is ugly; consider providing a way to not read
- // stored fields from the index in the first place.
- c.setMergeable(null);
- Boolean mergeable = c.isMergeable();
- if (mergeable == null) {
- log.error("Ephemeral error checking if change is submittable");
- return CLICK_FAILURE_TOOLTIP;
- }
- if (!mergeable) {
- return CLICK_FAILURE_OTHER_TOOLTIP;
- }
MergeOp.checkSubmitRule(c);
}
+
+ Collection<ChangeData> unmergeable = unmergeableChanges(cs);
+ if (unmergeable == null) {
+ return CLICK_FAILURE_TOOLTIP;
+ } 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;
- } catch (OrmException e) {
+ } catch (OrmException | IOException e) {
log.error("Error checking if change is submittable", e);
throw new OrmRuntimeException("Could not determine problems for the change", e);
}
@@ -400,6 +416,71 @@
return change != null ? change.getStatus().name().toLowerCase() : "deleted";
}
+ public Collection<ChangeData> unmergeableChanges(ChangeSet cs)
+ throws OrmException, IOException {
+ Set<ChangeData> mergeabilityMap = new HashSet<>();
+ for (ChangeData change : cs.changes()) {
+ mergeabilityMap.add(change);
+ }
+
+ Multimap<Branch.NameKey, ChangeData> cbb = cs.changesByBranch();
+ for (Branch.NameKey branch : cbb.keySet()) {
+ Collection<ChangeData> targetBranch = cbb.get(branch);
+ HashMap<Change.Id, RevCommit> commits =
+ findCommits(targetBranch, branch.getParentKey());
+
+ Set<ObjectId> allParents = Sets.newHashSetWithExpectedSize(cs.size());
+ for (RevCommit commit : commits.values()) {
+ for (RevCommit parent : commit.getParents()) {
+ allParents.add(parent.getId());
+ }
+ }
+
+ for (ChangeData change : targetBranch) {
+ RevCommit commit = commits.get(change.getId());
+ boolean isMergeCommit = commit.getParentCount() > 1;
+ boolean isLastInChain = !allParents.contains(commit.getId());
+
+ // Recheck mergeability rather than using value stored in the index,
+ // which may be stale.
+ // TODO(dborowitz): This is ugly; consider providing a way to not read
+ // stored fields from the index in the first place.
+ change.setMergeable(null);
+ Boolean mergeable = change.isMergeable();
+ if (mergeable == null) {
+ // Skip whole check, cannot determine if mergeable
+ return null;
+ }
+ if (mergeable) {
+ mergeabilityMap.remove(change);
+ }
+
+ if (isLastInChain && isMergeCommit && mergeable) {
+ for (ChangeData c : targetBranch) {
+ mergeabilityMap.remove(c);
+ }
+ break;
+ }
+ }
+ }
+ return mergeabilityMap;
+ }
+
+ private HashMap<Change.Id, RevCommit> findCommits(
+ Collection<ChangeData> changes, Project.NameKey project)
+ throws IOException, OrmException {
+ HashMap<Change.Id, RevCommit> commits = new HashMap<>();
+ try (Repository repo = repoManager.openRepository(project);
+ RevWalk walk = new RevWalk(repo)) {
+ for (ChangeData change : changes) {
+ RevCommit commit = walk.parseCommit(ObjectId.fromString(
+ psUtil.current(dbProvider.get(), change.notes()).getRevision().get()));
+ commits.put(change.getId(), commit);
+ }
+ }
+ return commits;
+ }
+
private RevisionResource onBehalfOf(RevisionResource rsrc, SubmitInput in)
throws AuthException, UnprocessableEntityException, OrmException {
ChangeControl caller = rsrc.getControl();
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..1596e31
--- /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.ReviewersUtil;
+import com.google.gerrit.server.IdentifiedUser.GenericFactory;
+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/git/BatchUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/BatchUpdate.java
index e09b8a7..693fb92 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/BatchUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/BatchUpdate.java
@@ -47,6 +47,7 @@
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.NoSuchRefException;
+import com.google.gerrit.server.schema.DisabledChangesReviewDbWrapper;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
@@ -612,7 +613,7 @@
private ChangeContext newChangeContext(Change.Id id) throws Exception {
Change c = newChanges.get(id);
if (c == null) {
- c = db.changes().get(id);
+ c = unwrap(db).changes().get(id);
}
// Pass in preloaded change to controlFor, to avoid:
// - reading from a db that does not belong to this update
@@ -629,4 +630,11 @@
op.postUpdate(ctx);
}
}
+
+ private static ReviewDb unwrap(ReviewDb db) {
+ if (db instanceof DisabledChangesReviewDbWrapper) {
+ db = ((DisabledChangesReviewDbWrapper) db).unsafeGetDelegate();
+ }
+ return db;
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/LabelNormalizer.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/LabelNormalizer.java
index 14be8c1..f3b2ac9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/LabelNormalizer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/LabelNormalizer.java
@@ -127,7 +127,7 @@
checkArgument(changeId.equals(ctl.getId()),
"Approval %s does not match change %s",
psa.getKey(), ctl.getChange().getKey());
- if (psa.isSubmit()) {
+ if (psa.isLegacySubmit()) {
unchanged.add(psa);
continue;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
index 586fed9..4a2eee4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
@@ -253,7 +253,7 @@
continue;
}
- if (a.isSubmit()) {
+ if (a.isLegacySubmit()) {
// Submit is treated specially, below (becomes committer)
//
if (submitAudit == null
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 5c3bcad..72517dc 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
@@ -73,6 +73,7 @@
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.ChangeMessage;
+import com.google.gerrit.reviewdb.client.LabelId;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.client.PatchSetInfo;
@@ -2560,6 +2561,16 @@
msg.setMessage(msgBuf.toString());
cmUtil.addChangeMessage(ctx.getDb(), update, msg);
+ PatchSetApproval submitter = new PatchSetApproval(
+ new PatchSetApproval.Key(
+ change.currentPatchSetId(),
+ ctx.getUser().getAccountId(),
+ LabelId.legacySubmit()),
+ (short) 1, ctx.getWhen());
+ update.putApproval(submitter.getLabel(), submitter.getValue());
+ ctx.getDb().patchSetApprovals().upsert(
+ Collections.singleton(submitter));
+
return true;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReplaceOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReplaceOp.java
index 0034ce4..3de1fc1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReplaceOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReplaceOp.java
@@ -301,7 +301,7 @@
for (PatchSetApproval a : approvalsUtil.byPatchSetUser(ctx.getDb(),
ctx.getControl(), priorPatchSetId,
ctx.getUser().getAccountId())) {
- if (a.isSubmit()) {
+ if (a.isLegacySubmit()) {
continue;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java
index bea5ade..6c7a4c4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java
@@ -129,7 +129,7 @@
.setValidatePolicy(CommitValidators.Policy.NONE);
try {
rebaseOp.updateRepo(ctx);
- } catch (MergeConflictException e) {
+ } catch (MergeConflictException | NoSuchChangeException e) {
toMerge.setStatusCode(CommitMergeStatus.REBASE_MERGE_CONFLICT);
throw new IntegrationException(
"Cannot rebase " + toMerge.name() + ": " + e.getMessage(), e);
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 3cbd55d..bdf9f052 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
@@ -337,7 +337,7 @@
new PatchSetApproval.Key(
psId,
ctx.getUser().getAccountId(),
- LabelId.SUBMIT),
+ LabelId.legacySubmit()),
(short) 1, ctx.getWhen());
byKey.put(submitter.getKey(), submitter);
submitter.setValue((short) 1);
@@ -373,7 +373,7 @@
// TODO(dborowitz): Don't use a label in notedb; just check when status
// change happened.
for (PatchSetApproval psa : normalized.unchanged()) {
- if (includeUnchanged || psa.isSubmit()) {
+ if (includeUnchanged || psa.isLegacySubmit()) {
logDebug("Adding submit label " + psa);
update.putApprovalFor(
psa.getAccountId(), psa.getLabel(), psa.getValue());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidators.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidators.java
index bc9b2a1..f54e071 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidators.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidators.java
@@ -20,11 +20,8 @@
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.PluginConfig;
@@ -97,9 +94,7 @@
+ "The change must be submitted by a Gerrit administrator.";
private final AllProjectsName allProjectsName;
- private final ReviewDb db;
private final ProjectCache projectCache;
- private final ApprovalsUtil approvalsUtil;
private final DynamicMap<ProjectConfigEntry> pluginConfigEntries;
public interface Factory {
@@ -108,13 +103,10 @@
@Inject
public ProjectConfigValidator(AllProjectsName allProjectsName,
- ReviewDb db, ProjectCache projectCache,
- ApprovalsUtil approvalsUtil,
+ ProjectCache projectCache,
DynamicMap<ProjectConfigEntry> pluginConfigEntries) {
this.allProjectsName = allProjectsName;
- this.db = db;
this.projectCache = projectCache;
- this.approvalsUtil = approvalsUtil;
this.pluginConfigEntries = pluginConfigEntries;
}
@@ -142,11 +134,6 @@
}
} else {
if (!oldParent.equals(newParent)) {
- PatchSetApproval psa =
- approvalsUtil.getSubmitter(db, commit.notes(), patchSetId);
- if (psa == null) {
- throw new MergeValidationException(SET_BY_ADMIN);
- }
if (!caller.getCapabilities().canAdministrateServer()) {
throw new MergeValidationException(SET_BY_ADMIN);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java
index f4ef4af..2248360 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeField.java
@@ -359,7 +359,7 @@
Set<String> allApprovals = Sets.newHashSet();
Set<String> distinctApprovals = Sets.newHashSet();
for (PatchSetApproval a : input.currentApprovals()) {
- if (a.getValue() != 0 && !a.isSubmit()) {
+ if (a.getValue() != 0 && !a.isLegacySubmit()) {
allApprovals.add(formatLabel(a.getLabel(), a.getValue(),
a.getAccountId()));
distinctApprovals.add(formatLabel(a.getLabel(), a.getValue()));
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 c96ed65..a0860c7 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
@@ -184,6 +184,15 @@
: Futures.<Object, IOException> immediateCheckedFuture(null);
}
+ /**
+ * Synchronously delete a change.
+ *
+ * @param id change ID to delete.
+ */
+ public void delete(Change.Id id) throws IOException {
+ new DeleteTask(id).call();
+ }
+
private Collection<ChangeIndex> getWriteIndexes() {
return indexes != null
? indexes.getWriteIndexes()
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java
index b2e765c..dba863e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java
@@ -61,6 +61,7 @@
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.InternalChangeQuery;
+import com.google.gerrit.server.schema.DisabledChangesReviewDbWrapper;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -186,7 +187,7 @@
public ChangeNotes create(ReviewDb db, Project.NameKey project,
Change.Id changeId) throws OrmException {
- Change change = db.changes().get(changeId);
+ Change change = unwrap(db).changes().get(changeId);
checkArgument(change.getProject().equals(project),
"passed project %s when creating ChangeNotes for %s, but actual"
+ " project is %s",
@@ -220,7 +221,7 @@
ReviewDb db, Change.Id changeId) throws OrmException {
checkState(!migration.readChanges(), "do not call"
+ " createFromIdOnlyWhenNotedbDisabled when notedb is enabled");
- Change change = db.changes().get(changeId);
+ Change change = unwrap(db).changes().get(changeId);
return new ChangeNotes(repoManager, migration, allUsers,
change.getProject(), change).load();
}
@@ -242,7 +243,7 @@
final ListeningExecutorService executorService, final ReviewDb db,
final Project.NameKey project, final Change.Id changeId) {
return Futures.makeChecked(
- Futures.transformAsync(db.changes().getAsync(changeId),
+ Futures.transformAsync(unwrap(db).changes().getAsync(changeId),
new AsyncFunction<Change, ChangeNotes>() {
@Override
public ListenableFuture<ChangeNotes> apply(
@@ -284,7 +285,7 @@
return notes;
}
- for (Change c : db.changes().get(changeIds)) {
+ for (Change c : unwrap(db).changes().get(changeIds)) {
notes.add(createFromChangeOnlyWhenNotedbDisabled(c));
}
return notes;
@@ -304,7 +305,7 @@
return notes;
}
- for (Change c : db.changes().get(changeIds)) {
+ for (Change c : unwrap(db).changes().get(changeIds)) {
if (c != null && project.equals(c.getDest().getParentKey())) {
ChangeNotes cn = createFromChangeOnlyWhenNotedbDisabled(c);
if (predicate.apply(cn)) {
@@ -330,7 +331,7 @@
}
}
} else {
- for (Change change : db.changes().all()) {
+ for (Change change : unwrap(db).changes().all()) {
ChangeNotes notes = createFromChangeOnlyWhenNotedbDisabled(change);
if (predicate.apply(notes)) {
m.put(change.getProject(), notes);
@@ -356,7 +357,7 @@
// A batch size of N may overload get(Iterable), so use something smaller,
// but still >1.
for (List<Change.Id> batch : Iterables.partition(ids, 30)) {
- for (Change change : db.changes().get(batch)) {
+ for (Change change : unwrap(db).changes().get(batch)) {
notes.add(createFromChangeOnlyWhenNotedbDisabled(change));
}
}
@@ -385,6 +386,13 @@
}
return ids;
}
+
+ private static ReviewDb unwrap(ReviewDb db) {
+ if (db instanceof DisabledChangesReviewDbWrapper) {
+ db = ((DisabledChangesReviewDbWrapper) db).unsafeGetDelegate();
+ }
+ return db;
+ }
}
private final Project.NameKey project;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchFile.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchFile.java
index e479ba9..2704be8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchFile.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchFile.java
@@ -150,9 +150,13 @@
if (tw == null) {
return Text.EMPTY;
}
- if (tw.getFileMode(0).getObjectType() != Constants.OBJ_BLOB) {
+ if (tw.getFileMode(0).getObjectType() == Constants.OBJ_BLOB) {
+ return new Text(repo.open(tw.getObjectId(0), Constants.OBJ_BLOB));
+ } else if (tw.getFileMode(0).getObjectType() == Constants.OBJ_COMMIT) {
+ String str = "Subproject commit " + ObjectId.toString(tw.getObjectId(0));
+ return new Text(str.getBytes());
+ } else {
return Text.EMPTY;
}
- return new Text(repo.open(tw.getObjectId(0), Constants.OBJ_BLOB));
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListEntry.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListEntry.java
index 2caa937..2b5e235 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListEntry.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListEntry.java
@@ -32,7 +32,6 @@
import org.eclipse.jgit.diff.Edit;
import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.patch.CombinedFileHeader;
import org.eclipse.jgit.patch.FileHeader;
import org.eclipse.jgit.util.IntList;
@@ -97,10 +96,7 @@
header = compact(hdr);
- if (hdr instanceof CombinedFileHeader
- || hdr.getHunks().isEmpty() //
- || hdr.getOldMode() == FileMode.GITLINK
- || hdr.getNewMode() == FileMode.GITLINK) {
+ if (hdr instanceof CombinedFileHeader || hdr.getHunks().isEmpty()) {
edits = Collections.emptyList();
} else {
edits = Collections.unmodifiableList(editList);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
index 10ea61c..63693e6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
@@ -312,14 +312,6 @@
private PatchListEntry newEntry(RevTree aTree, FileHeader fileHeader,
long size, long sizeDelta) {
- final FileMode oldMode = fileHeader.getOldMode();
- final FileMode newMode = fileHeader.getNewMode();
-
- if (oldMode == FileMode.GITLINK || newMode == FileMode.GITLINK) {
- return new PatchListEntry(fileHeader, Collections.<Edit> emptyList(),
- size, sizeDelta);
- }
-
if (aTree == null // want combined diff
|| fileHeader.getPatchType() != PatchType.UNIFIED
|| fileHeader.getHunks().isEmpty()) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
index 9072c2a..51c70f5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
@@ -179,9 +179,7 @@
}
boolean hugeFile = false;
- if (a.mode == FileMode.GITLINK || b.mode == FileMode.GITLINK) {
- // Do nothing
- } else if (a.src == b.src && a.size() <= context
+ if (a.src == b.src && a.size() <= context
&& content.getEdits().isEmpty()) {
// Odd special case; the files are identical (100% rename or copy)
// and the user has asked for context that is larger than the file.
@@ -471,6 +469,10 @@
} else if (mode.getObjectType() == Constants.OBJ_BLOB) {
srcContent = Text.asByteArray(db.open(id, Constants.OBJ_BLOB));
+ } else if (mode.getObjectType() == Constants.OBJ_COMMIT) {
+ String strContent = "Subproject commit " + ObjectId.toString(id);
+ srcContent = strContent.getBytes();
+
} else {
srcContent = Text.NO_BYTES;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java
index 445c8c5..fec858a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java
@@ -44,6 +44,7 @@
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.account.GroupBackend;
+import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.ProjectOwnerGroupsProvider;
import com.google.gerrit.server.config.RepositoryConfig;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
@@ -102,6 +103,7 @@
private final PersonIdent serverIdent;
private final Provider<CurrentUser> currentUser;
private final Provider<PutConfig> putConfig;
+ private final AllProjectsName allProjects;
private final String name;
@Inject
@@ -120,6 +122,7 @@
@GerritPersonIdent PersonIdent serverIdent,
Provider<CurrentUser> currentUser,
Provider<PutConfig> putConfig,
+ AllProjectsName allProjects,
@Assisted String name) {
this.projectsCollection = projectsCollection;
this.groupsCollection = groupsCollection;
@@ -137,6 +140,7 @@
this.serverIdent = serverIdent;
this.currentUser = currentUser;
this.putConfig = putConfig;
+ this.allProjects = allProjects;
this.name = name;
}
@@ -155,9 +159,9 @@
CreateProjectArgs args = new CreateProjectArgs();
args.setProjectName(ProjectUtil.stripGitSuffix(name));
- if (!Strings.isNullOrEmpty(input.parent)) {
- args.newParent = projectsCollection.get().parse(input.parent).getControl();
- }
+ String parentName = MoreObjects.firstNonNull(
+ Strings.emptyToNull(input.parent), allProjects.get());
+ args.newParent = projectsCollection.get().parse(parentName).getControl();
args.createEmptyCommit = input.createEmptyCommit;
args.permissionsOnly = input.permissionsOnly;
args.projectDescription = Strings.emptyToNull(input.description);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetParent.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetParent.java
index 8113c01..01aacfb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetParent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetParent.java
@@ -14,6 +14,8 @@
package com.google.gerrit.server.project;
+import static com.google.common.base.Preconditions.checkNotNull;
+
import com.google.common.base.MoreObjects;
import com.google.common.base.Predicate;
import com.google.common.base.Strings;
@@ -70,18 +72,18 @@
throws AuthException, ResourceConflictException,
ResourceNotFoundException, UnprocessableEntityException, IOException {
ProjectControl ctl = rsrc.getControl();
- validateParentUpdate(ctl, input.parent, checkIfAdmin);
+ String parentName = MoreObjects.firstNonNull(
+ Strings.emptyToNull(input.parent), allProjects.get());
+ validateParentUpdate(ctl, parentName, checkIfAdmin);
try (MetaDataUpdate md = updateFactory.create(rsrc.getNameKey())) {
ProjectConfig config = ProjectConfig.read(md);
Project project = config.getProject();
- project.setParentName(Strings.emptyToNull(input.parent));
+ project.setParentName(parentName);
String msg = Strings.emptyToNull(input.commitMessage);
if (msg == null) {
msg = String.format(
- "Changed parent to %s.\n",
- MoreObjects.firstNonNull(project.getParentName(),
- allProjects.get()));
+ "Changed parent to %s.\n", parentName);
} else if (!msg.endsWith("\n")) {
msg += "\n";
}
@@ -90,8 +92,9 @@
config.commit(md);
cache.evict(ctl.getProject());
- Project.NameKey parentName = project.getParent(allProjects);
- return parentName != null ? parentName.get() : "";
+ Project.NameKey parent = project.getParent(allProjects);
+ checkNotNull(parent);
+ return parent.get();
} catch (RepositoryNotFoundException notFound) {
throw new ResourceNotFoundException(rsrc.getName());
} catch (ConfigInvalidException e) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
index aa12b91..b2930e8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -857,7 +857,7 @@
public Optional<PatchSetApproval> getSubmitApproval()
throws OrmException {
for (PatchSetApproval psa : currentApprovals()) {
- if (psa.isSubmit()) {
+ if (psa.isLegacySubmit()) {
return Optional.fromNullable(psa);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/DisabledChangesReviewDbWrapper.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/DisabledChangesReviewDbWrapper.java
index 005b3b1..3501374 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/DisabledChangesReviewDbWrapper.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/DisabledChangesReviewDbWrapper.java
@@ -21,6 +21,7 @@
import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
+import com.google.gerrit.reviewdb.server.ChangeAccess;
import com.google.gerrit.reviewdb.server.ChangeMessageAccess;
import com.google.gerrit.reviewdb.server.PatchLineCommentAccess;
import com.google.gerrit.reviewdb.server.PatchSetAccess;
@@ -33,6 +34,7 @@
public class DisabledChangesReviewDbWrapper extends ReviewDbWrapper {
private static final String MSG = "This table has been migrated to notedb";
+ private final DisabledChangeAccess changes;
private final DisabledPatchSetApprovalAccess patchSetApprovals;
private final DisabledChangeMessageAccess changeMessages;
private final DisabledPatchSetAccess patchSets;
@@ -40,6 +42,7 @@
DisabledChangesReviewDbWrapper(ReviewDb db) {
super(db);
+ changes = new DisabledChangeAccess(delegate.changes());
patchSetApprovals =
new DisabledPatchSetApprovalAccess(delegate.patchSetApprovals());
changeMessages = new DisabledChangeMessageAccess(delegate.changeMessages());
@@ -53,6 +56,11 @@
}
@Override
+ public ChangeAccess changes() {
+ return changes;
+ }
+
+ @Override
public PatchSetApprovalAccess patchSetApprovals() {
return patchSetApprovals;
}
@@ -72,6 +80,38 @@
return patchComments;
}
+ private static class DisabledChangeAccess extends ChangeAccessWrapper {
+
+ protected DisabledChangeAccess(ChangeAccess delegate) {
+ super(delegate);
+ }
+
+ @Override
+ public ResultSet<Change> iterateAllEntities() {
+ throw new UnsupportedOperationException(MSG);
+ }
+
+ @Override
+ public CheckedFuture<Change, OrmException> getAsync(Change.Id key) {
+ throw new UnsupportedOperationException(MSG);
+ }
+
+ @Override
+ public ResultSet<Change> get(Iterable<Change.Id> keys) {
+ throw new UnsupportedOperationException(MSG);
+ }
+
+ @Override
+ public Change get(Change.Id id) throws OrmException {
+ throw new UnsupportedOperationException(MSG);
+ }
+
+ @Override
+ public ResultSet<Change> all() throws OrmException {
+ throw new UnsupportedOperationException(MSG);
+ }
+ }
+
private static class DisabledPatchSetApprovalAccess
extends PatchSetApprovalAccessWrapper {
DisabledPatchSetApprovalAccess(PatchSetApprovalAccess delegate) {
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/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/plugins/reviewnotes b/plugins/reviewnotes
index 47c74e2..0ea78c9 160000
--- a/plugins/reviewnotes
+++ b/plugins/reviewnotes
@@ -1 +1 @@
-Subproject commit 47c74e2da9443e028d8d52d95791e0cd15add822
+Subproject commit 0ea78c9ca7c70515c81681af05a65ec4dc32a542
diff --git a/polygerrit-ui/app/BUCK b/polygerrit-ui/app/BUCK
index 93cf614..5110350 100644
--- a/polygerrit-ui/app/BUCK
+++ b/polygerrit-ui/app/BUCK
@@ -6,6 +6,7 @@
['**'],
excludes = [
'BUCK',
+ '**/*_test.html',
'index.html',
] + WCT_TEST_PATTERNS + PY_TEST_PATTERNS)
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-view/gr-change-list-view.html b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.html
index 5b03274..6788cce 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.html
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.html
@@ -66,7 +66,7 @@
<gr-ajax
auto
url="/changes/"
- params="[[_computeQueryParams(_query, _offset)]]"
+ params="[[_computeQueryParams(_query, _offset, changesPerPage)]]"
last-response="{{_changes}}"
last-error="{{_lastError}}"
loading="{{_loading}}"></gr-ajax>
@@ -80,10 +80,11 @@
selected-index="{{viewState.selectedChangeIndex}}"
show-star="[[loggedIn]]"></gr-change-list>
<nav>
- <a href$="[[_computeNavLink(_query, _offset, -1)]]"
+ <a href$="[[_computeNavLink(_query, _offset, -1, changesPerPage)]]"
hidden$="[[_hidePrevArrow(_offset)]]">← Prev</a>
- <a href$="[[_computeNavLink(_query, _offset, 1)]]"
- hidden$="[[_hideNextArrow(_changes.length)]]">Next →</a>
+ <a href$="[[_computeNavLink(_query, _offset, 1, changesPerPage)]]"
+ hidden$="[[_hideNextArrow(_changes.length, changesPerPage)]]">
+ Next →</a>
</nav>
</div>
</template>
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.js b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.js
index d0a97c1d..a694fc9 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.js
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.js
@@ -14,8 +14,6 @@
(function() {
'use strict';
- var DEFAULT_NUM_CHANGES = 25;
-
Polymer({
is: 'gr-change-list-view',
@@ -51,6 +49,8 @@
value: function() { return {}; },
},
+ changesPerPage: Number,
+
/**
* Currently active query.
*/
@@ -103,13 +103,13 @@
this.fire('title-change', {title: this._query});
},
- _computeQueryParams: function(query, offset) {
+ _computeQueryParams: function(query, offset, changesPerPage) {
var options = this.listChangesOptionsToHex(
this.ListChangesOption.LABELS,
this.ListChangesOption.DETAILED_ACCOUNTS
);
var obj = {
- n: DEFAULT_NUM_CHANGES, // Number of results to return.
+ n: changesPerPage,
O: options,
S: offset || 0,
};
@@ -119,10 +119,10 @@
return obj;
},
- _computeNavLink: function(query, offset, direction) {
+ _computeNavLink: function(query, offset, direction, changesPerPage) {
// Offset could be a string when passed from the router.
offset = +(offset || 0);
- var newOffset = Math.max(0, offset + (25 * direction));
+ var newOffset = Math.max(0, offset + (changesPerPage * direction));
var href = '/q/' + query;
if (newOffset > 0) {
href += ',' + newOffset;
@@ -142,8 +142,8 @@
return offset == 0;
},
- _hideNextArrow: function(changesLen) {
- return changesLen < DEFAULT_NUM_CHANGES;
+ _hideNextArrow: function(changesLen, changesPerPage) {
+ return changesLen < changesPerPage;
},
});
})();
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-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 41cb058..1dbdbfb 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
@@ -121,6 +121,7 @@
.commitMessage {
font-family: var(--monospace-font-family);
flex: 0 0 72ch;
+ overflow: auto;
margin-right: 2em;
margin-bottom: 1em;
}
@@ -129,6 +130,9 @@
font-weight: bold;
margin-bottom: .25em;
}
+ .commitMessage gr-linked-text {
+ --linked-text-white-space: pre;
+ }
.commitAndRelated {
align-content: flex-start;
display: flex;
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..f5028a9 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">
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..bc7b2a6 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">
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_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..4538cec 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: 1.3em;
+ width: 1.3em;
+ 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="32"></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/core/gr-main-header/gr-main-header.html b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.html
index 129a321..af4a0e4 100644
--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.html
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.html
@@ -25,9 +25,11 @@
<template>
<style>
:host {
+ display: block;
+ }
+ nav {
align-items: center;
display: flex;
- overflow: hidden;
}
.bigTitle {
color: var(--primary-text-color);
@@ -37,6 +39,60 @@
.bigTitle:hover {
text-decoration: underline;
}
+ ul {
+ list-style: none;
+ }
+ .links {
+ margin-left: 1em;
+ }
+ .links ul {
+ display: none;
+ }
+ .links > li {
+ cursor: default;
+ display: inline-block;
+ margin-left: 1em;
+ padding: .4em 0;
+ position: relative;
+ }
+ .links li:hover ul {
+ background-color: #fff;
+ box-shadow: 0 1px 1px rgba(0, 0, 0, .3);
+ display: block;
+ left: -.75em;
+ position: absolute;
+ top: 2em;
+ z-index: 1000;
+ }
+ .links li ul li a:link,
+ .links li ul li a:visited {
+ color: #00e;
+ display: block;
+ padding: .5em .75em;
+ text-decoration: none;
+ white-space: nowrap;
+ }
+ .links li ul li:hover a {
+ background-color: var(--selection-background-color);
+ }
+ .linksTitle {
+ display: inline-block;
+ padding-right: 1em;
+ position: relative;
+ }
+ .downArrow {
+ border-left: .36em solid transparent;
+ border-right: .36em solid transparent;
+ border-top: .36em solid #ccc;
+ height: 0;
+ position: absolute;
+ right: 0;
+ top: calc(50% - .1em);
+ width: 0;
+ }
+ .links li:hover .downArrow {
+ border-top-color: #666;
+ }
.rightItems {
display: flex;
flex: 1;
@@ -73,14 +129,30 @@
}
}
</style>
- <a href="/" class="bigTitle">PolyGerrit</a>
- <div class="rightItems">
- <gr-search-bar value="{{params.query}}" role="search"></gr-search-bar>
- <div class="accountContainer" id="accountContainer">
- <a class="loginButton" href="/login" on-tap="_loginTapHandler">Login</a>
- <gr-account-dropdown account="[[account]]"></gr-account-dropdown>
+ <nav>
+ <a href="/" class="bigTitle">PolyGerrit</a>
+ <ul class="links">
+ <template is="dom-repeat" items="[[_links]]" as="linkGroup">
+ <li>
+ <span class="linksTitle">
+ [[linkGroup.title]] <i class="downArrow"></i>
+ </span>
+ <ul>
+ <template is="dom-repeat" items="[[linkGroup.links]]" as="link">
+ <li><a href="[[link.url]]">[[link.name]]</a></li>
+ </template>
+ </ul>
+ </li>
+ </template>
+ </ul>
+ <div class="rightItems">
+ <gr-search-bar value="{{searchQuery}}" role="search"></gr-search-bar>
+ <div class="accountContainer" id="accountContainer">
+ <a class="loginButton" href="/login" on-tap="_loginTapHandler">Login</a>
+ <gr-account-dropdown account="[[_account]]"></gr-account-dropdown>
+ </div>
</div>
- </div>
+ </nav>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
</template>
<script src="gr-main-header.js"></script>
diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js
index ca57625..173b88e 100644
--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.js
@@ -14,6 +14,24 @@
(function() {
'use strict';
+ var DEFAULT_LINKS = [{
+ title: 'Changes',
+ links: [
+ {
+ url: '/q/status:open',
+ name: 'Open',
+ },
+ {
+ url: '/q/status:merged',
+ name: 'Merged',
+ },
+ {
+ url: '/q/status:abandoned',
+ name: 'Abandoned',
+ },
+ ],
+ }];
+
Polymer({
is: 'gr-main-header',
@@ -22,14 +40,74 @@
},
properties: {
+ searchQuery: {
+ type: String,
+ notify: true,
+ },
+
+ _account: Object,
+ _defaultLinks: {
+ type: Array,
+ value: function() {
+ return DEFAULT_LINKS;
+ },
+ },
+ _links: {
+ type: Array,
+ computed: '_computeLinks(_defaultLinks, _userLinks)',
+ },
+ _userLinks: {
+ type: Array,
+ value: function() { return []; },
+ },
},
+ observers: [
+ '_accountLoaded(_account)',
+ ],
+
attached: function() {
+ this._loadAccount();
+ },
+
+ _computeLinks: function(defaultLinks, userLinks) {
+ var links = defaultLinks.slice();
+ if (userLinks && userLinks.length > 0) {
+ links.push({
+ title: 'Your',
+ links: userLinks,
+ });
+ }
+ return links;
+ },
+
+ _loadAccount: function() {
this.$.restAPI.getAccount().then(function(account) {
- var loggedIn = !!account;
- this.$.accountContainer.classList.toggle('loggedIn', loggedIn);
- this.$.accountContainer.classList.toggle('loggedOut', !loggedIn);
+ this._account = account;
+ this.$.accountContainer.classList.toggle('loggedIn', account != null);
+ this.$.accountContainer.classList.toggle('loggedOut', account == null);
}.bind(this));
},
+
+ _accountLoaded: function(account) {
+ if (!account) { return; }
+
+ this.$.restAPI.getPreferences().then(function(prefs) {
+ this._userLinks =
+ prefs.my.map(this._stripHashPrefix).filter(this._isSupportedLink);
+ }.bind(this));
+ },
+
+ _stripHashPrefix: function(linkObj) {
+ if (linkObj.url.indexOf('#') === 0) {
+ linkObj.url = linkObj.url.slice(1);
+ }
+ return linkObj;
+ },
+
+ _isSupportedLink: function(linkObj) {
+ // Groups are not yet supported.
+ return linkObj.url.indexOf('/groups') !== 0;
+ },
});
})();
diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.html b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.html
new file mode 100644
index 0000000..0b40d87
--- /dev/null
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.html
@@ -0,0 +1,89 @@
+<!DOCTYPE html>
+<!--
+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.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-main-header</title>
+
+<script src="../../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
+<script src="../../../bower_components/web-component-tester/browser.js"></script>
+
+<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="gr-main-header.html">
+
+<test-fixture id="basic">
+ <template>
+ <gr-main-header></gr-main-header>
+ </template>
+</test-fixture>
+
+<script>
+ suite('gr-main-header tests', function() {
+ var element;
+
+ setup(function() {
+ stub('gr-main-header', {
+ _loadAccount: function() {},
+ });
+ element = fixture('basic');
+ });
+
+ test('strip hash prefix', function() {
+ assert.deepEqual([
+ {url: '#/q/owner:self+is:draft'},
+ {url: 'https://awesometown.com/#hashyhash'},
+ ].map(element._stripHashPrefix),
+ [
+ {url: '/q/owner:self+is:draft'},
+ {url: 'https://awesometown.com/#hashyhash'},
+ ]);
+ });
+
+ test('filter unsupported urls', function() {
+ assert.deepEqual([
+ {url: '/q/owner:self+is:draft'},
+ {url: '/c/331788/'},
+ {url: '/groups/self'},
+ {url: 'https://awesometown.com/#hashyhash'},
+ ].filter(element._isSupportedLink),
+ [
+ {url: '/q/owner:self+is:draft'},
+ {url: '/c/331788/'},
+ {url: 'https://awesometown.com/#hashyhash'},
+ ]);
+ });
+
+ test('user links', function() {
+ var defaultLinks = [{
+ title: 'Faves',
+ links: [{
+ name: 'Pinterest',
+ url: 'https://pinterest.com',
+ }],
+ }];
+ var userLinks = [{
+ name: 'Facebook',
+ url: 'https://facebook.com',
+ }];
+ assert.deepEqual(element._computeLinks(defaultLinks, []), defaultLinks);
+ assert.deepEqual(element._computeLinks(defaultLinks, userLinks),
+ defaultLinks.concat({
+ title: 'Your',
+ links: userLinks,
+ }));
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.html b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.html
index c8c4523..f559794 100644
--- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.html
+++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.html
@@ -30,12 +30,11 @@
}
input {
border: 1px solid #d1d2d3;
- outline: none;
- }
- input {
+ border-radius: 2px 0 0 2px;
flex: 1;
font: inherit;
- border-radius: 2px 0 0 2px;
+ outline: none;
+ padding: 0 .25em;
}
gr-button {
background-color: #f1f2f3;
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_test.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
index 921d766..f3c1402 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">
diff --git a/polygerrit-ui/app/elements/gr-app.html b/polygerrit-ui/app/elements/gr-app.html
index fd6525d..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,29 +55,25 @@
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></gr-main-header>
+ <gr-main-header search-query="{{params.query}}"></gr-main-header>
<main>
<template is="dom-if" if="{{_showChangeListView}}" restamp="true">
<gr-change-list-view
params="[[params]]"
view-state="{{_viewState.changeListView}}"
- logged-in="[[_computeLoggedIn(account)]]"></gr-change-list-view>
+ changes-per-page="[[_preferences.changes_per_page]]"
+ 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">
@@ -90,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 475ea0d..31797dd 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,28 +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,
@@ -74,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));
},
@@ -104,8 +99,14 @@
_accountChanged: function() {
this._resolveAccountReady();
+
if (this.loggedIn) {
- this.$.diffPreferencesXHR.generateRequest();
+ 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
@@ -125,11 +126,11 @@
tab_size: 8,
theme: 'DEFAULT',
};
- }
- },
- _configChanged: function(config) {
- this._resolveConfigReady(config);
+ this._preferences = {
+ changes_per_page: 25,
+ };
+ }
},
_viewChanged: function(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-linked-text/gr-linked-text.html b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.html
index 68a98e8..37bca2e 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.html
+++ b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.html
@@ -24,8 +24,8 @@
display: block;
}
:host([pre]) span {
- white-space: pre-wrap;
- word-wrap: break-word;
+ white-space: var(--linked-text-white-space, pre-wrap);
+ word-wrap: var(--linked-text-work-wrap, break-word);
}
:host([disabled]) a {
color: inherit;
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 f69f8b1..27cd989 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,17 +82,33 @@
}.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');
},
+ getPreferences: function() {
+ return this._fetchSharedCacheURL('/accounts/self/preferences');
+ },
+
_fetchSharedCacheURL: function(url) {
if (this._sharedFetchPromises[url]) {
return this._sharedFetchPromises[url];
}
// TODO(andybons): Periodic cache invalidation.
if (this._cache[url] !== undefined) {
- return this._cache[url];
+ return Promise.resolve(this._cache[url]);
}
this._sharedFetchPromises[url] = this.fetchJSON(url).then(
function(response) {
@@ -104,7 +120,7 @@
}.bind(this)).catch(function(err) {
this._sharedFetchPromises[url] = undefined;
throw err;
- });
+ }.bind(this));
return this._sharedFetchPromises[url];
},
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 c397c8f..3902707f 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,14 +65,26 @@
Promise.all(promises).then(function(results) {
assert.deepEqual(results, [1, 1, 1]);
- fetchJSONStub.restore();
+ element._fetchSharedCacheURL('/foo').then(function(foo) {
+ assert.equal(foo, 1);
+ fetchJSONStub.restore();
+ done();
+ });
+ });
+ });
+
+ test('cached promise', function(done) {
+ var promise = Promise.reject('foo');
+ element._cache['/foo'] = promise;
+ element._fetchSharedCacheURL('/foo').catch(function(p) {
+ assert.equal(p, 'foo');
done();
});
});
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{}');
}});
});
@@ -89,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; }
}});
});
@@ -128,6 +140,7 @@
assert.deepEqual(obj.comments[0], {
message: 'this isn’t quite right',
});
+ fetchJSONStub.restore();
done();
});
});
@@ -178,6 +191,7 @@
assert.deepEqual(obj.comments[1], {
message: '¯\\_(ツ)_/¯',
});
+ fetchJSONStub.restore();
done();
});
});
diff --git a/polygerrit-ui/app/styles/app-theme.html b/polygerrit-ui/app/styles/app-theme.html
index df48e8a..11ea3b4 100644
--- a/polygerrit-ui/app/styles/app-theme.html
+++ b/polygerrit-ui/app/styles/app-theme.html
@@ -19,6 +19,7 @@
--primary-text-color: #000;
--search-border-color: #ddd;
--secondary-color: #f1f2f3;
+ --selection-background-color: #ebf5fb;
--default-text-color: #000;
--view-background-color: #fff;
--default-horizontal-margin: 1.25rem;
diff --git a/polygerrit-ui/app/scripts/fake-app.js b/polygerrit-ui/app/test/fake-app.js
similarity index 100%
rename from polygerrit-ui/app/scripts/fake-app.js
rename to polygerrit-ui/app/test/fake-app.js