Merge "Update rebase dialog to give parent autocomplete suggestions"
diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt
index bf9cb6d..a4f03d5 100644
--- a/Documentation/access-control.txt
+++ b/Documentation/access-control.txt
@@ -1299,7 +1299,8 @@
Allow to link:cmd-set-account.html[modify accounts over the ssh prompt].
This capability allows the granted group members to modify any user account
-setting.
+setting. In addition this capability is required to view secondary emails
+of other accounts.
[[capability_priority]]
=== Priority
@@ -1386,6 +1387,10 @@
of link:config-gerrit.html#accounts.visibility[accounts.visibility]
setting.
+This capability allows to view all accounts but not all account data.
+E.g. secondary emails of all accounts can only be viewed with the
+link:#capability_modifyAccount[Modify Account] capability.
+
[[capability_viewCaches]]
=== View Caches
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 91a837d..fd8c3fe 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -1252,6 +1252,12 @@
+
The default limit is 1024kB.
+[[change.disablePrivateChanges]]change.disablePrivateChanges::
++
+If set to true, users are not allowed to create private changes.
++
+The default is false.
+
[[changeCleanup]]
=== Section changeCleanup
@@ -1380,10 +1386,13 @@
example, to match the string `bug` in a case insensitive way the match
pattern `[bB][uU][gG]` needs to be used.
+
-The regular expression pattern is applied to the HTML form of the message
-in question, which means it needs to assume the data has been escaped.
-So `"` needs to be matched as `&quot;`, `<` as `&lt;`, and `'` as
-`&#39;`.
+Between the GWT UI and PolyGerrit, the commentlink.name.match regular
+expressions are applied differently. Whereas in the GWT UI the
+expressions are applied to the formatted and escaped HTML result, the
+PolyGerrit UI applies them only to the raw, unformatted and unescaped
+text form. PolyGerrit does not support regex matching against HTML.
+Comment link patterns that are written in this style should be updated
+to match text formats.
+
A common pattern to match is `bug\\s+(\\d+)`.
@@ -3745,12 +3754,15 @@
[[repository.name.defaultSubmitType]]repository.<name>.defaultSubmitType::
+
The default submit type for newly created projects. Supported values
-are `MERGE_IF_NECESSARY`, `FAST_FORWARD_ONLY`, `REBASE_IF_NECESSARY`,
+are `INHERIT`, `MERGE_IF_NECESSARY`, `FAST_FORWARD_ONLY`, `REBASE_IF_NECESSARY`,
`REBASE_ALWAYS`, `MERGE_ALWAYS` and `CHERRY_PICK`.
+
For more details see link:project-configuration.html#submit_type[Submit Types].
+
-By default, `MERGE_IF_NECESSARY`.
+This submit type is only applied at project creation time if a submit type is
+omitted from the link:rest-api-projects.html#project-input[ProjectInput]. If the
+submit type is unset in the project config at runtime, it defaults to
+link:project-configuration.html#merge_if_necessary[`MERGE_IF_NECESSARY`].
[[repository.name.ownerGroup]]repository.<name>.ownerGroup::
+
@@ -4396,8 +4408,6 @@
* `diffie-hellman-group1-sha1`
By default, all supported key exchange algorithms are available.
-Without Bouncy Castle, `diffie-hellman-group1-sha1` is the only
-available algorithm.
It is strongly recommended to disable at least `diffie-hellman-group1-sha1`
as it's known to be vulnerable (logjam attack). Additionally, if your setup
diff --git a/Documentation/config-project-config.txt b/Documentation/config-project-config.txt
index 3644845..3b2b65f 100644
--- a/Documentation/config-project-config.txt
+++ b/Documentation/config-project-config.txt
@@ -236,6 +236,10 @@
This option only takes effect in submit strategies which already modify the commit, i.e.
Cherry Pick, Rebase Always, and (perhaps) Rebase If Necessary.
+- 'rejectEmptyCommit': Defines whether empty commits should be rejected when a change is merged.
+Changes might not seem empty at first but when attempting to merge, rebasing can lead to an empty
+commit. If this option is set to 'true' the merge would fail.
+
Merge strategy
diff --git a/Documentation/dev-bazel.txt b/Documentation/dev-bazel.txt
index fc71d26..b482de1 100644
--- a/Documentation/dev-bazel.txt
+++ b/Documentation/dev-bazel.txt
@@ -1,13 +1,14 @@
= Gerrit Code Review - Building with Bazel
[[installation]]
-== Installation
+== Prerequisites
-You need to use Java 8 and Node.js for building gerrit.
+To build Gerrit from source, you need:
-You can install Bazel from the bazel.io:
-https://www.bazel.io/versions/master/docs/install.html
-
+* A Linux or macOS system (Windows is not supported at this time)
+* A JDK for Java 8
+* Node.js
+* link:https://www.bazel.io/versions/master/docs/install.html[Bazel]
[[build]]
== Building on the Command Line
diff --git a/Documentation/install.txt b/Documentation/install.txt
index c6977a4..0f121a0 100644
--- a/Documentation/install.txt
+++ b/Documentation/install.txt
@@ -175,8 +175,8 @@
[[installation_on_windows]]
== Installation on Windows
-If new site is going to be initialized with Bouncy Castle cryptography,
-ssh-keygen command must be available during the init phase. If you have
+The `ssh-keygen` command must be available during the init phase to
+generate SSH host keys. If you have
link:https://git-for-windows.github.io/[Git for Windows] installed,
start Command Prompt and temporary add directory with ssh-keygen to the
PATH environment variable just before running init command:
diff --git a/Documentation/intro-gerrit-walkthrough.txt b/Documentation/intro-gerrit-walkthrough.txt
index 071267a..fcb4de2 100644
--- a/Documentation/intro-gerrit-walkthrough.txt
+++ b/Documentation/intro-gerrit-walkthrough.txt
@@ -182,7 +182,7 @@
Later in the day, Max decides to check on his change and notices Hannah's
feedback. He opens up the source file and incorporates her feedback. Because
-Max's change includes a change-id, all he has to is follow the typical git
+Max's change includes a change-id, all he has to do is follow the typical git
workflow for updating a commit:
* Check out the commit
diff --git a/Documentation/intro-user.txt b/Documentation/intro-user.txt
index 6242fcf..b20c2c0 100644
--- a/Documentation/intro-user.txt
+++ b/Documentation/intro-user.txt
@@ -531,8 +531,10 @@
[[private-changes]]
== Private Changes
-Private changes are changes that are only visible to their owners and
-reviewers. Private changes are useful in a number of cases:
+Private changes are changes that are only visible to their owners, reviewers
+and users with the link:access-control.html#category_view_private_changes[
+View Private Changes] global capability. Private changes are useful in a number
+of cases:
* You want to check what the change looks like before formal review starts.
By marking the change private without reviewers, nobody can
diff --git a/Documentation/project-configuration.txt b/Documentation/project-configuration.txt
index 079ff49..6260f5b 100644
--- a/Documentation/project-configuration.txt
+++ b/Documentation/project-configuration.txt
@@ -57,6 +57,12 @@
its dependencies are also submitted, with exceptions documented below.
The following submit types are supported:
+[[submit_type_inherit]]
+* Inherit
++
+Inherit the submit type from the parent project. In `All-Projects`, this
+is equivalent to link:#merge_if_necessary[Merge If Necessary].
+
[[fast_forward_only]]
* Fast Forward Only
+
@@ -70,7 +76,8 @@
[[merge_if_necessary]]
* Merge If Necessary
+
-This is the default for a new project.
+This is the default for new projects, unless overridden by a global
+link:config-gerrit.html#repository.name.defaultSubmitType[`defaultSubmitType` option].
+
If the change being submitted is a strict superset of the destination
branch, then the branch is fast-forwarded to the change. If not,
diff --git a/Documentation/rest-api-accounts.txt b/Documentation/rest-api-accounts.txt
index 8dc3b9d..5912d1f 100644
--- a/Documentation/rest-api-accounts.txt
+++ b/Documentation/rest-api-accounts.txt
@@ -63,7 +63,9 @@
[[all-emails]]
--
-* `ALL_EMAILS`: Includes all registered emails.
+* `ALL_EMAILS`: Includes all registered emails. Requires the caller
+to have the link:access-control.html#capability_modifyAccount[Modify
+Account] global capability.
--
[[suggest-account]]
@@ -79,6 +81,10 @@
GET /accounts/?suggest&q=John HTTP/1.0
----
+Secondary emails are only included if the calling user has the
+link:access-control.html#capability_modifyAccount[Modify Account]
+capability.
+
.Response
----
HTTP/1.1 200 OK
@@ -2159,7 +2165,10 @@
|`secondary_emails`|optional|
A list of the secondary email addresses of the user. +
Only set for account queries when the link:#all-emails[ALL_EMAILS]
-option is set.
+option or the link:#suggest-account[suggest] parameter is set. +
+Secondary emails are only included if the calling user has the
+link:access-control.html#capability_modifyAccount[Modify Account], and
+hence is allowed to see secondary emails of other users.
|`username` |optional|The username of the user. +
Only set if detailed account information is requested. +
See option link:rest-api-changes.html#detailed-accounts[
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 1606b8a..0c30a4b 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -301,6 +301,12 @@
link:user-search.html#reviewedby[reviewedby:self].
--
+[[skip_mergeable]]
+--
+* `SKIP_MERGEABLE`: skip the `mergeable` field in
+link:#change-info[ChangeInfo]. For fast moving projects, this field must
+be recomputed often, which is slow for projects with big trees.
+
[[submittable]]
--
* `SUBMITTABLE`: include the `submittable` field in link:#change-info[ChangeInfo],
@@ -5658,7 +5664,8 @@
Not set for merged changes.
|`mergeable` |optional|
Whether the change is mergeable. +
-Not set for merged changes, or if the change has not yet been tested.
+Not set for merged changes, if the change has not yet been tested, or
+if the link:#skip_mergeable[skip_mergeable] option is set.
|`submittable` |optional|
Whether the change has been approved by the project submit rules. +
Only set if link:#submittable[requested].
@@ -6709,12 +6716,11 @@
Draft handling that defines how draft comments are handled that are
already in the database but that were not also described in this
input. +
-Allowed values are `DELETE`, `PUBLISH`, `PUBLISH_ALL_REVISIONS` and
-`KEEP`. All values except `PUBLISH_ALL_REVISIONS` operate only on drafts
-for a single revision. +
+Allowed values are `PUBLISH`, `PUBLISH_ALL_REVISIONS` and `KEEP`. All values
+except `PUBLISH_ALL_REVISIONS` operate only on drafts for a single revision. +
Only `KEEP` is allowed when used in conjunction with `on_behalf_of`. +
-If not set, the default is `DELETE`, unless `on_behalf_of` is set, in
-which case the default is `KEEP` and any other value is disallowed.
+If not set, the default is `KEEP`. If `on_behalf_of` is set, then no other value
+besides `KEEP` is allowed.
|`notify` |optional|
Notify handling that defines to whom email notifications should be sent
after the review is stored. +
diff --git a/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt
index 6e02786..8aa1f42 100644
--- a/Documentation/rest-api-config.txt
+++ b/Documentation/rest-api-config.txt
@@ -1398,6 +1398,8 @@
|`submit_whole_topic` ||
link:config-gerrit.html#change.submitWholeTopic[A configuration if
the whole topic is submitted].
+|`disable_private_changes` |not set if `false`|
+Returns true if private changes are disabled.
|=============================
[[check-account-external-ids-input]]
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index fec430f..34c0e72 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -494,7 +494,7 @@
{
"description": "This is a demo project.",
- "submit_type": "CHERRY_PICK",
+ "submit_type": "INHERIT",
"owners": [
"MyProject-Owners"
]
@@ -821,7 +821,12 @@
"configured_value": "15m",
"inherited_value": "20m"
},
- "submit_type": "MERGE_IF_NECESSARY",
+ "submit_type": "INHERIT",
+ "default_submit_type": {
+ "value": "MERGE_IF_NECESSARY",
+ "configured_value": "INHERIT",
+ "inherited_value": "MERGE_IF_NECESSARY"
+ },
"state": "ACTIVE",
"commentlinks": {},
"plugin_config": {
@@ -933,6 +938,11 @@
"inherited_value": "20m"
},
"submit_type": "REBASE_IF_NECESSARY",
+ "default_submit_type": {
+ "value": "REBASE_IF_NECESSARY",
+ "configured_value": "INHERIT",
+ "inherited_value": "REBASE_IF_NECESSARY"
+ },
"state": "ACTIVE",
"commentlinks": {}
}
@@ -1072,7 +1082,9 @@
Lists the access rights for a single project.
-As result a link:#project-access-info[ProjectAccessInfo] entity is returned.
+As result a
+link:rest-api-access.html#project-access-info[ProjectAccessInfo]
+entity is returned.
.Request
----
@@ -1156,7 +1168,9 @@
After removals have been applied, additions will be applied.
-As result a link:#project-access-info[ProjectAccessInfo] entity is returned.
+As result a
+link:rest-api-access.html#project-access-info[ProjectAccessInfo]
+entity is returned.
.Request
----
@@ -2846,10 +2860,12 @@
The link:config-gerrit.html#receive.maxObjectSizeLimit[max object size
limit] of this project as a link:#max-object-size-limit-info[
MaxObjectSizeLimitInfo] entity.
+|`default_submit_type` ||
+link:#submit-type-info[SubmitTypeInfo] that describes the default submit type of
+the project, when not overridden at the change level.
|`submit_type` ||
-The default submit type of the project, can be `MERGE_IF_NECESSARY`,
-`FAST_FORWARD_ONLY`, `REBASE_IF_NECESSARY`, `REBASE_ALWAYS`, `MERGE_ALWAYS` or
-`CHERRY_PICK`.
+Deprecated; equivalent to link:#submit-type-info[`value`] in
+`default_submit_type`.
|`match_author_to_committer_date` |optional|
link:#inherited-boolean-info[InheritedBooleanInfo] that indicates whether
a change's author date will be changed to match its submitter date upon submit.
@@ -2872,6 +2888,9 @@
|`actions` |optional|
Actions the caller might be able to perform on this project. The
information is a map of view names to
+|`reject_empty_commit` |optional|
+link:#inherited-boolean-info[InheritedBooleanInfo] that tells whether
+empty commits should be rejected when a change is merged.
link:rest-api-changes.html#action-info[ActionInfo] entities.
|=======================================================
@@ -2936,6 +2955,10 @@
|`plugin_config_values` |optional|
Plugin configuration values as map which maps the plugin name to a map
of parameter names to values.
+|`reject_empty_commit` |optional|
+Whether empty commits should be rejected when a change is merged.
+Can be `TRUE`, `FALSE` or `INHERIT`. +
+If not set, this setting is not updated.
|======================================================
[[config-parameter-info]]
@@ -3269,6 +3292,9 @@
|`plugin_config_values` |optional|
Plugin configuration values as map which maps the plugin name to a map
of parameter names to values.
+|`reject_empty_commit` |optional|
+Whether empty commits should be rejected when a change is merged
+(`TRUE`, `FALSE`, `INHERIT`).
|=========================================
[[project-parent-input]]
@@ -3317,6 +3343,27 @@
|`size_of_packed_objects` |Size of packed objects in bytes.
|======================================
+[[submit-type-info]]
+=== SubmitTypeInfo
+Information about the link:project-configuration.html#submit_type[default submit
+type of a project], taking into account project inheritance.
+
+Valid values for each field are `MERGE_IF_NECESSARY`, `FAST_FORWARD_ONLY`,
+`REBASE_IF_NECESSARY`, `REBASE_ALWAYS`, `MERGE_ALWAYS` or `CHERRY_PICK`, plus
+`INHERIT` where applicable.
+
+[options="header",cols="1,6"]
+|===============================
+|Field Name |Description
+|`value` |
+The effective submit type value. Never `INHERIT`.
+|`configured_value` |
+The configured value, can be one of the submit types, or `INHERIT` to inherit
+from the parent project.
+|`inherited_value` |
+The effective value that would be inherited from the parent. Never `INHERIT`.
+|===============================
+
[[tag-info]]
=== TagInfo
The `TagInfo` entity contains information about a tag.
diff --git a/SUBMITTING_PATCHES b/SUBMITTING_PATCHES
index 553ab34..5a82fd9 100644
--- a/SUBMITTING_PATCHES
+++ b/SUBMITTING_PATCHES
@@ -3,6 +3,7 @@
- Make small logical changes.
- Provide a meaningful commit message.
- Make sure all code is under the Apache License, 2.0.
+ - Make sure all commit messages have a Change-Id.
- Publish your changes for review:
git push https://gerrit.googlesource.com/gerrit HEAD:refs/for/master
@@ -67,6 +68,13 @@
https://gerrit-review.googlesource.com/#/settings/http-password
+Ensure you have installed the commit-msg hook that automatically
+generates and inserts a Change-Id line during "git commit". This can
+be done from the root directory of the local Git repository:
+
+ curl -Lo .git/hooks/commit-msg https://gerrit-review.googlesource.com/tools/hooks/commit-msg
+ chmod +x .git/hooks/commit-msg
+
Push your patches over HTTPS to the review server, possibly through
a remembered remote to make this easier in the future:
diff --git a/WORKSPACE b/WORKSPACE
index 21bbc8b..c16200b 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -1,14 +1,17 @@
workspace(name = "gerrit")
-load("//:version.bzl", "check_version")
-
-check_version("0.5.3")
-
load("//tools/bzl:maven_jar.bzl", "maven_jar", "GERRIT", "MAVEN_LOCAL")
load("//lib/codemirror:cm.bzl", "CM_VERSION", "DIFF_MATCH_PATCH_VERSION")
load("//plugins:external_plugin_deps.bzl", "external_plugin_deps")
http_archive(
+ name = "bazel_skylib",
+ sha256 = "bbccf674aa441c266df9894182d80de104cabd19be98be002f6d478aaa31574d",
+ strip_prefix = "bazel-skylib-2169ae1c374aab4a09aa90e65efe1a3aad4e279b",
+ urls = ["https://github.com/bazelbuild/bazel-skylib/archive/2169ae1c374aab4a09aa90e65efe1a3aad4e279b.tar.gz"],
+)
+
+http_archive(
name = "io_bazel_rules_closure",
sha256 = "25f5399f18d8bf9ce435f85c6bbf671ec4820bc4396b3022cc5dc4bc66303609",
strip_prefix = "rules_closure-0.4.2",
@@ -24,6 +27,10 @@
url = "https://raw.githubusercontent.com/google/closure-compiler/775609aad61e14aef289ebec4bfc09ad88877f9e/contrib/externs/polymer-1.0.js",
)
+load("@bazel_skylib//:lib.bzl", "versions")
+
+versions.check(minimum_bazel_version = "0.7.0")
+
load("@io_bazel_rules_closure//closure:defs.bzl", "closure_repositories")
# Prevent redundant loading of dependencies.
@@ -278,6 +285,7 @@
sha1 = "4b95f4897fa13f2cd904aee711aeafc0c5295cd8",
)
+# When upgrading commons-compress, also upgrade tukaani-xz
maven_jar(
name = "commons_compress",
artifact = "org.apache.commons:commons-compress:1.13",
@@ -418,10 +426,11 @@
sha1 = "514df6a7c7938de35c7f68dc8b8f22df86037f38",
)
+# Transitive dependency of commons-compress
maven_jar(
name = "tukaani_xz",
- artifact = "org.tukaani:xz:1.6",
- sha1 = "05b6f921f1810bdf90e25471968f741f87168b64",
+ artifact = "org.tukaani:xz:1.4",
+ sha1 = "18a9a2ce6abf32ea1b5fd31dae5210ad93f4e5e3",
)
# When upgrading Lucene, make sure it's compatible with Elasticsearch
@@ -1077,8 +1086,8 @@
bower_archive(
name = "paper-button",
package = "polymerelements/paper-button",
- sha1 = "41a8fec68d93dad223ad2076d68515334b2c8d7b",
- version = "1.0.11",
+ sha1 = "3b01774f58a8085d3c903fc5a32944b26ab7be72",
+ version = "2.0.0",
)
bower_archive(
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/FormatUtil.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/FormatUtil.java
index 7307264..74fcdc2 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/FormatUtil.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/FormatUtil.java
@@ -83,17 +83,6 @@
return createAccountFormatter().name(info);
}
- public static AccountInfo asInfo(com.google.gerrit.common.data.AccountInfo acct) {
- if (acct == null) {
- return AccountInfo.create(0, null, null, null);
- }
- return AccountInfo.create(
- acct.getId() != null ? acct.getId().get() : 0,
- acct.getFullName(),
- acct.getPreferredEmail(),
- acct.getUsername());
- }
-
private static AccountFormatter createAccountFormatter() {
return new AccountFormatter(Gerrit.info().user().anonymousCowardName());
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java
index e46ba72..cb3e9f0 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java
@@ -89,6 +89,7 @@
suggestions.add("ownerin:");
suggestions.add("author:");
suggestions.add("committer:");
+ suggestions.add("assignee:");
suggestions.add("reviewer:");
suggestions.add("reviewer:self");
@@ -136,6 +137,7 @@
suggestions.add("is:mergeable");
suggestions.add("is:ignored");
suggestions.add("is:wip");
+ suggestions.add("is:assigned");
suggestions.add("status:");
suggestions.add("status:open");
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
index b556519..c0947a8 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
@@ -133,6 +133,8 @@
String headingProjectSubmitType();
+ String projectSubmitType_INHERIT();
+
String projectSubmitType_FAST_FORWARD_ONLY();
String projectSubmitType_MERGE_ALWAYS();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
index 62f3778..8d6878f 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
@@ -57,6 +57,7 @@
headingAuditLog = Audit Log
headingProjectSubmitType = Submit Type
+projectSubmitType_INHERIT = Inherit
projectSubmitType_FAST_FORWARD_ONLY = Fast Forward Only
projectSubmitType_MERGE_IF_NECESSARY = Merge if Necessary
projectSubmitType_REBASE_ALWAYS = Rebase Always
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
index 4e94250..64e147d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectInfoScreen.java
@@ -30,6 +30,7 @@
import com.google.gerrit.client.projects.ConfigInfo.ConfigParameterInfo;
import com.google.gerrit.client.projects.ConfigInfo.ConfigParameterValue;
import com.google.gerrit.client.projects.ConfigInfo.InheritedBooleanInfo;
+import com.google.gerrit.client.projects.ConfigInfo.SubmitTypeInfo;
import com.google.gerrit.client.projects.ProjectApi;
import com.google.gerrit.client.rpc.CallbackGroup;
import com.google.gerrit.client.rpc.GerritCallback;
@@ -335,13 +336,15 @@
grid.addHtml(AdminConstants.I.useSignedOffBy(), signedOffBy);
}
- private void setSubmitType(SubmitType newSubmitType) {
+ private void setSubmitType(SubmitTypeInfo newSubmitType) {
int index = -1;
- if (submitType != null) {
+ if (newSubmitType != null) {
for (int i = 0; i < submitType.getItemCount(); i++) {
- if (newSubmitType.name().equals(submitType.getValue(i))) {
+ if (submitType.getValue(i).equals(SubmitType.INHERIT.name())) {
+ submitType.setItemText(i, getInheritString(newSubmitType));
+ }
+ if (newSubmitType.configuredValue().name().equals(submitType.getValue(i))) {
index = i;
- break;
}
}
submitType.setSelectedIndex(index);
@@ -349,6 +352,13 @@
}
}
+ private static String getInheritString(SubmitTypeInfo submitType) {
+ return Util.toLongString(SubmitType.INHERIT)
+ + " ("
+ + Util.toLongString(submitType.inheritedValue())
+ + ")";
+ }
+
private void setState(ProjectState newState) {
if (state != null) {
for (int i = 0; i < state.getItemCount(); i++) {
@@ -419,7 +429,7 @@
setBool(privateByDefault, result.privateByDefault());
setBool(enableReviewerByEmail, result.enableReviewerByEmail());
setBool(matchAuthorToCommitterDate, result.matchAuthorToCommitterDate());
- setSubmitType(result.submitType());
+ setSubmitType(result.defaultSubmitType());
setState(result.state());
maxObjectSizeLimit.setText(result.maxObjectSizeLimit().configuredValue());
if (result.maxObjectSizeLimit().inheritedValue() != null) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/Util.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/Util.java
index 2e4926d..bbc8a1d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/Util.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/Util.java
@@ -35,6 +35,8 @@
return "";
}
switch (type) {
+ case INHERIT:
+ return AdminConstants.I.projectSubmitType_INHERIT();
case FAST_FORWARD_ONLY:
return AdminConstants.I.projectSubmitType_FAST_FORWARD_ONLY();
case MERGE_IF_NECESSARY:
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfo.java
index b8effdf..f670ac7 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfo.java
@@ -70,6 +70,8 @@
return SubmitType.valueOf(submitTypeRaw());
}
+ public final native SubmitTypeInfo defaultSubmitType() /*-{ return this.default_submit_type; }-*/;
+
public final native NativeMap<NativeMap<ConfigParameterInfo>> pluginConfig()
/*-{ return this.plugin_config || {}; }-*/ ;
@@ -232,4 +234,26 @@
protected ConfigParameterValue() {}
}
+
+ public static class SubmitTypeInfo extends JavaScriptObject {
+ public final SubmitType value() {
+ return SubmitType.valueOf(valueRaw());
+ }
+
+ public final SubmitType configuredValue() {
+ return SubmitType.valueOf(configuredValueRaw());
+ }
+
+ public final SubmitType inheritedValue() {
+ return SubmitType.valueOf(inheritedValueRaw());
+ }
+
+ private final native String valueRaw() /*-{ return this.value; }-*/;
+
+ private final native String configuredValueRaw() /*-{ return this.configured_value; }-*/;
+
+ private final native String inheritedValueRaw() /*-{ return this.inherited_value; }-*/;
+
+ protected SubmitTypeInfo() {}
+ }
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java
index 3766dd9..66afdb2 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java
@@ -176,7 +176,9 @@
in.setRejectImplicitMerges(rejectImplicitMerges);
in.setPrivateByDefault(privateByDefault);
in.setMaxObjectSizeLimit(maxObjectSizeLimit);
- in.setSubmitType(submitType);
+ if (submitType != null) {
+ in.setSubmitType(submitType);
+ }
in.setState(state);
in.setPluginConfigValues(pluginConfigValues);
in.setEnableReviewerByEmail(enableReviewerByEmail);
diff --git a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index c3349f1..88e322a 100644
--- a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -436,6 +436,7 @@
in.useContentMerge = ann.useContributorAgreements();
in.useSignedOffBy = ann.useSignedOffBy();
in.useContentMerge = ann.useContentMerge();
+ in.rejectEmptyCommit = ann.rejectEmptyCommit();
} else {
// Defaults should match TestProjectConfig, omitting nullable values.
in.createEmptyCommit = true;
diff --git a/java/com/google/gerrit/acceptance/GerritServer.java b/java/com/google/gerrit/acceptance/GerritServer.java
index 0b14cf1..1b9e8aa 100644
--- a/java/com/google/gerrit/acceptance/GerritServer.java
+++ b/java/com/google/gerrit/acceptance/GerritServer.java
@@ -26,7 +26,6 @@
import com.google.gerrit.lucene.LuceneIndexModule;
import com.google.gerrit.pgm.Daemon;
import com.google.gerrit.pgm.Init;
-import com.google.gerrit.pgm.init.InitSshd;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.receive.AsyncReceiveCommits;
import com.google.gerrit.server.ssh.NoSshModule;
@@ -457,7 +456,8 @@
URI uri = URI.create(url);
String addr = cfg.getString("sshd", null, "listenAddress");
- if (!InitSshd.isOff(addr)) {
+ // We do not use InitSshd.isOff to avoid coupling GerritServer to the SSH code.
+ if (!"off".equalsIgnoreCase(addr)) {
sshdAddress = SocketUtil.resolve(cfg.getString("sshd", null, "listenAddress"), 0);
}
httpAddress = new InetSocketAddress(uri.getHost(), uri.getPort());
diff --git a/java/com/google/gerrit/acceptance/TestProjectInput.java b/java/com/google/gerrit/acceptance/TestProjectInput.java
index 739d4f5..eada6434 100644
--- a/java/com/google/gerrit/acceptance/TestProjectInput.java
+++ b/java/com/google/gerrit/acceptance/TestProjectInput.java
@@ -45,6 +45,8 @@
InheritableBoolean requireChangeId() default InheritableBoolean.INHERIT;
+ InheritableBoolean rejectEmptyCommit() default InheritableBoolean.INHERIT;
+
// Fields specific to acceptance test behavior.
/** Username to use for initial clone, passed to {@link AccountCreator}. */
diff --git a/java/com/google/gerrit/common/data/AccountInfo.java b/java/com/google/gerrit/common/data/AccountInfo.java
deleted file mode 100644
index 788a26d..0000000
--- a/java/com/google/gerrit/common/data/AccountInfo.java
+++ /dev/null
@@ -1,77 +0,0 @@
-// Copyright (C) 2008 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.data;
-
-import com.google.gerrit.reviewdb.client.Account;
-
-/** Summary information about an {@link Account}, for simple tabular displays. */
-public class AccountInfo {
- protected Account.Id id;
- protected String fullName;
- protected String preferredEmail;
- protected String username;
-
- protected AccountInfo() {}
-
- /**
- * Create an 'Anonymous Coward' account info, when only the id is known.
- *
- * <p>This constructor should only be a last-ditch effort, when the usual account lookup has
- * failed and a stale account id has been discovered in the data store.
- */
- public AccountInfo(Account.Id id) {
- this.id = id;
- }
-
- /**
- * Create an account description from a real data store record.
- *
- * @param a the data store record holding the specific account details.
- */
- public AccountInfo(Account a) {
- id = a.getId();
- fullName = a.getFullName();
- preferredEmail = a.getPreferredEmail();
- username = a.getUserName();
- }
-
- /** @return the unique local id of the account */
- public Account.Id getId() {
- return id;
- }
-
- public void setFullName(String n) {
- fullName = n;
- }
-
- /** @return the full name of the account holder; null if not supplied */
- public String getFullName() {
- return fullName;
- }
-
- /** @return the email address of the account holder; null if not supplied */
- public String getPreferredEmail() {
- return preferredEmail;
- }
-
- public void setPreferredEmail(String email) {
- preferredEmail = email;
- }
-
- /** @return the username of the account holder */
- public String getUsername() {
- return username;
- }
-}
diff --git a/java/com/google/gerrit/common/data/AgreementInfo.java b/java/com/google/gerrit/common/data/AgreementInfo.java
deleted file mode 100644
index 4fb4053..0000000
--- a/java/com/google/gerrit/common/data/AgreementInfo.java
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (C) 2008 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.data;
-
-import java.util.List;
-import java.util.Map;
-
-public class AgreementInfo {
- public List<String> accepted;
- public Map<String, ContributorAgreement> agreements;
-
- public AgreementInfo() {}
-
- public void setAccepted(List<String> a) {
- accepted = a;
- }
-
- public void setAgreements(Map<String, ContributorAgreement> a) {
- agreements = a;
- }
-}
diff --git a/java/com/google/gerrit/common/data/SubmitTypeRecord.java b/java/com/google/gerrit/common/data/SubmitTypeRecord.java
index a01d83d..d16da96 100644
--- a/java/com/google/gerrit/common/data/SubmitTypeRecord.java
+++ b/java/com/google/gerrit/common/data/SubmitTypeRecord.java
@@ -48,6 +48,9 @@
public final String errorMessage;
private SubmitTypeRecord(Status status, SubmitType type, String errorMessage) {
+ if (type == SubmitType.INHERIT) {
+ throw new IllegalArgumentException("Cannot output submit type " + type);
+ }
this.status = status;
this.type = type;
this.errorMessage = errorMessage;
diff --git a/java/com/google/gerrit/common/data/testing/BUILD b/java/com/google/gerrit/common/data/testing/BUILD
new file mode 100644
index 0000000..83f1c06
--- /dev/null
+++ b/java/com/google/gerrit/common/data/testing/BUILD
@@ -0,0 +1,11 @@
+java_library(
+ name = "common-data-test-util",
+ testonly = 1,
+ srcs = glob(["**/*.java"]),
+ visibility = ["//visibility:public"],
+ deps = [
+ "//java/com/google/gerrit/common:server",
+ "//java/com/google/gerrit/reviewdb:server",
+ "//lib:truth",
+ ],
+)
diff --git a/java/com/google/gerrit/common/data/testing/GroupReferenceSubject.java b/java/com/google/gerrit/common/data/testing/GroupReferenceSubject.java
new file mode 100644
index 0000000..1988d66
--- /dev/null
+++ b/java/com/google/gerrit/common/data/testing/GroupReferenceSubject.java
@@ -0,0 +1,48 @@
+// Copyright (C) 2018 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.data.testing;
+
+import static com.google.common.truth.Truth.assertAbout;
+
+import com.google.common.truth.ComparableSubject;
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.StringSubject;
+import com.google.common.truth.Subject;
+import com.google.common.truth.Truth;
+import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+
+public class GroupReferenceSubject extends Subject<GroupReferenceSubject, GroupReference> {
+
+ public static GroupReferenceSubject assertThat(GroupReference group) {
+ return assertAbout(GroupReferenceSubject::new).that(group);
+ }
+
+ private GroupReferenceSubject(FailureMetadata metadata, GroupReference group) {
+ super(metadata, group);
+ }
+
+ public ComparableSubject<?, AccountGroup.UUID> groupUuid() {
+ isNotNull();
+ GroupReference group = actual();
+ return Truth.assertThat(group.getUUID()).named("groupUuid");
+ }
+
+ public StringSubject name() {
+ isNotNull();
+ GroupReference group = actual();
+ return Truth.assertThat(group.getName()).named("name");
+ }
+}
diff --git a/java/com/google/gerrit/extensions/api/accounts/Accounts.java b/java/com/google/gerrit/extensions/api/accounts/Accounts.java
index e92d229..651e786 100644
--- a/java/com/google/gerrit/extensions/api/accounts/Accounts.java
+++ b/java/com/google/gerrit/extensions/api/accounts/Accounts.java
@@ -137,6 +137,7 @@
private String query;
private int limit;
private int start;
+ private boolean suggest;
private EnumSet<ListAccountsOption> options = EnumSet.noneOf(ListAccountsOption.class);
/** Execute query and return a list of accounts. */
@@ -166,6 +167,11 @@
return this;
}
+ public QueryRequest withSuggest(boolean suggest) {
+ this.suggest = suggest;
+ return this;
+ }
+
public QueryRequest withOption(ListAccountsOption options) {
this.options.add(options);
return this;
@@ -193,6 +199,10 @@
return start;
}
+ public boolean getSuggest() {
+ return suggest;
+ }
+
public EnumSet<ListAccountsOption> getOptions() {
return options;
}
diff --git a/java/com/google/gerrit/extensions/api/changes/ReviewInput.java b/java/com/google/gerrit/extensions/api/changes/ReviewInput.java
index 2c945be..a13fb75 100644
--- a/java/com/google/gerrit/extensions/api/changes/ReviewInput.java
+++ b/java/com/google/gerrit/extensions/api/changes/ReviewInput.java
@@ -48,8 +48,8 @@
* How to process draft comments already in the database that were not also described in this
* input request.
*
- * <p>Defaults to DELETE, unless {@link #onBehalfOf} is set, in which case it defaults to KEEP and
- * any other value is disallowed.
+ * <p>If not set, the default is {@link DraftHandling#KEEP}. If {@link #onBehalfOf} is set, then
+ * no other value besides {@code KEEP} is allowed.
*/
public DraftHandling drafts;
@@ -87,15 +87,12 @@
public boolean ready;
public enum DraftHandling {
- /** Delete pending drafts on this revision only. */
- DELETE,
+ /** Leave pending drafts alone. */
+ KEEP,
/** Publish pending drafts on this revision only. */
PUBLISH,
- /** Leave pending drafts alone. */
- KEEP,
-
/** Publish pending drafts on all revisions. */
PUBLISH_ALL_REVISIONS
}
diff --git a/java/com/google/gerrit/extensions/api/projects/ConfigInfo.java b/java/com/google/gerrit/extensions/api/projects/ConfigInfo.java
index 7fa65cf..80115aa 100644
--- a/java/com/google/gerrit/extensions/api/projects/ConfigInfo.java
+++ b/java/com/google/gerrit/extensions/api/projects/ConfigInfo.java
@@ -35,9 +35,12 @@
public InheritedBooleanInfo privateByDefault;
public InheritedBooleanInfo enableReviewerByEmail;
public InheritedBooleanInfo matchAuthorToCommitterDate;
+ public InheritedBooleanInfo rejectEmptyCommit;
public MaxObjectSizeLimitInfo maxObjectSizeLimit;
+ @Deprecated // Equivalent to defaultSubmitType.value
public SubmitType submitType;
+ public SubmitTypeInfo defaultSubmitType;
public ProjectState state;
public Map<String, Map<String, ConfigParameterInfo>> pluginConfig;
public Map<String, ActionInfo> actions;
@@ -72,4 +75,10 @@
public List<String> permittedValues;
public List<String> values;
}
+
+ public static class SubmitTypeInfo {
+ public SubmitType value;
+ public SubmitType configuredValue;
+ public SubmitType inheritedValue;
+ }
}
diff --git a/java/com/google/gerrit/extensions/api/projects/ConfigInput.java b/java/com/google/gerrit/extensions/api/projects/ConfigInput.java
index 0c1cec4..37a2e8b 100644
--- a/java/com/google/gerrit/extensions/api/projects/ConfigInput.java
+++ b/java/com/google/gerrit/extensions/api/projects/ConfigInput.java
@@ -32,6 +32,7 @@
public InheritableBoolean privateByDefault;
public InheritableBoolean enableReviewerByEmail;
public InheritableBoolean matchAuthorToCommitterDate;
+ public InheritableBoolean rejectEmptyCommit;
public String maxObjectSizeLimit;
public SubmitType submitType;
public ProjectState state;
diff --git a/java/com/google/gerrit/extensions/api/projects/ProjectInput.java b/java/com/google/gerrit/extensions/api/projects/ProjectInput.java
index 612c49c..b7079ae 100644
--- a/java/com/google/gerrit/extensions/api/projects/ProjectInput.java
+++ b/java/com/google/gerrit/extensions/api/projects/ProjectInput.java
@@ -33,6 +33,7 @@
public InheritableBoolean useContentMerge;
public InheritableBoolean requireChangeId;
public InheritableBoolean createNewChangeForAllNotInTarget;
+ public InheritableBoolean rejectEmptyCommit;
public String maxObjectSizeLimit;
public Map<String, Map<String, ConfigValue>> pluginConfigValues;
}
diff --git a/java/com/google/gerrit/extensions/client/ListChangesOption.java b/java/com/google/gerrit/extensions/client/ListChangesOption.java
index ee7d039..ffc5029 100644
--- a/java/com/google/gerrit/extensions/client/ListChangesOption.java
+++ b/java/com/google/gerrit/extensions/client/ListChangesOption.java
@@ -75,7 +75,10 @@
SUBMITTABLE(20),
/** If tracking Ids are included, include detailed tracking Ids info. */
- TRACKING_IDS(21);
+ TRACKING_IDS(21),
+
+ /** Skip mergeability data */
+ SKIP_MERGEABLE(22);
private final int value;
diff --git a/java/com/google/gerrit/extensions/client/ProjectState.java b/java/com/google/gerrit/extensions/client/ProjectState.java
index e5bc194..4aee69c 100644
--- a/java/com/google/gerrit/extensions/client/ProjectState.java
+++ b/java/com/google/gerrit/extensions/client/ProjectState.java
@@ -15,8 +15,14 @@
package com.google.gerrit.extensions.client;
public enum ProjectState {
+ /** Permits reading project state and contents as well as mutating data. */
ACTIVE(true, true),
+ /** Permits reading project state and contents. Does not permit any modifications. */
READ_ONLY(true, false),
+ /**
+ * Hides the project as if it was deleted, but makes requests fail with an error message that
+ * reveals the project's existence.
+ */
HIDDEN(false, false);
private final boolean permitsRead;
diff --git a/java/com/google/gerrit/extensions/client/SubmitType.java b/java/com/google/gerrit/extensions/client/SubmitType.java
index b52e89a..0e2f362 100644
--- a/java/com/google/gerrit/extensions/client/SubmitType.java
+++ b/java/com/google/gerrit/extensions/client/SubmitType.java
@@ -15,10 +15,11 @@
package com.google.gerrit.extensions.client;
public enum SubmitType {
+ INHERIT,
FAST_FORWARD_ONLY,
MERGE_IF_NECESSARY,
REBASE_IF_NECESSARY,
REBASE_ALWAYS,
MERGE_ALWAYS,
- CHERRY_PICK
+ CHERRY_PICK;
}
diff --git a/java/com/google/gerrit/extensions/common/ChangeConfigInfo.java b/java/com/google/gerrit/extensions/common/ChangeConfigInfo.java
index b710121..9e02ae5 100644
--- a/java/com/google/gerrit/extensions/common/ChangeConfigInfo.java
+++ b/java/com/google/gerrit/extensions/common/ChangeConfigInfo.java
@@ -18,6 +18,7 @@
public Boolean allowBlame;
public Boolean showAssigneeInChangesTable;
public Boolean allowDrafts;
+ public Boolean disablePrivateChanges;
public int largeChange;
public String replyLabel;
public String replyTooltip;
diff --git a/java/com/google/gerrit/gpg/server/DeleteGpgKey.java b/java/com/google/gerrit/gpg/server/DeleteGpgKey.java
index b9d89ee..6d132c8 100644
--- a/java/com/google/gerrit/gpg/server/DeleteGpgKey.java
+++ b/java/com/google/gerrit/gpg/server/DeleteGpgKey.java
@@ -24,8 +24,9 @@
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.gpg.PublicKeyStore;
import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.account.AccountsUpdate;
import com.google.gerrit.server.account.externalids.ExternalId;
-import com.google.gerrit.server.account.externalids.ExternalIdsUpdate;
+import com.google.gerrit.server.account.externalids.ExternalIds;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -41,16 +42,19 @@
private final Provider<PersonIdent> serverIdent;
private final Provider<PublicKeyStore> storeProvider;
- private final ExternalIdsUpdate.User externalIdsUpdateFactory;
+ private final AccountsUpdate.User accountsUpdateFactory;
+ private final ExternalIds externalIds;
@Inject
DeleteGpgKey(
@GerritPersonIdent Provider<PersonIdent> serverIdent,
Provider<PublicKeyStore> storeProvider,
- ExternalIdsUpdate.User externalIdsUpdateFactory) {
+ AccountsUpdate.User accountsUpdateFactory,
+ ExternalIds externalIds) {
this.serverIdent = serverIdent;
this.storeProvider = storeProvider;
- this.externalIdsUpdateFactory = externalIdsUpdateFactory;
+ this.accountsUpdateFactory = accountsUpdateFactory;
+ this.externalIds = externalIds;
}
@Override
@@ -58,12 +62,16 @@
throws ResourceConflictException, PGPException, OrmException, IOException,
ConfigInvalidException {
PGPPublicKey key = rsrc.getKeyRing().getPublicKey();
- externalIdsUpdateFactory
- .create()
- .delete(
- rsrc.getUser().getAccountId(),
+ ExternalId extId =
+ externalIds.get(
ExternalId.Key.create(
SCHEME_GPGKEY, BaseEncoding.base16().encode(key.getFingerprint())));
+ accountsUpdateFactory
+ .create()
+ .update(
+ "Delete GPG Key via API",
+ rsrc.getUser().getAccountId(),
+ u -> u.deleteExternalId(extId));
try (PublicKeyStore store = storeProvider.get()) {
store.remove(rsrc.getKeyRing().getPublicKey().getFingerprint());
diff --git a/java/com/google/gerrit/gpg/server/GpgKeys.java b/java/com/google/gerrit/gpg/server/GpgKeys.java
index 63c0476..c4e35e0 100644
--- a/java/com/google/gerrit/gpg/server/GpgKeys.java
+++ b/java/com/google/gerrit/gpg/server/GpgKeys.java
@@ -92,7 +92,8 @@
throws ResourceNotFoundException, PGPException, OrmException, IOException {
checkVisible(self, parent);
- byte[] fp = parseFingerprint(id.get(), getGpgExtIds(parent));
+ ExternalId gpgKeyExtId = findGpgKey(id.get(), getGpgExtIds(parent));
+ byte[] fp = parseFingerprint(gpgKeyExtId);
try (PublicKeyStore store = storeProvider.get()) {
long keyId = keyId(fp);
for (PGPPublicKeyRing keyRing : store.get(keyId)) {
@@ -106,30 +107,34 @@
throw new ResourceNotFoundException(id);
}
- static byte[] parseFingerprint(String str, Iterable<ExternalId> existingExtIds)
+ static ExternalId findGpgKey(String str, Iterable<ExternalId> existingExtIds)
throws ResourceNotFoundException {
str = CharMatcher.whitespace().removeFrom(str).toUpperCase();
if ((str.length() != 8 && str.length() != 40)
|| !CharMatcher.anyOf("0123456789ABCDEF").matchesAllOf(str)) {
throw new ResourceNotFoundException(str);
}
- byte[] fp = null;
+ ExternalId gpgKeyExtId = null;
for (ExternalId extId : existingExtIds) {
String fpStr = extId.key().id();
if (!fpStr.endsWith(str)) {
continue;
- } else if (fp != null) {
+ } else if (gpgKeyExtId != null) {
throw new ResourceNotFoundException("Multiple keys found for " + str);
}
- fp = BaseEncoding.base16().decode(fpStr);
+ gpgKeyExtId = extId;
if (str.length() == 40) {
break;
}
}
- if (fp == null) {
+ if (gpgKeyExtId == null) {
throw new ResourceNotFoundException(str);
}
- return fp;
+ return gpgKeyExtId;
+ }
+
+ static byte[] parseFingerprint(ExternalId gpgKeyExtId) {
+ return BaseEncoding.base16().decode(gpgKeyExtId.key().id());
}
@Override
@@ -145,8 +150,7 @@
Map<String, GpgKeyInfo> keys = new HashMap<>();
try (PublicKeyStore store = storeProvider.get()) {
for (ExternalId extId : getGpgExtIds(rsrc)) {
- String fpStr = extId.key().id();
- byte[] fp = BaseEncoding.base16().decode(fpStr);
+ byte[] fp = parseFingerprint(extId);
boolean found = false;
for (PGPPublicKeyRing keyRing : store.get(keyId(fp))) {
if (Arrays.equals(keyRing.getPublicKey().getFingerprint(), fp)) {
diff --git a/java/com/google/gerrit/gpg/server/PostGpgKeys.java b/java/com/google/gerrit/gpg/server/PostGpgKeys.java
index d8ed855..4996e0e 100644
--- a/java/com/google/gerrit/gpg/server/PostGpgKeys.java
+++ b/java/com/google/gerrit/gpg/server/PostGpgKeys.java
@@ -18,14 +18,12 @@
import static com.google.gerrit.gpg.PublicKeyStore.keyToString;
import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_GPGKEY;
import static java.nio.charset.StandardCharsets.UTF_8;
-import static java.util.stream.Collectors.toList;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
import com.google.common.io.BaseEncoding;
import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.extensions.api.accounts.GpgKeysInput;
@@ -45,9 +43,9 @@
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountResource;
import com.google.gerrit.server.account.AccountState;
+import com.google.gerrit.server.account.AccountsUpdate;
import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.server.account.externalids.ExternalIds;
-import com.google.gerrit.server.account.externalids.ExternalIdsUpdate;
import com.google.gerrit.server.mail.send.AddKeySender;
import com.google.gerrit.server.query.account.InternalAccountQuery;
import com.google.gwtorm.server.OrmException;
@@ -61,7 +59,6 @@
import java.util.Collection;
import java.util.List;
import java.util.Map;
-import java.util.Set;
import org.bouncycastle.bcpg.ArmoredInputStream;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKey;
@@ -85,7 +82,7 @@
private final AddKeySender.Factory addKeyFactory;
private final Provider<InternalAccountQuery> accountQueryProvider;
private final ExternalIds externalIds;
- private final ExternalIdsUpdate.User externalIdsUpdateFactory;
+ private final AccountsUpdate.User accountsUpdateFactory;
@Inject
PostGpgKeys(
@@ -96,7 +93,7 @@
AddKeySender.Factory addKeyFactory,
Provider<InternalAccountQuery> accountQueryProvider,
ExternalIds externalIds,
- ExternalIdsUpdate.User externalIdsUpdateFactory) {
+ AccountsUpdate.User accountsUpdateFactory) {
this.serverIdent = serverIdent;
this.self = self;
this.storeProvider = storeProvider;
@@ -104,7 +101,7 @@
this.addKeyFactory = addKeyFactory;
this.accountQueryProvider = accountQueryProvider;
this.externalIds = externalIds;
- this.externalIdsUpdateFactory = externalIdsUpdateFactory;
+ this.accountsUpdateFactory = accountsUpdateFactory;
}
@Override
@@ -116,8 +113,9 @@
Collection<ExternalId> existingExtIds =
externalIds.byAccount(rsrc.getUser().getAccountId(), SCHEME_GPGKEY);
try (PublicKeyStore store = storeProvider.get()) {
- Set<Fingerprint> toRemove = readKeysToRemove(input, existingExtIds);
- List<PGPPublicKeyRing> newKeys = readKeysToAdd(input, toRemove);
+ Map<ExternalId, Fingerprint> toRemove = readKeysToRemove(input, existingExtIds);
+ Collection<Fingerprint> fingerprintsToRemove = toRemove.values();
+ List<PGPPublicKeyRing> newKeys = readKeysToAdd(input, fingerprintsToRemove);
List<ExternalId> newExtIds = new ArrayList<>(existingExtIds.size());
for (PGPPublicKeyRing keyRing : newKeys) {
@@ -133,26 +131,29 @@
}
}
- storeKeys(rsrc, newKeys, toRemove);
+ storeKeys(rsrc, newKeys, fingerprintsToRemove);
- List<ExternalId.Key> extIdKeysToRemove =
- toRemove.stream().map(fp -> toExtIdKey(fp.get())).collect(toList());
- externalIdsUpdateFactory
+ accountsUpdateFactory
.create()
- .replace(rsrc.getUser().getAccountId(), extIdKeysToRemove, newExtIds);
- return toJson(newKeys, toRemove, store, rsrc.getUser());
+ .update(
+ "Update GPG Keys via API",
+ rsrc.getUser().getAccountId(),
+ u -> u.replaceExternalIds(toRemove.keySet(), newExtIds));
+ return toJson(newKeys, fingerprintsToRemove, store, rsrc.getUser());
}
}
- private Set<Fingerprint> readKeysToRemove(
+ private Map<ExternalId, Fingerprint> readKeysToRemove(
GpgKeysInput input, Collection<ExternalId> existingExtIds) {
if (input.delete == null || input.delete.isEmpty()) {
- return ImmutableSet.of();
+ return ImmutableMap.of();
}
- Set<Fingerprint> fingerprints = Sets.newHashSetWithExpectedSize(input.delete.size());
+ Map<ExternalId, Fingerprint> fingerprints =
+ Maps.newHashMapWithExpectedSize(input.delete.size());
for (String id : input.delete) {
try {
- fingerprints.add(new Fingerprint(GpgKeys.parseFingerprint(id, existingExtIds)));
+ ExternalId gpgKeyExtId = GpgKeys.findGpgKey(id, existingExtIds);
+ fingerprints.put(gpgKeyExtId, new Fingerprint(GpgKeys.parseFingerprint(gpgKeyExtId)));
} catch (ResourceNotFoundException e) {
// Skip removal.
}
@@ -160,7 +161,7 @@
return fingerprints;
}
- private List<PGPPublicKeyRing> readKeysToAdd(GpgKeysInput input, Set<Fingerprint> toRemove)
+ private List<PGPPublicKeyRing> readKeysToAdd(GpgKeysInput input, Collection<Fingerprint> toRemove)
throws BadRequestException, IOException {
if (input.add == null || input.add.isEmpty()) {
return ImmutableList.of();
@@ -188,7 +189,7 @@
}
private void storeKeys(
- AccountResource rsrc, List<PGPPublicKeyRing> keyRings, Set<Fingerprint> toRemove)
+ AccountResource rsrc, List<PGPPublicKeyRing> keyRings, Collection<Fingerprint> toRemove)
throws BadRequestException, ResourceConflictException, PGPException, IOException {
try (PublicKeyStore store = storeProvider.get()) {
List<String> addedKeys = new ArrayList<>();
@@ -269,7 +270,7 @@
private Map<String, GpgKeyInfo> toJson(
Collection<PGPPublicKeyRing> keys,
- Set<Fingerprint> deleted,
+ Collection<Fingerprint> deleted,
PublicKeyStore store,
IdentifiedUser user)
throws IOException {
diff --git a/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java b/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
index 1f095e0..853d173 100644
--- a/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
+++ b/java/com/google/gerrit/httpd/auth/become/BecomeAnyAccountLoginServlet.java
@@ -183,9 +183,9 @@
return HtmlDomUtil.toUTF8(doc);
}
- private AuthResult auth(Account account) {
+ private AuthResult auth(AccountState account) {
if (account != null) {
- return new AuthResult(account.getId(), null, false);
+ return new AuthResult(account.getAccount().getId(), null, false);
}
return null;
}
@@ -218,13 +218,8 @@
private AuthResult byPreferredEmail(String email) {
try (ReviewDb db = schema.open()) {
- Optional<Account> match =
- queryProvider
- .get()
- .byPreferredEmail(email)
- .stream()
- .map(AccountState::getAccount)
- .findFirst();
+ Optional<AccountState> match =
+ queryProvider.get().byPreferredEmail(email).stream().findFirst();
return match.isPresent() ? auth(match.get()) : null;
} catch (OrmException e) {
getServletContext().log("cannot query database", e);
diff --git a/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java b/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java
index 4c8918d..88154b0 100644
--- a/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java
+++ b/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java
@@ -224,7 +224,9 @@
detail.setOwnerOf(ownerOf);
detail.setCanUpload(
canWriteProjectConfig
- || (checkReadConfig && perm.ref(RefNames.REFS_CONFIG).testOrFalse(CREATE_CHANGE)));
+ || (checkReadConfig
+ && perm.ref(RefNames.REFS_CONFIG).testOrFalse(CREATE_CHANGE)
+ && projectState.statePermitsWrite()));
detail.setConfigVisible(canWriteProjectConfig || checkReadConfig);
detail.setGroupInfo(buildGroupInfo(local));
detail.setLabelTypes(projectState.getLabelTypes());
diff --git a/java/com/google/gerrit/httpd/rpc/project/ProjectAccessHandler.java b/java/com/google/gerrit/httpd/rpc/project/ProjectAccessHandler.java
index 0240e2e..e44d680 100644
--- a/java/com/google/gerrit/httpd/rpc/project/ProjectAccessHandler.java
+++ b/java/com/google/gerrit/httpd/rpc/project/ProjectAccessHandler.java
@@ -110,7 +110,7 @@
public final T call()
throws NoSuchProjectException, IOException, ConfigInvalidException, InvalidNameException,
NoSuchGroupException, OrmException, UpdateParentFailedException,
- PermissionDeniedException, PermissionBackendException {
+ PermissionDeniedException, PermissionBackendException, ResourceConflictException {
try {
contributorAgreements.check(projectName, user);
} catch (AuthException e) {
@@ -195,7 +195,7 @@
protected abstract T updateProjectConfig(
ProjectConfig config, MetaDataUpdate md, boolean parentProjectUpdate)
throws IOException, NoSuchProjectException, ConfigInvalidException, OrmException,
- PermissionDeniedException, PermissionBackendException;
+ PermissionDeniedException, PermissionBackendException, ResourceConflictException;
private void replace(ProjectConfig config, Set<String> toDelete, AccessSection section)
throws NoSuchGroupException {
diff --git a/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java b/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java
index 81957d6..497354d 100644
--- a/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java
+++ b/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java
@@ -23,6 +23,7 @@
import com.google.gerrit.common.errors.PermissionDeniedException;
import com.google.gerrit.extensions.api.changes.AddReviewerInput;
import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.reviewdb.client.Change;
@@ -134,7 +135,7 @@
protected Change.Id updateProjectConfig(
ProjectConfig config, MetaDataUpdate md, boolean parentProjectUpdate)
throws IOException, OrmException, PermissionDeniedException, PermissionBackendException,
- ConfigInvalidException {
+ ConfigInvalidException, ResourceConflictException {
PermissionBackend.ForProject perm = permissionBackend.user(user).project(config.getName());
if (!check(perm, ProjectPermission.READ_CONFIG)) {
throw new PermissionDeniedException(RefNames.REFS_CONFIG + " not visible");
@@ -145,6 +146,8 @@
throw new PermissionDeniedException("cannot create change for " + RefNames.REFS_CONFIG);
}
+ projectCache.checkedGet(config.getName()).checkStatePermitsWrite();
+
md.setInsertChangeId(true);
Change.Id changeId = new Change.Id(seq.nextChangeId());
RevCommit commit =
diff --git a/java/com/google/gerrit/pgm/init/AccountsOnInit.java b/java/com/google/gerrit/pgm/init/AccountsOnInit.java
index fbe9b62..0a94b42 100644
--- a/java/com/google/gerrit/pgm/init/AccountsOnInit.java
+++ b/java/com/google/gerrit/pgm/init/AccountsOnInit.java
@@ -70,7 +70,7 @@
new GerritPersonIdentProvider(flags.cfg).get(), account.getRegisteredOn());
Config accountConfig = new Config();
- AccountConfig.writeToConfig(
+ AccountConfig.writeToAccountConfig(
InternalAccountUpdate.builder()
.setActive(account.isActive())
.setFullName(account.getFullName())
diff --git a/java/com/google/gerrit/pgm/init/GroupsOnInit.java b/java/com/google/gerrit/pgm/init/GroupsOnInit.java
index 3385244..7fe227d 100644
--- a/java/com/google/gerrit/pgm/init/GroupsOnInit.java
+++ b/java/com/google/gerrit/pgm/init/GroupsOnInit.java
@@ -150,7 +150,7 @@
File allUsersRepoPath = getPathToAllUsersRepository();
if (allUsersRepoPath != null) {
try (Repository allUsersRepo = new FileRepository(allUsersRepoPath)) {
- return GroupNameNotes.loadAllGroupReferences(allUsersRepo).stream();
+ return GroupNameNotes.loadAllGroups(allUsersRepo).stream();
}
}
return Stream.empty();
diff --git a/java/com/google/gerrit/pgm/init/InitAdminUser.java b/java/com/google/gerrit/pgm/init/InitAdminUser.java
index 3251c01..e9f5cd5 100644
--- a/java/com/google/gerrit/pgm/init/InitAdminUser.java
+++ b/java/com/google/gerrit/pgm/init/InitAdminUser.java
@@ -21,6 +21,7 @@
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.errors.NoSuchGroupException;
import com.google.gerrit.extensions.client.AuthType;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
import com.google.gerrit.pgm.init.api.AllUsersNameOnInitProvider;
import com.google.gerrit.pgm.init.api.ConsoleUI;
import com.google.gerrit.pgm.init.api.InitFlags;
@@ -151,7 +152,12 @@
}
AccountState as =
- new AccountState(new AllUsersName(allUsers.get()), a, extIds, new HashMap<>());
+ new AccountState(
+ new AllUsersName(allUsers.get()),
+ a,
+ extIds,
+ new HashMap<>(),
+ GeneralPreferencesInfo.defaults());
for (AccountIndex accountIndex : accountIndexCollection.getWriteIndexes()) {
accountIndex.replace(as);
}
diff --git a/java/com/google/gerrit/pgm/init/InitSshd.java b/java/com/google/gerrit/pgm/init/InitSshd.java
index 043f3ee..d2e280d 100644
--- a/java/com/google/gerrit/pgm/init/InitSshd.java
+++ b/java/com/google/gerrit/pgm/init/InitSshd.java
@@ -73,7 +73,7 @@
remover.remove("bc(pg|pkix|prov)-.*[.]jar");
}
- public static boolean isOff(String listenHostname) {
+ static boolean isOff(String listenHostname) {
return "off".equalsIgnoreCase(listenHostname)
|| "none".equalsIgnoreCase(listenHostname)
|| "no".equalsIgnoreCase(listenHostname);
@@ -82,7 +82,6 @@
private void generateSshHostKeys() throws InterruptedException, IOException {
if (!exists(site.ssh_key)
&& (!exists(site.ssh_rsa)
- || !exists(site.ssh_dsa)
|| !exists(site.ssh_ed25519)
|| !exists(site.ssh_ecdsa_256)
|| !exists(site.ssh_ecdsa_384)
@@ -116,26 +115,6 @@
.waitFor();
}
- if (!exists(site.ssh_dsa)) {
- System.err.print(" dsa...");
- System.err.flush();
- new ProcessBuilder(
- "ssh-keygen",
- "-q" /* quiet */,
- "-t",
- "dsa",
- "-P",
- emptyPassphraseArg,
- "-C",
- comment,
- "-f",
- site.ssh_dsa.toAbsolutePath().toString())
- .redirectError(Redirect.INHERIT)
- .redirectOutput(Redirect.INHERIT)
- .start()
- .waitFor();
- }
-
if (!exists(site.ssh_ed25519)) {
System.err.print(" ed25519...");
System.err.flush();
diff --git a/java/com/google/gerrit/reviewdb/client/Account.java b/java/com/google/gerrit/reviewdb/client/Account.java
index bce07aa..1f9ae0e 100644
--- a/java/com/google/gerrit/reviewdb/client/Account.java
+++ b/java/com/google/gerrit/reviewdb/client/Account.java
@@ -19,7 +19,6 @@
import static com.google.gerrit.reviewdb.client.RefNames.REFS_USERS;
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
-import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
import com.google.gwtorm.client.Column;
import com.google.gwtorm.client.IntKey;
import java.sql.Timestamp;
@@ -180,9 +179,6 @@
/** <i>computed</i> the username selected from the identities. */
protected String userName;
- /** <i>stored in git, used for caching</i> the user's preferences. */
- private GeneralPreferencesInfo generalPreferences;
-
/**
* ID of the user branch from which the account was read, {@code null} if the account was read
* from ReviewDb.
@@ -286,14 +282,6 @@
return registeredOn;
}
- public GeneralPreferencesInfo getGeneralPreferencesInfo() {
- return generalPreferences;
- }
-
- public void setGeneralPreferences(GeneralPreferencesInfo p) {
- generalPreferences = p;
- }
-
public String getMetaId() {
return metaId;
}
diff --git a/java/com/google/gerrit/reviewdb/client/BooleanProjectConfig.java b/java/com/google/gerrit/reviewdb/client/BooleanProjectConfig.java
index ef8156b..765e38c 100644
--- a/java/com/google/gerrit/reviewdb/client/BooleanProjectConfig.java
+++ b/java/com/google/gerrit/reviewdb/client/BooleanProjectConfig.java
@@ -39,7 +39,8 @@
REJECT_IMPLICIT_MERGES("receive", "rejectImplicitMerges"),
PRIVATE_BY_DEFAULT("change", "privateByDefault"),
ENABLE_REVIEWER_BY_EMAIL("reviewer", "enableByEmail"),
- MATCH_AUTHOR_TO_COMMITTER_DATE("project", "matchAuthorToCommitterDate");
+ MATCH_AUTHOR_TO_COMMITTER_DATE("submit", "matchAuthorToCommitterDate"),
+ REJECT_EMPTY_COMMIT("submit", "rejectEmptyCommit");
// Git config
private final String section;
diff --git a/java/com/google/gerrit/reviewdb/client/Project.java b/java/com/google/gerrit/reviewdb/client/Project.java
index c66b646..921667e 100644
--- a/java/com/google/gerrit/reviewdb/client/Project.java
+++ b/java/com/google/gerrit/reviewdb/client/Project.java
@@ -25,6 +25,12 @@
/** Projects match a source code repository managed by Gerrit */
public final class Project {
+ /** Default submit type for new projects. */
+ public static final SubmitType DEFAULT_SUBMIT_TYPE = SubmitType.MERGE_IF_NECESSARY;
+
+ /** Default submit type for root project (All-Projects). */
+ public static final SubmitType DEFAULT_ALL_PROJECTS_SUBMIT_TYPE = SubmitType.MERGE_IF_NECESSARY;
+
/** Project name key */
public static class NameKey extends StringKey<com.google.gwtorm.client.Key<?>> {
private static final long serialVersionUID = 1L;
@@ -137,7 +143,14 @@
maxObjectSizeLimit = limit;
}
- public SubmitType getSubmitType() {
+ /**
+ * Submit type as configured in {@code project.config}.
+ *
+ * <p>Does not take inheritance into account, i.e. may return {@link SubmitType#INHERIT}.
+ *
+ * @return submit type.
+ */
+ public SubmitType getConfiguredSubmitType() {
return submitType;
}
diff --git a/java/com/google/gerrit/server/account/AccountCacheImpl.java b/java/com/google/gerrit/server/account/AccountCacheImpl.java
index 9894751..963da62 100644
--- a/java/com/google/gerrit/server/account/AccountCacheImpl.java
+++ b/java/com/google/gerrit/server/account/AccountCacheImpl.java
@@ -130,50 +130,25 @@
private AccountState missing(Account.Id accountId) {
Account account = new Account(accountId, TimeUtil.nowTs());
account.setActive(false);
- return new AccountState(allUsersName, account, Collections.emptySet(), new HashMap<>());
+ return new AccountState(
+ allUsersName,
+ account,
+ Collections.emptySet(),
+ new HashMap<>(),
+ GeneralPreferencesInfo.defaults());
}
static class ByIdLoader extends CacheLoader<Account.Id, Optional<AccountState>> {
- private final AllUsersName allUsersName;
private final Accounts accounts;
- private final GeneralPreferencesLoader loader;
- private final Provider<WatchConfig.Accessor> watchConfig;
- private final ExternalIds externalIds;
@Inject
- ByIdLoader(
- AllUsersName allUsersName,
- Accounts accounts,
- GeneralPreferencesLoader loader,
- Provider<WatchConfig.Accessor> watchConfig,
- ExternalIds externalIds) {
- this.allUsersName = allUsersName;
+ ByIdLoader(Accounts accounts) {
this.accounts = accounts;
- this.loader = loader;
- this.watchConfig = watchConfig;
- this.externalIds = externalIds;
}
@Override
public Optional<AccountState> load(Account.Id who) throws Exception {
- Account account = accounts.get(who);
- if (account == null) {
- return Optional.empty();
- }
-
- try {
- account.setGeneralPreferences(loader.load(who));
- } catch (IOException | ConfigInvalidException e) {
- log.warn("Cannot load GeneralPreferences for " + who + " (using default)", e);
- account.setGeneralPreferences(GeneralPreferencesInfo.defaults());
- }
-
- return Optional.of(
- new AccountState(
- allUsersName,
- account,
- externalIds.byAccount(who),
- watchConfig.get().getProjectWatches(who)));
+ return Optional.ofNullable(accounts.get(who));
}
}
}
diff --git a/java/com/google/gerrit/server/account/AccountConfig.java b/java/com/google/gerrit/server/account/AccountConfig.java
index 1283270..b20aa0b 100644
--- a/java/com/google/gerrit/server/account/AccountConfig.java
+++ b/java/com/google/gerrit/server/account/AccountConfig.java
@@ -19,24 +19,31 @@
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
-import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.account.WatchConfig.NotifyType;
+import com.google.gerrit.server.account.WatchConfig.ProjectWatchKey;
+import com.google.gerrit.server.account.externalids.ExternalIds;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ValidationError;
import com.google.gerrit.server.git.VersionedMetaData;
-import com.google.gerrit.server.mail.send.OutgoingEmailValidator;
import com.google.gwtorm.server.OrmDuplicateKeyException;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
+import java.util.Set;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevSort;
@@ -75,26 +82,50 @@
public static final String KEY_PREFERRED_EMAIL = "preferredEmail";
public static final String KEY_STATUS = "status";
- @Nullable private final OutgoingEmailValidator emailValidator;
private final Account.Id accountId;
+ private final Repository repo;
private final String ref;
private Optional<Account> loadedAccount;
+ private Optional<ObjectId> externalIdsRev;
+ private WatchConfig watchConfig;
+ private PreferencesConfig prefConfig;
private Optional<InternalAccountUpdate> accountUpdate = Optional.empty();
private Timestamp registeredOn;
+ private boolean eagerParsing;
private List<ValidationError> validationErrors;
- public AccountConfig(@Nullable OutgoingEmailValidator emailValidator, Account.Id accountId) {
- this.emailValidator = emailValidator;
- this.accountId = checkNotNull(accountId);
+ public AccountConfig(Account.Id accountId, Repository allUsersRepo) {
+ this.accountId = checkNotNull(accountId, "accountId");
+ this.repo = checkNotNull(allUsersRepo, "allUsersRepo");
this.ref = RefNames.refsUsers(accountId);
}
+ /**
+ * Sets whether all account data should be eagerly parsed.
+ *
+ * <p>Eager parsing should only be used if the caller is interested in validation errors for all
+ * account data (see {@link #getValidationErrors()}.
+ *
+ * @param eagerParsing whether all account data should be eagerly parsed
+ * @return this AccountConfig instance for chaining
+ */
+ public AccountConfig setEagerParsing(boolean eagerParsing) {
+ checkState(loadedAccount == null, "Account %s already loaded", accountId.get());
+ this.eagerParsing = eagerParsing;
+ return this;
+ }
+
@Override
protected String getRefName() {
return ref;
}
+ public AccountConfig load() throws IOException, ConfigInvalidException {
+ load(repo);
+ return this;
+ }
+
/**
* Get the loaded account.
*
@@ -108,6 +139,40 @@
}
/**
+ * Returns the revision of the {@code refs/meta/external-ids} branch.
+ *
+ * <p>This revision can be used to load the external IDs of the loaded account lazily via {@link
+ * ExternalIds#byAccount(com.google.gerrit.reviewdb.client.Account.Id, ObjectId)}.
+ *
+ * @return revision of the {@code refs/meta/external-ids} branch, {@link Optional#empty()} if no
+ * {@code refs/meta/external-ids} branch exists
+ */
+ public Optional<ObjectId> getExternalIdsRev() {
+ checkLoaded();
+ return externalIdsRev;
+ }
+
+ /**
+ * Get the project watches of the loaded account.
+ *
+ * @return the project watches of the loaded account
+ */
+ public Map<ProjectWatchKey, Set<NotifyType>> getProjectWatches() {
+ checkLoaded();
+ return watchConfig.getProjectWatches();
+ }
+
+ /**
+ * Get the general preferences of the loaded account.
+ *
+ * @return the general preferences of the loaded account
+ */
+ public GeneralPreferencesInfo getGeneralPreferences() {
+ checkLoaded();
+ return prefConfig.getGeneralPreferences();
+ }
+
+ /**
* Sets the account. This means the loaded account will be overwritten with the given account.
*
* <p>Changing the registration date of an account is not supported.
@@ -115,7 +180,7 @@
* @param account account that should be set
* @throws IllegalStateException if the account was not loaded yet
*/
- public void setAccount(Account account) {
+ public AccountConfig setAccount(Account account) {
checkLoaded();
this.loadedAccount = Optional.of(account);
this.accountUpdate =
@@ -127,6 +192,7 @@
.setStatus(account.getStatus())
.build());
this.registeredOn = account.getRegisteredOn();
+ return this;
}
/**
@@ -155,8 +221,9 @@
return loadedAccount.get();
}
- public void setAccountUpdate(InternalAccountUpdate accountUpdate) {
+ public AccountConfig setAccountUpdate(InternalAccountUpdate accountUpdate) {
this.accountUpdate = Optional.of(accountUpdate);
+ return this;
}
@Override
@@ -167,9 +234,26 @@
rw.sort(RevSort.REVERSE);
registeredOn = new Timestamp(rw.next().getCommitTime() * 1000L);
- Config cfg = readConfig(ACCOUNT_CONFIG);
+ Config accountConfig = readConfig(ACCOUNT_CONFIG);
+ loadedAccount = Optional.of(parse(accountConfig, revision.name()));
- loadedAccount = Optional.of(parse(cfg, revision.name()));
+ Ref externalIdsRef = repo.exactRef(RefNames.REFS_EXTERNAL_IDS);
+ externalIdsRev =
+ externalIdsRef != null ? Optional.of(externalIdsRef.getObjectId()) : Optional.empty();
+
+ watchConfig = new WatchConfig(accountId, readConfig(WatchConfig.WATCH_CONFIG), this);
+
+ prefConfig =
+ new PreferencesConfig(
+ accountId,
+ readConfig(PreferencesConfig.PREFERENCES_CONFIG),
+ PreferencesConfig.readDefaultConfig(repo),
+ this);
+
+ if (eagerParsing) {
+ watchConfig.parse();
+ prefConfig.parse();
+ }
} else {
loadedAccount = Optional.empty();
}
@@ -182,11 +266,6 @@
String preferredEmail = get(cfg, KEY_PREFERRED_EMAIL);
account.setPreferredEmail(preferredEmail);
- if (emailValidator != null && !emailValidator.isValid(preferredEmail)) {
- error(
- new ValidationError(
- ACCOUNT_CONFIG, String.format("Invalid preferred email: %s", preferredEmail)));
- }
account.setStatus(get(cfg, KEY_STATUS));
account.setMetaId(metaId);
@@ -221,21 +300,28 @@
commit.setCommitter(new PersonIdent(commit.getCommitter(), registeredOn));
}
- Config cfg = readConfig(ACCOUNT_CONFIG);
- if (accountUpdate.isPresent()) {
- writeToConfig(accountUpdate.get(), cfg);
- }
- saveConfig(ACCOUNT_CONFIG, cfg);
+ Config accountConfig = saveAccount();
+ saveProjectWatches();
+ saveGeneralPreferences();
// metaId is set in the commit(MetaDataUpdate) method after the commit is created
- loadedAccount = Optional.of(parse(cfg, null));
+ loadedAccount = Optional.of(parse(accountConfig, null));
accountUpdate = Optional.empty();
return true;
}
- public static void writeToConfig(InternalAccountUpdate accountUpdate, Config cfg) {
+ private Config saveAccount() throws IOException, ConfigInvalidException {
+ Config accountConfig = readConfig(ACCOUNT_CONFIG);
+ if (accountUpdate.isPresent()) {
+ writeToAccountConfig(accountUpdate.get(), accountConfig);
+ }
+ saveConfig(ACCOUNT_CONFIG, accountConfig);
+ return accountConfig;
+ }
+
+ public static void writeToAccountConfig(InternalAccountUpdate accountUpdate, Config cfg) {
accountUpdate.getActive().ifPresent(active -> setActive(cfg, active));
accountUpdate.getFullName().ifPresent(fullName -> set(cfg, KEY_FULL_NAME, fullName));
accountUpdate
@@ -244,6 +330,28 @@
accountUpdate.getStatus().ifPresent(status -> set(cfg, KEY_STATUS, status));
}
+ private void saveProjectWatches() throws IOException {
+ if (accountUpdate.isPresent()
+ && (!accountUpdate.get().getDeletedProjectWatches().isEmpty()
+ || !accountUpdate.get().getUpdatedProjectWatches().isEmpty())) {
+ Map<ProjectWatchKey, Set<NotifyType>> projectWatches = watchConfig.getProjectWatches();
+ accountUpdate.get().getDeletedProjectWatches().forEach(pw -> projectWatches.remove(pw));
+ accountUpdate
+ .get()
+ .getUpdatedProjectWatches()
+ .forEach((pw, nt) -> projectWatches.put(pw, nt));
+ saveConfig(WatchConfig.WATCH_CONFIG, watchConfig.save(projectWatches));
+ }
+ }
+
+ private void saveGeneralPreferences() throws IOException, ConfigInvalidException {
+ if (accountUpdate.isPresent() && accountUpdate.get().getGeneralPreferences().isPresent()) {
+ saveConfig(
+ PreferencesConfig.PREFERENCES_CONFIG,
+ prefConfig.saveGeneralPreferences(accountUpdate.get().getGeneralPreferences().get()));
+ }
+ }
+
/**
* Sets/Unsets {@code account.active} in the given config.
*
@@ -298,7 +406,10 @@
}
/**
- * Get the validation errors, if any were discovered during load.
+ * Get the validation errors, if any were discovered during parsing the account data.
+ *
+ * <p>To get validation errors for all account data request eager parsing before loading the
+ * account (see {@link #setEagerParsing(boolean)}).
*
* @return list of errors; empty list if there are no errors.
*/
diff --git a/java/com/google/gerrit/server/account/AccountManager.java b/java/com/google/gerrit/server/account/AccountManager.java
index 3f87e5f..aac515d 100644
--- a/java/com/google/gerrit/server/account/AccountManager.java
+++ b/java/com/google/gerrit/server/account/AccountManager.java
@@ -14,6 +14,9 @@
package com.google.gerrit.server.account;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_USERNAME;
+
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@@ -21,7 +24,6 @@
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.data.Permission;
-import com.google.gerrit.common.errors.NameAlreadyUsedException;
import com.google.gerrit.common.errors.NoSuchGroupException;
import com.google.gerrit.extensions.client.AccountFieldName;
import com.google.gerrit.reviewdb.client.Account;
@@ -33,12 +35,12 @@
import com.google.gerrit.server.account.externalids.DuplicateExternalIdKeyException;
import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.server.account.externalids.ExternalIds;
-import com.google.gerrit.server.account.externalids.ExternalIdsUpdate;
import com.google.gerrit.server.auth.NoSuchUserException;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.group.db.GroupsUpdate;
import com.google.gerrit.server.group.db.InternalGroupUpdate;
import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.ssh.SshKeyCache;
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.SchemaFactory;
import com.google.inject.Inject;
@@ -67,11 +69,10 @@
private final AccountCache byIdCache;
private final Realm realm;
private final IdentifiedUser.GenericFactory userFactory;
- private final ChangeUserName.Factory changeUserNameFactory;
+ private final SshKeyCache sshKeyCache;
private final ProjectCache projectCache;
private final AtomicBoolean awaitsFirstAccountCheck;
private final ExternalIds externalIds;
- private final ExternalIdsUpdate.Server externalIdsUpdateFactory;
private final GroupsUpdate.Factory groupsUpdateFactory;
private final boolean autoUpdateAccountActiveStatus;
private final SetInactiveFlag setInactiveFlag;
@@ -86,10 +87,9 @@
AccountCache byIdCache,
Realm accountMapper,
IdentifiedUser.GenericFactory userFactory,
- ChangeUserName.Factory changeUserNameFactory,
+ SshKeyCache sshKeyCache,
ProjectCache projectCache,
ExternalIds externalIds,
- ExternalIdsUpdate.Server externalIdsUpdateFactory,
GroupsUpdate.Factory groupsUpdateFactory,
SetInactiveFlag setInactiveFlag) {
this.schema = schema;
@@ -99,12 +99,11 @@
this.byIdCache = byIdCache;
this.realm = accountMapper;
this.userFactory = userFactory;
- this.changeUserNameFactory = changeUserNameFactory;
+ this.sshKeyCache = sshKeyCache;
this.projectCache = projectCache;
this.awaitsFirstAccountCheck =
new AtomicBoolean(cfg.getBoolean("capability", "makeFirstUserAdmin", true));
this.externalIds = externalIds;
- this.externalIdsUpdateFactory = externalIdsUpdateFactory;
this.groupsUpdateFactory = groupsUpdateFactory;
this.autoUpdateAccountActiveStatus =
cfg.getBoolean("auth", "autoUpdateAccountActiveStatus", false);
@@ -262,6 +261,8 @@
ExternalId extId =
ExternalId.createWithEmail(who.getExternalIdKey(), newId, who.getEmailAddress());
+ ExternalId userNameExtId =
+ !Strings.isNullOrEmpty(who.getUserName()) ? createUsername(newId, who.getUserName()) : null;
boolean isFirstAccount = awaitsFirstAccountCheck.getAndSet(false) && !accounts.hasAnyAccount();
@@ -273,10 +274,14 @@
.insert(
"Create Account on First Login",
newId,
- u ->
- u.setFullName(who.getDisplayName())
- .setPreferredEmail(extId.email())
- .addExternalId(extId));
+ u -> {
+ u.setFullName(who.getDisplayName())
+ .setPreferredEmail(extId.email())
+ .addExternalId(extId);
+ if (userNameExtId != null) {
+ u.addExternalId(userNameExtId);
+ }
+ });
} catch (DuplicateExternalIdKeyException e) {
throw new AccountException(
"Cannot assign external ID \""
@@ -291,6 +296,10 @@
awaitsFirstAccountCheck.set(isFirstAccount);
}
+ if (userNameExtId != null) {
+ sshKeyCache.evict(who.getUserName());
+ }
+
IdentifiedUser user = userFactory.create(newId);
if (isFirstAccount) {
@@ -309,37 +318,23 @@
addGroupMember(db, adminGroupUuid, user);
}
- if (who.getUserName() != null) {
- // Only set if the name hasn't been used yet, but was given to us.
- //
- try {
- changeUserNameFactory.create("Set Username on Login", user, who.getUserName()).call();
- } catch (NameAlreadyUsedException e) {
- String message =
- "Cannot assign user name \""
- + who.getUserName()
- + "\" to account "
- + newId
- + "; name already in use.";
- handleSettingUserNameFailure(account, extId, message, e, false);
- } catch (InvalidUserNameException e) {
- String message =
- "Cannot assign user name \""
- + who.getUserName()
- + "\" to account "
- + newId
- + "; name does not conform.";
- handleSettingUserNameFailure(account, extId, message, e, false);
- } catch (OrmException e) {
- String message = "Cannot assign user name";
- handleSettingUserNameFailure(account, extId, message, e, true);
- }
- }
-
realm.onCreateAccount(who, account);
return new AuthResult(newId, extId.key(), true);
}
+ private ExternalId createUsername(Account.Id accountId, String username)
+ throws AccountUserNameException {
+ checkArgument(!Strings.isNullOrEmpty(username));
+
+ if (!ExternalId.isValidUsername(username)) {
+ throw new AccountUserNameException(
+ String.format(
+ "Cannot assign user name \"%s\" to account %s; name does not conform.",
+ username, accountId));
+ }
+ return ExternalId.create(SCHEME_USERNAME, username, accountId);
+ }
+
private void addGroupMember(ReviewDb db, AccountGroup.UUID groupUuid, IdentifiedUser user)
throws OrmException, IOException, ConfigInvalidException, AccountException {
// The user initiated this request by logging in. -> Attribute all modifications to that user.
@@ -357,43 +352,6 @@
}
/**
- * This method handles an exception that occurred during the setting of the user name for a newly
- * created account. If the realm does not allow the user to set a user name manually this method
- * deletes the newly created account and throws an {@link AccountUserNameException}. In any case
- * the error message is logged.
- *
- * @param account the newly created account
- * @param extId the newly created external id
- * @param errorMessage the error message
- * @param e the exception that occurred during the setting of the user name for the new account
- * @param logException flag that decides whether the exception should be included into the log
- * @throws AccountUserNameException thrown if the realm does not allow the user to manually set
- * the user name
- * @throws OrmException thrown if cleaning the database failed
- */
- private void handleSettingUserNameFailure(
- Account account, ExternalId extId, String errorMessage, Exception e, boolean logException)
- throws AccountUserNameException, OrmException, IOException, ConfigInvalidException {
- if (logException) {
- log.error(errorMessage, e);
- } else {
- log.error(errorMessage);
- }
- if (!realm.allowsEdit(AccountFieldName.USER_NAME)) {
- // setting the given user name has failed, but the realm does not
- // allow the user to manually set a user name,
- // this means we would end with an account without user name
- // (without 'username:<USERNAME>' external ID),
- // such an account cannot be used for uploading changes,
- // this is why the best we can do here is to fail early and cleanup
- // the database
- accountsUpdateFactory.create().delete(account);
- externalIdsUpdateFactory.create().delete(extId);
- throw new AccountUserNameException(errorMessage, e);
- }
- }
-
- /**
* Link another authentication identity to an existing account.
*
* @param to account to link the identity onto.
@@ -453,7 +411,12 @@
.filter(e -> e.key().equals(who.getExternalIdKey()))
.findAny()
.isPresent())) {
- externalIdsUpdateFactory.create().delete(filteredExtIdsByScheme);
+ accountsUpdateFactory
+ .create()
+ .update(
+ "Delete External IDs on Update Link",
+ to,
+ u -> u.deleteExternalIds(filteredExtIdsByScheme));
}
return link(to, who);
}
@@ -498,27 +461,17 @@
}
}
- externalIdsUpdateFactory.create().delete(extIds);
-
- if (extIds.stream().anyMatch(e -> e.email() != null)) {
- accountsUpdateFactory
- .create()
- .update(
- "Clear Preferred Email on Unlinking External ID\n"
- + "\n"
- + "The preferred email is cleared because the corresponding external ID\n"
- + "was removed.",
- from,
- (a, u) -> {
- if (a.getPreferredEmail() != null) {
- for (ExternalId extId : extIds) {
- if (a.getPreferredEmail().equals(extId.email())) {
- u.setPreferredEmail(null);
- break;
- }
- }
- }
- });
- }
+ accountsUpdateFactory
+ .create()
+ .update(
+ "Unlink External ID" + (extIds.size() > 1 ? "s" : ""),
+ from,
+ (a, u) -> {
+ u.deleteExternalIds(extIds);
+ if (a.getPreferredEmail() != null
+ && extIds.stream().anyMatch(e -> a.getPreferredEmail().equals(e.email()))) {
+ u.setPreferredEmail(null);
+ }
+ });
}
}
diff --git a/java/com/google/gerrit/server/account/AccountState.java b/java/com/google/gerrit/server/account/AccountState.java
index 01d31c9..6601df7 100644
--- a/java/com/google/gerrit/server/account/AccountState.java
+++ b/java/com/google/gerrit/server/account/AccountState.java
@@ -22,6 +22,7 @@
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.CurrentUser.PropertyKey;
import com.google.gerrit.server.IdentifiedUser;
@@ -51,17 +52,20 @@
private final Account account;
private final Collection<ExternalId> externalIds;
private final Map<ProjectWatchKey, Set<NotifyType>> projectWatches;
+ private final GeneralPreferencesInfo generalPreferences;
private Cache<IdentifiedUser.PropertyKey<Object>, Object> properties;
public AccountState(
AllUsersName allUsersName,
Account account,
Collection<ExternalId> externalIds,
- Map<ProjectWatchKey, Set<NotifyType>> projectWatches) {
+ Map<ProjectWatchKey, Set<NotifyType>> projectWatches,
+ GeneralPreferencesInfo generalPreferences) {
this.allUsersName = allUsersName;
this.account = account;
this.externalIds = externalIds;
this.projectWatches = projectWatches;
+ this.generalPreferences = generalPreferences;
this.account.setUserName(getUserName(externalIds));
}
@@ -117,6 +121,11 @@
return projectWatches;
}
+ /** The general preferences of the account. */
+ public GeneralPreferencesInfo getGeneralPreferences() {
+ return generalPreferences;
+ }
+
public static String getUserName(Collection<ExternalId> ids) {
for (ExternalId extId : ids) {
if (extId.isScheme(SCHEME_USERNAME)) {
diff --git a/java/com/google/gerrit/server/account/AccountUserNameException.java b/java/com/google/gerrit/server/account/AccountUserNameException.java
index f1a2555..a1f1df2 100644
--- a/java/com/google/gerrit/server/account/AccountUserNameException.java
+++ b/java/com/google/gerrit/server/account/AccountUserNameException.java
@@ -21,6 +21,10 @@
public class AccountUserNameException extends AccountException {
private static final long serialVersionUID = 1L;
+ public AccountUserNameException(String message) {
+ super(message);
+ }
+
public AccountUserNameException(String message, Throwable why) {
super(message, why);
}
diff --git a/java/com/google/gerrit/server/account/Accounts.java b/java/com/google/gerrit/server/account/Accounts.java
index 7b04610..45831ae 100644
--- a/java/com/google/gerrit/server/account/Accounts.java
+++ b/java/com/google/gerrit/server/account/Accounts.java
@@ -18,12 +18,13 @@
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toSet;
+import com.google.common.collect.ImmutableSet;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.account.externalids.ExternalIds;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.mail.send.OutgoingEmailValidator;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.IOException;
@@ -46,28 +47,25 @@
private final GitRepositoryManager repoManager;
private final AllUsersName allUsersName;
- private final OutgoingEmailValidator emailValidator;
+ private final ExternalIds externalIds;
@Inject
- Accounts(
- GitRepositoryManager repoManager,
- AllUsersName allUsersName,
- OutgoingEmailValidator emailValidator) {
+ Accounts(GitRepositoryManager repoManager, AllUsersName allUsersName, ExternalIds externalIds) {
this.repoManager = repoManager;
this.allUsersName = allUsersName;
- this.emailValidator = emailValidator;
+ this.externalIds = externalIds;
}
@Nullable
- public Account get(Account.Id accountId) throws IOException, ConfigInvalidException {
+ public AccountState get(Account.Id accountId) throws IOException, ConfigInvalidException {
try (Repository repo = repoManager.openRepository(allUsersName)) {
return read(repo, accountId).orElse(null);
}
}
- public List<Account> get(Collection<Account.Id> accountIds)
+ public List<AccountState> get(Collection<Account.Id> accountIds)
throws IOException, ConfigInvalidException {
- List<Account> accounts = new ArrayList<>(accountIds.size());
+ List<AccountState> accounts = new ArrayList<>(accountIds.size());
try (Repository repo = repoManager.openRepository(allUsersName)) {
for (Account.Id accountId : accountIds) {
read(repo, accountId).ifPresent(accounts::add);
@@ -81,9 +79,9 @@
*
* @return all accounts
*/
- public List<Account> all() throws IOException {
+ public List<AccountState> all() throws IOException {
Set<Account.Id> accountIds = allIds();
- List<Account> accounts = new ArrayList<>(accountIds.size());
+ List<AccountState> accounts = new ArrayList<>(accountIds.size());
try (Repository repo = repoManager.openRepository(allUsersName)) {
for (Account.Id accountId : accountIds) {
try {
@@ -136,11 +134,22 @@
}
}
- private Optional<Account> read(Repository allUsersRepository, Account.Id accountId)
+ private Optional<AccountState> read(Repository allUsersRepository, Account.Id accountId)
throws IOException, ConfigInvalidException {
- AccountConfig accountConfig = new AccountConfig(emailValidator, accountId);
- accountConfig.load(allUsersRepository);
- return accountConfig.getLoadedAccount();
+ AccountConfig accountConfig = new AccountConfig(accountId, allUsersRepository).load();
+ if (!accountConfig.getLoadedAccount().isPresent()) {
+ return Optional.empty();
+ }
+ Account account = accountConfig.getLoadedAccount().get();
+ return Optional.of(
+ new AccountState(
+ allUsersName,
+ account,
+ accountConfig.getExternalIdsRev().isPresent()
+ ? externalIds.byAccount(accountId, accountConfig.getExternalIdsRev().get())
+ : ImmutableSet.of(),
+ accountConfig.getProjectWatches(),
+ accountConfig.getGeneralPreferences()));
}
public static Stream<Account.Id> readUserRefs(Repository repo) throws IOException {
diff --git a/java/com/google/gerrit/server/account/AccountsConsistencyChecker.java b/java/com/google/gerrit/server/account/AccountsConsistencyChecker.java
index 0085303..0b63927 100644
--- a/java/com/google/gerrit/server/account/AccountsConsistencyChecker.java
+++ b/java/com/google/gerrit/server/account/AccountsConsistencyChecker.java
@@ -16,7 +16,6 @@
import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo.ConsistencyProblemInfo;
import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.server.account.externalids.ExternalIds;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.IOException;
@@ -26,21 +25,20 @@
@Singleton
public class AccountsConsistencyChecker {
private final Accounts accounts;
- private final ExternalIds externalIds;
@Inject
- AccountsConsistencyChecker(Accounts accounts, ExternalIds externalIds) {
+ AccountsConsistencyChecker(Accounts accounts) {
this.accounts = accounts;
- this.externalIds = externalIds;
}
public List<ConsistencyProblemInfo> check() throws IOException {
List<ConsistencyProblemInfo> problems = new ArrayList<>();
- for (Account account : accounts.all()) {
+ for (AccountState accountState : accounts.all()) {
+ Account account = accountState.getAccount();
if (account.getPreferredEmail() != null) {
- if (!externalIds
- .byAccount(account.getId())
+ if (!accountState
+ .getExternalIds()
.stream()
.anyMatch(e -> account.getPreferredEmail().equals(e.email()))) {
addError(
diff --git a/java/com/google/gerrit/server/account/AccountsUpdate.java b/java/com/google/gerrit/server/account/AccountsUpdate.java
index ee3cf87f..fb8067c 100644
--- a/java/com/google/gerrit/server/account/AccountsUpdate.java
+++ b/java/com/google/gerrit/server/account/AccountsUpdate.java
@@ -24,8 +24,6 @@
import com.google.common.util.concurrent.Runnables;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.externalids.ExternalIdNotes;
@@ -35,7 +33,6 @@
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.index.change.ReindexAfterRefUpdate;
-import com.google.gerrit.server.mail.send.OutgoingEmailValidator;
import com.google.gerrit.server.update.RefUpdateUtil;
import com.google.gerrit.server.update.RetryHelper;
import com.google.gerrit.server.update.RetryHelper.ActionType;
@@ -51,11 +48,7 @@
import java.util.function.Consumer;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.BatchRefUpdate;
-import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.RefUpdate;
-import org.eclipse.jgit.lib.RefUpdate.Result;
import org.eclipse.jgit.lib.Repository;
/**
@@ -82,7 +75,7 @@
* <p>Allows to read the current state of an account and to prepare updates to it.
*/
@FunctionalInterface
- public static interface AccountUpdater {
+ public interface AccountUpdater {
/**
* Prepare updates to an account.
*
@@ -94,12 +87,11 @@
*/
void update(Account account, InternalAccountUpdate.Builder update);
- public static AccountUpdater join(List<AccountUpdater> updaters) {
+ static AccountUpdater join(List<AccountUpdater> updaters) {
return (a, u) -> updaters.stream().forEach(updater -> updater.update(a, u));
}
- public static AccountUpdater joinConsumers(
- List<Consumer<InternalAccountUpdate.Builder>> consumers) {
+ static AccountUpdater joinConsumers(List<Consumer<InternalAccountUpdate.Builder>> consumers) {
return join(Lists.transform(consumers, AccountUpdater::fromConsumer));
}
@@ -119,7 +111,6 @@
private final GitRepositoryManager repoManager;
private final GitReferenceUpdated gitRefUpdated;
private final AllUsersName allUsersName;
- private final OutgoingEmailValidator emailValidator;
private final Provider<PersonIdent> serverIdentProvider;
private final Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory;
private final RetryHelper retryHelper;
@@ -130,7 +121,6 @@
GitRepositoryManager repoManager,
GitReferenceUpdated gitRefUpdated,
AllUsersName allUsersName,
- OutgoingEmailValidator emailValidator,
@GerritPersonIdent Provider<PersonIdent> serverIdentProvider,
Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory,
RetryHelper retryHelper,
@@ -138,7 +128,6 @@
this.repoManager = repoManager;
this.gitRefUpdated = gitRefUpdated;
this.allUsersName = allUsersName;
- this.emailValidator = emailValidator;
this.serverIdentProvider = serverIdentProvider;
this.metaDataUpdateInternalFactory = metaDataUpdateInternalFactory;
this.retryHelper = retryHelper;
@@ -152,7 +141,6 @@
gitRefUpdated,
null,
allUsersName,
- emailValidator,
metaDataUpdateInternalFactory,
retryHelper,
extIdNotesFactory,
@@ -175,7 +163,6 @@
private final GitRepositoryManager repoManager;
private final GitReferenceUpdated gitRefUpdated;
private final AllUsersName allUsersName;
- private final OutgoingEmailValidator emailValidator;
private final Provider<PersonIdent> serverIdentProvider;
private final Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory;
private final RetryHelper retryHelper;
@@ -186,7 +173,6 @@
GitRepositoryManager repoManager,
GitReferenceUpdated gitRefUpdated,
AllUsersName allUsersName,
- OutgoingEmailValidator emailValidator,
@GerritPersonIdent Provider<PersonIdent> serverIdentProvider,
Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory,
RetryHelper retryHelper,
@@ -194,7 +180,6 @@
this.repoManager = repoManager;
this.gitRefUpdated = gitRefUpdated;
this.allUsersName = allUsersName;
- this.emailValidator = emailValidator;
this.serverIdentProvider = serverIdentProvider;
this.metaDataUpdateInternalFactory = metaDataUpdateInternalFactory;
this.retryHelper = retryHelper;
@@ -208,7 +193,6 @@
gitRefUpdated,
null,
allUsersName,
- emailValidator,
metaDataUpdateInternalFactory,
retryHelper,
extIdNotesFactory,
@@ -228,7 +212,6 @@
private final GitRepositoryManager repoManager;
private final GitReferenceUpdated gitRefUpdated;
private final AllUsersName allUsersName;
- private final OutgoingEmailValidator emailValidator;
private final Provider<PersonIdent> serverIdentProvider;
private final Provider<IdentifiedUser> identifiedUser;
private final Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory;
@@ -240,7 +223,6 @@
GitRepositoryManager repoManager,
GitReferenceUpdated gitRefUpdated,
AllUsersName allUsersName,
- OutgoingEmailValidator emailValidator,
@GerritPersonIdent Provider<PersonIdent> serverIdentProvider,
Provider<IdentifiedUser> identifiedUser,
Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory,
@@ -250,7 +232,6 @@
this.gitRefUpdated = gitRefUpdated;
this.allUsersName = allUsersName;
this.serverIdentProvider = serverIdentProvider;
- this.emailValidator = emailValidator;
this.identifiedUser = identifiedUser;
this.metaDataUpdateInternalFactory = metaDataUpdateInternalFactory;
this.retryHelper = retryHelper;
@@ -266,7 +247,6 @@
gitRefUpdated,
user,
allUsersName,
- emailValidator,
metaDataUpdateInternalFactory,
retryHelper,
extIdNotesFactory,
@@ -283,7 +263,6 @@
private final GitReferenceUpdated gitRefUpdated;
@Nullable private final IdentifiedUser currentUser;
private final AllUsersName allUsersName;
- private final OutgoingEmailValidator emailValidator;
private final Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory;
private final RetryHelper retryHelper;
private final ExternalIdNotesLoader extIdNotesLoader;
@@ -296,7 +275,6 @@
GitReferenceUpdated gitRefUpdated,
@Nullable IdentifiedUser currentUser,
AllUsersName allUsersName,
- OutgoingEmailValidator emailValidator,
Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory,
RetryHelper retryHelper,
ExternalIdNotesLoader extIdNotesLoader,
@@ -307,7 +285,6 @@
gitRefUpdated,
currentUser,
allUsersName,
- emailValidator,
metaDataUpdateInternalFactory,
retryHelper,
extIdNotesLoader,
@@ -322,7 +299,6 @@
GitReferenceUpdated gitRefUpdated,
@Nullable IdentifiedUser currentUser,
AllUsersName allUsersName,
- OutgoingEmailValidator emailValidator,
Provider<MetaDataUpdate.InternalFactory> metaDataUpdateInternalFactory,
RetryHelper retryHelper,
ExternalIdNotesLoader extIdNotesLoader,
@@ -333,7 +309,6 @@
this.gitRefUpdated = checkNotNull(gitRefUpdated, "gitRefUpdated");
this.currentUser = currentUser;
this.allUsersName = checkNotNull(allUsersName, "allUsersName");
- this.emailValidator = checkNotNull(emailValidator, "emailValidator");
this.metaDataUpdateInternalFactory =
checkNotNull(metaDataUpdateInternalFactory, "metaDataUpdateInternalFactory");
this.retryHelper = checkNotNull(retryHelper, "retryHelper");
@@ -446,81 +421,10 @@
});
}
- /**
- * Deletes the account.
- *
- * @param account the account that should be deleted
- * @throws IOException if deleting the user branch fails due to an IO error
- * @throws OrmException if deleting the user branch fails
- * @throws ConfigInvalidException
- */
- public void delete(Account account) throws IOException, OrmException, ConfigInvalidException {
- deleteByKey(account.getId());
- }
-
- /**
- * Deletes the account.
- *
- * @param accountId the ID of the account that should be deleted
- * @throws IOException if deleting the user branch fails due to an IO error
- * @throws OrmException if deleting the user branch fails
- * @throws ConfigInvalidException
- */
- public void deleteByKey(Account.Id accountId)
- throws IOException, OrmException, ConfigInvalidException {
- deleteAccount(accountId);
- }
-
- private Account deleteAccount(Account.Id accountId)
- throws IOException, OrmException, ConfigInvalidException {
- return retryHelper.execute(
- ActionType.ACCOUNT_UPDATE,
- () -> {
- deleteUserBranch(accountId);
- return null;
- });
- }
-
- private void deleteUserBranch(Account.Id accountId) throws IOException {
- try (Repository repo = repoManager.openRepository(allUsersName)) {
- deleteUserBranch(repo, allUsersName, gitRefUpdated, currentUser, authorIdent, accountId);
- }
- }
-
- public static void deleteUserBranch(
- Repository repo,
- Project.NameKey project,
- GitReferenceUpdated gitRefUpdated,
- @Nullable IdentifiedUser user,
- PersonIdent refLogIdent,
- Account.Id accountId)
- throws IOException {
- String refName = RefNames.refsUsers(accountId);
- Ref ref = repo.exactRef(refName);
- if (ref == null) {
- return;
- }
-
- RefUpdate ru = repo.updateRef(refName);
- ru.setExpectedOldObjectId(ref.getObjectId());
- ru.setNewObjectId(ObjectId.zeroId());
- ru.setForceUpdate(true);
- ru.setRefLogIdent(refLogIdent);
- ru.setRefLogMessage("Delete Account", true);
- Result result = ru.delete();
- if (result != Result.FORCED) {
- throw new IOException(String.format("Failed to delete ref %s: %s", refName, result.name()));
- }
- gitRefUpdated.fire(project, ru, user != null ? user.getAccount() : null);
- }
-
private AccountConfig read(Repository allUsersRepo, Account.Id accountId)
throws IOException, ConfigInvalidException {
- AccountConfig accountConfig = new AccountConfig(emailValidator, accountId);
- accountConfig.load(allUsersRepo);
-
+ AccountConfig accountConfig = new AccountConfig(accountId, allUsersRepo).load();
afterReadRevision.run();
-
return accountConfig;
}
diff --git a/java/com/google/gerrit/server/account/ChangeUserName.java b/java/com/google/gerrit/server/account/ChangeUserName.java
deleted file mode 100644
index b332b75..0000000
--- a/java/com/google/gerrit/server/account/ChangeUserName.java
+++ /dev/null
@@ -1,112 +0,0 @@
-// Copyright (C) 2009 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.account;
-
-import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_USERNAME;
-
-import com.google.gerrit.common.Nullable;
-import com.google.gerrit.common.errors.NameAlreadyUsedException;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.account.externalids.ExternalId;
-import com.google.gerrit.server.account.externalids.ExternalIds;
-import com.google.gerrit.server.ssh.SshKeyCache;
-import com.google.gwtjsonrpc.common.VoidResult;
-import com.google.gwtorm.server.OrmDuplicateKeyException;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-import java.io.IOException;
-import java.util.concurrent.Callable;
-import java.util.regex.Pattern;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-
-/** Operation to change the username of an account. */
-public class ChangeUserName implements Callable<VoidResult> {
- public static final String USERNAME_CANNOT_BE_CHANGED = "Username cannot be changed.";
-
- private static final Pattern USER_NAME_PATTERN = Pattern.compile(Account.USER_NAME_PATTERN);
-
- /** Generic factory to change any user's username. */
- public interface Factory {
- ChangeUserName create(
- @Assisted("message") String message,
- IdentifiedUser user,
- @Assisted("newUsername") String newUsername);
- }
-
- private final SshKeyCache sshKeyCache;
- private final ExternalIds externalIds;
- private final AccountsUpdate.Server accountsUpdate;
-
- private final String message;
- private final IdentifiedUser user;
- private final String newUsername;
-
- @Inject
- ChangeUserName(
- SshKeyCache sshKeyCache,
- ExternalIds externalIds,
- AccountsUpdate.Server accountsUpdate,
- @Assisted("message") String message,
- @Assisted IdentifiedUser user,
- @Nullable @Assisted("newUsername") String newUsername) {
- this.sshKeyCache = sshKeyCache;
- this.externalIds = externalIds;
- this.accountsUpdate = accountsUpdate;
- this.message = message;
- this.user = user;
- this.newUsername = newUsername;
- }
-
- @Override
- public VoidResult call()
- throws OrmException, NameAlreadyUsedException, InvalidUserNameException, IOException,
- ConfigInvalidException {
- if (!externalIds.byAccount(user.getAccountId(), SCHEME_USERNAME).isEmpty()) {
- throw new IllegalStateException(USERNAME_CANNOT_BE_CHANGED);
- }
-
- if (newUsername != null && !newUsername.isEmpty()) {
- if (!USER_NAME_PATTERN.matcher(newUsername).matches()) {
- throw new InvalidUserNameException();
- }
-
- ExternalId.Key key = ExternalId.Key.create(SCHEME_USERNAME, newUsername);
- try {
- accountsUpdate
- .create()
- .update(
- message,
- user.getAccountId(),
- u -> u.addExternalId(ExternalId.create(key, user.getAccountId(), null, null)));
- } catch (OrmDuplicateKeyException dupeErr) {
- // If we are using this identity, don't report the exception.
- //
- ExternalId other = externalIds.get(key);
- if (other != null && other.accountId().equals(user.getAccountId())) {
- return VoidResult.INSTANCE;
- }
-
- // Otherwise, someone else has this identity.
- //
- throw new NameAlreadyUsedException(newUsername);
- }
- }
-
- sshKeyCache.evict(newUsername);
- return VoidResult.INSTANCE;
- }
-}
diff --git a/java/com/google/gerrit/server/account/GeneralPreferencesLoader.java b/java/com/google/gerrit/server/account/GeneralPreferencesLoader.java
deleted file mode 100644
index 8043773..0000000
--- a/java/com/google/gerrit/server/account/GeneralPreferencesLoader.java
+++ /dev/null
@@ -1,198 +0,0 @@
-// Copyright (C) 2015 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.server.account;
-
-import static com.google.gerrit.server.config.ConfigUtil.loadSection;
-import static com.google.gerrit.server.config.ConfigUtil.skipField;
-import static com.google.gerrit.server.git.UserConfigSections.CHANGE_TABLE;
-import static com.google.gerrit.server.git.UserConfigSections.CHANGE_TABLE_COLUMN;
-import static com.google.gerrit.server.git.UserConfigSections.KEY_ID;
-import static com.google.gerrit.server.git.UserConfigSections.KEY_MATCH;
-import static com.google.gerrit.server.git.UserConfigSections.KEY_TARGET;
-import static com.google.gerrit.server.git.UserConfigSections.KEY_TOKEN;
-import static com.google.gerrit.server.git.UserConfigSections.KEY_URL;
-import static com.google.gerrit.server.git.UserConfigSections.URL_ALIAS;
-
-import com.google.common.base.Strings;
-import com.google.common.collect.Lists;
-import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
-import com.google.gerrit.extensions.client.MenuItem;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.UserConfigSections;
-import com.google.inject.Inject;
-import com.google.inject.Singleton;
-import java.io.IOException;
-import java.lang.reflect.Field;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.errors.RepositoryNotFoundException;
-import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.Repository;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-@Singleton
-public class GeneralPreferencesLoader {
- private static final Logger log = LoggerFactory.getLogger(GeneralPreferencesLoader.class);
-
- private final GitRepositoryManager gitMgr;
- private final AllUsersName allUsersName;
-
- @Inject
- public GeneralPreferencesLoader(GitRepositoryManager gitMgr, AllUsersName allUsersName) {
- this.gitMgr = gitMgr;
- this.allUsersName = allUsersName;
- }
-
- public GeneralPreferencesInfo load(Account.Id id)
- throws IOException, ConfigInvalidException, RepositoryNotFoundException {
- return read(id, null);
- }
-
- public GeneralPreferencesInfo merge(Account.Id id, GeneralPreferencesInfo in)
- throws IOException, ConfigInvalidException, RepositoryNotFoundException {
- return read(id, in);
- }
-
- private GeneralPreferencesInfo read(Account.Id id, GeneralPreferencesInfo in)
- throws IOException, ConfigInvalidException, RepositoryNotFoundException {
- try (Repository allUsers = gitMgr.openRepository(allUsersName)) {
- // Load all users default prefs
- VersionedAccountPreferences dp = VersionedAccountPreferences.forDefault();
- dp.load(allUsers);
-
- // Load user prefs
- VersionedAccountPreferences p = VersionedAccountPreferences.forUser(id);
- p.load(allUsers);
- GeneralPreferencesInfo r =
- loadSection(
- p.getConfig(),
- UserConfigSections.GENERAL,
- null,
- new GeneralPreferencesInfo(),
- readDefaultsFromGit(dp.getConfig(), in),
- in);
- loadChangeTableColumns(r, p, dp);
- return loadMyMenusAndUrlAliases(r, p, dp);
- }
- }
-
- public GeneralPreferencesInfo readDefaultsFromGit(Repository git, GeneralPreferencesInfo in)
- throws ConfigInvalidException, IOException {
- VersionedAccountPreferences dp = VersionedAccountPreferences.forDefault();
- dp.load(git);
- return readDefaultsFromGit(dp.getConfig(), in);
- }
-
- private GeneralPreferencesInfo readDefaultsFromGit(Config config, GeneralPreferencesInfo in)
- throws ConfigInvalidException {
- GeneralPreferencesInfo allUserPrefs = new GeneralPreferencesInfo();
- loadSection(
- config,
- UserConfigSections.GENERAL,
- null,
- allUserPrefs,
- GeneralPreferencesInfo.defaults(),
- in);
- return updateDefaults(allUserPrefs);
- }
-
- private GeneralPreferencesInfo updateDefaults(GeneralPreferencesInfo input) {
- GeneralPreferencesInfo result = GeneralPreferencesInfo.defaults();
- try {
- for (Field field : input.getClass().getDeclaredFields()) {
- if (skipField(field)) {
- continue;
- }
- Object newVal = field.get(input);
- if (newVal != null) {
- field.set(result, newVal);
- }
- }
- } catch (IllegalAccessException e) {
- log.error("Cannot get default general preferences from " + allUsersName.get(), e);
- return GeneralPreferencesInfo.defaults();
- }
- return result;
- }
-
- public GeneralPreferencesInfo loadMyMenusAndUrlAliases(
- GeneralPreferencesInfo r, VersionedAccountPreferences v, VersionedAccountPreferences d) {
- r.my = my(v);
- if (r.my.isEmpty() && !v.isDefaults()) {
- r.my = my(d);
- }
- if (r.my.isEmpty()) {
- r.my.add(new MenuItem("Changes", "#/dashboard/self", null));
- r.my.add(new MenuItem("Draft Comments", "#/q/has:draft", null));
- r.my.add(new MenuItem("Edits", "#/q/has:edit", null));
- r.my.add(new MenuItem("Watched Changes", "#/q/is:watched+is:open", null));
- r.my.add(new MenuItem("Starred Changes", "#/q/is:starred", null));
- r.my.add(new MenuItem("Groups", "#/groups/self", null));
- }
-
- r.urlAliases = urlAliases(v);
- if (r.urlAliases == null && !v.isDefaults()) {
- r.urlAliases = urlAliases(d);
- }
- return r;
- }
-
- private static List<MenuItem> my(VersionedAccountPreferences v) {
- List<MenuItem> my = new ArrayList<>();
- Config cfg = v.getConfig();
- for (String subsection : cfg.getSubsections(UserConfigSections.MY)) {
- String url = my(cfg, subsection, KEY_URL, "#/");
- String target = my(cfg, subsection, KEY_TARGET, url.startsWith("#") ? null : "_blank");
- my.add(new MenuItem(subsection, url, target, my(cfg, subsection, KEY_ID, null)));
- }
- return my;
- }
-
- private static String my(Config cfg, String subsection, String key, String defaultValue) {
- String val = cfg.getString(UserConfigSections.MY, subsection, key);
- return !Strings.isNullOrEmpty(val) ? val : defaultValue;
- }
-
- public GeneralPreferencesInfo loadChangeTableColumns(
- GeneralPreferencesInfo r, VersionedAccountPreferences v, VersionedAccountPreferences d) {
- r.changeTable = changeTable(v);
-
- if (r.changeTable.isEmpty() && !v.isDefaults()) {
- r.changeTable = changeTable(d);
- }
- return r;
- }
-
- private static List<String> changeTable(VersionedAccountPreferences v) {
- return Lists.newArrayList(v.getConfig().getStringList(CHANGE_TABLE, null, CHANGE_TABLE_COLUMN));
- }
-
- private static Map<String, String> urlAliases(VersionedAccountPreferences v) {
- HashMap<String, String> urlAliases = new HashMap<>();
- Config cfg = v.getConfig();
- for (String subsection : cfg.getSubsections(URL_ALIAS)) {
- urlAliases.put(
- cfg.getString(URL_ALIAS, subsection, KEY_MATCH),
- cfg.getString(URL_ALIAS, subsection, KEY_TOKEN));
- }
- return !urlAliases.isEmpty() ? urlAliases : null;
- }
-}
diff --git a/java/com/google/gerrit/server/account/InternalAccountUpdate.java b/java/com/google/gerrit/server/account/InternalAccountUpdate.java
index ea778ca..05c431e 100644
--- a/java/com/google/gerrit/server/account/InternalAccountUpdate.java
+++ b/java/com/google/gerrit/server/account/InternalAccountUpdate.java
@@ -16,12 +16,18 @@
import com.google.auto.value.AutoValue;
import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.server.account.WatchConfig.NotifyType;
+import com.google.gerrit.server.account.WatchConfig.ProjectWatchKey;
import com.google.gerrit.server.account.externalids.DuplicateExternalIdKeyException;
import com.google.gerrit.server.account.externalids.ExternalId;
import java.util.Collection;
+import java.util.Map;
import java.util.Optional;
+import java.util.Set;
/**
* Class to prepare updates to an account.
@@ -93,6 +99,30 @@
public abstract ImmutableSet<ExternalId> getDeletedExternalIds();
/**
+ * Returns external IDs that should be updated for the account.
+ *
+ * @return external IDs that should be updated for the account
+ */
+ public abstract ImmutableMap<ProjectWatchKey, Set<NotifyType>> getUpdatedProjectWatches();
+
+ /**
+ * Returns project watches that should be deleted for the account.
+ *
+ * @return project watches that should be deleted for the account
+ */
+ public abstract ImmutableSet<ProjectWatchKey> getDeletedProjectWatches();
+
+ /**
+ * Returns the new value for the general preferences.
+ *
+ * <p>Only preferences that are non-null in the returned GeneralPreferencesInfo should be updated.
+ *
+ * @return the new value for the general preferences, {@code Optional#empty()} if the general
+ * preferences are not being updated, the wrapped value is never {@code null}
+ */
+ public abstract Optional<GeneralPreferencesInfo> getGeneralPreferences();
+
+ /**
* Class to build an account update.
*
* <p>Account data is only updated if the corresponding setter is invoked. If a setter is not
@@ -239,7 +269,7 @@
}
/**
- * Delete external IDs for the account.
+ * Deletes external IDs for the account.
*
* <p>The account IDs of the external IDs must match the account ID of the account that is
* updated.
@@ -278,6 +308,83 @@
}
/**
+ * Returns a builder for the map of updated project watches.
+ *
+ * @return builder for the map of updated project watches.
+ */
+ abstract ImmutableMap.Builder<ProjectWatchKey, Set<NotifyType>> updatedProjectWatchesBuilder();
+
+ /**
+ * Updates a project watch for the account.
+ *
+ * <p>If no project watch with the key exists the project watch is created.
+ *
+ * @param projectWatchKey key of the project watch that should be updated
+ * @param notifyTypes the notify types that should be set for the project watch
+ * @return the builder
+ */
+ public Builder updateProjectWatch(
+ ProjectWatchKey projectWatchKey, Set<NotifyType> notifyTypes) {
+ return updateProjectWatches(ImmutableMap.of(projectWatchKey, notifyTypes));
+ }
+
+ /**
+ * Updates project watches for the account.
+ *
+ * <p>If any of the project watches already exists, it is overwritten. New project watches are
+ * inserted.
+ *
+ * @param projectWatches project watches that should be updated
+ * @return the builder
+ */
+ public Builder updateProjectWatches(Map<ProjectWatchKey, Set<NotifyType>> projectWatches) {
+ updatedProjectWatchesBuilder().putAll(projectWatches);
+ return this;
+ }
+
+ /**
+ * Returns a builder for the set of deleted project watches.
+ *
+ * @return builder for the set of deleted project watches.
+ */
+ abstract ImmutableSet.Builder<ProjectWatchKey> deletedProjectWatchesBuilder();
+
+ /**
+ * Deletes a project watch for the account.
+ *
+ * <p>If no project watch with the ID exists this is a no-op.
+ *
+ * @param projectWatch project watch that should be deleted
+ * @return the builder
+ */
+ public Builder deleteProjectWatch(ProjectWatchKey projectWatch) {
+ return deleteProjectWatches(ImmutableSet.of(projectWatch));
+ }
+
+ /**
+ * Deletes project watches for the account.
+ *
+ * <p>For non-existing project watches this is a no-op.
+ *
+ * @param projectWatches project watches that should be deleted
+ * @return the builder
+ */
+ public Builder deleteProjectWatches(Collection<ProjectWatchKey> projectWatches) {
+ deletedProjectWatchesBuilder().addAll(projectWatches);
+ return this;
+ }
+
+ /**
+ * Sets the general preferences for the account.
+ *
+ * <p>Updates any preference that is non-null in the provided GeneralPreferencesInfo.
+ *
+ * @param generalPreferences the general preferences that should be set
+ * @return the builder
+ */
+ public abstract Builder setGeneralPreferences(GeneralPreferencesInfo generalPreferences);
+
+ /**
* Builds the account update.
*
* @return the account update
@@ -385,6 +492,34 @@
delegate.deleteExternalIds(extIds);
return this;
}
+
+ @Override
+ ImmutableMap.Builder<ProjectWatchKey, Set<NotifyType>> updatedProjectWatchesBuilder() {
+ return delegate.updatedProjectWatchesBuilder();
+ }
+
+ @Override
+ public Builder updateProjectWatches(Map<ProjectWatchKey, Set<NotifyType>> projectWatches) {
+ delegate.updateProjectWatches(projectWatches);
+ return this;
+ }
+
+ @Override
+ ImmutableSet.Builder<ProjectWatchKey> deletedProjectWatchesBuilder() {
+ return delegate.deletedProjectWatchesBuilder();
+ }
+
+ @Override
+ public Builder deleteProjectWatches(Collection<ProjectWatchKey> projectWatches) {
+ delegate.deleteProjectWatches(projectWatches);
+ return this;
+ }
+
+ @Override
+ public Builder setGeneralPreferences(GeneralPreferencesInfo generalPreferences) {
+ delegate.setGeneralPreferences(generalPreferences);
+ return this;
+ }
}
}
}
diff --git a/java/com/google/gerrit/server/account/PreferencesConfig.java b/java/com/google/gerrit/server/account/PreferencesConfig.java
new file mode 100644
index 0000000..32df659
--- /dev/null
+++ b/java/com/google/gerrit/server/account/PreferencesConfig.java
@@ -0,0 +1,368 @@
+// Copyright (C) 2018 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.account;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.gerrit.server.config.ConfigUtil.loadSection;
+import static com.google.gerrit.server.config.ConfigUtil.skipField;
+import static com.google.gerrit.server.config.ConfigUtil.storeSection;
+import static com.google.gerrit.server.git.UserConfigSections.CHANGE_TABLE;
+import static com.google.gerrit.server.git.UserConfigSections.CHANGE_TABLE_COLUMN;
+import static com.google.gerrit.server.git.UserConfigSections.KEY_ID;
+import static com.google.gerrit.server.git.UserConfigSections.KEY_MATCH;
+import static com.google.gerrit.server.git.UserConfigSections.KEY_TARGET;
+import static com.google.gerrit.server.git.UserConfigSections.KEY_TOKEN;
+import static com.google.gerrit.server.git.UserConfigSections.KEY_URL;
+import static com.google.gerrit.server.git.UserConfigSections.URL_ALIAS;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
+import com.google.gerrit.extensions.client.MenuItem;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.git.MetaDataUpdate;
+import com.google.gerrit.server.git.UserConfigSections;
+import com.google.gerrit.server.git.ValidationError;
+import com.google.gerrit.server.git.VersionedMetaData;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.Repository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class PreferencesConfig {
+ private static final Logger log = LoggerFactory.getLogger(PreferencesConfig.class);
+
+ public static final String PREFERENCES_CONFIG = "preferences.config";
+
+ private final Account.Id accountId;
+ private final Config cfg;
+ private final Config defaultCfg;
+ private final ValidationError.Sink validationErrorSink;
+
+ private GeneralPreferencesInfo generalPreferences;
+
+ public PreferencesConfig(
+ Account.Id accountId,
+ Config cfg,
+ Config defaultCfg,
+ ValidationError.Sink validationErrorSink) {
+ this.accountId = checkNotNull(accountId, "accountId");
+ this.cfg = checkNotNull(cfg, "cfg");
+ this.defaultCfg = checkNotNull(defaultCfg, "defaultCfg");
+ this.validationErrorSink = checkNotNull(validationErrorSink, "validationErrorSink");
+ }
+
+ public GeneralPreferencesInfo getGeneralPreferences() {
+ if (generalPreferences == null) {
+ parse();
+ }
+ return generalPreferences;
+ }
+
+ public void parse() {
+ generalPreferences = parse(null);
+ }
+
+ public Config saveGeneralPreferences(GeneralPreferencesInfo input) throws ConfigInvalidException {
+ // merge configs
+ input = parse(input);
+
+ storeSection(
+ cfg, UserConfigSections.GENERAL, null, input, parseDefaultPreferences(defaultCfg, null));
+ setChangeTable(cfg, input.changeTable);
+ setMy(cfg, input.my);
+ setUrlAliases(cfg, input.urlAliases);
+
+ // evict the cached general preferences
+ this.generalPreferences = null;
+
+ return cfg;
+ }
+
+ private GeneralPreferencesInfo parse(@Nullable GeneralPreferencesInfo input) {
+ try {
+ return parse(cfg, defaultCfg, input);
+ } catch (ConfigInvalidException e) {
+ validationErrorSink.error(
+ new ValidationError(
+ PREFERENCES_CONFIG,
+ String.format(
+ "Invalid general preferences for account %d: %s",
+ accountId.get(), e.getMessage())));
+ return new GeneralPreferencesInfo();
+ }
+ }
+
+ private static GeneralPreferencesInfo parse(
+ Config cfg, @Nullable Config defaultCfg, @Nullable GeneralPreferencesInfo input)
+ throws ConfigInvalidException {
+ GeneralPreferencesInfo r =
+ loadSection(
+ cfg,
+ UserConfigSections.GENERAL,
+ null,
+ new GeneralPreferencesInfo(),
+ defaultCfg != null
+ ? parseDefaultPreferences(defaultCfg, input)
+ : GeneralPreferencesInfo.defaults(),
+ input);
+ if (input != null) {
+ r.changeTable = input.changeTable;
+ r.my = input.my;
+ r.urlAliases = input.urlAliases;
+ } else {
+ r.changeTable = parseChangeTableColumns(cfg, defaultCfg);
+ r.my = parseMyMenus(cfg, defaultCfg);
+ r.urlAliases = parseUrlAliases(cfg, defaultCfg);
+ }
+ return r;
+ }
+
+ private static GeneralPreferencesInfo parseDefaultPreferences(
+ Config defaultCfg, GeneralPreferencesInfo input) throws ConfigInvalidException {
+ GeneralPreferencesInfo allUserPrefs = new GeneralPreferencesInfo();
+ loadSection(
+ defaultCfg,
+ UserConfigSections.GENERAL,
+ null,
+ allUserPrefs,
+ GeneralPreferencesInfo.defaults(),
+ input);
+ return updateDefaults(allUserPrefs);
+ }
+
+ private static GeneralPreferencesInfo updateDefaults(GeneralPreferencesInfo input) {
+ GeneralPreferencesInfo result = GeneralPreferencesInfo.defaults();
+ try {
+ for (Field field : input.getClass().getDeclaredFields()) {
+ if (skipField(field)) {
+ continue;
+ }
+ Object newVal = field.get(input);
+ if (newVal != null) {
+ field.set(result, newVal);
+ }
+ }
+ } catch (IllegalAccessException e) {
+ log.error("Failed to apply default general preferences", e);
+ return GeneralPreferencesInfo.defaults();
+ }
+ return result;
+ }
+
+ private static List<String> parseChangeTableColumns(Config cfg, @Nullable Config defaultCfg) {
+ List<String> changeTable = changeTable(cfg);
+ if (changeTable == null && defaultCfg != null) {
+ changeTable = changeTable(defaultCfg);
+ }
+ return changeTable;
+ }
+
+ private static List<MenuItem> parseMyMenus(Config cfg, @Nullable Config defaultCfg) {
+ List<MenuItem> my = my(cfg);
+ if (my.isEmpty() && defaultCfg != null) {
+ my = my(defaultCfg);
+ }
+ if (my.isEmpty()) {
+ my.add(new MenuItem("Changes", "#/dashboard/self", null));
+ my.add(new MenuItem("Draft Comments", "#/q/has:draft", null));
+ my.add(new MenuItem("Edits", "#/q/has:edit", null));
+ my.add(new MenuItem("Watched Changes", "#/q/is:watched+is:open", null));
+ my.add(new MenuItem("Starred Changes", "#/q/is:starred", null));
+ my.add(new MenuItem("Groups", "#/groups/self", null));
+ }
+ return my;
+ }
+
+ private static Map<String, String> parseUrlAliases(Config cfg, @Nullable Config defaultCfg) {
+ Map<String, String> urlAliases = urlAliases(cfg);
+ if (urlAliases == null && defaultCfg != null) {
+ urlAliases = urlAliases(defaultCfg);
+ }
+ return urlAliases;
+ }
+
+ public static GeneralPreferencesInfo readDefaultPreferences(Repository allUsersRepo)
+ throws IOException, ConfigInvalidException {
+ return parse(readDefaultConfig(allUsersRepo), null, null);
+ }
+
+ static Config readDefaultConfig(Repository allUsersRepo)
+ throws IOException, ConfigInvalidException {
+ VersionedDefaultPreferences defaultPrefs = new VersionedDefaultPreferences();
+ defaultPrefs.load(allUsersRepo);
+ return defaultPrefs.getConfig();
+ }
+
+ public static GeneralPreferencesInfo updateDefaultPreferences(
+ MetaDataUpdate md, GeneralPreferencesInfo input) throws IOException, ConfigInvalidException {
+ VersionedDefaultPreferences defaultPrefs = new VersionedDefaultPreferences();
+ defaultPrefs.load(md);
+ storeSection(
+ defaultPrefs.getConfig(),
+ UserConfigSections.GENERAL,
+ null,
+ input,
+ GeneralPreferencesInfo.defaults());
+ setMy(defaultPrefs.getConfig(), input.my);
+ setChangeTable(defaultPrefs.getConfig(), input.changeTable);
+ setUrlAliases(defaultPrefs.getConfig(), input.urlAliases);
+ defaultPrefs.commit(md);
+
+ return parse(defaultPrefs.getConfig(), null, null);
+ }
+
+ private static List<String> changeTable(Config cfg) {
+ return Lists.newArrayList(cfg.getStringList(CHANGE_TABLE, null, CHANGE_TABLE_COLUMN));
+ }
+
+ private static void setChangeTable(Config cfg, List<String> changeTable) {
+ if (changeTable != null) {
+ unsetSection(cfg, UserConfigSections.CHANGE_TABLE);
+ cfg.setStringList(UserConfigSections.CHANGE_TABLE, null, CHANGE_TABLE_COLUMN, changeTable);
+ }
+ }
+
+ private static List<MenuItem> my(Config cfg) {
+ List<MenuItem> my = new ArrayList<>();
+ for (String subsection : cfg.getSubsections(UserConfigSections.MY)) {
+ String url = my(cfg, subsection, KEY_URL, "#/");
+ String target = my(cfg, subsection, KEY_TARGET, url.startsWith("#") ? null : "_blank");
+ my.add(new MenuItem(subsection, url, target, my(cfg, subsection, KEY_ID, null)));
+ }
+ return my;
+ }
+
+ private static String my(Config cfg, String subsection, String key, String defaultValue) {
+ String val = cfg.getString(UserConfigSections.MY, subsection, key);
+ return !Strings.isNullOrEmpty(val) ? val : defaultValue;
+ }
+
+ private static void setMy(Config cfg, List<MenuItem> my) {
+ if (my != null) {
+ unsetSection(cfg, UserConfigSections.MY);
+ for (MenuItem item : my) {
+ checkState(!isNullOrEmpty(item.name), "MenuItem.name must not be null or empty");
+ checkState(!isNullOrEmpty(item.url), "MenuItem.url must not be null or empty");
+
+ setMy(cfg, item.name, KEY_URL, item.url);
+ setMy(cfg, item.name, KEY_TARGET, item.target);
+ setMy(cfg, item.name, KEY_ID, item.id);
+ }
+ }
+ }
+
+ public static void validateMy(List<MenuItem> my) throws BadRequestException {
+ if (my == null) {
+ return;
+ }
+ for (MenuItem item : my) {
+ checkRequiredMenuItemField(item.name, "name");
+ checkRequiredMenuItemField(item.url, "URL");
+ }
+ }
+
+ private static void checkRequiredMenuItemField(String value, String name)
+ throws BadRequestException {
+ if (isNullOrEmpty(value)) {
+ throw new BadRequestException(name + " for menu item is required");
+ }
+ }
+
+ private static boolean isNullOrEmpty(String value) {
+ return value == null || value.trim().isEmpty();
+ }
+
+ private static void setMy(Config cfg, String section, String key, @Nullable String val) {
+ if (val == null || val.trim().isEmpty()) {
+ cfg.unset(UserConfigSections.MY, section.trim(), key);
+ } else {
+ cfg.setString(UserConfigSections.MY, section.trim(), key, val.trim());
+ }
+ }
+
+ private static Map<String, String> urlAliases(Config cfg) {
+ HashMap<String, String> urlAliases = new HashMap<>();
+ for (String subsection : cfg.getSubsections(URL_ALIAS)) {
+ urlAliases.put(
+ cfg.getString(URL_ALIAS, subsection, KEY_MATCH),
+ cfg.getString(URL_ALIAS, subsection, KEY_TOKEN));
+ }
+ return !urlAliases.isEmpty() ? urlAliases : null;
+ }
+
+ private static void setUrlAliases(Config cfg, Map<String, String> urlAliases) {
+ if (urlAliases != null) {
+ for (String subsection : cfg.getSubsections(URL_ALIAS)) {
+ cfg.unsetSection(URL_ALIAS, subsection);
+ }
+
+ int i = 1;
+ for (Entry<String, String> e : urlAliases.entrySet()) {
+ cfg.setString(URL_ALIAS, URL_ALIAS + i, KEY_MATCH, e.getKey());
+ cfg.setString(URL_ALIAS, URL_ALIAS + i, KEY_TOKEN, e.getValue());
+ i++;
+ }
+ }
+ }
+
+ private static void unsetSection(Config cfg, String section) {
+ cfg.unsetSection(section, null);
+ for (String subsection : cfg.getSubsections(section)) {
+ cfg.unsetSection(section, subsection);
+ }
+ }
+
+ private static class VersionedDefaultPreferences extends VersionedMetaData {
+ private Config cfg;
+
+ @Override
+ protected String getRefName() {
+ return RefNames.REFS_USERS_DEFAULT;
+ }
+
+ private Config getConfig() {
+ checkState(cfg != null, "Default preferences not loaded yet.");
+ return cfg;
+ }
+
+ @Override
+ protected void onLoad() throws IOException, ConfigInvalidException {
+ cfg = readConfig(PREFERENCES_CONFIG);
+ }
+
+ @Override
+ protected boolean onSave(CommitBuilder commit) throws IOException, ConfigInvalidException {
+ if (Strings.isNullOrEmpty(commit.getMessage())) {
+ commit.setMessage("Update default preferences\n");
+ }
+ saveConfig(PREFERENCES_CONFIG, cfg);
+ return true;
+ }
+ }
+}
diff --git a/java/com/google/gerrit/server/account/WatchConfig.java b/java/com/google/gerrit/server/account/WatchConfig.java
index 667ca37..7adadf7 100644
--- a/java/com/google/gerrit/server/account/WatchConfig.java
+++ b/java/com/google/gerrit/server/account/WatchConfig.java
@@ -15,7 +15,7 @@
package com.google.gerrit.server.account;
import static com.google.common.base.MoreObjects.firstNonNull;
-import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.base.Preconditions.checkNotNull;
import com.google.auto.value.AutoValue;
import com.google.common.annotations.VisibleForTesting;
@@ -23,7 +23,6 @@
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.MultimapBuilder;
@@ -31,17 +30,7 @@
import com.google.gerrit.common.Nullable;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ValidationError;
-import com.google.gerrit.server.git.VersionedMetaData;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.google.inject.Singleton;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
@@ -49,10 +38,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.Repository;
/**
* ‘watch.config’ file in the user branch in the All-Users repository that contains the watch
@@ -85,91 +71,7 @@
*
* <p>Unknown notify types are ignored and removed on save.
*/
-public class WatchConfig extends VersionedMetaData implements ValidationError.Sink {
- @Singleton
- public static class Accessor {
- private final GitRepositoryManager repoManager;
- private final AllUsersName allUsersName;
- private final Provider<MetaDataUpdate.User> metaDataUpdateFactory;
- private final IdentifiedUser.GenericFactory userFactory;
-
- @Inject
- Accessor(
- GitRepositoryManager repoManager,
- AllUsersName allUsersName,
- Provider<MetaDataUpdate.User> metaDataUpdateFactory,
- IdentifiedUser.GenericFactory userFactory) {
- this.repoManager = repoManager;
- this.allUsersName = allUsersName;
- this.metaDataUpdateFactory = metaDataUpdateFactory;
- this.userFactory = userFactory;
- }
-
- public Map<ProjectWatchKey, Set<NotifyType>> getProjectWatches(Account.Id accountId)
- throws IOException, ConfigInvalidException {
- try (Repository git = repoManager.openRepository(allUsersName)) {
- WatchConfig watchConfig = new WatchConfig(accountId);
- watchConfig.load(git);
- return watchConfig.getProjectWatches();
- }
- }
-
- public synchronized void upsertProjectWatches(
- Account.Id accountId, Map<ProjectWatchKey, Set<NotifyType>> newProjectWatches)
- throws IOException, ConfigInvalidException {
- WatchConfig watchConfig = read(accountId);
- Map<ProjectWatchKey, Set<NotifyType>> projectWatches = watchConfig.getProjectWatches();
- projectWatches.putAll(newProjectWatches);
- commit(watchConfig);
- }
-
- public synchronized void deleteProjectWatches(
- Account.Id accountId, Collection<ProjectWatchKey> projectWatchKeys)
- throws IOException, ConfigInvalidException {
- WatchConfig watchConfig = read(accountId);
- Map<ProjectWatchKey, Set<NotifyType>> projectWatches = watchConfig.getProjectWatches();
- boolean commit = false;
- for (ProjectWatchKey key : projectWatchKeys) {
- if (projectWatches.remove(key) != null) {
- commit = true;
- }
- }
- if (commit) {
- commit(watchConfig);
- }
- }
-
- public synchronized void deleteAllProjectWatches(Account.Id accountId)
- throws IOException, ConfigInvalidException {
- WatchConfig watchConfig = read(accountId);
- boolean commit = false;
- if (!watchConfig.getProjectWatches().isEmpty()) {
- watchConfig.getProjectWatches().clear();
- commit = true;
- }
- if (commit) {
- commit(watchConfig);
- }
- }
-
- private WatchConfig read(Account.Id accountId) throws IOException, ConfigInvalidException {
- try (Repository git = repoManager.openRepository(allUsersName)) {
- WatchConfig watchConfig = new WatchConfig(accountId);
- watchConfig.load(git);
- return watchConfig;
- }
- }
-
- private void commit(WatchConfig watchConfig) throws IOException {
- try (MetaDataUpdate md =
- metaDataUpdateFactory
- .get()
- .create(allUsersName, userFactory.create(watchConfig.accountId))) {
- watchConfig.commit(md);
- }
- }
- }
-
+public class WatchConfig {
@AutoValue
public abstract static class ProjectWatchKey {
public static ProjectWatchKey create(Project.NameKey project, @Nullable String filter) {
@@ -199,25 +101,26 @@
public static final String KEY_NOTIFY = "notify";
private final Account.Id accountId;
- private final String ref;
+ private final Config cfg;
+ private final ValidationError.Sink validationErrorSink;
private Map<ProjectWatchKey, Set<NotifyType>> projectWatches;
- private List<ValidationError> validationErrors;
- public WatchConfig(Account.Id accountId) {
- this.accountId = accountId;
- this.ref = RefNames.refsUsers(accountId);
+ public WatchConfig(Account.Id accountId, Config cfg, ValidationError.Sink validationErrorSink) {
+ this.accountId = checkNotNull(accountId, "accountId");
+ this.cfg = checkNotNull(cfg, "cfg");
+ this.validationErrorSink = checkNotNull(validationErrorSink, "validationErrorSink");
}
- @Override
- protected String getRefName() {
- return ref;
+ public Map<ProjectWatchKey, Set<NotifyType>> getProjectWatches() {
+ if (projectWatches == null) {
+ parse();
+ }
+ return projectWatches;
}
- @Override
- protected void onLoad() throws IOException, ConfigInvalidException {
- Config cfg = readConfig(WATCH_CONFIG);
- projectWatches = parse(accountId, cfg, this);
+ public void parse() {
+ projectWatches = parse(accountId, cfg, validationErrorSink);
}
@VisibleForTesting
@@ -248,24 +151,8 @@
return projectWatches;
}
- Map<ProjectWatchKey, Set<NotifyType>> getProjectWatches() {
- checkLoaded();
- return projectWatches;
- }
-
- public void setProjectWatches(Map<ProjectWatchKey, Set<NotifyType>> projectWatches) {
+ public Config save(Map<ProjectWatchKey, Set<NotifyType>> projectWatches) {
this.projectWatches = projectWatches;
- }
-
- @Override
- protected boolean onSave(CommitBuilder commit) throws IOException, ConfigInvalidException {
- checkLoaded();
-
- if (Strings.isNullOrEmpty(commit.getMessage())) {
- commit.setMessage("Updated watch configuration\n");
- }
-
- Config cfg = readConfig(WATCH_CONFIG);
for (String projectName : cfg.getSubsections(PROJECT)) {
cfg.unsetSection(PROJECT, projectName);
@@ -282,32 +169,7 @@
cfg.setStringList(PROJECT, e.getKey(), KEY_NOTIFY, new ArrayList<>(e.getValue()));
}
- saveConfig(WATCH_CONFIG, cfg);
- return true;
- }
-
- private void checkLoaded() {
- checkState(projectWatches != null, "project watches not loaded yet");
- }
-
- @Override
- public void error(ValidationError error) {
- if (validationErrors == null) {
- validationErrors = new ArrayList<>(4);
- }
- validationErrors.add(error);
- }
-
- /**
- * Get the validation errors, if any were discovered during load.
- *
- * @return list of errors; empty list if there are no errors.
- */
- public List<ValidationError> getValidationErrors() {
- if (validationErrors != null) {
- return ImmutableList.copyOf(validationErrors);
- }
- return ImmutableList.of();
+ return cfg;
}
@AutoValue
@@ -356,7 +218,7 @@
return create(filter, notifyTypes);
}
- public static NotifyValue create(@Nullable String filter, Set<NotifyType> notifyTypes) {
+ public static NotifyValue create(@Nullable String filter, Collection<NotifyType> notifyTypes) {
return new AutoValue_WatchConfig_NotifyValue(
Strings.emptyToNull(filter), Sets.immutableEnumSet(notifyTypes));
}
diff --git a/java/com/google/gerrit/server/account/externalids/DisabledExternalIdCache.java b/java/com/google/gerrit/server/account/externalids/DisabledExternalIdCache.java
index 1033641..5894051 100644
--- a/java/com/google/gerrit/server/account/externalids/DisabledExternalIdCache.java
+++ b/java/com/google/gerrit/server/account/externalids/DisabledExternalIdCache.java
@@ -35,20 +35,6 @@
}
@Override
- public void onCreate(ObjectId oldNotesRev, ObjectId newNotesRev, Collection<ExternalId> extId) {}
-
- @Override
- public void onUpdate(ObjectId oldNotesRev, ObjectId newNotesRev, Collection<ExternalId> extId) {}
-
- @Override
- public void onReplace(
- ObjectId oldNotesRev,
- ObjectId newNotesRev,
- Account.Id accountId,
- Collection<ExternalId> toRemove,
- Collection<ExternalId> toAdd) {}
-
- @Override
public void onReplace(
ObjectId oldNotesRev,
ObjectId newNotesRev,
@@ -56,14 +42,16 @@
Collection<ExternalId> toAdd) {}
@Override
- public void onRemove(ObjectId oldNotesRev, ObjectId newNotesRev, Collection<ExternalId> extId) {}
-
- @Override
public ImmutableSet<ExternalId> byAccount(Account.Id accountId) {
throw new UnsupportedOperationException();
}
@Override
+ public ImmutableSet<ExternalId> byAccount(Account.Id accountId, ObjectId rev) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public ImmutableSetMultimap<Account.Id, ExternalId> allByAccount() throws IOException {
throw new UnsupportedOperationException();
}
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalId.java b/java/com/google/gerrit/server/account/externalids/ExternalId.java
index 1ac8f3d..8be7092 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalId.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalId.java
@@ -33,6 +33,7 @@
import java.util.Collection;
import java.util.Objects;
import java.util.Set;
+import java.util.regex.Pattern;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants;
@@ -40,6 +41,12 @@
@AutoValue
public abstract class ExternalId implements Serializable {
+ private static final Pattern USER_NAME_PATTERN = Pattern.compile(Account.USER_NAME_PATTERN);
+
+ public static boolean isValidUsername(String username) {
+ return USER_NAME_PATTERN.matcher(username).matches();
+ }
+
private static final long serialVersionUID = 1L;
private static final String EXTERNAL_ID_SECTION = "externalId";
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalIdCache.java b/java/com/google/gerrit/server/account/externalids/ExternalIdCache.java
index d928e15..ffb4e76 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalIdCache.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalIdCache.java
@@ -19,7 +19,6 @@
import com.google.gerrit.reviewdb.client.Account;
import java.io.IOException;
import java.util.Collection;
-import java.util.Collections;
import org.eclipse.jgit.lib.ObjectId;
/**
@@ -29,20 +28,6 @@
* cache is up to date.
*/
interface ExternalIdCache {
- void onCreate(ObjectId oldNotesRev, ObjectId newNotesRev, Collection<ExternalId> extId)
- throws IOException;
-
- void onUpdate(ObjectId oldNotesRev, ObjectId newNotesRev, Collection<ExternalId> extId)
- throws IOException;
-
- void onReplace(
- ObjectId oldNotesRev,
- ObjectId newNotesRev,
- Account.Id accountId,
- Collection<ExternalId> toRemove,
- Collection<ExternalId> toAdd)
- throws IOException;
-
void onReplace(
ObjectId oldNotesRev,
ObjectId newNotesRev,
@@ -50,11 +35,10 @@
Collection<ExternalId> toAdd)
throws IOException;
- void onRemove(ObjectId oldNotesRev, ObjectId newNotesRev, Collection<ExternalId> extId)
- throws IOException;
-
ImmutableSet<ExternalId> byAccount(Account.Id accountId) throws IOException;
+ ImmutableSet<ExternalId> byAccount(Account.Id accountId, ObjectId rev) throws IOException;
+
ImmutableSetMultimap<Account.Id, ExternalId> allByAccount() throws IOException;
ImmutableSetMultimap<String, ExternalId> byEmails(String... emails) throws IOException;
@@ -64,19 +48,4 @@
default ImmutableSet<ExternalId> byEmail(String email) throws IOException {
return byEmails(email).get(email);
}
-
- default void onCreate(ObjectId oldNotesRev, ObjectId newNotesRev, ExternalId extId)
- throws IOException {
- onCreate(oldNotesRev, newNotesRev, Collections.singleton(extId));
- }
-
- default void onRemove(ObjectId oldNotesRev, ObjectId newNotesRev, ExternalId extId)
- throws IOException {
- onRemove(oldNotesRev, newNotesRev, Collections.singleton(extId));
- }
-
- default void onUpdate(ObjectId oldNotesRev, ObjectId newNotesRev, ExternalId updatedExtId)
- throws IOException {
- onUpdate(oldNotesRev, newNotesRev, Collections.singleton(updatedExtId));
- }
}
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalIdCacheImpl.java b/java/com/google/gerrit/server/account/externalids/ExternalIdCacheImpl.java
index 6f80713..25789a1 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalIdCacheImpl.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalIdCacheImpl.java
@@ -15,17 +15,14 @@
package com.google.gerrit.server.account.externalids;
import static com.google.common.collect.ImmutableSetMultimap.toImmutableSetMultimap;
-import static java.util.stream.Collectors.toSet;
import com.google.auto.value.AutoValue;
import com.google.common.base.Strings;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
-import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
-import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.MultimapBuilder;
@@ -77,73 +74,6 @@
}
@Override
- public void onCreate(ObjectId oldNotesRev, ObjectId newNotesRev, Collection<ExternalId> extIds)
- throws IOException {
- updateCache(
- oldNotesRev,
- newNotesRev,
- m -> {
- for (ExternalId extId : extIds) {
- extId.checkThatBlobIdIsSet();
- m.put(extId.accountId(), extId);
- }
- });
- }
-
- @Override
- public void onRemove(ObjectId oldNotesRev, ObjectId newNotesRev, Collection<ExternalId> extIds)
- throws IOException {
- updateCache(
- oldNotesRev,
- newNotesRev,
- m -> {
- for (ExternalId extId : extIds) {
- m.remove(extId.accountId(), extId);
- }
- });
- }
-
- @Override
- public void onUpdate(
- ObjectId oldNotesRev, ObjectId newNotesRev, Collection<ExternalId> updatedExtIds)
- throws IOException {
- updateCache(
- oldNotesRev,
- newNotesRev,
- m -> {
- removeKeys(m.values(), updatedExtIds.stream().map(e -> e.key()).collect(toSet()));
- for (ExternalId updatedExtId : updatedExtIds) {
- updatedExtId.checkThatBlobIdIsSet();
- m.put(updatedExtId.accountId(), updatedExtId);
- }
- });
- }
-
- @Override
- public void onReplace(
- ObjectId oldNotesRev,
- ObjectId newNotesRev,
- Account.Id accountId,
- Collection<ExternalId> toRemove,
- Collection<ExternalId> toAdd)
- throws IOException {
- ExternalIdNotes.checkSameAccount(Iterables.concat(toRemove, toAdd), accountId);
-
- updateCache(
- oldNotesRev,
- newNotesRev,
- m -> {
- for (ExternalId extId : toRemove) {
- m.remove(extId.accountId(), extId);
- }
- for (ExternalId extId : toAdd) {
- extId.checkThatBlobIdIsSet();
- m.put(extId.accountId(), extId);
- }
- });
- }
-
- @Override
public void onReplace(
ObjectId oldNotesRev,
ObjectId newNotesRev,
@@ -170,6 +100,11 @@
}
@Override
+ public ImmutableSet<ExternalId> byAccount(Account.Id accountId, ObjectId rev) throws IOException {
+ return get(rev).byAccount().get(accountId);
+ }
+
+ @Override
public ImmutableSetMultimap<Account.Id, ExternalId> allByAccount() throws IOException {
return get().byAccount();
}
@@ -190,8 +125,12 @@
}
private AllExternalIds get() throws IOException {
+ return get(externalIdReader.readRevision());
+ }
+
+ private AllExternalIds get(ObjectId rev) throws IOException {
try {
- return extIdsByAccount.get(externalIdReader.readRevision());
+ return extIdsByAccount.get(rev);
} catch (ExecutionException e) {
throw new IOException("Cannot load external ids", e);
}
@@ -221,10 +160,6 @@
}
}
- private static void removeKeys(Collection<ExternalId> ids, Collection<ExternalId.Key> toRemove) {
- Collections2.transform(ids, e -> e.key()).removeAll(toRemove);
- }
-
private static class Loader extends CacheLoader<ObjectId, AllExternalIds> {
private final ExternalIdReader externalIdReader;
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalIdNotes.java b/java/com/google/gerrit/server/account/externalids/ExternalIdNotes.java
index f663247..8e69f2e 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalIdNotes.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalIdNotes.java
@@ -171,7 +171,7 @@
private Runnable afterReadRevision;
private boolean readOnly = false;
- ExternalIdNotes(
+ private ExternalIdNotes(
ExternalIdCache externalIdCache,
@Nullable AccountCache accountCache,
Repository allUsersRepo) {
@@ -205,7 +205,7 @@
*
* @return {@link ExternalIdNotes} instance for chaining
*/
- ExternalIdNotes load() throws IOException, ConfigInvalidException {
+ private ExternalIdNotes load() throws IOException, ConfigInvalidException {
load(repo);
return this;
}
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalIds.java b/java/com/google/gerrit/server/account/externalids/ExternalIds.java
index 5732bce..a8ecc40 100644
--- a/java/com/google/gerrit/server/account/externalids/ExternalIds.java
+++ b/java/com/google/gerrit/server/account/externalids/ExternalIds.java
@@ -75,6 +75,20 @@
return byAccount(accountId).stream().filter(e -> e.key().isScheme(scheme)).collect(toSet());
}
+ /** Returns the external IDs of the specified account. */
+ public Set<ExternalId> byAccount(Account.Id accountId, ObjectId rev) throws IOException {
+ return externalIdCache.byAccount(accountId, rev);
+ }
+
+ /** Returns the external IDs of the specified account that have the given scheme. */
+ public Set<ExternalId> byAccount(Account.Id accountId, String scheme, ObjectId rev)
+ throws IOException {
+ return byAccount(accountId, rev)
+ .stream()
+ .filter(e -> e.key().isScheme(scheme))
+ .collect(toSet());
+ }
+
/** Returns all external IDs by account. */
public ImmutableSetMultimap<Account.Id, ExternalId> allByAccount() throws IOException {
return externalIdCache.allByAccount();
diff --git a/java/com/google/gerrit/server/account/externalids/ExternalIdsUpdate.java b/java/com/google/gerrit/server/account/externalids/ExternalIdsUpdate.java
deleted file mode 100644
index 028fd8d..0000000
--- a/java/com/google/gerrit/server/account/externalids/ExternalIdsUpdate.java
+++ /dev/null
@@ -1,460 +0,0 @@
-// 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.account.externalids;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.util.concurrent.Runnables;
-import com.google.gerrit.common.Nullable;
-import com.google.gerrit.metrics.Counter0;
-import com.google.gerrit.metrics.Description;
-import com.google.gerrit.metrics.MetricMaker;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.server.account.AccountCache;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.MetaDataUpdate;
-import com.google.gerrit.server.update.RetryHelper;
-import com.google.gerrit.server.update.RetryHelper.ActionType;
-import com.google.gwtorm.server.OrmDuplicateKeyException;
-import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.google.inject.Singleton;
-import java.io.IOException;
-import java.util.Collection;
-import java.util.Collections;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.Repository;
-
-/**
- * Updates externalIds in ReviewDb and NoteDb.
- *
- * <p>In NoteDb external IDs are stored in the All-Users repository in a Git Notes branch called
- * refs/meta/external-ids where the sha1 of the external ID is used as note name. Each note content
- * is a git config file that contains an external ID. It has exactly one externalId subsection with
- * an accountId and optionally email and password:
- *
- * <pre>
- * [externalId "username:jdoe"]
- * accountId = 1003407
- * email = jdoe@example.com
- * password = bcrypt:4:LCbmSBDivK/hhGVQMfkDpA==:XcWn0pKYSVU/UJgOvhidkEtmqCp6oKB7
- * </pre>
- *
- * For NoteDb each method call results in one commit on refs/meta/external-ids branch.
- *
- * <p>On updating external IDs this class takes care to evict affected accounts from the account
- * cache and thus triggers reindex for them.
- */
-public class ExternalIdsUpdate {
- /**
- * Factory to create an ExternalIdsUpdate instance for updating external IDs by the Gerrit server.
- *
- * <p>The Gerrit server identity will be used as author and committer for all commits that update
- * the external IDs.
- */
- @Singleton
- public static class Server {
- private final GitRepositoryManager repoManager;
- private final Provider<MetaDataUpdate.Server> metaDataUpdateServerFactory;
- private final AccountCache accountCache;
- private final AllUsersName allUsersName;
- private final MetricMaker metricMaker;
- private final ExternalIds externalIds;
- private final ExternalIdCache externalIdCache;
- private final RetryHelper retryHelper;
-
- @Inject
- public Server(
- GitRepositoryManager repoManager,
- Provider<MetaDataUpdate.Server> metaDataUpdateServerFactory,
- AccountCache accountCache,
- AllUsersName allUsersName,
- MetricMaker metricMaker,
- ExternalIds externalIds,
- ExternalIdCache externalIdCache,
- RetryHelper retryHelper) {
- this.repoManager = repoManager;
- this.metaDataUpdateServerFactory = metaDataUpdateServerFactory;
- this.accountCache = accountCache;
- this.allUsersName = allUsersName;
- this.metricMaker = metricMaker;
- this.externalIds = externalIds;
- this.externalIdCache = externalIdCache;
- this.retryHelper = retryHelper;
- }
-
- public ExternalIdsUpdate create() {
- return new ExternalIdsUpdate(
- repoManager,
- () -> metaDataUpdateServerFactory.get().create(allUsersName),
- accountCache,
- allUsersName,
- metricMaker,
- externalIds,
- externalIdCache,
- retryHelper);
- }
- }
-
- /**
- * Factory to create an ExternalIdsUpdate instance for updating external IDs by the Gerrit server.
- *
- * <p>Using this class no reindex will be performed for the affected accounts and they will also
- * not be evicted from the account cache.
- *
- * <p>The Gerrit server identity will be used as author and committer for all commits that update
- * the external IDs.
- */
- @Singleton
- public static class ServerNoReindex {
- private final GitRepositoryManager repoManager;
- private final Provider<MetaDataUpdate.Server> metaDataUpdateServerFactory;
- private final AllUsersName allUsersName;
- private final MetricMaker metricMaker;
- private final ExternalIds externalIds;
- private final ExternalIdCache externalIdCache;
- private final RetryHelper retryHelper;
-
- @Inject
- public ServerNoReindex(
- GitRepositoryManager repoManager,
- Provider<MetaDataUpdate.Server> metaDataUpdateServerFactory,
- AllUsersName allUsersName,
- MetricMaker metricMaker,
- ExternalIds externalIds,
- ExternalIdCache externalIdCache,
- RetryHelper retryHelper) {
- this.repoManager = repoManager;
- this.metaDataUpdateServerFactory = metaDataUpdateServerFactory;
- this.allUsersName = allUsersName;
- this.metricMaker = metricMaker;
- this.externalIds = externalIds;
- this.externalIdCache = externalIdCache;
- this.retryHelper = retryHelper;
- }
-
- public ExternalIdsUpdate create() {
- return new ExternalIdsUpdate(
- repoManager,
- () -> metaDataUpdateServerFactory.get().create(allUsersName),
- null,
- allUsersName,
- metricMaker,
- externalIds,
- externalIdCache,
- retryHelper);
- }
- }
-
- /**
- * Factory to create an ExternalIdsUpdate instance for updating external IDs by the current user.
- *
- * <p>The identity of the current user will be used as author for all commits that update the
- * external IDs. The Gerrit server identity will be used as committer.
- */
- @Singleton
- public static class User {
- private final GitRepositoryManager repoManager;
- private final Provider<MetaDataUpdate.User> metaDataUpdateUserFactory;
- private final AccountCache accountCache;
- private final AllUsersName allUsersName;
- private final MetricMaker metricMaker;
- private final ExternalIds externalIds;
- private final ExternalIdCache externalIdCache;
- private final RetryHelper retryHelper;
-
- @Inject
- public User(
- GitRepositoryManager repoManager,
- Provider<MetaDataUpdate.User> metaDataUpdateUserFactory,
- AccountCache accountCache,
- AllUsersName allUsersName,
- MetricMaker metricMaker,
- ExternalIds externalIds,
- ExternalIdCache externalIdCache,
- RetryHelper retryHelper) {
- this.repoManager = repoManager;
- this.metaDataUpdateUserFactory = metaDataUpdateUserFactory;
- this.accountCache = accountCache;
- this.allUsersName = allUsersName;
- this.metricMaker = metricMaker;
- this.externalIds = externalIds;
- this.externalIdCache = externalIdCache;
- this.retryHelper = retryHelper;
- }
-
- public ExternalIdsUpdate create() {
- return new ExternalIdsUpdate(
- repoManager,
- () -> metaDataUpdateUserFactory.get().create(allUsersName),
- accountCache,
- allUsersName,
- metricMaker,
- externalIds,
- externalIdCache,
- retryHelper);
- }
- }
-
- private final GitRepositoryManager repoManager;
- private final MetaDataUpdateFactory metaDataUpdateFactory;
- @Nullable private final AccountCache accountCache;
- private final AllUsersName allUsersName;
- private final ExternalIds externalIds;
- private final ExternalIdCache externalIdCache;
- private final RetryHelper retryHelper;
- private final Runnable afterReadRevision;
- private final Counter0 updateCount;
-
- private ExternalIdsUpdate(
- GitRepositoryManager repoManager,
- MetaDataUpdateFactory metaDataUpdateFactory,
- @Nullable AccountCache accountCache,
- AllUsersName allUsersName,
- MetricMaker metricMaker,
- ExternalIds externalIds,
- ExternalIdCache externalIdCache,
- RetryHelper retryHelper) {
- this(
- repoManager,
- metaDataUpdateFactory,
- accountCache,
- allUsersName,
- metricMaker,
- externalIds,
- externalIdCache,
- retryHelper,
- Runnables.doNothing());
- }
-
- @VisibleForTesting
- public ExternalIdsUpdate(
- GitRepositoryManager repoManager,
- MetaDataUpdateFactory metaDataUpdateFactory,
- @Nullable AccountCache accountCache,
- AllUsersName allUsersName,
- MetricMaker metricMaker,
- ExternalIds externalIds,
- ExternalIdCache externalIdCache,
- RetryHelper retryHelper,
- Runnable afterReadRevision) {
- this.repoManager = checkNotNull(repoManager, "repoManager");
- this.metaDataUpdateFactory = checkNotNull(metaDataUpdateFactory, "metaDataUpdateFactory");
- this.accountCache = accountCache;
- this.allUsersName = checkNotNull(allUsersName, "allUsersName");
- this.externalIds = checkNotNull(externalIds, "externalIds");
- this.externalIdCache = checkNotNull(externalIdCache, "externalIdCache");
- this.retryHelper = checkNotNull(retryHelper, "retryHelper");
- this.afterReadRevision = checkNotNull(afterReadRevision, "afterReadRevision");
- this.updateCount =
- metricMaker.newCounter(
- "notedb/external_id_update_count",
- new Description("Total number of external ID updates.").setRate().setUnit("updates"));
- }
-
- /**
- * Inserts a new external ID.
- *
- * <p>If the external ID already exists, the insert fails with {@link OrmDuplicateKeyException}.
- */
- public void insert(ExternalId extId) throws IOException, ConfigInvalidException, OrmException {
- insert(Collections.singleton(extId));
- }
-
- /**
- * Inserts new external IDs.
- *
- * <p>If any of the external ID already exists, the insert fails with {@link
- * OrmDuplicateKeyException}.
- */
- public void insert(Collection<ExternalId> extIds)
- throws IOException, ConfigInvalidException, OrmException {
- updateNoteMap(n -> n.insert(extIds));
- }
-
- /**
- * Inserts or updates an external ID.
- *
- * <p>If the external ID already exists, it is overwritten, otherwise it is inserted.
- */
- public void upsert(ExternalId extId) throws IOException, ConfigInvalidException, OrmException {
- upsert(Collections.singleton(extId));
- }
-
- /**
- * Inserts or updates external IDs.
- *
- * <p>If any of the external IDs already exists, it is overwritten. New external IDs are inserted.
- */
- public void upsert(Collection<ExternalId> extIds)
- throws IOException, ConfigInvalidException, OrmException {
- updateNoteMap(n -> n.upsert(extIds));
- }
-
- /**
- * Deletes an external ID.
- *
- * @throws IllegalStateException is thrown if there is an existing external ID that has the same
- * key, but otherwise doesn't match the specified external ID.
- */
- public void delete(ExternalId extId) throws IOException, ConfigInvalidException, OrmException {
- delete(Collections.singleton(extId));
- }
-
- /**
- * Deletes external IDs.
- *
- * @throws IllegalStateException is thrown if there is an existing external ID that has the same
- * key as any of the external IDs that should be deleted, but otherwise doesn't match the that
- * external ID.
- */
- public void delete(Collection<ExternalId> extIds)
- throws IOException, ConfigInvalidException, OrmException {
- updateNoteMap(n -> n.delete(extIds));
- }
-
- /**
- * Delete an external ID by key.
- *
- * @throws IllegalStateException is thrown if the external ID does not belong to the specified
- * account.
- */
- public void delete(Account.Id accountId, ExternalId.Key extIdKey)
- throws IOException, ConfigInvalidException, OrmException {
- delete(accountId, Collections.singleton(extIdKey));
- }
-
- /**
- * Delete external IDs by external ID key.
- *
- * @throws IllegalStateException is thrown if any of the external IDs does not belong to the
- * specified account.
- */
- public void delete(Account.Id accountId, Collection<ExternalId.Key> extIdKeys)
- throws IOException, ConfigInvalidException, OrmException {
- updateNoteMap(n -> n.delete(accountId, extIdKeys));
- }
-
- /**
- * Delete external IDs by external ID key.
- *
- * <p>The external IDs are deleted regardless of which account they belong to.
- */
- public void deleteByKeys(Collection<ExternalId.Key> extIdKeys)
- throws IOException, ConfigInvalidException, OrmException {
- updateNoteMap(n -> n.deleteByKeys(extIdKeys));
- }
-
- /** Deletes all external IDs of the specified account. */
- public void deleteAll(Account.Id accountId)
- throws IOException, ConfigInvalidException, OrmException {
- delete(externalIds.byAccount(accountId));
- }
-
- /**
- * Replaces external IDs for an account by external ID keys.
- *
- * <p>Deletion of external IDs is done before adding the new external IDs. This means if an
- * external ID key is specified for deletion and an external ID with the same key is specified to
- * be added, the old external ID with that key is deleted first and then the new external ID is
- * added (so the external ID for that key is replaced).
- *
- * @throws IllegalStateException is thrown if any of the specified external IDs does not belong to
- * the specified account.
- */
- public void replace(
- Account.Id accountId, Collection<ExternalId.Key> toDelete, Collection<ExternalId> toAdd)
- throws IOException, ConfigInvalidException, OrmException {
- updateNoteMap(n -> n.replace(accountId, toDelete, toAdd));
- }
-
- /**
- * Replaces external IDs for an account by external ID keys.
- *
- * <p>Deletion of external IDs is done before adding the new external IDs. This means if an
- * external ID key is specified for deletion and an external ID with the same key is specified to
- * be added, the old external ID with that key is deleted first and then the new external ID is
- * added (so the external ID for that key is replaced).
- *
- * <p>The external IDs are replaced regardless of which account they belong to.
- */
- public void replaceByKeys(Collection<ExternalId.Key> toDelete, Collection<ExternalId> toAdd)
- throws IOException, ConfigInvalidException, OrmException {
- updateNoteMap(n -> n.replaceByKeys(toDelete, toAdd));
- }
-
- /**
- * Replaces an external ID.
- *
- * @throws IllegalStateException is thrown if the specified external IDs belong to different
- * accounts.
- */
- public void replace(ExternalId toDelete, ExternalId toAdd)
- throws IOException, ConfigInvalidException, OrmException {
- replace(Collections.singleton(toDelete), Collections.singleton(toAdd));
- }
-
- /**
- * Replaces external IDs.
- *
- * <p>Deletion of external IDs is done before adding the new external IDs. This means if an
- * external ID is specified for deletion and an external ID with the same key is specified to be
- * added, the old external ID with that key is deleted first and then the new external ID is added
- * (so the external ID for that key is replaced).
- *
- * @throws IllegalStateException is thrown if the specified external IDs belong to different
- * accounts.
- */
- public void replace(Collection<ExternalId> toDelete, Collection<ExternalId> toAdd)
- throws IOException, ConfigInvalidException, OrmException {
- updateNoteMap(n -> n.replace(toDelete, toAdd));
- }
-
- private void updateNoteMap(ExternalIdUpdater updater)
- throws IOException, ConfigInvalidException, OrmException {
- retryHelper.execute(
- ActionType.ACCOUNT_UPDATE,
- () -> {
- try (Repository repo = repoManager.openRepository(allUsersName)) {
- ExternalIdNotes extIdNotes =
- new ExternalIdNotes(externalIdCache, accountCache, repo)
- .setAfterReadRevision(afterReadRevision)
- .load();
- updater.update(extIdNotes);
- try (MetaDataUpdate metaDataUpdate = metaDataUpdateFactory.create()) {
- extIdNotes.commit(metaDataUpdate);
- }
- extIdNotes.updateCaches();
- updateCount.increment();
- return null;
- }
- });
- }
-
- @FunctionalInterface
- private static interface ExternalIdUpdater {
- void update(ExternalIdNotes extIdsNotes)
- throws IOException, ConfigInvalidException, OrmException;
- }
-
- @VisibleForTesting
- @FunctionalInterface
- public static interface MetaDataUpdateFactory {
- MetaDataUpdate create() throws IOException;
- }
-}
diff --git a/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java b/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java
index 232cc63..366ebfb 100644
--- a/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java
+++ b/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java
@@ -382,8 +382,12 @@
}
@Override
- public List<EmailInfo> getEmails() {
- return getEmails.apply(account);
+ public List<EmailInfo> getEmails() throws RestApiException {
+ try {
+ return getEmails.apply(account);
+ } catch (Exception e) {
+ throw asRestApiException("Cannot get emails", e);
+ }
}
@Override
diff --git a/java/com/google/gerrit/server/api/accounts/AccountsImpl.java b/java/com/google/gerrit/server/api/accounts/AccountsImpl.java
index fac16dc..f5f1a34 100644
--- a/java/com/google/gerrit/server/api/accounts/AccountsImpl.java
+++ b/java/com/google/gerrit/server/api/accounts/AccountsImpl.java
@@ -156,6 +156,7 @@
myQueryAccounts.setQuery(r.getQuery());
myQueryAccounts.setLimit(r.getLimit());
myQueryAccounts.setStart(r.getStart());
+ myQueryAccounts.setSuggest(r.getSuggest());
for (ListAccountsOption option : r.getOptions()) {
myQueryAccounts.addOption(option);
}
diff --git a/java/com/google/gerrit/server/change/ChangeInserter.java b/java/com/google/gerrit/server/change/ChangeInserter.java
index 8ed1ef0..5e60906 100644
--- a/java/com/google/gerrit/server/change/ChangeInserter.java
+++ b/java/com/google/gerrit/server/change/ChangeInserter.java
@@ -569,7 +569,8 @@
new Branch.NameKey(ctx.getProject(), refName),
ctx.getIdentifiedUser(),
new NoSshInfo(),
- ctx.getRevWalk())
+ ctx.getRevWalk(),
+ change)
.validate(event);
}
} catch (CommitValidationException e) {
diff --git a/java/com/google/gerrit/server/change/ChangeJson.java b/java/com/google/gerrit/server/change/ChangeJson.java
index 81558e3..8ba755f 100644
--- a/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/java/com/google/gerrit/server/change/ChangeJson.java
@@ -33,6 +33,7 @@
import static com.google.gerrit.extensions.client.ListChangesOption.PUSH_CERTIFICATES;
import static com.google.gerrit.extensions.client.ListChangesOption.REVIEWED;
import static com.google.gerrit.extensions.client.ListChangesOption.REVIEWER_UPDATES;
+import static com.google.gerrit.extensions.client.ListChangesOption.SKIP_MERGEABLE;
import static com.google.gerrit.extensions.client.ListChangesOption.SUBMITTABLE;
import static com.google.gerrit.extensions.client.ListChangesOption.TRACKING_IDS;
import static com.google.gerrit.extensions.client.ListChangesOption.WEB_LINKS;
@@ -521,7 +522,9 @@
if (str.isOk()) {
out.submitType = str.type;
}
- out.mergeable = cd.isMergeable();
+ if (!has(SKIP_MERGEABLE)) {
+ out.mergeable = cd.isMergeable();
+ }
if (has(SUBMITTABLE)) {
out.submittable = submittable(cd);
}
diff --git a/java/com/google/gerrit/server/change/ChangeResource.java b/java/com/google/gerrit/server/change/ChangeResource.java
index 4166bf7..8c40ad1 100644
--- a/java/com/google/gerrit/server/change/ChangeResource.java
+++ b/java/com/google/gerrit/server/change/ChangeResource.java
@@ -103,7 +103,7 @@
}
public PermissionBackend.ForChange permissions() {
- return permissionBackend.user(user).change(notes);
+ return permissionBackend.user(user).database(db).change(notes);
}
public CurrentUser getUser() {
diff --git a/java/com/google/gerrit/server/change/MergeabilityCacheImpl.java b/java/com/google/gerrit/server/change/MergeabilityCacheImpl.java
index 119051e..7ba18e8 100644
--- a/java/com/google/gerrit/server/change/MergeabilityCacheImpl.java
+++ b/java/com/google/gerrit/server/change/MergeabilityCacheImpl.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.change;
+import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.gerrit.server.ioutil.BasicSerialization.readString;
@@ -59,6 +60,7 @@
public static final ImmutableBiMap<SubmitType, Character> SUBMIT_TYPES =
new ImmutableBiMap.Builder<SubmitType, Character>()
+ .put(SubmitType.INHERIT, 'I')
.put(SubmitType.FAST_FORWARD_ONLY, 'F')
.put(SubmitType.MERGE_IF_NECESSARY, 'M')
.put(SubmitType.REBASE_ALWAYS, 'P')
@@ -98,6 +100,11 @@
private String mergeStrategy;
public EntryKey(ObjectId commit, ObjectId into, SubmitType submitType, String mergeStrategy) {
+ checkArgument(
+ submitType != SubmitType.INHERIT,
+ "Cannot cache %s.%s",
+ SubmitType.class.getSimpleName(),
+ submitType);
this.commit = checkNotNull(commit, "commit");
this.into = checkNotNull(into, "into");
this.submitType = checkNotNull(submitType, "submitType");
diff --git a/java/com/google/gerrit/server/change/PatchSetInserter.java b/java/com/google/gerrit/server/change/PatchSetInserter.java
index d298730..3a32f8f 100644
--- a/java/com/google/gerrit/server/change/PatchSetInserter.java
+++ b/java/com/google/gerrit/server/change/PatchSetInserter.java
@@ -319,6 +319,7 @@
.change(origNotes)
.check(ChangePermission.ADD_PATCH_SET);
}
+ projectCache.checkedGet(ctx.getProject()).checkStatePermitsWrite();
if (!validate) {
return;
}
@@ -344,7 +345,8 @@
origNotes.getChange().getDest(),
ctx.getIdentifiedUser(),
new NoSshInfo(),
- ctx.getRevWalk())
+ ctx.getRevWalk(),
+ origNotes.getChange())
.validate(event);
} catch (CommitValidationException e) {
throw new ResourceConflictException(e.getFullMessage());
diff --git a/java/com/google/gerrit/server/config/GerritGlobalModule.java b/java/com/google/gerrit/server/config/GerritGlobalModule.java
index 1dda0e5..74e19b6 100644
--- a/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -86,7 +86,6 @@
import com.google.gerrit.server.account.AccountResolver;
import com.google.gerrit.server.account.AccountVisibilityProvider;
import com.google.gerrit.server.account.CapabilityCollection;
-import com.google.gerrit.server.account.ChangeUserName;
import com.google.gerrit.server.account.EmailExpander;
import com.google.gerrit.server.account.GroupCacheImpl;
import com.google.gerrit.server.account.GroupControl;
@@ -166,7 +165,6 @@
import com.google.gerrit.server.project.PermissionCollection;
import com.google.gerrit.server.project.ProjectCacheImpl;
import com.google.gerrit.server.project.ProjectNameLockManager;
-import com.google.gerrit.server.project.ProjectNode;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.project.SectionSortCache;
import com.google.gerrit.server.project.SubmitRuleEvaluator;
@@ -264,7 +262,6 @@
factory(MergeUtil.Factory.class);
factory(PatchScriptFactory.Factory.class);
factory(PluginUser.Factory.class);
- factory(ProjectNode.Factory.class);
factory(ProjectState.Factory.class);
factory(RegisterNewEmailSender.Factory.class);
factory(ReplacePatchSetSender.Factory.class);
@@ -421,7 +418,6 @@
factory(VersionedAuthorizedKeys.Factory.class);
bind(AccountManager.class);
- factory(ChangeUserName.Factory.class);
bind(new TypeLiteral<List<CommentLinkInfo>>() {})
.toProvider(CommentLinkProvider.class)
diff --git a/java/com/google/gerrit/server/config/SitePaths.java b/java/com/google/gerrit/server/config/SitePaths.java
index 3748bfd..11ec50c 100644
--- a/java/com/google/gerrit/server/config/SitePaths.java
+++ b/java/com/google/gerrit/server/config/SitePaths.java
@@ -58,7 +58,6 @@
public final Path ssl_keystore;
public final Path ssh_key;
public final Path ssh_rsa;
- public final Path ssh_dsa;
public final Path ssh_ecdsa_256;
public final Path ssh_ecdsa_384;
public final Path ssh_ecdsa_521;
@@ -106,7 +105,6 @@
ssl_keystore = etc_dir.resolve("keystore");
ssh_key = etc_dir.resolve("ssh_host_key");
ssh_rsa = etc_dir.resolve("ssh_host_rsa_key");
- ssh_dsa = etc_dir.resolve("ssh_host_dsa_key");
ssh_ecdsa_256 = etc_dir.resolve("ssh_host_ecdsa_key");
ssh_ecdsa_384 = etc_dir.resolve("ssh_host_ecdsa_384_key");
ssh_ecdsa_521 = etc_dir.resolve("ssh_host_ecdsa_521_key");
diff --git a/java/com/google/gerrit/server/edit/ChangeEditModifier.java b/java/com/google/gerrit/server/edit/ChangeEditModifier.java
index 82fa596..64f5ae7 100644
--- a/java/com/google/gerrit/server/edit/ChangeEditModifier.java
+++ b/java/com/google/gerrit/server/edit/ChangeEditModifier.java
@@ -20,6 +20,7 @@
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.MergeConflictException;
import com.google.gerrit.extensions.restapi.RawInput;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.RefNames;
@@ -40,6 +41,7 @@
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.util.CommitMessageUtil;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -83,6 +85,7 @@
private final PermissionBackend permissionBackend;
private final ChangeEditUtil changeEditUtil;
private final PatchSetUtil patchSetUtil;
+ private final ProjectCache projectCache;
@Inject
ChangeEditModifier(
@@ -92,7 +95,8 @@
Provider<CurrentUser> currentUser,
PermissionBackend permissionBackend,
ChangeEditUtil changeEditUtil,
- PatchSetUtil patchSetUtil) {
+ PatchSetUtil patchSetUtil,
+ ProjectCache projectCache) {
this.indexer = indexer;
this.reviewDb = reviewDb;
this.currentUser = currentUser;
@@ -100,6 +104,7 @@
this.tz = gerritIdent.getTimeZone();
this.changeEditUtil = changeEditUtil;
this.patchSetUtil = patchSetUtil;
+ this.projectCache = projectCache;
}
/**
@@ -113,7 +118,7 @@
*/
public void createEdit(Repository repository, ChangeNotes notes)
throws AuthException, IOException, InvalidChangeOperationException, OrmException,
- PermissionBackendException {
+ PermissionBackendException, ResourceConflictException {
assertCanEdit(notes);
Optional<ChangeEdit> changeEdit = lookupChangeEdit(notes);
@@ -141,7 +146,7 @@
*/
public void rebaseEdit(Repository repository, ChangeNotes notes)
throws AuthException, InvalidChangeOperationException, IOException, OrmException,
- MergeConflictException, PermissionBackendException {
+ MergeConflictException, PermissionBackendException, ResourceConflictException {
assertCanEdit(notes);
Optional<ChangeEdit> optionalChangeEdit = lookupChangeEdit(notes);
@@ -206,7 +211,7 @@
*/
public void modifyMessage(Repository repository, ChangeNotes notes, String newCommitMessage)
throws AuthException, IOException, UnchangedCommitMessageException, OrmException,
- PermissionBackendException, BadRequestException {
+ PermissionBackendException, BadRequestException, ResourceConflictException {
assertCanEdit(notes);
newCommitMessage = CommitMessageUtil.checkAndSanitizeCommitMessage(newCommitMessage);
@@ -244,11 +249,12 @@
* @throws AuthException if the user isn't authenticated or not allowed to use change edits
* @throws InvalidChangeOperationException if the file already had the specified content
* @throws PermissionBackendException
+ * @throws ResourceConflictException if the project state does not permit the operation
*/
public void modifyFile(
Repository repository, ChangeNotes notes, String filePath, RawInput newContent)
throws AuthException, InvalidChangeOperationException, IOException, OrmException,
- PermissionBackendException {
+ PermissionBackendException, ResourceConflictException {
modifyTree(repository, notes, new ChangeFileContentModification(filePath, newContent));
}
@@ -262,10 +268,11 @@
* @throws AuthException if the user isn't authenticated or not allowed to use change edits
* @throws InvalidChangeOperationException if the file does not exist
* @throws PermissionBackendException
+ * @throws ResourceConflictException if the project state does not permit the operation
*/
public void deleteFile(Repository repository, ChangeNotes notes, String file)
throws AuthException, InvalidChangeOperationException, IOException, OrmException,
- PermissionBackendException {
+ PermissionBackendException, ResourceConflictException {
modifyTree(repository, notes, new DeleteFileModification(file));
}
@@ -281,11 +288,12 @@
* @throws InvalidChangeOperationException if the file was already renamed to the specified new
* name
* @throws PermissionBackendException
+ * @throws ResourceConflictException if the project state does not permit the operation
*/
public void renameFile(
Repository repository, ChangeNotes notes, String currentFilePath, String newFilePath)
throws AuthException, InvalidChangeOperationException, IOException, OrmException,
- PermissionBackendException {
+ PermissionBackendException, ResourceConflictException {
modifyTree(repository, notes, new RenameFileModification(currentFilePath, newFilePath));
}
@@ -303,14 +311,14 @@
*/
public void restoreFile(Repository repository, ChangeNotes notes, String file)
throws AuthException, InvalidChangeOperationException, IOException, OrmException,
- PermissionBackendException {
+ PermissionBackendException, ResourceConflictException {
modifyTree(repository, notes, new RestoreFileModification(file));
}
private void modifyTree(
Repository repository, ChangeNotes notes, TreeModification treeModification)
throws AuthException, IOException, OrmException, InvalidChangeOperationException,
- PermissionBackendException {
+ PermissionBackendException, ResourceConflictException {
assertCanEdit(notes);
Optional<ChangeEdit> optionalChangeEdit = lookupChangeEdit(notes);
@@ -355,7 +363,7 @@
PatchSet patchSet,
List<TreeModification> treeModifications)
throws AuthException, IOException, InvalidChangeOperationException, MergeConflictException,
- OrmException, PermissionBackendException {
+ OrmException, PermissionBackendException, ResourceConflictException {
assertCanEdit(notes);
Optional<ChangeEdit> optionalChangeEdit = lookupChangeEdit(notes);
@@ -385,7 +393,8 @@
return createEdit(repository, notes, patchSet, newEditCommit, nowTimestamp);
}
- private void assertCanEdit(ChangeNotes notes) throws AuthException, PermissionBackendException {
+ private void assertCanEdit(ChangeNotes notes)
+ throws AuthException, PermissionBackendException, IOException, ResourceConflictException {
if (!currentUser.get().isIdentifiedUser()) {
throw new AuthException("Authentication required");
}
@@ -395,6 +404,7 @@
.database(reviewDb)
.change(notes)
.check(ChangePermission.ADD_PATCH_SET);
+ projectCache.checkedGet(notes.getProjectName()).checkStatePermitsWrite();
} catch (AuthException denied) {
throw new AuthException("edit not permitted", denied);
}
diff --git a/java/com/google/gerrit/server/git/MultiBaseLocalDiskRepositoryManager.java b/java/com/google/gerrit/server/git/MultiBaseLocalDiskRepositoryManager.java
index df6f5bb..6fafe4e 100644
--- a/java/com/google/gerrit/server/git/MultiBaseLocalDiskRepositoryManager.java
+++ b/java/com/google/gerrit/server/git/MultiBaseLocalDiskRepositoryManager.java
@@ -1,16 +1,16 @@
-//Copyright (C) 2015 The Android Open Source Project
+// Copyright (C) 2015 The Android Open Source Project
//
-//Licensed under the Apache License, Version 2.0 (the "License");
-//you may not use this file except in compliance with the License.
-//You may obtain a copy of the License at
+// 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
+// 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.
+// 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.git;
diff --git a/java/com/google/gerrit/server/git/ProjectConfig.java b/java/com/google/gerrit/server/git/ProjectConfig.java
index e5b4a17e..fec1ae3 100644
--- a/java/com/google/gerrit/server/git/ProjectConfig.java
+++ b/java/com/google/gerrit/server/git/ProjectConfig.java
@@ -16,6 +16,7 @@
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.gerrit.common.data.Permission.isPermission;
+import static com.google.gerrit.reviewdb.client.Project.DEFAULT_SUBMIT_TYPE;
import com.google.common.base.CharMatcher;
import com.google.common.base.Joiner;
@@ -42,7 +43,6 @@
import com.google.gerrit.common.errors.InvalidNameException;
import com.google.gerrit.extensions.client.InheritableBoolean;
import com.google.gerrit.extensions.client.ProjectState;
-import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.BooleanProjectConfig;
import com.google.gerrit.reviewdb.client.Branch;
@@ -151,7 +151,6 @@
private static final String PLUGIN = "plugin";
- private static final SubmitType DEFAULT_SUBMIT_ACTION = SubmitType.MERGE_IF_NECESSARY;
private static final ProjectState DEFAULT_STATE_VALUE = ProjectState.ACTIVE;
private static final String EXTENSION_PANELS = "extension-panels";
@@ -519,7 +518,7 @@
p.setMaxObjectSizeLimit(rc.getString(RECEIVE, null, KEY_MAX_OBJECT_SIZE_LIMIT));
- p.setSubmitType(getEnum(rc, SUBMIT, null, KEY_ACTION, DEFAULT_SUBMIT_ACTION));
+ p.setSubmitType(getEnum(rc, SUBMIT, null, KEY_ACTION, DEFAULT_SUBMIT_TYPE));
p.setState(getEnum(rc, PROJECT, null, KEY_STATE, DEFAULT_STATE_VALUE));
p.setDefaultDashboard(rc.getString(DASHBOARD, null, KEY_DEFAULT));
@@ -1043,7 +1042,7 @@
KEY_MAX_OBJECT_SIZE_LIMIT,
validMaxObjectSizeLimit(p.getMaxObjectSizeLimit()));
- set(rc, SUBMIT, null, KEY_ACTION, p.getSubmitType(), DEFAULT_SUBMIT_ACTION);
+ set(rc, SUBMIT, null, KEY_ACTION, p.getConfiguredSubmitType(), DEFAULT_SUBMIT_TYPE);
set(rc, PROJECT, null, KEY_STATE, p.getState(), DEFAULT_STATE_VALUE);
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
index a058aec..292fa7a 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
@@ -376,6 +376,7 @@
private MagicBranchInput magicBranch;
private boolean newChangeForAllNotInTarget;
private String setFullNameTo;
+ private boolean setChangeAsPrivate;
// Handles for outputting back over the wire to the end user.
private Task newProgress;
@@ -1036,7 +1037,7 @@
// Must pass explicit user instead of injecting a provider into CreateRefControl, since
// Provider<CurrentUser> within ReceiveCommits will always return anonymous.
createRefControl.checkCreateRef(Providers.of(user), rp.getRepository(), branch, obj);
- } catch (AuthException denied) {
+ } catch (AuthException | ResourceConflictException denied) {
reject(cmd, "prohibited by Gerrit: " + denied.getMessage());
return;
}
@@ -1059,6 +1060,9 @@
} catch (AuthException err) {
ok = false;
}
+ if (!projectState.statePermitsWrite()) {
+ reject(cmd, "prohibited by Gerrit: project state does not permit write");
+ }
if (ok) {
if (isHead(cmd) && !isCommit(cmd)) {
return;
@@ -1116,7 +1120,7 @@
private boolean canDelete(ReceiveCommand cmd) throws PermissionBackendException {
try {
permissions.ref(cmd.getRefName()).check(RefPermission.DELETE);
- return true;
+ return projectState.statePermitsWrite();
} catch (AuthException e) {
return false;
}
@@ -1155,6 +1159,9 @@
if (!validRefOperation(cmd)) {
return;
}
+ if (!projectState.statePermitsWrite()) {
+ cmd.setResult(REJECTED_NONFASTFORWARD, " project state does not permit write.");
+ }
actualCommands.add(cmd);
} else {
cmd.setResult(
@@ -1333,11 +1340,10 @@
this.publish = cmd.getRefName().startsWith(MagicBranch.NEW_PUBLISH_CHANGE);
this.labelTypes = labelTypes;
this.notesMigration = notesMigration;
- GeneralPreferencesInfo prefs = user.getAccount().getGeneralPreferencesInfo();
+ GeneralPreferencesInfo prefs = user.state().getGeneralPreferences();
this.defaultPublishComments =
prefs != null
- ? firstNonNull(
- user.getAccount().getGeneralPreferencesInfo().publishCommentsOnPush, false)
+ ? firstNonNull(user.state().getGeneralPreferences().publishCommentsOnPush, false)
: false;
}
@@ -1515,12 +1521,28 @@
reject(cmd, denied.getMessage());
return;
}
+ if (!projectState.statePermitsWrite()) {
+ reject(cmd, "project state does not permit write");
+ return;
+ }
if (magicBranch.isPrivate && magicBranch.removePrivate) {
reject(cmd, "the options 'private' and 'remove-private' are mutually exclusive");
return;
}
+ boolean privateByDefault =
+ projectCache.get(project.getNameKey()).is(BooleanProjectConfig.PRIVATE_BY_DEFAULT);
+ setChangeAsPrivate =
+ magicBranch.draft
+ || magicBranch.isPrivate
+ || (privateByDefault && !magicBranch.removePrivate);
+
+ if (receiveConfig.disablePrivateChanges && setChangeAsPrivate) {
+ reject(cmd, "private changes are disabled");
+ return;
+ }
+
if (magicBranch.workInProgress && magicBranch.ready) {
reject(cmd, "the options 'wip' and 'ready' are mutually exclusive");
return;
@@ -1538,6 +1560,10 @@
reject(cmd, e.getMessage());
return;
}
+ if (!projectState.statePermitsWrite()) {
+ reject(cmd, "project state does not permit write");
+ return;
+ }
}
RevWalk walk = rp.getRevWalk();
@@ -1830,7 +1856,8 @@
logDebug("Creating new change for {} even though it is already tracked", name);
}
- if (!validCommit(rp.getRevWalk(), magicBranch.perm, magicBranch.dest, magicBranch.cmd, c)) {
+ if (!validCommit(
+ rp.getRevWalk(), magicBranch.perm, magicBranch.dest, magicBranch.cmd, c, null)) {
// Not a change the user can propose? Abort as early as possible.
newChanges = Collections.emptyList();
logDebug("Aborting early due to invalid commit");
@@ -2133,18 +2160,13 @@
}
private void setChangeId(int id) {
- boolean privateByDefault =
- projectCache.get(project.getNameKey()).is(BooleanProjectConfig.PRIVATE_BY_DEFAULT);
changeId = new Change.Id(id);
ins =
changeInserterFactory
.create(changeId, commit, refName)
.setTopic(magicBranch.topic)
- .setPrivate(
- magicBranch.draft
- || magicBranch.isPrivate
- || (privateByDefault && !magicBranch.removePrivate))
+ .setPrivate(setChangeAsPrivate)
.setWorkInProgress(magicBranch.workInProgress)
// Changes already validated in validateNewCommits.
.setValidate(false);
@@ -2383,6 +2405,10 @@
return false;
}
+ if (!projectState.statePermitsWrite()) {
+ reject(inputCommand, "cannot add patch set to " + ontoChange + ".");
+ return false;
+ }
if (change.getStatus().isClosed()) {
reject(inputCommand, "change " + ontoChange + " closed");
return false;
@@ -2409,7 +2435,7 @@
}
PermissionBackend.ForRef perm = permissions.ref(change.getDest().get());
- if (!validCommit(rp.getRevWalk(), perm, change.getDest(), inputCommand, newCommit)) {
+ if (!validCommit(rp.getRevWalk(), perm, change.getDest(), inputCommand, newCommit, change)) {
return false;
}
rp.getRevWalk().parseBody(priorCommit);
@@ -2773,7 +2799,7 @@
}
if (existing.keySet().contains(c)) {
continue;
- } else if (!validCommit(walk, perm, branch, cmd, c)) {
+ } else if (!validCommit(walk, perm, branch, cmd, c, null)) {
break;
}
@@ -2795,7 +2821,8 @@
PermissionBackend.ForRef perm,
Branch.NameKey branch,
ReceiveCommand cmd,
- ObjectId id)
+ ObjectId id,
+ @Nullable Change change)
throws IOException {
if (validCommits.contains(id)) {
@@ -2813,9 +2840,10 @@
&& magicBranch.merged;
CommitValidators validators =
isMerged
- ? commitValidatorsFactory.forMergedCommits(perm, user.asIdentifiedUser())
+ ? commitValidatorsFactory.forMergedCommits(
+ project.getNameKey(), perm, user.asIdentifiedUser())
: commitValidatorsFactory.forReceiveCommits(
- perm, branch, user.asIdentifiedUser(), sshInfo, repo, rw);
+ perm, branch, user.asIdentifiedUser(), sshInfo, repo, rw, change);
messages.addAll(validators.validate(receiveEvent));
} catch (CommitValidationException e) {
logDebug("Commit validation failed on {}", c.name());
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveConfig.java b/java/com/google/gerrit/server/git/receive/ReceiveConfig.java
index 89158d3..cdbf310 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveConfig.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveConfig.java
@@ -28,6 +28,7 @@
final boolean checkMagicRefs;
final boolean checkReferencedObjectsAreReachable;
final int maxBatchCommits;
+ final boolean disablePrivateChanges;
private final int systemMaxBatchChanges;
private final AccountLimits.Factory limitsFactory;
@@ -38,6 +39,7 @@
config.getBoolean("receive", null, "checkReferencedObjectsAreReachable", true);
maxBatchCommits = config.getInt("receive", null, "maxBatchCommits", 10000);
systemMaxBatchChanges = config.getInt("receive", "maxBatchChanges", 0);
+ disablePrivateChanges = config.getBoolean("change", null, "disablePrivateChanges", false);
this.limitsFactory = limitsFactory;
}
diff --git a/java/com/google/gerrit/server/git/strategy/CherryPick.java b/java/com/google/gerrit/server/git/strategy/CherryPick.java
index 77aa950..7367a92 100644
--- a/java/com/google/gerrit/server/git/strategy/CherryPick.java
+++ b/java/com/google/gerrit/server/git/strategy/CherryPick.java
@@ -15,10 +15,12 @@
package com.google.gerrit.server.git.strategy;
import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.gerrit.server.git.strategy.CommitMergeStatus.EMPTY_COMMIT;
import static com.google.gerrit.server.git.strategy.CommitMergeStatus.SKIPPED_IDENTICAL_TREE;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.extensions.restapi.MergeConflictException;
+import com.google.gerrit.reviewdb.client.BooleanProjectConfig;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetInfo;
import com.google.gerrit.server.ChangeUtil;
@@ -123,6 +125,10 @@
toMerge.setStatusCode(CommitMergeStatus.PATH_CONFLICT);
return;
} catch (MergeIdenticalTreeException mie) {
+ if (args.project.is(BooleanProjectConfig.REJECT_EMPTY_COMMIT)) {
+ toMerge.setStatusCode(EMPTY_COMMIT);
+ return;
+ }
toMerge.setStatusCode(SKIPPED_IDENTICAL_TREE);
return;
}
diff --git a/java/com/google/gerrit/server/git/strategy/CommitMergeStatus.java b/java/com/google/gerrit/server/git/strategy/CommitMergeStatus.java
index e5c253d..634c909 100644
--- a/java/com/google/gerrit/server/git/strategy/CommitMergeStatus.java
+++ b/java/com/google/gerrit/server/git/strategy/CommitMergeStatus.java
@@ -60,7 +60,12 @@
NOT_FAST_FORWARD(
"Project policy requires all submissions to be a fast-forward.\n"
+ "\n"
- + "Please rebase the change locally and upload again for review.");
+ + "Please rebase the change locally and upload again for review."),
+
+ EMPTY_COMMIT(
+ "Change could not be merged because the commit is empty.\n"
+ + "\n"
+ + "Project policy requires all commits to contain modifications to at least one file.");
private final String message;
diff --git a/java/com/google/gerrit/server/git/strategy/FastForwardOp.java b/java/com/google/gerrit/server/git/strategy/FastForwardOp.java
index a3b10cb..50c75ef 100644
--- a/java/com/google/gerrit/server/git/strategy/FastForwardOp.java
+++ b/java/com/google/gerrit/server/git/strategy/FastForwardOp.java
@@ -14,6 +14,9 @@
package com.google.gerrit.server.git.strategy;
+import static com.google.gerrit.server.git.strategy.CommitMergeStatus.EMPTY_COMMIT;
+
+import com.google.gerrit.reviewdb.client.BooleanProjectConfig;
import com.google.gerrit.server.git.CodeReviewCommit;
import com.google.gerrit.server.git.IntegrationException;
import com.google.gerrit.server.update.RepoContext;
@@ -25,6 +28,12 @@
@Override
protected void updateRepoImpl(RepoContext ctx) throws IntegrationException {
+ if (args.project.is(BooleanProjectConfig.REJECT_EMPTY_COMMIT)
+ && toMerge.getTree().equals(toMerge.getParent(0).getTree())) {
+ toMerge.setStatusCode(EMPTY_COMMIT);
+ return;
+ }
+
args.mergeTip.moveTipTo(toMerge, toMerge);
}
}
diff --git a/java/com/google/gerrit/server/git/strategy/MergeOneOp.java b/java/com/google/gerrit/server/git/strategy/MergeOneOp.java
index 3c3812d..b6d97b9 100644
--- a/java/com/google/gerrit/server/git/strategy/MergeOneOp.java
+++ b/java/com/google/gerrit/server/git/strategy/MergeOneOp.java
@@ -14,6 +14,9 @@
package com.google.gerrit.server.git.strategy;
+import static com.google.gerrit.server.git.strategy.CommitMergeStatus.EMPTY_COMMIT;
+
+import com.google.gerrit.reviewdb.client.BooleanProjectConfig;
import com.google.gerrit.server.git.CodeReviewCommit;
import com.google.gerrit.server.git.IntegrationException;
import com.google.gerrit.server.update.RepoContext;
@@ -47,6 +50,11 @@
args.destBranch,
args.mergeTip.getCurrentTip(),
toMerge);
+ if (args.project.is(BooleanProjectConfig.REJECT_EMPTY_COMMIT)
+ && merged.getTree().equals(merged.getParent(0).getTree())) {
+ toMerge.setStatusCode(EMPTY_COMMIT);
+ return;
+ }
args.mergeTip.moveTipTo(amendGitlink(merged), toMerge);
}
}
diff --git a/java/com/google/gerrit/server/git/strategy/RebaseSubmitStrategy.java b/java/com/google/gerrit/server/git/strategy/RebaseSubmitStrategy.java
index a3e4e16..80107e8 100644
--- a/java/com/google/gerrit/server/git/strategy/RebaseSubmitStrategy.java
+++ b/java/com/google/gerrit/server/git/strategy/RebaseSubmitStrategy.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.git.strategy;
import static com.google.common.base.Preconditions.checkState;
+import static com.google.gerrit.server.git.strategy.CommitMergeStatus.EMPTY_COMMIT;
import static com.google.gerrit.server.git.strategy.CommitMergeStatus.SKIPPED_IDENTICAL_TREE;
import com.google.common.collect.ImmutableList;
@@ -124,6 +125,12 @@
if (args.mergeUtil.canFastForward(
args.mergeSorter, args.mergeTip.getCurrentTip(), args.rw, toMerge)) {
if (!rebaseAlways) {
+ if (args.project.is(BooleanProjectConfig.REJECT_EMPTY_COMMIT)
+ && toMerge.getTree().equals(toMerge.getParent(0).getTree())) {
+ toMerge.setStatusCode(EMPTY_COMMIT);
+ return;
+ }
+
args.mergeTip.moveTipTo(amendGitlink(toMerge), toMerge);
toMerge.setStatusCode(CommitMergeStatus.CLEAN_MERGE);
acceptMergeTip(args.mergeTip);
@@ -192,6 +199,11 @@
newCommit = args.rw.parseCommit(rebaseOp.getRebasedCommit());
newPatchSetId = rebaseOp.getPatchSetId();
}
+ if (args.project.is(BooleanProjectConfig.REJECT_EMPTY_COMMIT)
+ && newCommit.getTree().equals(newCommit.getParent(0).getTree())) {
+ toMerge.setStatusCode(EMPTY_COMMIT);
+ return;
+ }
newCommit = amendGitlink(newCommit);
newCommit.copyFrom(toMerge);
newCommit.setPatchsetId(newPatchSetId);
diff --git a/java/com/google/gerrit/server/git/strategy/SubmitDryRun.java b/java/com/google/gerrit/server/git/strategy/SubmitDryRun.java
index 3a954fb..585361c 100644
--- a/java/com/google/gerrit/server/git/strategy/SubmitDryRun.java
+++ b/java/com/google/gerrit/server/git/strategy/SubmitDryRun.java
@@ -133,6 +133,7 @@
return RebaseIfNecessary.dryRun(args, repo, tipCommit, toMergeCommit);
case REBASE_ALWAYS:
return RebaseAlways.dryRun(args, repo, tipCommit, toMergeCommit);
+ case INHERIT:
default:
String errorMsg = "No submit strategy for: " + submitType;
log.error(errorMsg);
diff --git a/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java b/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java
index 7678623..8600322 100644
--- a/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java
+++ b/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java
@@ -96,6 +96,7 @@
return new RebaseIfNecessary(args);
case REBASE_ALWAYS:
return new RebaseAlways(args);
+ case INHERIT:
default:
String errorMsg = "No submit strategy for: " + submitType;
log.error(errorMsg);
diff --git a/java/com/google/gerrit/server/git/strategy/SubmitStrategyListener.java b/java/com/google/gerrit/server/git/strategy/SubmitStrategyListener.java
index 57094af..271e392 100644
--- a/java/com/google/gerrit/server/git/strategy/SubmitStrategyListener.java
+++ b/java/com/google/gerrit/server/git/strategy/SubmitStrategyListener.java
@@ -128,6 +128,7 @@
case CANNOT_CHERRY_PICK_ROOT:
case CANNOT_REBASE_ROOT:
case NOT_FAST_FORWARD:
+ case EMPTY_COMMIT:
// TODO(dborowitz): Reformat these messages to be more appropriate for
// short problem descriptions.
commitStatus.problem(id, CharMatcher.is('\n').collapseFrom(s.getMessage(), ' '));
diff --git a/java/com/google/gerrit/server/git/strategy/SubmitStrategyOp.java b/java/com/google/gerrit/server/git/strategy/SubmitStrategyOp.java
index 9a362d4..bd095ef 100644
--- a/java/com/google/gerrit/server/git/strategy/SubmitStrategyOp.java
+++ b/java/com/google/gerrit/server/git/strategy/SubmitStrategyOp.java
@@ -465,6 +465,7 @@
case REBASE_IF_NECESSARY:
case REBASE_ALWAYS:
return message(ctx, commit, CommitMergeStatus.CLEAN_REBASE);
+ case INHERIT:
default:
throw new IllegalStateException(
"unexpected submit type "
diff --git a/java/com/google/gerrit/server/git/validators/AccountValidator.java b/java/com/google/gerrit/server/git/validators/AccountValidator.java
index 0f89413..bba49ea 100644
--- a/java/com/google/gerrit/server/git/validators/AccountValidator.java
+++ b/java/com/google/gerrit/server/git/validators/AccountValidator.java
@@ -14,11 +14,14 @@
package com.google.gerrit.server.git.validators;
+import static java.util.stream.Collectors.toSet;
+
import com.google.common.collect.ImmutableList;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountConfig;
+import com.google.gerrit.server.git.ValidationError;
import com.google.gerrit.server.mail.send.OutgoingEmailValidator;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -28,6 +31,7 @@
import java.util.Optional;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevWalk;
public class AccountValidator {
@@ -42,20 +46,21 @@
}
public List<String> validate(
- Account.Id accountId, RevWalk rw, @Nullable ObjectId oldId, ObjectId newId)
+ Account.Id accountId, Repository repo, RevWalk rw, @Nullable ObjectId oldId, ObjectId newId)
throws IOException {
Optional<Account> oldAccount = Optional.empty();
if (oldId != null && !ObjectId.zeroId().equals(oldId)) {
try {
- oldAccount = loadAccount(accountId, rw, oldId);
+ oldAccount = loadAccount(accountId, repo, rw, oldId, null);
} catch (ConfigInvalidException e) {
// ignore, maybe the new commit is repairing it now
}
}
+ List<String> messages = new ArrayList<>();
Optional<Account> newAccount;
try {
- newAccount = loadAccount(accountId, rw, newId);
+ newAccount = loadAccount(accountId, repo, rw, newId, messages);
} catch (ConfigInvalidException e) {
return ImmutableList.of(
String.format(
@@ -67,7 +72,6 @@
return ImmutableList.of(String.format("account '%s' does not exist", accountId.get()));
}
- List<String> messages = new ArrayList<>();
if (accountId.equals(self.get().getAccountId()) && !newAccount.get().isActive()) {
messages.add("cannot deactivate own account");
}
@@ -87,11 +91,24 @@
return ImmutableList.copyOf(messages);
}
- private Optional<Account> loadAccount(Account.Id accountId, RevWalk rw, ObjectId commit)
+ private Optional<Account> loadAccount(
+ Account.Id accountId,
+ Repository repo,
+ RevWalk rw,
+ ObjectId commit,
+ @Nullable List<String> messages)
throws IOException, ConfigInvalidException {
rw.reset();
- AccountConfig accountConfig = new AccountConfig(null, accountId);
- accountConfig.load(rw, commit);
+ AccountConfig accountConfig = new AccountConfig(accountId, repo);
+ accountConfig.setEagerParsing(true).load(rw, commit);
+ if (messages != null) {
+ messages.addAll(
+ accountConfig
+ .getValidationErrors()
+ .stream()
+ .map(ValidationError::getMessage)
+ .collect(toSet()));
+ }
return accountConfig.getLoadedAccount();
}
}
diff --git a/java/com/google/gerrit/server/git/validators/CommitValidators.java b/java/com/google/gerrit/server/git/validators/CommitValidators.java
index fc280c2..05ca98b 100644
--- a/java/com/google/gerrit/server/git/validators/CommitValidators.java
+++ b/java/com/google/gerrit/server/git/validators/CommitValidators.java
@@ -19,6 +19,7 @@
import static com.google.gerrit.reviewdb.client.RefNames.REFS_CONFIG;
import static java.util.stream.Collectors.toList;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.CharMatcher;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.common.FooterConstants;
@@ -30,11 +31,13 @@
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.BooleanProjectConfig;
import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Branch.NameKey;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.account.WatchConfig;
import com.google.gerrit.server.account.externalids.ExternalIdsConsistencyChecker;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.AllUsersName;
@@ -42,9 +45,11 @@
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.events.CommitReceivedEvent;
import com.google.gerrit.server.git.BanCommit;
+import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.git.ValidationError;
import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackend.ForRef;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.permissions.RefPermission;
import com.google.gerrit.server.project.ProjectCache;
@@ -85,6 +90,7 @@
private final PersonIdent gerritIdent;
private final String canonicalWebUrl;
private final DynamicSet<CommitValidationListener> pluginValidators;
+ private final GitRepositoryManager repoManager;
private final AllUsersName allUsers;
private final AllProjectsName allProjects;
private final ExternalIdsConsistencyChecker externalIdsConsistencyChecker;
@@ -98,6 +104,7 @@
@CanonicalWebUrl @Nullable String canonicalWebUrl,
@GerritServerConfig Config cfg,
DynamicSet<CommitValidationListener> pluginValidators,
+ GitRepositoryManager repoManager,
AllUsersName allUsers,
AllProjectsName allProjects,
ExternalIdsConsistencyChecker externalIdsConsistencyChecker,
@@ -106,6 +113,7 @@
this.gerritIdent = gerritIdent;
this.canonicalWebUrl = canonicalWebUrl;
this.pluginValidators = pluginValidators;
+ this.repoManager = repoManager;
this.allUsers = allUsers;
this.allProjects = allProjects;
this.externalIdsConsistencyChecker = externalIdsConsistencyChecker;
@@ -121,54 +129,67 @@
IdentifiedUser user,
SshInfo sshInfo,
Repository repo,
- RevWalk rw)
+ RevWalk rw,
+ @Nullable Change change)
throws IOException {
NoteMap rejectCommits = BanCommit.loadRejectCommitsMap(repo, rw);
ProjectState projectState = projectCache.checkedGet(branch.getParentKey());
return new CommitValidators(
ImmutableList.of(
new UploadMergesPermissionValidator(perm),
+ new ProjectStateValidationListener(projectState),
new AmendedGerritMergeCommitValidationListener(perm, gerritIdent),
new AuthorUploaderValidator(user, perm, canonicalWebUrl),
new CommitterUploaderValidator(user, perm, canonicalWebUrl),
new SignedOffByValidator(user, perm, projectState),
new ChangeIdValidator(
- projectState, user, canonicalWebUrl, installCommitMsgHookCommand, sshInfo),
+ projectState,
+ user,
+ canonicalWebUrl,
+ installCommitMsgHookCommand,
+ sshInfo,
+ change),
new ConfigValidator(branch, user, rw, allUsers, allProjects),
new BannedCommitsValidator(rejectCommits),
new PluginCommitValidationListener(pluginValidators),
new ExternalIdUpdateListener(allUsers, externalIdsConsistencyChecker),
- new AccountCommitValidator(allUsers, accountValidator),
+ new AccountCommitValidator(repoManager, allUsers, accountValidator),
new GroupCommitValidator(allUsers)));
}
public CommitValidators forGerritCommits(
- PermissionBackend.ForRef perm,
- Branch.NameKey branch,
+ ForRef perm,
+ NameKey branch,
IdentifiedUser user,
SshInfo sshInfo,
- RevWalk rw)
+ RevWalk rw,
+ @Nullable Change change)
throws IOException {
+ ProjectState projectState = projectCache.checkedGet(branch.getParentKey());
return new CommitValidators(
ImmutableList.of(
new UploadMergesPermissionValidator(perm),
+ new ProjectStateValidationListener(projectState),
new AmendedGerritMergeCommitValidationListener(perm, gerritIdent),
new AuthorUploaderValidator(user, perm, canonicalWebUrl),
new SignedOffByValidator(user, perm, projectCache.checkedGet(branch.getParentKey())),
new ChangeIdValidator(
- projectCache.checkedGet(branch.getParentKey()),
+ projectState,
user,
canonicalWebUrl,
installCommitMsgHookCommand,
- sshInfo),
+ sshInfo,
+ change),
new ConfigValidator(branch, user, rw, allUsers, allProjects),
new PluginCommitValidationListener(pluginValidators),
new ExternalIdUpdateListener(allUsers, externalIdsConsistencyChecker),
- new AccountCommitValidator(allUsers, accountValidator),
+ new AccountCommitValidator(repoManager, allUsers, accountValidator),
new GroupCommitValidator(allUsers)));
}
- public CommitValidators forMergedCommits(PermissionBackend.ForRef perm, IdentifiedUser user) {
+ public CommitValidators forMergedCommits(
+ Project.NameKey project, PermissionBackend.ForRef perm, IdentifiedUser user)
+ throws IOException {
// Generally only include validators that are based on permissions of the
// user creating a change for a merged commit; generally exclude
// validators that would require amending the change in order to correct.
@@ -185,6 +206,7 @@
return new CommitValidators(
ImmutableList.of(
new UploadMergesPermissionValidator(perm),
+ new ProjectStateValidationListener(projectCache.checkedGet(project)),
new AuthorUploaderValidator(user, perm, canonicalWebUrl),
new CommitterUploaderValidator(user, perm, canonicalWebUrl)));
}
@@ -225,6 +247,15 @@
"[%s] invalid "
+ FooterConstants.CHANGE_ID.getName()
+ " line format in commit message footer";
+
+ @VisibleForTesting
+ public static final String CHANGE_ID_MISMATCH_MSG =
+ "[%s] "
+ + FooterConstants.CHANGE_ID.getName()
+ + " in commit message footer does not match"
+ + FooterConstants.CHANGE_ID.getName()
+ + " of target change";
+
private static final Pattern CHANGE_ID = Pattern.compile(CHANGE_ID_PATTERN);
private final ProjectState projectState;
@@ -232,18 +263,21 @@
private final String installCommitMsgHookCommand;
private final SshInfo sshInfo;
private final IdentifiedUser user;
+ private final Change change;
public ChangeIdValidator(
ProjectState projectState,
IdentifiedUser user,
String canonicalWebUrl,
String installCommitMsgHookCommand,
- SshInfo sshInfo) {
+ SshInfo sshInfo,
+ Change change) {
this.projectState = projectState;
this.canonicalWebUrl = canonicalWebUrl;
this.installCommitMsgHookCommand = installCommitMsgHookCommand;
this.sshInfo = sshInfo;
this.user = user;
+ this.change = change;
}
@Override
@@ -283,7 +317,12 @@
messages.add(getMissingChangeIdErrorMsg(errMsg, receiveEvent.commit));
throw new CommitValidationException(errMsg, messages);
}
+ if (change != null && !v.equals(change.getKey().get())) {
+ String errMsg = String.format(CHANGE_ID_MISMATCH_MSG, sha1);
+ throw new CommitValidationException(errMsg);
+ }
}
+
return Collections.emptyList();
}
@@ -418,34 +457,6 @@
}
}
- if (allUsers.equals(branch.getParentKey()) && RefNames.isRefsUsers(branch.get())) {
- List<CommitValidationMessage> messages = new ArrayList<>();
- Account.Id accountId = Account.Id.fromRef(branch.get());
- if (accountId != null) {
- try {
- WatchConfig wc = new WatchConfig(accountId);
- wc.load(rw, receiveEvent.command.getNewId());
- if (!wc.getValidationErrors().isEmpty()) {
- addError("Invalid project configuration:", messages);
- for (ValidationError err : wc.getValidationErrors()) {
- addError(" " + err.getMessage(), messages);
- }
- throw new ConfigInvalidException("invalid watch configuration");
- }
- } catch (IOException | ConfigInvalidException e) {
- log.error(
- "User "
- + user.getUserName()
- + " tried to push an invalid watch configuration "
- + receiveEvent.command.getNewId().name()
- + " for account "
- + accountId.get(),
- e);
- throw new CommitValidationException("invalid watch configuration", messages);
- }
- }
- }
-
return Collections.emptyList();
}
}
@@ -729,10 +740,15 @@
}
public static class AccountCommitValidator implements CommitValidationListener {
+ private final GitRepositoryManager repoManager;
private final AllUsersName allUsers;
private final AccountValidator accountValidator;
- public AccountCommitValidator(AllUsersName allUsers, AccountValidator accountValidator) {
+ public AccountCommitValidator(
+ GitRepositoryManager repoManager,
+ AllUsersName allUsers,
+ AccountValidator accountValidator) {
+ this.repoManager = repoManager;
this.allUsers = allUsers;
this.accountValidator = accountValidator;
}
@@ -755,10 +771,11 @@
return Collections.emptyList();
}
- try {
+ try (Repository repo = repoManager.openRepository(allUsers)) {
List<String> errorMessages =
accountValidator.validate(
accountId,
+ repo,
receiveEvent.revWalk,
receiveEvent.command.getOldId(),
receiveEvent.commit);
@@ -808,6 +825,24 @@
}
}
+ /** Rejects updates to projects that don't allow writes. */
+ public static class ProjectStateValidationListener implements CommitValidationListener {
+ private final ProjectState projectState;
+
+ public ProjectStateValidationListener(ProjectState projectState) {
+ this.projectState = projectState;
+ }
+
+ @Override
+ public List<CommitValidationMessage> onCommitReceived(CommitReceivedEvent receiveEvent)
+ throws CommitValidationException {
+ if (projectState.statePermitsWrite()) {
+ return Collections.emptyList();
+ }
+ throw new CommitValidationException("project state does not permit write");
+ }
+ }
+
private static CommitValidationMessage invalidEmail(
RevCommit c,
String type,
diff --git a/java/com/google/gerrit/server/git/validators/MergeValidators.java b/java/com/google/gerrit/server/git/validators/MergeValidators.java
index 22bb05f..5b20ff6 100644
--- a/java/com/google/gerrit/server/git/validators/MergeValidators.java
+++ b/java/com/google/gerrit/server/git/validators/MergeValidators.java
@@ -285,7 +285,7 @@
}
try (RevWalk rw = new RevWalk(repo)) {
- List<String> errorMessages = accountValidator.validate(accountId, rw, null, commit);
+ List<String> errorMessages = accountValidator.validate(accountId, repo, rw, null, commit);
if (!errorMessages.isEmpty()) {
throw new MergeValidationException(
"invalid account configuration: " + Joiner.on("; ").join(errorMessages));
diff --git a/java/com/google/gerrit/server/group/db/GroupNameNotes.java b/java/com/google/gerrit/server/group/db/GroupNameNotes.java
index 1e6ebdd..87aee8e 100644
--- a/java/com/google/gerrit/server/group/db/GroupNameNotes.java
+++ b/java/com/google/gerrit/server/group/db/GroupNameNotes.java
@@ -21,8 +21,10 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
+import com.google.common.collect.HashMultiset;
import com.google.common.collect.ImmutableBiMap;
-import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Multiset;
import com.google.common.hash.Hashing;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.GroupReference;
@@ -32,11 +34,9 @@
import com.google.gwtorm.server.OrmDuplicateKeyException;
import java.io.IOException;
import java.util.Collection;
-import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
-import java.util.Set;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.BatchRefUpdate;
import org.eclipse.jgit.lib.CommitBuilder;
@@ -68,8 +68,80 @@
@VisibleForTesting
static final String UNIQUE_REF_ERROR = "GroupReference collection must contain unique references";
- public static void updateGroupNames(
- Repository allUsersRepo,
+ public static GroupNameNotes forRename(
+ Repository repository,
+ AccountGroup.UUID groupUuid,
+ AccountGroup.NameKey oldName,
+ AccountGroup.NameKey newName)
+ throws IOException, ConfigInvalidException, OrmDuplicateKeyException {
+ checkNotNull(oldName);
+ checkNotNull(newName);
+
+ GroupNameNotes groupNameNotes = new GroupNameNotes(groupUuid, oldName, newName);
+ groupNameNotes.load(repository);
+ groupNameNotes.ensureNewNameIsNotUsed();
+ return groupNameNotes;
+ }
+
+ public static GroupNameNotes forNewGroup(
+ Repository repository, AccountGroup.UUID groupUuid, AccountGroup.NameKey groupName)
+ throws IOException, ConfigInvalidException, OrmDuplicateKeyException {
+ checkNotNull(groupName);
+
+ GroupNameNotes groupNameNotes = new GroupNameNotes(groupUuid, null, groupName);
+ groupNameNotes.load(repository);
+ groupNameNotes.ensureNewNameIsNotUsed();
+ return groupNameNotes;
+ }
+
+ public static Optional<GroupReference> loadGroup(
+ Repository repository, AccountGroup.NameKey groupName)
+ throws IOException, ConfigInvalidException {
+ Ref ref = repository.exactRef(RefNames.REFS_GROUPNAMES);
+ if (ref == null) {
+ return Optional.empty();
+ }
+
+ try (RevWalk revWalk = new RevWalk(repository);
+ ObjectReader reader = revWalk.getObjectReader()) {
+ RevCommit notesCommit = revWalk.parseCommit(ref.getObjectId());
+ NoteMap noteMap = NoteMap.read(reader, notesCommit);
+ ObjectId noteDataBlobId = noteMap.get(getNoteKey(groupName));
+ if (noteDataBlobId == null) {
+ return Optional.empty();
+ }
+ return Optional.of(getGroupReference(reader, noteDataBlobId));
+ }
+ }
+
+ public static ImmutableList<GroupReference> loadAllGroups(Repository repository)
+ throws IOException, ConfigInvalidException {
+ Ref ref = repository.exactRef(RefNames.REFS_GROUPNAMES);
+ if (ref == null) {
+ return ImmutableList.of();
+ }
+ try (RevWalk revWalk = new RevWalk(repository);
+ ObjectReader reader = revWalk.getObjectReader()) {
+ RevCommit notesCommit = revWalk.parseCommit(ref.getObjectId());
+ NoteMap noteMap = NoteMap.read(reader, notesCommit);
+
+ Multiset<GroupReference> groupReferences = HashMultiset.create();
+ for (Note note : noteMap) {
+ GroupReference groupReference = getGroupReference(reader, note.getData());
+ int numOfOccurrences = groupReferences.add(groupReference, 1);
+ if (numOfOccurrences > 1) {
+ GroupsNoteDbConsistencyChecker.logConsistencyProblemAsWarning(
+ "The UUID of group %s (%s) is duplicate in group name notes",
+ groupReference.getName(), groupReference.getUUID());
+ }
+ }
+
+ return ImmutableList.copyOf(groupReferences);
+ }
+ }
+
+ public static void updateAllGroups(
+ Repository repository,
ObjectInserter inserter,
BatchRefUpdate bru,
Collection<GroupReference> groupReferences,
@@ -82,7 +154,7 @@
RevWalk rw = new RevWalk(reader)) {
// Always start from an empty map, discarding old notes.
NoteMap noteMap = NoteMap.newEmptyMap();
- Ref ref = allUsersRepo.exactRef(RefNames.REFS_GROUPNAMES);
+ Ref ref = repository.exactRef(RefNames.REFS_GROUPNAMES);
RevCommit oldCommit = ref != null ? rw.parseCommit(ref.getObjectId()) : null;
for (Map.Entry<AccountGroup.UUID, String> e : biMap.entrySet()) {
@@ -124,8 +196,8 @@
}
private final AccountGroup.UUID groupUuid;
- private final Optional<AccountGroup.NameKey> oldGroupName;
- private final Optional<AccountGroup.NameKey> newGroupName;
+ private Optional<AccountGroup.NameKey> oldGroupName;
+ private Optional<AccountGroup.NameKey> newGroupName;
private boolean nameConflicting;
@@ -144,77 +216,6 @@
}
}
- public static GroupNameNotes loadForRename(
- Repository repository,
- AccountGroup.UUID groupUuid,
- AccountGroup.NameKey oldName,
- AccountGroup.NameKey newName)
- throws IOException, ConfigInvalidException, OrmDuplicateKeyException {
- checkNotNull(oldName);
- checkNotNull(newName);
-
- GroupNameNotes groupNameNotes = new GroupNameNotes(groupUuid, oldName, newName);
- groupNameNotes.load(repository);
- groupNameNotes.ensureNewNameIsNotUsed();
- return groupNameNotes;
- }
-
- public static GroupNameNotes loadForNewGroup(
- Repository repository, AccountGroup.UUID groupUuid, AccountGroup.NameKey groupName)
- throws IOException, ConfigInvalidException, OrmDuplicateKeyException {
- checkNotNull(groupName);
-
- GroupNameNotes groupNameNotes = new GroupNameNotes(groupUuid, null, groupName);
- groupNameNotes.load(repository);
- groupNameNotes.ensureNewNameIsNotUsed();
- return groupNameNotes;
- }
-
- public static ImmutableSet<GroupReference> loadAllGroupReferences(Repository repository)
- throws IOException, ConfigInvalidException {
- Ref ref = repository.exactRef(RefNames.REFS_GROUPNAMES);
- if (ref == null) {
- return ImmutableSet.of();
- }
- try (RevWalk revWalk = new RevWalk(repository);
- ObjectReader reader = revWalk.getObjectReader()) {
- RevCommit notesCommit = revWalk.parseCommit(ref.getObjectId());
- NoteMap noteMap = NoteMap.read(reader, notesCommit);
-
- Set<GroupReference> groupReferences = new LinkedHashSet<>();
- for (Note note : noteMap) {
- GroupReference groupReference = getGroupReference(reader, note.getData());
- boolean result = groupReferences.add(groupReference);
- if (!result) {
- GroupsNoteDbConsistencyChecker.logConsistencyProblemAsWarning(
- "The UUID of group %s (%s) is duplicate in group name notes",
- groupReference.getName(), groupReference.getUUID());
- }
- }
-
- return ImmutableSet.copyOf(groupReferences);
- }
- }
-
- public static Optional<GroupReference> loadOneGroupReference(
- Repository allUsersRepo, String groupName) throws IOException, ConfigInvalidException {
- Ref ref = allUsersRepo.exactRef(RefNames.REFS_GROUPNAMES);
- if (ref == null) {
- return Optional.empty();
- }
-
- try (RevWalk revWalk = new RevWalk(allUsersRepo);
- ObjectReader reader = revWalk.getObjectReader()) {
- RevCommit notesCommit = revWalk.parseCommit(ref.getObjectId());
- NoteMap noteMap = NoteMap.read(reader, notesCommit);
- ObjectId noteDataBlobId = noteMap.get(getNoteKey(new AccountGroup.NameKey(groupName)));
- if (noteDataBlobId == null) {
- return Optional.empty();
- }
- return Optional.of(getGroupReference(reader, noteDataBlobId));
- }
- }
-
@Override
protected String getRefName() {
return RefNames.REFS_GROUPNAMES;
@@ -278,6 +279,9 @@
commit.setTreeId(noteMap.writeTree(inserter));
commit.setMessage(getCommitMessage());
+ oldGroupName = Optional.empty();
+ newGroupName = Optional.empty();
+
return true;
}
@@ -311,8 +315,7 @@
return config.toText();
}
- @VisibleForTesting
- public static GroupReference getGroupReference(ObjectReader reader, ObjectId noteDataBlobId)
+ private static GroupReference getGroupReference(ObjectReader reader, ObjectId noteDataBlobId)
throws IOException, ConfigInvalidException {
byte[] noteData = reader.open(noteDataBlobId, OBJ_BLOB).getCachedBytes();
return getFromNoteData(noteData);
diff --git a/java/com/google/gerrit/server/group/db/Groups.java b/java/com/google/gerrit/server/group/db/Groups.java
index f3232be..d0ea9ca 100644
--- a/java/com/google/gerrit/server/group/db/Groups.java
+++ b/java/com/google/gerrit/server/group/db/Groups.java
@@ -192,7 +192,7 @@
throws OrmException, IOException, ConfigInvalidException {
if (groupsMigration.readFromNoteDb()) {
try (Repository allUsersRepo = repoManager.openRepository(allUsersName)) {
- return GroupNameNotes.loadAllGroupReferences(allUsersRepo).stream();
+ return GroupNameNotes.loadAllGroups(allUsersRepo).stream();
}
}
@@ -298,10 +298,9 @@
.filter(groupUuid -> !AccountGroup.isInternalGroup(groupUuid));
}
- private Stream<AccountGroup.UUID> getExternalGroupsFromNoteDb(Repository allUsersRepo)
+ private static Stream<AccountGroup.UUID> getExternalGroupsFromNoteDb(Repository allUsersRepo)
throws IOException, ConfigInvalidException {
- ImmutableSet<GroupReference> allInternalGroups =
- GroupNameNotes.loadAllGroupReferences(allUsersRepo);
+ ImmutableList<GroupReference> allInternalGroups = GroupNameNotes.loadAllGroups(allUsersRepo);
ImmutableSet.Builder<AccountGroup.UUID> allSubgroups = ImmutableSet.builder();
for (GroupReference internalGroup : allInternalGroups) {
Optional<InternalGroup> group = getGroupFromNoteDb(allUsersRepo, internalGroup.getUUID());
diff --git a/java/com/google/gerrit/server/group/db/GroupsConsistencyChecker.java b/java/com/google/gerrit/server/group/db/GroupsConsistencyChecker.java
index 541697a..a0eae4a 100644
--- a/java/com/google/gerrit/server/group/db/GroupsConsistencyChecker.java
+++ b/java/com/google/gerrit/server/group/db/GroupsConsistencyChecker.java
@@ -20,6 +20,7 @@
import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo.ConsistencyProblemInfo;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.account.Accounts;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.config.AllUsersName;
@@ -113,7 +114,7 @@
}
for (Account.Id id : g.getMembers().asList()) {
- Account account;
+ AccountState account;
try {
account = accounts.get(id);
} catch (ConfigInvalidException e) {
diff --git a/java/com/google/gerrit/server/group/db/GroupsNoteDbConsistencyChecker.java b/java/com/google/gerrit/server/group/db/GroupsNoteDbConsistencyChecker.java
index 65ac12b..9f0cb3a 100644
--- a/java/com/google/gerrit/server/group/db/GroupsNoteDbConsistencyChecker.java
+++ b/java/com/google/gerrit/server/group/db/GroupsNoteDbConsistencyChecker.java
@@ -218,7 +218,7 @@
Repository allUsersRepo, InternalGroup group) throws IOException {
List<ConsistencyCheckInfo.ConsistencyProblemInfo> problems =
GroupsNoteDbConsistencyChecker.checkWithGroupNameNotes(
- allUsersRepo, group.getName(), group.getGroupUUID());
+ allUsersRepo, group.getNameKey(), group.getGroupUUID());
problems.forEach(GroupsNoteDbConsistencyChecker::logConsistencyProblem);
}
@@ -232,10 +232,10 @@
*/
@VisibleForTesting
static List<ConsistencyProblemInfo> checkWithGroupNameNotes(
- Repository allUsersRepo, String groupName, AccountGroup.UUID groupUUID) throws IOException {
+ Repository allUsersRepo, AccountGroup.NameKey groupName, AccountGroup.UUID groupUUID)
+ throws IOException {
try {
- Optional<GroupReference> groupRef =
- GroupNameNotes.loadOneGroupReference(allUsersRepo, groupName);
+ Optional<GroupReference> groupRef = GroupNameNotes.loadGroup(allUsersRepo, groupName);
if (!groupRef.isPresent()) {
return ImmutableList.of(
@@ -243,7 +243,6 @@
}
AccountGroup.UUID uuid = groupRef.get().getUUID();
- String name = groupRef.get().getName();
List<ConsistencyProblemInfo> problems = new ArrayList<>();
if (!Objects.equals(groupUUID, uuid)) {
@@ -253,9 +252,11 @@
groupName, groupUUID, uuid));
}
- if (!Objects.equals(groupName, name)) {
+ String name = groupName.get();
+ String actualName = groupRef.get().getName();
+ if (!Objects.equals(name, actualName)) {
problems.add(
- warning("group note of name '%s' claims to represent name of '%s'", groupName, name));
+ warning("group note of name '%s' claims to represent name of '%s'", name, actualName));
}
return problems;
} catch (ConfigInvalidException e) {
diff --git a/java/com/google/gerrit/server/group/db/GroupsUpdate.java b/java/com/google/gerrit/server/group/db/GroupsUpdate.java
index d93c8bd..fd5c453 100644
--- a/java/com/google/gerrit/server/group/db/GroupsUpdate.java
+++ b/java/com/google/gerrit/server/group/db/GroupsUpdate.java
@@ -482,7 +482,7 @@
try (Repository allUsersRepo = repoManager.openRepository(allUsersName)) {
AccountGroup.NameKey groupName = groupUpdate.getName().orElseGet(groupCreation::getNameKey);
GroupNameNotes groupNameNotes =
- GroupNameNotes.loadForNewGroup(allUsersRepo, groupCreation.getGroupUUID(), groupName);
+ GroupNameNotes.forNewGroup(allUsersRepo, groupCreation.getGroupUUID(), groupName);
GroupConfig groupConfig = GroupConfig.createForNewGroup(allUsersRepo, groupCreation);
groupConfig.setGroupUpdate(groupUpdate, this::getAccountNameEmail, this::getGroupName);
@@ -515,7 +515,7 @@
if (groupUpdate.getName().isPresent()) {
AccountGroup.NameKey oldName = originalGroup.getNameKey();
AccountGroup.NameKey newName = groupUpdate.getName().get();
- groupNameNotes = GroupNameNotes.loadForRename(allUsersRepo, groupUuid, oldName, newName);
+ groupNameNotes = GroupNameNotes.forRename(allUsersRepo, groupUuid, oldName, newName);
}
commit(allUsersRepo, groupConfig, groupNameNotes);
diff --git a/java/com/google/gerrit/server/group/db/testing/GroupTestUtil.java b/java/com/google/gerrit/server/group/db/testing/GroupTestUtil.java
index 46fd666..9197a01 100644
--- a/java/com/google/gerrit/server/group/db/testing/GroupTestUtil.java
+++ b/java/com/google/gerrit/server/group/db/testing/GroupTestUtil.java
@@ -15,14 +15,10 @@
package com.google.gerrit.server.group.db.testing;
import static com.google.common.collect.ImmutableList.toImmutableList;
-import static com.google.gerrit.server.group.db.GroupNameNotes.getGroupReference;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Streams;
-import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.extensions.common.CommitInfo;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.git.CommitUtil;
import com.google.gerrit.server.git.GitRepositoryManager;
@@ -31,29 +27,12 @@
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.notes.Note;
-import org.eclipse.jgit.notes.NoteMap;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevSort;
import org.eclipse.jgit.revwalk.RevWalk;
/** Test utilities for low-level NoteDb groups. */
public class GroupTestUtil {
- public static ImmutableMap<String, String> readNameToUuidMap(Repository repo) throws Exception {
- ImmutableMap.Builder<String, String> result = ImmutableMap.builder();
- try (RevWalk rw = new RevWalk(repo)) {
- Ref ref = repo.exactRef(RefNames.REFS_GROUPNAMES);
- if (ref != null) {
- NoteMap noteMap = NoteMap.read(rw.getObjectReader(), rw.parseCommit(ref.getObjectId()));
- for (Note note : noteMap) {
- GroupReference gr = getGroupReference(rw.getObjectReader(), note.getData());
- result.put(gr.getName(), gr.getUUID().get());
- }
- }
- }
- return result.build();
- }
-
// TODO(dborowitz): Move somewhere even more common.
public static ImmutableList<CommitInfo> log(Repository repo, String refName) throws Exception {
try (RevWalk rw = new RevWalk(repo)) {
diff --git a/java/com/google/gerrit/server/mail/send/OutgoingEmail.java b/java/com/google/gerrit/server/mail/send/OutgoingEmail.java
index ec67d9d..8eb55fa 100644
--- a/java/com/google/gerrit/server/mail/send/OutgoingEmail.java
+++ b/java/com/google/gerrit/server/mail/send/OutgoingEmail.java
@@ -121,34 +121,38 @@
Set<Address> smtpRcptToPlaintextOnly = new HashSet<>();
if (shouldSendMessage()) {
if (fromId != null) {
- final Account fromUser = args.accountCache.get(fromId).getAccount();
- GeneralPreferencesInfo senderPrefs = fromUser.getGeneralPreferencesInfo();
-
- if (senderPrefs != null && senderPrefs.getEmailStrategy() == CC_ON_OWN_COMMENTS) {
- // If we are impersonating a user, make sure they receive a CC of
- // this message so they can always review and audit what we sent
- // on their behalf to others.
- //
- add(RecipientType.CC, fromId);
- } else if (!accountsToNotify.containsValue(fromId) && rcptTo.remove(fromId)) {
- // If they don't want a copy, but we queued one up anyway,
- // drop them from the recipient lists.
- //
- removeUser(fromUser);
+ AccountState fromUser = args.accountCache.get(fromId);
+ if (fromUser != null) {
+ GeneralPreferencesInfo senderPrefs = fromUser.getGeneralPreferences();
+ if (senderPrefs != null && senderPrefs.getEmailStrategy() == CC_ON_OWN_COMMENTS) {
+ // If we are impersonating a user, make sure they receive a CC of
+ // this message so they can always review and audit what we sent
+ // on their behalf to others.
+ //
+ add(RecipientType.CC, fromId);
+ } else if (!accountsToNotify.containsValue(fromId) && rcptTo.remove(fromId)) {
+ // If they don't want a copy, but we queued one up anyway,
+ // drop them from the recipient lists.
+ //
+ removeUser(fromUser.getAccount());
+ }
}
}
// Check the preferences of all recipients. If any user has disabled
// his email notifications then drop him from recipients' list.
// In addition, check if users only want to receive plaintext email.
for (Account.Id id : rcptTo) {
- Account thisUser = args.accountCache.get(id).getAccount();
- GeneralPreferencesInfo prefs = thisUser.getGeneralPreferencesInfo();
- if (prefs == null || prefs.getEmailStrategy() == DISABLED) {
- removeUser(thisUser);
- } else if (useHtml() && prefs.getEmailFormat() == EmailFormat.PLAINTEXT) {
- removeUser(thisUser);
- smtpRcptToPlaintextOnly.add(
- new Address(thisUser.getFullName(), thisUser.getPreferredEmail()));
+ AccountState thisUser = args.accountCache.get(id);
+ if (thisUser != null) {
+ Account thisUserAccount = thisUser.getAccount();
+ GeneralPreferencesInfo prefs = thisUser.getGeneralPreferences();
+ if (prefs == null || prefs.getEmailStrategy() == DISABLED) {
+ removeUser(thisUserAccount);
+ } else if (useHtml() && prefs.getEmailFormat() == EmailFormat.PLAINTEXT) {
+ removeUser(thisUserAccount);
+ smtpRcptToPlaintextOnly.add(
+ new Address(thisUserAccount.getFullName(), thisUserAccount.getPreferredEmail()));
+ }
}
if (smtpRcptTo.isEmpty() && smtpRcptToPlaintextOnly.isEmpty()) {
return;
diff --git a/java/com/google/gerrit/server/permissions/RefPermission.java b/java/com/google/gerrit/server/permissions/RefPermission.java
index 607162e..0d4d6ff 100644
--- a/java/com/google/gerrit/server/permissions/RefPermission.java
+++ b/java/com/google/gerrit/server/permissions/RefPermission.java
@@ -35,6 +35,9 @@
/** Create a change to code review a commit. */
CREATE_CHANGE,
+ /** Create a tag. */
+ CREATE_TAG(Permission.CREATE_TAG),
+
/**
* Creates changes, then also immediately submits them during {@code push}.
*
diff --git a/java/com/google/gerrit/server/project/BooleanProjectConfigTransformations.java b/java/com/google/gerrit/server/project/BooleanProjectConfigTransformations.java
index 1f94025..3b75256 100644
--- a/java/com/google/gerrit/server/project/BooleanProjectConfigTransformations.java
+++ b/java/com/google/gerrit/server/project/BooleanProjectConfigTransformations.java
@@ -65,6 +65,9 @@
BooleanProjectConfig.MATCH_AUTHOR_TO_COMMITTER_DATE,
new Mapper(
i -> i.matchAuthorToCommitterDate, (i, v) -> i.matchAuthorToCommitterDate = v))
+ .put(
+ BooleanProjectConfig.REJECT_EMPTY_COMMIT,
+ new Mapper(i -> i.rejectEmptyCommit, (i, v) -> i.rejectEmptyCommit = v))
.build();
static {
diff --git a/java/com/google/gerrit/server/project/ChangeControl.java b/java/com/google/gerrit/server/project/ChangeControl.java
index 63dd9a9..10c04af 100644
--- a/java/com/google/gerrit/server/project/ChangeControl.java
+++ b/java/com/google/gerrit/server/project/ChangeControl.java
@@ -22,6 +22,7 @@
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.LabelFunction;
import com.google.gerrit.common.data.LabelType;
+import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRange;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.reviewdb.client.Account;
@@ -87,7 +88,7 @@
private final ChangeNotes notes;
private final PatchSetUtil patchSetUtil;
- ChangeControl(
+ private ChangeControl(
ChangeData.Factory changeDataFactory,
ApprovalsUtil approvalsUtil,
RefControl refControl,
@@ -100,53 +101,44 @@
this.patchSetUtil = patchSetUtil;
}
- ChangeControl forUser(CurrentUser who) {
+ ForChange asForChange(@Nullable ChangeData cd, @Nullable Provider<ReviewDb> db) {
+ return new ForChangeImpl(cd, db);
+ }
+
+ private ChangeControl forUser(CurrentUser who) {
if (getUser().equals(who)) {
return this;
}
return new ChangeControl(
- changeDataFactory, approvalsUtil, getRefControl().forUser(who), notes, patchSetUtil);
- }
-
- private RefControl getRefControl() {
- return refControl;
+ changeDataFactory, approvalsUtil, refControl.forUser(who), notes, patchSetUtil);
}
private CurrentUser getUser() {
- return getRefControl().getUser();
+ return refControl.getUser();
}
private ProjectControl getProjectControl() {
- return getRefControl().getProjectControl();
+ return refControl.getProjectControl();
}
private Change getChange() {
return notes.getChange();
}
- private ChangeNotes getNotes() {
- return notes;
- }
-
/** Can this user see this change? */
private boolean isVisible(ReviewDb db, @Nullable ChangeData cd) throws OrmException {
if (getChange().isPrivate() && !isPrivateVisible(db, cd)) {
return false;
}
- return isRefVisible();
- }
-
- /** Can the user see this change? Does not account for draft status */
- private boolean isRefVisible() {
- return getRefControl().isVisible();
+ return refControl.isVisible() && getProjectControl().getProject().getState().permitsRead();
}
/** Can this user abandon this change? */
private boolean canAbandon(ReviewDb db) throws OrmException {
return (isOwner() // owner (aka creator) of the change can abandon
- || getRefControl().isOwner() // branch owner can abandon
+ || refControl.isOwner() // branch owner can abandon
|| getProjectControl().isOwner() // project owner can abandon
- || getRefControl().canAbandon() // user can abandon a specific ref
+ || refControl.canPerform(Permission.ABANDON) // user can abandon a specific ref
|| getProjectControl().isAdmin())
&& !isPatchSetLocked(db);
}
@@ -156,7 +148,7 @@
switch (status) {
case NEW:
case ABANDONED:
- return (isOwner() && getRefControl().canDeleteOwnChanges())
+ return (isOwner() && refControl.canPerform(Permission.DELETE_OWN_CHANGES))
|| getProjectControl().isAdmin();
case MERGED:
default:
@@ -166,7 +158,7 @@
/** Can this user rebase this change? */
private boolean canRebase(ReviewDb db) throws OrmException {
- return (isOwner() || getRefControl().canSubmit(isOwner()) || getRefControl().canRebase())
+ return (isOwner() || refControl.canSubmit(isOwner()) || refControl.canRebase())
&& refControl.asForRef().testOrFalse(RefPermission.CREATE_CHANGE)
&& !isPatchSetLocked(db);
}
@@ -179,18 +171,18 @@
/** The range of permitted values associated with a label permission. */
private PermissionRange getRange(String permission) {
- return getRefControl().getRange(permission, isOwner());
+ return refControl.getRange(permission, isOwner());
}
/** Can this user add a patch set to this change? */
private boolean canAddPatchSet(ReviewDb db) throws OrmException {
- if (!refControl.asForRef().testOrFalse(RefPermission.CREATE_CHANGE) || isPatchSetLocked(db)) {
+ if (!(refControl.asForRef().testOrFalse(RefPermission.CREATE_CHANGE)) || isPatchSetLocked(db)) {
return false;
}
if (isOwner()) {
return true;
}
- return getRefControl().canAddPatchSet();
+ return refControl.canAddPatchSet();
}
/** Is the current patch set locked against state changes? */
@@ -201,11 +193,11 @@
for (PatchSetApproval ap :
approvalsUtil.byPatchSet(
- db, getNotes(), getUser(), getChange().currentPatchSetId(), null, null)) {
+ db, notes, getUser(), getChange().currentPatchSetId(), null, null)) {
LabelType type =
getProjectControl()
.getProjectState()
- .getLabelTypes(getNotes(), getUser())
+ .getLabelTypes(notes, getUser())
.byLabel(ap.getLabel());
if (type != null
&& ap.getValue() == 1
@@ -238,7 +230,8 @@
/** Is this user a reviewer for the change? */
private boolean isReviewer(ReviewDb db, @Nullable ChangeData cd) throws OrmException {
if (getUser().isIdentifiedUser()) {
- Collection<Account.Id> results = changeData(db, cd).reviewers().all();
+ cd = cd != null ? cd : changeDataFactory.create(db, notes);
+ Collection<Account.Id> results = cd.reviewers().all();
return results.contains(getUser().getAccountId());
}
return false;
@@ -248,19 +241,20 @@
private boolean canEditTopicName() {
if (getChange().getStatus().isOpen()) {
return isOwner() // owner (aka creator) of the change can edit topic
- || getRefControl().isOwner() // branch owner can edit topic
+ || refControl.isOwner() // branch owner can edit topic
|| getProjectControl().isOwner() // project owner can edit topic
- || getRefControl().canEditTopicName() // user can edit topic on a specific ref
+ || refControl.canPerform(
+ Permission.EDIT_TOPIC_NAME) // user can edit topic on a specific ref
|| getProjectControl().isAdmin();
}
- return getRefControl().canForceEditTopicName();
+ return refControl.canForceEditTopicName();
}
/** Can this user edit the description? */
private boolean canEditDescription() {
if (getChange().getStatus().isOpen()) {
return isOwner() // owner (aka creator) of the change can edit desc
- || getRefControl().isOwner() // branch owner can edit desc
+ || refControl.isOwner() // branch owner can edit desc
|| getProjectControl().isOwner() // project owner can edit desc
|| getProjectControl().isAdmin();
}
@@ -270,34 +264,27 @@
private boolean canEditAssignee() {
return isOwner()
|| getProjectControl().isOwner()
- || getRefControl().canEditAssignee()
+ || refControl.canPerform(Permission.EDIT_ASSIGNEE)
|| isAssignee();
}
/** Can this user edit the hashtag name? */
private boolean canEditHashtags() {
return isOwner() // owner (aka creator) of the change can edit hashtags
- || getRefControl().isOwner() // branch owner can edit hashtags
+ || refControl.isOwner() // branch owner can edit hashtags
|| getProjectControl().isOwner() // project owner can edit hashtags
- || getRefControl().canEditHashtags() // user can edit hashtag on a specific ref
+ || refControl.canPerform(
+ Permission.EDIT_HASHTAGS) // user can edit hashtag on a specific ref
|| getProjectControl().isAdmin();
}
- private ChangeData changeData(ReviewDb db, @Nullable ChangeData cd) {
- return cd != null ? cd : changeDataFactory.create(db, getNotes());
- }
-
private boolean isPrivateVisible(ReviewDb db, ChangeData cd) throws OrmException {
return isOwner()
|| isReviewer(db, cd)
- || getRefControl().canViewPrivateChanges()
+ || refControl.canPerform(Permission.VIEW_PRIVATE_CHANGES)
|| getUser().isInternalUser();
}
- ForChange asForChange(@Nullable ChangeData cd, @Nullable Provider<ReviewDb> db) {
- return new ForChangeImpl(cd, db);
- }
-
private class ForChangeImpl extends ForChange {
private ChangeData cd;
private Map<String, PermissionRange> labels;
@@ -321,7 +308,7 @@
if (cd == null) {
ReviewDb reviewDb = db();
checkState(reviewDb != null, "need ReviewDb");
- cd = changeDataFactory.create(reviewDb, getNotes());
+ cd = changeDataFactory.create(reviewDb, notes);
}
return cd;
}
@@ -391,11 +378,11 @@
case RESTORE:
return canRestore(db());
case SUBMIT:
- return getRefControl().canSubmit(isOwner());
+ return refControl.canSubmit(isOwner());
case REMOVE_REVIEWER:
case SUBMIT_AS:
- return getRefControl().canPerform(perm.permissionName().get());
+ return refControl.canPerform(perm.permissionName().get());
}
} catch (OrmException e) {
throw new PermissionBackendException("unavailable", e);
@@ -428,7 +415,7 @@
}
}
- static <T extends ChangePermissionOrLabel> Set<T> newSet(Collection<T> permSet) {
+ private static <T extends ChangePermissionOrLabel> Set<T> newSet(Collection<T> permSet) {
if (permSet instanceof EnumSet) {
@SuppressWarnings({"unchecked", "rawtypes"})
Set<T> s = ((EnumSet) permSet).clone();
diff --git a/java/com/google/gerrit/server/project/CreateProjectArgs.java b/java/com/google/gerrit/server/project/CreateProjectArgs.java
index b98ffc2..e4623b2 100644
--- a/java/com/google/gerrit/server/project/CreateProjectArgs.java
+++ b/java/com/google/gerrit/server/project/CreateProjectArgs.java
@@ -34,6 +34,7 @@
public InheritableBoolean contentMerge;
public InheritableBoolean newChangeForAllNotInTarget;
public InheritableBoolean changeIdRequired;
+ public InheritableBoolean rejectEmptyCommit;
public boolean createEmptyCommit;
public String maxObjectSizeLimit;
diff --git a/java/com/google/gerrit/server/project/CreateRefControl.java b/java/com/google/gerrit/server/project/CreateRefControl.java
index de6fba2..d45bed9 100644
--- a/java/com/google/gerrit/server/project/CreateRefControl.java
+++ b/java/com/google/gerrit/server/project/CreateRefControl.java
@@ -16,6 +16,7 @@
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.permissions.PermissionBackend;
@@ -41,11 +42,14 @@
private final PermissionBackend permissionBackend;
private final ProjectCache projectCache;
+ private final Reachable reachable;
@Inject
- CreateRefControl(PermissionBackend permissionBackend, ProjectCache projectCache) {
+ CreateRefControl(
+ PermissionBackend permissionBackend, ProjectCache projectCache, Reachable reachable) {
this.permissionBackend = permissionBackend;
this.projectCache = projectCache;
+ this.reachable = reachable;
}
/**
@@ -57,25 +61,25 @@
* @param object the object the user will start the reference with
* @throws AuthException if creation is denied; the message explains the denial.
* @throws PermissionBackendException on failure of permission checks.
+ * @throws ResourceConflictException if the project state does not permit the operation
*/
public void checkCreateRef(
Provider<? extends CurrentUser> user,
Repository repo,
Branch.NameKey branch,
RevObject object)
- throws AuthException, PermissionBackendException, NoSuchProjectException, IOException {
+ throws AuthException, PermissionBackendException, NoSuchProjectException, IOException,
+ ResourceConflictException {
ProjectState ps = projectCache.checkedGet(branch.getParentKey());
if (ps == null) {
throw new NoSuchProjectException(branch.getParentKey());
}
- if (!ps.getProject().getState().permitsWrite()) {
- throw new AuthException("project state does not permit write");
- }
+ ps.checkStatePermitsWrite();
PermissionBackend.ForRef perm = permissionBackend.user(user).ref(branch);
if (object instanceof RevCommit) {
perm.check(RefPermission.CREATE);
- checkCreateCommit(user, repo, (RevCommit) object, ps, perm);
+ checkCreateCommit(repo, (RevCommit) object, ps, perm);
} else if (object instanceof RevTag) {
RevTag tag = (RevTag) object;
try (RevWalk rw = new RevWalk(repo)) {
@@ -95,7 +99,7 @@
RevObject target = tag.getObject();
if (target instanceof RevCommit) {
- checkCreateCommit(user, repo, (RevCommit) target, ps, perm);
+ checkCreateCommit(repo, (RevCommit) target, ps, perm);
} else {
checkCreateRef(user, repo, branch, target);
}
@@ -118,11 +122,7 @@
* new commit to the repository.
*/
private void checkCreateCommit(
- Provider<? extends CurrentUser> user,
- Repository repo,
- RevCommit commit,
- ProjectState projectState,
- PermissionBackend.ForRef forRef)
+ Repository repo, RevCommit commit, ProjectState projectState, PermissionBackend.ForRef forRef)
throws AuthException, PermissionBackendException {
try {
// If the user has update (push) permission, they can create the ref regardless
@@ -132,8 +132,7 @@
} catch (AuthException denied) {
// Fall through to check reachability.
}
-
- if (projectState.controlFor(user.get()).isReachableFromHeadsOrTags(repo, commit)) {
+ if (reachable.fromHeadsOrTags(projectState, repo, commit)) {
// If the user has no push permissions, check whether the object is
// merged into a branch or tag readable by this user. If so, they are
// not effectively "pushing" more objects, so they can create the ref
diff --git a/java/com/google/gerrit/server/project/DefaultPermissionBackendModule.java b/java/com/google/gerrit/server/project/DefaultPermissionBackendModule.java
index bdfc67f..44c5e0b 100644
--- a/java/com/google/gerrit/server/project/DefaultPermissionBackendModule.java
+++ b/java/com/google/gerrit/server/project/DefaultPermissionBackendModule.java
@@ -30,8 +30,7 @@
@Override
protected void configure() {
// TODO(sop) Hide ProjectControl, RefControl, ChangeControl related bindings.
- bind(ProjectControl.GenericFactory.class);
- factory(ProjectControl.AssistedFactory.class);
+ factory(ProjectControl.Factory.class);
bind(ChangeControl.Factory.class);
}
}
diff --git a/java/com/google/gerrit/server/project/ProjectCache.java b/java/com/google/gerrit/server/project/ProjectCache.java
index 63052bd..9ebcc99 100644
--- a/java/com/google/gerrit/server/project/ProjectCache.java
+++ b/java/com/google/gerrit/server/project/ProjectCache.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.project;
+import com.google.common.collect.ImmutableSortedSet;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.Project;
import java.io.IOException;
@@ -67,8 +68,14 @@
*/
void remove(Project p) throws IOException;
+ /**
+ * Remove information about the given project from the cache. It will no longer be returned from
+ * {@link #all()}.
+ */
+ void remove(Project.NameKey name) throws IOException;
+
/** @return sorted iteration of projects. */
- Iterable<Project.NameKey> all();
+ ImmutableSortedSet<Project.NameKey> all();
/**
* @return estimated set of relevant groups extracted from hot project access rules. If the cache
@@ -82,7 +89,7 @@
* @param prefix common prefix.
* @return sorted iteration of projects sharing the same prefix.
*/
- Iterable<Project.NameKey> byName(String prefix);
+ ImmutableSortedSet<Project.NameKey> byName(String prefix);
/** Notify the cache that a new project was constructed. */
void onCreateProject(Project.NameKey newProjectName) throws IOException;
diff --git a/java/com/google/gerrit/server/project/ProjectCacheImpl.java b/java/com/google/gerrit/server/project/ProjectCacheImpl.java
index 4b2161b..68270e2 100644
--- a/java/com/google/gerrit/server/project/ProjectCacheImpl.java
+++ b/java/com/google/gerrit/server/project/ProjectCacheImpl.java
@@ -19,6 +19,8 @@
import com.google.common.base.Throwables;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Sets;
import com.google.gerrit.lifecycle.LifecycleModule;
import com.google.gerrit.reviewdb.client.AccountGroup;
@@ -36,12 +38,8 @@
import com.google.inject.TypeLiteral;
import com.google.inject.name.Named;
import java.io.IOException;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
-import java.util.SortedSet;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@@ -64,7 +62,7 @@
protected void configure() {
cache(CACHE_NAME, String.class, ProjectState.class).loader(Loader.class);
- cache(CACHE_LIST, ListKey.class, new TypeLiteral<SortedSet<Project.NameKey>>() {})
+ cache(CACHE_LIST, ListKey.class, new TypeLiteral<ImmutableSortedSet<Project.NameKey>>() {})
.maximumWeight(1)
.loader(Lister.class);
@@ -86,7 +84,7 @@
private final AllProjectsName allProjectsName;
private final AllUsersName allUsersName;
private final LoadingCache<String, ProjectState> byName;
- private final LoadingCache<ListKey, SortedSet<Project.NameKey>> list;
+ private final LoadingCache<ListKey, ImmutableSortedSet<Project.NameKey>> list;
private final Lock listLock;
private final ProjectCacheClock clock;
private final Provider<ProjectIndexer> indexer;
@@ -96,7 +94,7 @@
final AllProjectsName allProjectsName,
final AllUsersName allUsersName,
@Named(CACHE_NAME) LoadingCache<String, ProjectState> byName,
- @Named(CACHE_LIST) LoadingCache<ListKey, SortedSet<Project.NameKey>> list,
+ @Named(CACHE_LIST) LoadingCache<ListKey, ImmutableSortedSet<Project.NameKey>> list,
ProjectCacheClock clock,
Provider<ProjectIndexer> indexer) {
this.allProjectsName = allProjectsName;
@@ -176,26 +174,32 @@
@Override
public void remove(Project p) throws IOException {
+ remove(p.getNameKey());
+ }
+
+ @Override
+ public void remove(Project.NameKey name) throws IOException {
listLock.lock();
try {
- SortedSet<Project.NameKey> n = Sets.newTreeSet(list.get(ListKey.ALL));
- n.remove(p.getNameKey());
- list.put(ListKey.ALL, Collections.unmodifiableSortedSet(n));
+ list.put(
+ ListKey.ALL,
+ ImmutableSortedSet.copyOf(Sets.difference(list.get(ListKey.ALL), ImmutableSet.of(name))));
} catch (ExecutionException e) {
log.warn("Cannot list available projects", e);
} finally {
listLock.unlock();
}
- evict(p);
+ evict(name);
}
@Override
public void onCreateProject(Project.NameKey newProjectName) throws IOException {
listLock.lock();
try {
- SortedSet<Project.NameKey> n = Sets.newTreeSet(list.get(ListKey.ALL));
- n.add(newProjectName);
- list.put(ListKey.ALL, Collections.unmodifiableSortedSet(n));
+ list.put(
+ ListKey.ALL,
+ ImmutableSortedSet.copyOf(
+ Sets.union(list.get(ListKey.ALL), ImmutableSet.of(newProjectName))));
} catch (ExecutionException e) {
log.warn("Cannot list available projects", e);
} finally {
@@ -205,12 +209,12 @@
}
@Override
- public SortedSet<Project.NameKey> all() {
+ public ImmutableSortedSet<Project.NameKey> all() {
try {
return list.get(ListKey.ALL);
} catch (ExecutionException e) {
log.warn("Cannot list available projects", e);
- return Collections.emptySortedSet();
+ return ImmutableSortedSet.of();
}
}
@@ -228,58 +232,16 @@
}
@Override
- public Iterable<Project.NameKey> byName(String pfx) {
- final Iterable<Project.NameKey> src;
+ public ImmutableSortedSet<Project.NameKey> byName(String pfx) {
+ Project.NameKey start = new Project.NameKey(pfx);
+ Project.NameKey end = new Project.NameKey(pfx + Character.MAX_VALUE);
try {
- src = list.get(ListKey.ALL).tailSet(new Project.NameKey(pfx));
+ // Right endpoint is exclusive, but U+FFFF is a non-character so no project ends with it.
+ return list.get(ListKey.ALL).subSet(start, end);
} catch (ExecutionException e) {
log.warn("Cannot look up projects for prefix " + pfx, e);
- return Collections.emptyList();
+ return ImmutableSortedSet.of();
}
- return new Iterable<Project.NameKey>() {
- @Override
- public Iterator<Project.NameKey> iterator() {
- return new Iterator<Project.NameKey>() {
- private Iterator<Project.NameKey> itr = src.iterator();
- private Project.NameKey next;
-
- @Override
- public boolean hasNext() {
- if (next != null) {
- return true;
- }
-
- if (!itr.hasNext()) {
- return false;
- }
-
- Project.NameKey r = itr.next();
- if (r.get().startsWith(pfx)) {
- next = r;
- return true;
- }
- itr = Collections.<Project.NameKey>emptyList().iterator();
- return false;
- }
-
- @Override
- public Project.NameKey next() {
- if (!hasNext()) {
- throw new NoSuchElementException();
- }
-
- Project.NameKey r = next;
- next = null;
- return r;
- }
-
- @Override
- public void remove() {
- throw new UnsupportedOperationException();
- }
- };
- }
- };
}
static class Loader extends CacheLoader<String, ProjectState> {
@@ -315,7 +277,7 @@
private ListKey() {}
}
- static class Lister extends CacheLoader<ListKey, SortedSet<Project.NameKey>> {
+ static class Lister extends CacheLoader<ListKey, ImmutableSortedSet<Project.NameKey>> {
private final GitRepositoryManager mgr;
@Inject
@@ -324,8 +286,8 @@
}
@Override
- public SortedSet<Project.NameKey> load(ListKey key) throws Exception {
- return mgr.list();
+ public ImmutableSortedSet<Project.NameKey> load(ListKey key) throws Exception {
+ return ImmutableSortedSet.copyOf(mgr.list());
}
}
}
diff --git a/java/com/google/gerrit/server/project/ProjectControl.java b/java/com/google/gerrit/server/project/ProjectControl.java
index 3e6d10e..68dbf86 100644
--- a/java/com/google/gerrit/server/project/ProjectControl.java
+++ b/java/com/google/gerrit/server/project/ProjectControl.java
@@ -49,7 +49,6 @@
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.google.inject.assistedinject.Assisted;
-import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
@@ -58,30 +57,10 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
/** Access control management for a user accessing a project's data. */
-public class ProjectControl {
- public static class GenericFactory {
- private final ProjectCache projectCache;
-
- @Inject
- GenericFactory(ProjectCache pc) {
- projectCache = pc;
- }
-
- public ProjectControl controlFor(Project.NameKey nameKey, CurrentUser user)
- throws NoSuchProjectException, IOException {
- final ProjectState p = projectCache.checkedGet(nameKey);
- if (p == null) {
- throw new NoSuchProjectException(nameKey);
- }
- return p.controlFor(user);
- }
- }
-
- interface AssistedFactory {
+class ProjectControl {
+ interface Factory {
ProjectControl create(CurrentUser who, ProjectState ps);
}
@@ -103,7 +82,6 @@
private final PermissionBackend.WithUser perm;
private final CurrentUser user;
private final ProjectState state;
- private final Reachable reachable;
private final ChangeControl.Factory changeControlFactory;
private final PermissionCollection.Factory permissionFilter;
@@ -116,7 +94,6 @@
@GitUploadPackGroups Set<AccountGroup.UUID> uploadGroups,
@GitReceivePackGroups Set<AccountGroup.UUID> receiveGroups,
PermissionCollection.Factory permissionFilter,
- Reachable reachable,
ChangeControl.Factory changeControlFactory,
PermissionBackend permissionBackend,
@Assisted CurrentUser who,
@@ -125,7 +102,6 @@
this.uploadGroups = uploadGroups;
this.receiveGroups = receiveGroups;
this.permissionFilter = permissionFilter;
- this.reachable = reachable;
this.perm = permissionBackend.user(who);
user = who;
state = ps;
@@ -138,6 +114,10 @@
return r;
}
+ ForProject asForProject() {
+ return new ForProjectImpl();
+ }
+
ChangeControl controlFor(ReviewDb db, Change change) throws OrmException {
return changeControlFactory.create(
controlForRef(change.getDest()), db, change.getProject(), change.getId());
@@ -164,10 +144,6 @@
return ctl;
}
- boolean isReachableFromHeadsOrTags(Repository repo, RevCommit commit) {
- return reachable.fromHeadsOrTags(state, repo, commit);
- }
-
CurrentUser getUser() {
return user;
}
@@ -195,6 +171,19 @@
|| isOwner();
}
+ boolean isAdmin() {
+ try {
+ perm.check(GlobalPermission.ADMINISTRATE_SERVER);
+ return true;
+ } catch (AuthException | PermissionBackendException e) {
+ return false;
+ }
+ }
+
+ boolean match(PermissionRule rule, boolean isChangeOwner) {
+ return match(rule.getGroup().getUUID(), isChangeOwner);
+ }
+
/** Can the user run upload pack? */
private boolean canRunUploadPack() {
for (AccountGroup.UUID group : uploadGroups) {
@@ -241,15 +230,6 @@
return false;
}
- boolean isAdmin() {
- try {
- perm.check(GlobalPermission.ADMINISTRATE_SERVER);
- return true;
- } catch (AuthException | PermissionBackendException e) {
- return false;
- }
- }
-
private boolean isDeclaredOwner() {
if (declaredOwner == null) {
GroupMembership effectiveGroups = user.getEffectiveGroups();
@@ -325,19 +305,15 @@
return allSections;
}
- boolean match(PermissionRule rule) {
+ private boolean match(PermissionRule rule) {
return match(rule.getGroup().getUUID());
}
- boolean match(PermissionRule rule, boolean isChangeOwner) {
- return match(rule.getGroup().getUUID(), isChangeOwner);
- }
-
- boolean match(AccountGroup.UUID uuid) {
+ private boolean match(AccountGroup.UUID uuid) {
return match(uuid, false);
}
- boolean match(AccountGroup.UUID uuid, boolean isChangeOwner) {
+ private boolean match(AccountGroup.UUID uuid, boolean isChangeOwner) {
if (SystemGroupBackend.PROJECT_OWNERS.equals(uuid)) {
return isDeclaredOwner();
} else if (SystemGroupBackend.CHANGE_OWNER.equals(uuid)) {
@@ -347,14 +323,6 @@
}
}
- boolean canRead() {
- return !isHidden() && allRefsAreVisible(Collections.emptySet());
- }
-
- ForProject asForProject() {
- return new ForProjectImpl();
- }
-
private class ForProjectImpl extends ForProject {
@Override
public ForProject user(CurrentUser user) {
diff --git a/java/com/google/gerrit/server/project/ProjectState.java b/java/com/google/gerrit/server/project/ProjectState.java
index a189d92..6a5ecd8 100644
--- a/java/com/google/gerrit/server/project/ProjectState.java
+++ b/java/com/google/gerrit/server/project/ProjectState.java
@@ -31,6 +31,8 @@
import com.google.gerrit.common.data.SubscribeSection;
import com.google.gerrit.extensions.api.projects.CommentLinkInfo;
import com.google.gerrit.extensions.api.projects.ThemeInfo;
+import com.google.gerrit.extensions.client.SubmitType;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.BooleanProjectConfig;
import com.google.gerrit.reviewdb.client.Branch;
@@ -85,7 +87,7 @@
private final SitePaths sitePaths;
private final AllProjectsName allProjectsName;
private final ProjectCache projectCache;
- private final ProjectControl.AssistedFactory projectControlFactory;
+ private final ProjectControl.Factory projectControlFactory;
private final PrologEnvironment.Factory envFactory;
private final GitRepositoryManager gitMgr;
private final RulesCache rulesCache;
@@ -119,7 +121,7 @@
final ProjectCache projectCache,
final AllProjectsName allProjectsName,
final AllUsersName allUsersName,
- final ProjectControl.AssistedFactory projectControlFactory,
+ final ProjectControl.Factory projectControlFactory,
final PrologEnvironment.Factory envFactory,
final GitRepositoryManager gitMgr,
final RulesCache rulesCache,
@@ -257,6 +259,21 @@
return config.getMaxObjectSizeLimit();
}
+ public boolean statePermitsRead() {
+ return getProject().getState().permitsRead();
+ }
+
+ public boolean statePermitsWrite() {
+ return getProject().getState().permitsWrite();
+ }
+
+ public void checkStatePermitsWrite() throws ResourceConflictException {
+ if (!statePermitsWrite()) {
+ throw new ResourceConflictException(
+ "project state " + getProject().getState().name() + " does not permit write");
+ }
+ }
+
/** Get the sections that pertain only to this project. */
List<SectionMatcher> getLocalAccessSections() {
List<SectionMatcher> sm = localAccessSections;
@@ -490,6 +507,16 @@
return getGroups(getLocalAccessSections());
}
+ public SubmitType getSubmitType() {
+ for (ProjectState s : tree()) {
+ SubmitType t = s.getProject().getConfiguredSubmitType();
+ if (t != SubmitType.INHERIT) {
+ return t;
+ }
+ }
+ return Project.DEFAULT_ALL_PROJECTS_SUBMIT_TYPE;
+ }
+
private static Set<GroupReference> getGroups(List<SectionMatcher> sectionMatcherList) {
final Set<GroupReference> all = new HashSet<>();
for (SectionMatcher matcher : sectionMatcherList) {
diff --git a/java/com/google/gerrit/server/project/RefControl.java b/java/com/google/gerrit/server/project/RefControl.java
index 0651b1d..f0a9b64 100644
--- a/java/com/google/gerrit/server/project/RefControl.java
+++ b/java/com/google/gerrit/server/project/RefControl.java
@@ -32,6 +32,7 @@
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.permissions.RefPermission;
import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.util.MagicBranch;
import com.google.gwtorm.server.OrmException;
import com.google.inject.util.Providers;
import java.util.ArrayList;
@@ -45,7 +46,7 @@
import java.util.Set;
/** Manages access control for Git references (aka branches, tags). */
-public class RefControl {
+class RefControl {
private final ProjectControl projectControl;
private final String refName;
@@ -67,10 +68,6 @@
this.effective = new HashMap<>();
}
- String getRefName() {
- return refName;
- }
-
ProjectControl getProjectControl() {
return projectControl;
}
@@ -82,9 +79,9 @@
RefControl forUser(CurrentUser who) {
ProjectControl newCtl = projectControl.forUser(who);
if (relevant.isUserSpecific()) {
- return newCtl.controlForRef(getRefName());
+ return newCtl.controlForRef(refName);
}
- return new RefControl(newCtl, getRefName(), relevant);
+ return new RefControl(newCtl, refName, relevant);
}
/** Is this user a ref owner? */
@@ -103,37 +100,21 @@
/** Can this user see this reference exists? */
boolean isVisible() {
if (isVisible == null) {
- isVisible =
- (getUser().isInternalUser() || canPerform(Permission.READ))
- && isProjectStatePermittingRead();
+ isVisible = getUser().isInternalUser() || canPerform(Permission.READ);
}
return isVisible;
}
- private boolean canUpload() {
- return projectControl.controlForRef("refs/for/" + getRefName()).canPerform(Permission.PUSH)
- && isProjectStatePermittingWrite();
- }
-
/** @return true if this user can add a new patch set to this ref */
boolean canAddPatchSet() {
return projectControl
- .controlForRef("refs/for/" + getRefName())
- .canPerform(Permission.ADD_PATCH_SET)
- && isProjectStatePermittingWrite();
- }
-
- /** @return true if this user can submit merge patch sets to this ref */
- private boolean canUploadMerges() {
- return projectControl
- .controlForRef("refs/for/" + getRefName())
- .canPerform(Permission.PUSH_MERGE)
- && isProjectStatePermittingWrite();
+ .controlForRef(MagicBranch.NEW_CHANGE + refName)
+ .canPerform(Permission.ADD_PATCH_SET);
}
/** @return true if this user can rebase changes on this ref */
boolean canRebase() {
- return canPerform(Permission.REBASE) && isProjectStatePermittingWrite();
+ return canPerform(Permission.REBASE);
}
/** @return true if this user can submit patch sets to this ref */
@@ -146,7 +127,52 @@
// granting of powers beyond submitting to the configuration.
return projectControl.isOwner();
}
- return canPerform(Permission.SUBMIT, isChangeOwner) && isProjectStatePermittingWrite();
+ return canPerform(Permission.SUBMIT, isChangeOwner);
+ }
+
+ /** @return true if this user can force edit topic names. */
+ boolean canForceEditTopicName() {
+ return canForcePerform(Permission.EDIT_TOPIC_NAME);
+ }
+
+ /** The range of permitted values associated with a label permission. */
+ PermissionRange getRange(String permission) {
+ return getRange(permission, false);
+ }
+
+ /** The range of permitted values associated with a label permission. */
+ PermissionRange getRange(String permission, boolean isChangeOwner) {
+ if (Permission.hasRange(permission)) {
+ return toRange(permission, access(permission, isChangeOwner));
+ }
+ return null;
+ }
+
+ /** True if the user is blocked from using this permission. */
+ boolean isBlocked(String permissionName) {
+ return !doCanPerform(permissionName, false, true);
+ }
+
+ /** True if the user has this permission. Works only for non labels. */
+ boolean canPerform(String permissionName) {
+ return canPerform(permissionName, false);
+ }
+
+ ForRef asForRef() {
+ return new ForRefImpl();
+ }
+
+ private boolean canPerform(String permissionName, boolean isChangeOwner) {
+ return doCanPerform(permissionName, isChangeOwner, false);
+ }
+
+ private boolean canUpload() {
+ return projectControl.controlForRef("refs/for/" + refName).canPerform(Permission.PUSH);
+ }
+
+ /** @return true if this user can submit merge patch sets to this ref */
+ private boolean canUploadMerges() {
+ return projectControl.controlForRef("refs/for/" + refName).canPerform(Permission.PUSH_MERGE);
}
/** @return true if the user can update the reference as a fast-forward. */
@@ -165,15 +191,11 @@
return false;
}
}
- return canPerform(Permission.PUSH) && isProjectStatePermittingWrite();
+ return canPerform(Permission.PUSH);
}
/** @return true if the user can rewind (force push) the reference. */
private boolean canForceUpdate() {
- if (!isProjectStatePermittingWrite()) {
- return false;
- }
-
if (canPushWithForce()) {
return true;
}
@@ -192,17 +214,8 @@
}
}
- private boolean isProjectStatePermittingWrite() {
- return getProjectControl().getProject().getState().permitsWrite();
- }
-
- private boolean isProjectStatePermittingRead() {
- return getProjectControl().getProject().getState().permitsRead();
- }
-
private boolean canPushWithForce() {
- if (!isProjectStatePermittingWrite()
- || (RefNames.REFS_CONFIG.equals(refName) && !projectControl.isOwner())) {
+ if (RefNames.REFS_CONFIG.equals(refName) && !projectControl.isOwner()) {
// Pushing requires being at least project owner, in addition to push.
// Pushing configuration changes modifies the access control
// rules. Allowing this to be done by a non-project-owner opens
@@ -219,7 +232,7 @@
* @return {@code true} if the user specified can delete a Git ref.
*/
private boolean canDelete() {
- if (!isProjectStatePermittingWrite() || (RefNames.REFS_CONFIG.equals(refName))) {
+ if (RefNames.REFS_CONFIG.equals(refName)) {
// Never allow removal of the refs/meta/config branch.
// Deleting the branch would destroy all Gerrit specific
// metadata about the project, including its access rules.
@@ -266,53 +279,6 @@
return canPerform(Permission.FORGE_SERVER);
}
- /** @return true if this user can abandon a change for this ref */
- boolean canAbandon() {
- return canPerform(Permission.ABANDON);
- }
-
- /** @return true if this user can view private changes. */
- boolean canViewPrivateChanges() {
- return canPerform(Permission.VIEW_PRIVATE_CHANGES);
- }
-
- /** @return true if this user can delete their own changes. */
- boolean canDeleteOwnChanges() {
- return canPerform(Permission.DELETE_OWN_CHANGES);
- }
-
- /** @return true if this user can edit topic names. */
- boolean canEditTopicName() {
- return canPerform(Permission.EDIT_TOPIC_NAME);
- }
-
- /** @return true if this user can edit hashtag names. */
- boolean canEditHashtags() {
- return canPerform(Permission.EDIT_HASHTAGS);
- }
-
- boolean canEditAssignee() {
- return canPerform(Permission.EDIT_ASSIGNEE);
- }
-
- /** @return true if this user can force edit topic names. */
- boolean canForceEditTopicName() {
- return canForcePerform(Permission.EDIT_TOPIC_NAME);
- }
-
- /** The range of permitted values associated with a label permission. */
- PermissionRange getRange(String permission) {
- return getRange(permission, false);
- }
-
- /** The range of permitted values associated with a label permission. */
- PermissionRange getRange(String permission, boolean isChangeOwner) {
- if (Permission.hasRange(permission)) {
- return toRange(permission, access(permission, isChangeOwner));
- }
- return null;
- }
-
private static class AllowedRange {
private int allowMin;
private int allowMax;
@@ -376,20 +342,6 @@
return new PermissionRange(permissionName, min, max);
}
- /** True if the user has this permission. Works only for non labels. */
- public boolean canPerform(String permissionName) {
- return canPerform(permissionName, false);
- }
-
- boolean canPerform(String permissionName, boolean isChangeOwner) {
- return doCanPerform(permissionName, isChangeOwner, false);
- }
-
- /** True if the user is blocked from using this permission. */
- boolean isBlocked(String permissionName) {
- return !doCanPerform(permissionName, false, true);
- }
-
private boolean doCanPerform(String permissionName, boolean isChangeOwner, boolean blockOnly) {
List<PermissionRule> access = access(permissionName, isChangeOwner);
List<PermissionRule> overridden = relevant.getOverridden(permissionName);
@@ -481,10 +433,6 @@
return mine;
}
- ForRef asForRef() {
- return new ForRefImpl();
- }
-
private class ForRefImpl extends ForRef {
@Override
public ForRef user(CurrentUser user) {
@@ -523,7 +471,7 @@
@Override
public void check(RefPermission perm) throws AuthException, PermissionBackendException {
if (!can(perm)) {
- throw new AuthException(perm.describeForException() + " not permitted for " + getRefName());
+ throw new AuthException(perm.describeForException() + " not permitted for " + refName);
}
}
@@ -542,7 +490,7 @@
private boolean can(RefPermission perm) throws PermissionBackendException {
switch (perm) {
case READ:
- return isVisible();
+ return isVisible() && getProjectControl().getProjectState().statePermitsRead();
case CREATE:
// TODO This isn't an accurate test.
return canPerform(perm.permissionName().get());
@@ -567,11 +515,14 @@
case CREATE_CHANGE:
return canUpload();
+ case CREATE_TAG:
+ return canPerform(Permission.CREATE_TAG);
+
case UPDATE_BY_SUBMIT:
- return projectControl.controlForRef("refs/for/" + getRefName()).canSubmit(true);
+ return projectControl.controlForRef(MagicBranch.NEW_CHANGE + refName).canSubmit(true);
case READ_PRIVATE_CHANGES:
- return canViewPrivateChanges();
+ return canPerform(Permission.VIEW_PRIVATE_CHANGES);
case READ_CONFIG:
return projectControl
diff --git a/java/com/google/gerrit/server/project/RemoveReviewerControl.java b/java/com/google/gerrit/server/project/RemoveReviewerControl.java
index 118814d..e91d36e 100644
--- a/java/com/google/gerrit/server/project/RemoveReviewerControl.java
+++ b/java/com/google/gerrit/server/project/RemoveReviewerControl.java
@@ -35,16 +35,16 @@
public class RemoveReviewerControl {
private final PermissionBackend permissionBackend;
private final Provider<ReviewDb> dbProvider;
- private final ProjectControl.GenericFactory projectControlFactory;
+ private final ProjectCache projectCache;
@Inject
RemoveReviewerControl(
PermissionBackend permissionBackend,
Provider<ReviewDb> dbProvider,
- ProjectControl.GenericFactory projectControlFactory) {
+ ProjectCache projectCache) {
this.permissionBackend = permissionBackend;
this.dbProvider = dbProvider;
- this.projectControlFactory = projectControlFactory;
+ this.projectCache = projectCache;
}
/**
@@ -116,7 +116,11 @@
// Users with the remove reviewer permission, the branch owner, project
// owner and site admin can remove anyone
// TODO(hiesel): Remove all Control usage
- ProjectControl ctl = projectControlFactory.controlFor(change.getProject(), currentUser);
+ ProjectState projectState = projectCache.checkedGet(change.getProject());
+ if (projectState == null) {
+ throw new NoSuchProjectException(change.getProject());
+ }
+ ProjectControl ctl = projectState.controlFor(currentUser);
if (ctl.controlForRef(change.getDest()).isOwner() // branch owner
|| ctl.isOwner() // project owner
|| ctl.isAdmin()) { // project admin
diff --git a/java/com/google/gerrit/server/query/account/AccountPredicates.java b/java/com/google/gerrit/server/query/account/AccountPredicates.java
index 9213353..22df2ce 100644
--- a/java/com/google/gerrit/server/query/account/AccountPredicates.java
+++ b/java/com/google/gerrit/server/query/account/AccountPredicates.java
@@ -17,6 +17,7 @@
import com.google.common.collect.Lists;
import com.google.common.primitives.Ints;
import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.Schema;
import com.google.gerrit.index.query.IndexPredicate;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryBuilder;
@@ -35,14 +36,26 @@
return Predicate.and(p, isActive());
}
- public static Predicate<AccountState> defaultPredicate(String query) {
+ public static Predicate<AccountState> defaultPredicate(
+ Schema<AccountState> schema, boolean canSeeSecondaryEmails, String query) {
// Adapt the capacity of this list when adding more default predicates.
List<Predicate<AccountState>> preds = Lists.newArrayListWithCapacity(3);
Integer id = Ints.tryParse(query);
if (id != null) {
preds.add(id(new Account.Id(id)));
}
- preds.add(equalsName(query));
+ if (canSeeSecondaryEmails) {
+ preds.add(equalsNameIcludingSecondaryEmails(query));
+ } else {
+ if (schema.hasField(AccountField.NAME_PART_NO_SECONDARY_EMAIL)) {
+ preds.add(equalsName(query));
+ } else {
+ preds.add(AccountPredicates.fullName(query));
+ if (schema.hasField(AccountField.PREFERRED_EMAIL)) {
+ preds.add(AccountPredicates.preferredEmail(query));
+ }
+ }
+ }
preds.add(username(query));
// Adapt the capacity of the "predicates" list when adding more default
// predicates.
@@ -54,7 +67,7 @@
AccountField.ID, AccountQueryBuilder.FIELD_ACCOUNT, accountId.toString());
}
- public static Predicate<AccountState> email(String email) {
+ public static Predicate<AccountState> emailIncludingSecondaryEmails(String email) {
return new AccountPredicate(
AccountField.EMAIL, AccountQueryBuilder.FIELD_EMAIL, email.toLowerCase());
}
@@ -71,12 +84,19 @@
AccountField.PREFERRED_EMAIL_EXACT, AccountQueryBuilder.FIELD_PREFERRED_EMAIL_EXACT, email);
}
- public static Predicate<AccountState> equalsName(String name) {
+ public static Predicate<AccountState> equalsNameIcludingSecondaryEmails(String name) {
return new AccountPredicate(
AccountField.NAME_PART, AccountQueryBuilder.FIELD_NAME, name.toLowerCase());
}
- public static Predicate<AccountState> externalId(String externalId) {
+ public static Predicate<AccountState> equalsName(String name) {
+ return new AccountPredicate(
+ AccountField.NAME_PART_NO_SECONDARY_EMAIL,
+ AccountQueryBuilder.FIELD_NAME,
+ name.toLowerCase());
+ }
+
+ public static Predicate<AccountState> externalIdIncludingSecondaryEmails(String externalId) {
return new AccountPredicate(AccountField.EXTERNAL_ID, externalId);
}
diff --git a/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java b/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java
index 946a729..055b423 100644
--- a/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java
@@ -18,6 +18,8 @@
import com.google.common.collect.Lists;
import com.google.common.primitives.Ints;
import com.google.gerrit.common.errors.NotSignedInException;
+import com.google.gerrit.index.Index;
+import com.google.gerrit.index.Schema;
import com.google.gerrit.index.query.LimitPredicate;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryBuilder;
@@ -26,12 +28,21 @@
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountState;
+import com.google.gerrit.server.index.account.AccountField;
+import com.google.gerrit.server.index.account.AccountIndexCollection;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.ProvisionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/** Parses a query string meant to be applied to account objects. */
public class AccountQueryBuilder extends QueryBuilder<AccountState> {
+ private static final Logger log = LoggerFactory.getLogger(AccountQueryBuilder.class);
+
public static final String FIELD_ACCOUNT = "account";
public static final String FIELD_EMAIL = "email";
public static final String FIELD_LIMIT = "limit";
@@ -46,10 +57,17 @@
public static class Arguments {
private final Provider<CurrentUser> self;
+ private final AccountIndexCollection indexes;
+ private final PermissionBackend permissionBackend;
@Inject
- public Arguments(Provider<CurrentUser> self) {
+ public Arguments(
+ Provider<CurrentUser> self,
+ AccountIndexCollection indexes,
+ PermissionBackend permissionBackend) {
this.self = self;
+ this.indexes = indexes;
+ this.permissionBackend = permissionBackend;
}
IdentifiedUser getIdentifiedUser() throws QueryParseException {
@@ -71,6 +89,11 @@
throw new QueryParseException(NotSignedInException.MESSAGE, e);
}
}
+
+ Schema<AccountState> schema() {
+ Index<?, AccountState> index = indexes != null ? indexes.getSearchIndex() : null;
+ return index != null ? index.getSchema() : null;
+ }
}
private final Arguments args;
@@ -82,8 +105,17 @@
}
@Operator
- public Predicate<AccountState> email(String email) {
- return AccountPredicates.email(email);
+ public Predicate<AccountState> email(String email)
+ throws PermissionBackendException, QueryParseException {
+ if (canSeeSecondaryEmails()) {
+ return AccountPredicates.emailIncludingSecondaryEmails(email);
+ }
+
+ if (args.schema().hasField(AccountField.PREFERRED_EMAIL)) {
+ return AccountPredicates.preferredEmail(email);
+ }
+
+ throw new QueryParseException("'email' operator is not supported by account index version");
}
@Operator
@@ -107,8 +139,17 @@
}
@Operator
- public Predicate<AccountState> name(String name) {
- return AccountPredicates.equalsName(name);
+ public Predicate<AccountState> name(String name)
+ throws PermissionBackendException, QueryParseException {
+ if (canSeeSecondaryEmails()) {
+ return AccountPredicates.equalsNameIcludingSecondaryEmails(name);
+ }
+
+ if (args.schema().hasField(AccountField.NAME_PART_NO_SECONDARY_EMAIL)) {
+ return AccountPredicates.equalsName(name);
+ }
+
+ return AccountPredicates.fullName(name);
}
@Operator
@@ -124,7 +165,8 @@
@Override
protected Predicate<AccountState> defaultField(String query) {
- Predicate<AccountState> defaultPredicate = AccountPredicates.defaultPredicate(query);
+ Predicate<AccountState> defaultPredicate =
+ AccountPredicates.defaultPredicate(args.schema(), checkedCanSeeSecondaryEmails(), query);
if ("self".equalsIgnoreCase(query) || "me".equalsIgnoreCase(query)) {
try {
return Predicate.or(defaultPredicate, AccountPredicates.id(self()));
@@ -138,4 +180,20 @@
private Account.Id self() throws QueryParseException {
return args.getIdentifiedUser().getAccountId();
}
+
+ private boolean canSeeSecondaryEmails() throws PermissionBackendException, QueryParseException {
+ return args.permissionBackend.user(args.getUser()).test(GlobalPermission.MODIFY_ACCOUNT);
+ }
+
+ private boolean checkedCanSeeSecondaryEmails() {
+ try {
+ return canSeeSecondaryEmails();
+ } catch (PermissionBackendException e) {
+ log.error("Permission check failed", e);
+ return false;
+ } catch (QueryParseException e) {
+ // User is not signed in.
+ return false;
+ }
+ }
}
diff --git a/java/com/google/gerrit/server/query/account/InternalAccountQuery.java b/java/com/google/gerrit/server/query/account/InternalAccountQuery.java
index 2fb837c..f1be580 100644
--- a/java/com/google/gerrit/server/query/account/InternalAccountQuery.java
+++ b/java/com/google/gerrit/server/query/account/InternalAccountQuery.java
@@ -83,7 +83,7 @@
}
public List<AccountState> byDefault(String query) throws OrmException {
- return query(AccountPredicates.defaultPredicate(query));
+ return query(AccountPredicates.defaultPredicate(schema(), true, query));
}
public List<AccountState> byExternalId(String scheme, String id) throws OrmException {
@@ -91,7 +91,7 @@
}
public List<AccountState> byExternalId(ExternalId.Key externalId) throws OrmException {
- return query(AccountPredicates.externalId(externalId.toString()));
+ return query(AccountPredicates.externalIdIncludingSecondaryEmails(externalId.toString()));
}
public AccountState oneByExternalId(String externalId) throws OrmException {
diff --git a/java/com/google/gerrit/server/query/change/RegexPathPredicate.java b/java/com/google/gerrit/server/query/change/RegexPathPredicate.java
index 46b4cd5..3764a98 100644
--- a/java/com/google/gerrit/server/query/change/RegexPathPredicate.java
+++ b/java/com/google/gerrit/server/query/change/RegexPathPredicate.java
@@ -33,7 +33,7 @@
} catch (IOException e) {
throw new OrmException(e);
}
- return RegexListSearcher.ofStrings(getValue()).hasMatch(files);
+ return RegexListSearcher.ofStrings(getValue()).search(files).findAny().isPresent();
}
@Override
diff --git a/java/com/google/gerrit/server/restapi/account/DeleteWatchedProjects.java b/java/com/google/gerrit/server/restapi/account/DeleteWatchedProjects.java
index 1388523..5a1f6bf 100644
--- a/java/com/google/gerrit/server/restapi/account/DeleteWatchedProjects.java
+++ b/java/com/google/gerrit/server/restapi/account/DeleteWatchedProjects.java
@@ -24,9 +24,8 @@
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountResource;
-import com.google.gerrit.server.account.WatchConfig;
+import com.google.gerrit.server.account.AccountsUpdate;
import com.google.gerrit.server.account.WatchConfig.ProjectWatchKey;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
@@ -45,19 +44,16 @@
implements RestModifyView<AccountResource, List<ProjectWatchInfo>> {
private final Provider<IdentifiedUser> self;
private final PermissionBackend permissionBackend;
- private final AccountCache accountCache;
- private final WatchConfig.Accessor watchConfig;
+ private final AccountsUpdate.User accountsUpdate;
@Inject
DeleteWatchedProjects(
Provider<IdentifiedUser> self,
PermissionBackend permissionBackend,
- AccountCache accountCache,
- WatchConfig.Accessor watchConfig) {
+ AccountsUpdate.User accountsUpdate) {
this.self = self;
this.permissionBackend = permissionBackend;
- this.accountCache = accountCache;
- this.watchConfig = watchConfig;
+ this.accountsUpdate = accountsUpdate;
}
@Override
@@ -72,14 +68,18 @@
}
Account.Id accountId = rsrc.getUser().getAccountId();
- watchConfig.deleteProjectWatches(
- accountId,
- input
- .stream()
- .filter(Objects::nonNull)
- .map(w -> ProjectWatchKey.create(new Project.NameKey(w.project), w.filter))
- .collect(toList()));
- accountCache.evict(accountId);
+ accountsUpdate
+ .create()
+ .update(
+ "Delete Project Watches via API",
+ accountId,
+ u ->
+ u.deleteProjectWatches(
+ input
+ .stream()
+ .filter(Objects::nonNull)
+ .map(w -> ProjectWatchKey.create(new Project.NameKey(w.project), w.filter))
+ .collect(toList())));
return Response.none();
}
}
diff --git a/java/com/google/gerrit/server/restapi/account/GetEmails.java b/java/com/google/gerrit/server/restapi/account/GetEmails.java
index 9c482a3..640cc64 100644
--- a/java/com/google/gerrit/server/restapi/account/GetEmails.java
+++ b/java/com/google/gerrit/server/restapi/account/GetEmails.java
@@ -15,8 +15,15 @@
package com.google.gerrit.server.restapi.account;
import com.google.gerrit.extensions.common.EmailInfo;
+import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.AccountResource;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.util.ArrayList;
import java.util.Collections;
@@ -25,9 +32,22 @@
@Singleton
public class GetEmails implements RestReadView<AccountResource> {
+ private final Provider<CurrentUser> self;
+ private final PermissionBackend permissionBackend;
+
+ @Inject
+ GetEmails(Provider<CurrentUser> self, PermissionBackend permissionBackend) {
+ this.self = self;
+ this.permissionBackend = permissionBackend;
+ }
@Override
- public List<EmailInfo> apply(AccountResource rsrc) {
+ public List<EmailInfo> apply(AccountResource rsrc)
+ throws AuthException, PermissionBackendException {
+ if (self.get() != rsrc.getUser()) {
+ permissionBackend.user(self).check(GlobalPermission.MODIFY_ACCOUNT);
+ }
+
List<EmailInfo> emails = new ArrayList<>();
for (String email : rsrc.getUser().getEmailAddresses()) {
if (email != null) {
diff --git a/java/com/google/gerrit/server/restapi/account/GetPreferences.java b/java/com/google/gerrit/server/restapi/account/GetPreferences.java
index b071ade..46bc389 100644
--- a/java/com/google/gerrit/server/restapi/account/GetPreferences.java
+++ b/java/com/google/gerrit/server/restapi/account/GetPreferences.java
@@ -50,6 +50,6 @@
}
Account.Id id = rsrc.getUser().getAccountId();
- return accountCache.get(id).getAccount().getGeneralPreferencesInfo();
+ return accountCache.get(id).getGeneralPreferences();
}
}
diff --git a/java/com/google/gerrit/server/restapi/account/GetWatchedProjects.java b/java/com/google/gerrit/server/restapi/account/GetWatchedProjects.java
index b465029..ffddc7c 100644
--- a/java/com/google/gerrit/server/restapi/account/GetWatchedProjects.java
+++ b/java/com/google/gerrit/server/restapi/account/GetWatchedProjects.java
@@ -18,11 +18,13 @@
import com.google.common.collect.ComparisonChain;
import com.google.gerrit.extensions.client.ProjectWatchInfo;
import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountResource;
-import com.google.gerrit.server.account.WatchConfig;
+import com.google.gerrit.server.account.AccountState;
+import com.google.gerrit.server.account.Accounts;
import com.google.gerrit.server.account.WatchConfig.NotifyType;
import com.google.gerrit.server.account.WatchConfig.ProjectWatchKey;
import com.google.gerrit.server.permissions.GlobalPermission;
@@ -45,30 +47,31 @@
public class GetWatchedProjects implements RestReadView<AccountResource> {
private final PermissionBackend permissionBackend;
private final Provider<IdentifiedUser> self;
- private final WatchConfig.Accessor watchConfig;
+ private final Accounts accounts;
@Inject
public GetWatchedProjects(
- PermissionBackend permissionBackend,
- Provider<IdentifiedUser> self,
- WatchConfig.Accessor watchConfig) {
+ PermissionBackend permissionBackend, Provider<IdentifiedUser> self, Accounts accounts) {
this.permissionBackend = permissionBackend;
this.self = self;
- this.watchConfig = watchConfig;
+ this.accounts = accounts;
}
@Override
public List<ProjectWatchInfo> apply(AccountResource rsrc)
throws OrmException, AuthException, IOException, ConfigInvalidException,
- PermissionBackendException {
+ PermissionBackendException, ResourceNotFoundException {
if (self.get() != rsrc.getUser()) {
permissionBackend.user(self).check(GlobalPermission.ADMINISTRATE_SERVER);
}
Account.Id accountId = rsrc.getUser().getAccountId();
+ AccountState account = accounts.get(accountId);
+ if (account == null) {
+ throw new ResourceNotFoundException();
+ }
List<ProjectWatchInfo> projectWatchInfos = new ArrayList<>();
- for (Map.Entry<ProjectWatchKey, Set<NotifyType>> e :
- watchConfig.getProjectWatches(accountId).entrySet()) {
+ for (Map.Entry<ProjectWatchKey, Set<NotifyType>> e : account.getProjectWatches().entrySet()) {
ProjectWatchInfo pwi = new ProjectWatchInfo();
pwi.filter = e.getKey().filter();
pwi.project = e.getKey().project().get();
diff --git a/java/com/google/gerrit/server/restapi/account/Index.java b/java/com/google/gerrit/server/restapi/account/Index.java
index 2f4a87c..20a381a 100644
--- a/java/com/google/gerrit/server/restapi/account/Index.java
+++ b/java/com/google/gerrit/server/restapi/account/Index.java
@@ -1,16 +1,16 @@
-//Copyright (C) 2016 The Android Open Source Project
+// 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
+// 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
+// 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.
+// 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.restapi.account;
diff --git a/java/com/google/gerrit/server/restapi/account/PostWatchedProjects.java b/java/com/google/gerrit/server/restapi/account/PostWatchedProjects.java
index 145ce0e..bceaaf6 100644
--- a/java/com/google/gerrit/server/restapi/account/PostWatchedProjects.java
+++ b/java/com/google/gerrit/server/restapi/account/PostWatchedProjects.java
@@ -19,10 +19,9 @@
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountResource;
+import com.google.gerrit.server.account.AccountsUpdate;
import com.google.gerrit.server.account.WatchConfig;
import com.google.gerrit.server.account.WatchConfig.NotifyType;
import com.google.gerrit.server.account.WatchConfig.ProjectWatchKey;
@@ -49,8 +48,7 @@
private final PermissionBackend permissionBackend;
private final GetWatchedProjects getWatchedProjects;
private final ProjectsCollection projectsCollection;
- private final AccountCache accountCache;
- private final WatchConfig.Accessor watchConfig;
+ private final AccountsUpdate.User accountsUpdate;
@Inject
public PostWatchedProjects(
@@ -58,14 +56,12 @@
PermissionBackend permissionBackend,
GetWatchedProjects getWatchedProjects,
ProjectsCollection projectsCollection,
- AccountCache accountCache,
- WatchConfig.Accessor watchConfig) {
+ AccountsUpdate.User accountsUpdate) {
this.self = self;
this.permissionBackend = permissionBackend;
this.getWatchedProjects = getWatchedProjects;
this.projectsCollection = projectsCollection;
- this.accountCache = accountCache;
- this.watchConfig = watchConfig;
+ this.accountsUpdate = accountsUpdate;
}
@Override
@@ -76,9 +72,13 @@
permissionBackend.user(self).check(GlobalPermission.ADMINISTRATE_SERVER);
}
- Account.Id accountId = rsrc.getUser().getAccountId();
- watchConfig.upsertProjectWatches(accountId, asMap(input));
- accountCache.evict(accountId);
+ Map<ProjectWatchKey, Set<NotifyType>> projectWatches = asMap(input);
+ accountsUpdate
+ .create()
+ .update(
+ "Update Project Watches via API",
+ rsrc.getUser().getAccountId(),
+ u -> u.updateProjectWatches(projectWatches));
return getWatchedProjects.apply(rsrc);
}
diff --git a/java/com/google/gerrit/server/restapi/account/PutUsername.java b/java/com/google/gerrit/server/restapi/account/PutUsername.java
index 646fd44..fc40152 100644
--- a/java/com/google/gerrit/server/restapi/account/PutUsername.java
+++ b/java/com/google/gerrit/server/restapi/account/PutUsername.java
@@ -14,7 +14,9 @@
package com.google.gerrit.server.restapi.account;
-import com.google.gerrit.common.errors.NameAlreadyUsedException;
+import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_USERNAME;
+
+import com.google.common.base.Strings;
import com.google.gerrit.extensions.api.accounts.UsernameInput;
import com.google.gerrit.extensions.client.AccountFieldName;
import com.google.gerrit.extensions.restapi.AuthException;
@@ -22,14 +24,18 @@
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
+import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.AccountResource;
-import com.google.gerrit.server.account.ChangeUserName;
-import com.google.gerrit.server.account.InvalidUserNameException;
+import com.google.gerrit.server.account.AccountsUpdate;
import com.google.gerrit.server.account.Realm;
+import com.google.gerrit.server.account.externalids.ExternalId;
+import com.google.gerrit.server.account.externalids.ExternalIds;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.ssh.SshKeyCache;
+import com.google.gwtorm.server.OrmDuplicateKeyException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -40,19 +46,25 @@
@Singleton
public class PutUsername implements RestModifyView<AccountResource, UsernameInput> {
private final Provider<CurrentUser> self;
- private final ChangeUserName.Factory changeUserNameFactory;
private final PermissionBackend permissionBackend;
+ private final ExternalIds externalIds;
+ private final AccountsUpdate.Server accountsUpdate;
+ private final SshKeyCache sshKeyCache;
private final Realm realm;
@Inject
PutUsername(
Provider<CurrentUser> self,
- ChangeUserName.Factory changeUserNameFactory,
PermissionBackend permissionBackend,
+ ExternalIds externalIds,
+ AccountsUpdate.Server accountsUpdate,
+ SshKeyCache sshKeyCache,
Realm realm) {
this.self = self;
- this.changeUserNameFactory = changeUserNameFactory;
this.permissionBackend = permissionBackend;
+ this.externalIds = externalIds;
+ this.accountsUpdate = accountsUpdate;
+ this.sshKeyCache = sshKeyCache;
this.realm = realm;
}
@@ -73,19 +85,39 @@
input = new UsernameInput();
}
- try {
- changeUserNameFactory.create("Set Username via API", rsrc.getUser(), input.username).call();
- } catch (IllegalStateException e) {
- if (ChangeUserName.USERNAME_CANNOT_BE_CHANGED.equals(e.getMessage())) {
- throw new MethodNotAllowedException(e.getMessage());
- }
- throw e;
- } catch (InvalidUserNameException e) {
+ Account.Id accountId = rsrc.getUser().getAccountId();
+ if (!externalIds.byAccount(accountId, SCHEME_USERNAME).isEmpty()) {
+ throw new MethodNotAllowedException("Username cannot be changed.");
+ }
+
+ if (Strings.isNullOrEmpty(input.username)) {
+ return input.username;
+ }
+
+ if (!ExternalId.isValidUsername(input.username)) {
throw new UnprocessableEntityException("invalid username");
- } catch (NameAlreadyUsedException e) {
+ }
+
+ ExternalId.Key key = ExternalId.Key.create(SCHEME_USERNAME, input.username);
+ try {
+ accountsUpdate
+ .create()
+ .update(
+ "Set Username via API",
+ accountId,
+ u -> u.addExternalId(ExternalId.create(key, accountId, null, null)));
+ } catch (OrmDuplicateKeyException dupeErr) {
+ // If we are using this identity, don't report the exception.
+ ExternalId other = externalIds.get(key);
+ if (other != null && other.accountId().equals(accountId)) {
+ return input.username;
+ }
+
+ // Otherwise, someone else has this identity.
throw new ResourceConflictException("username already used");
}
+ sshKeyCache.evict(input.username);
return input.username;
}
}
diff --git a/java/com/google/gerrit/server/restapi/account/QueryAccounts.java b/java/com/google/gerrit/server/restapi/account/QueryAccounts.java
index f508fa2..fa4550d 100644
--- a/java/com/google/gerrit/server/restapi/account/QueryAccounts.java
+++ b/java/com/google/gerrit/server/restapi/account/QueryAccounts.java
@@ -21,22 +21,28 @@
import com.google.gerrit.extensions.common.AccountVisibility;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
+import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.index.query.QueryParseException;
import com.google.gerrit.index.query.QueryResult;
import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.AccountDirectory.FillOptions;
import com.google.gerrit.server.account.AccountInfoComparator;
import com.google.gerrit.server.account.AccountLoader;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.query.account.AccountPredicates;
import com.google.gerrit.server.query.account.AccountQueryBuilder;
import com.google.gerrit.server.query.account.AccountQueryProcessor;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
+import com.google.inject.Provider;
import java.util.Collections;
import java.util.EnumSet;
import java.util.LinkedHashMap;
@@ -49,6 +55,8 @@
public class QueryAccounts implements RestReadView<TopLevelResource> {
private static final int MAX_SUGGEST_RESULTS = 100;
+ private final Provider<CurrentUser> self;
+ private final PermissionBackend permissionBackend;
private final AccountLoader.Factory accountLoaderFactory;
private final AccountQueryBuilder queryBuilder;
private final AccountQueryProcessor queryProcessor;
@@ -117,10 +125,14 @@
@Inject
QueryAccounts(
+ Provider<CurrentUser> self,
+ PermissionBackend permissionBackend,
AccountLoader.Factory accountLoaderFactory,
AccountQueryBuilder queryBuilder,
AccountQueryProcessor queryProcessor,
@GerritServerConfig Config cfg) {
+ this.self = self;
+ this.permissionBackend = permissionBackend;
this.accountLoaderFactory = accountLoaderFactory;
this.queryBuilder = queryBuilder;
this.queryProcessor = queryProcessor;
@@ -143,7 +155,7 @@
@Override
public List<AccountInfo> apply(TopLevelResource rsrc)
- throws OrmException, BadRequestException, MethodNotAllowedException {
+ throws OrmException, RestApiException, PermissionBackendException {
if (Strings.isNullOrEmpty(query)) {
throw new BadRequestException("missing query field");
}
@@ -156,14 +168,21 @@
if (options.contains(ListAccountsOption.DETAILS)) {
fillOptions.addAll(AccountLoader.DETAILED_OPTIONS);
}
+ boolean modifyAccountCapabilityChecked = false;
if (options.contains(ListAccountsOption.ALL_EMAILS)) {
+ permissionBackend.user(self).check(GlobalPermission.MODIFY_ACCOUNT);
+ modifyAccountCapabilityChecked = true;
fillOptions.add(FillOptions.EMAIL);
fillOptions.add(FillOptions.SECONDARY_EMAILS);
}
if (suggest) {
fillOptions.addAll(AccountLoader.DETAILED_OPTIONS);
fillOptions.add(FillOptions.EMAIL);
- fillOptions.add(FillOptions.SECONDARY_EMAILS);
+
+ if (modifyAccountCapabilityChecked
+ || permissionBackend.user(self).test(GlobalPermission.MODIFY_ACCOUNT)) {
+ fillOptions.add(FillOptions.SECONDARY_EMAILS);
+ }
}
accountLoader = accountLoaderFactory.create(fillOptions);
diff --git a/java/com/google/gerrit/server/restapi/account/SetPreferences.java b/java/com/google/gerrit/server/restapi/account/SetPreferences.java
index 2c9f97a..9b2b231 100644
--- a/java/com/google/gerrit/server/restapi/account/SetPreferences.java
+++ b/java/com/google/gerrit/server/restapi/account/SetPreferences.java
@@ -14,19 +14,8 @@
package com.google.gerrit.server.restapi.account;
-import static com.google.gerrit.server.config.ConfigUtil.storeSection;
-import static com.google.gerrit.server.git.UserConfigSections.CHANGE_TABLE_COLUMN;
-import static com.google.gerrit.server.git.UserConfigSections.KEY_ID;
-import static com.google.gerrit.server.git.UserConfigSections.KEY_MATCH;
-import static com.google.gerrit.server.git.UserConfigSections.KEY_TARGET;
-import static com.google.gerrit.server.git.UserConfigSections.KEY_TOKEN;
-import static com.google.gerrit.server.git.UserConfigSections.KEY_URL;
-import static com.google.gerrit.server.git.UserConfigSections.URL_ALIAS;
-
import com.google.common.base.Strings;
-import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
-import com.google.gerrit.extensions.client.MenuItem;
import com.google.gerrit.extensions.config.DownloadScheme;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AuthException;
@@ -36,33 +25,24 @@
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountResource;
-import com.google.gerrit.server.account.GeneralPreferencesLoader;
-import com.google.gerrit.server.account.VersionedAccountPreferences;
-import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.git.MetaDataUpdate;
-import com.google.gerrit.server.git.UserConfigSections;
+import com.google.gerrit.server.account.AccountsUpdate;
+import com.google.gerrit.server.account.PreferencesConfig;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.errors.RepositoryNotFoundException;
-import org.eclipse.jgit.lib.Config;
@Singleton
public class SetPreferences implements RestModifyView<AccountResource, GeneralPreferencesInfo> {
private final Provider<CurrentUser> self;
private final AccountCache cache;
private final PermissionBackend permissionBackend;
- private final GeneralPreferencesLoader loader;
- private final Provider<MetaDataUpdate.User> metaDataUpdateFactory;
- private final AllUsersName allUsersName;
+ private final AccountsUpdate.User accountsUpdate;
private final DynamicMap<DownloadScheme> downloadSchemes;
@Inject
@@ -70,124 +50,31 @@
Provider<CurrentUser> self,
AccountCache cache,
PermissionBackend permissionBackend,
- GeneralPreferencesLoader loader,
- Provider<MetaDataUpdate.User> metaDataUpdateFactory,
- AllUsersName allUsersName,
+ AccountsUpdate.User accountsUpdate,
DynamicMap<DownloadScheme> downloadSchemes) {
this.self = self;
- this.loader = loader;
this.cache = cache;
this.permissionBackend = permissionBackend;
- this.metaDataUpdateFactory = metaDataUpdateFactory;
- this.allUsersName = allUsersName;
+ this.accountsUpdate = accountsUpdate;
this.downloadSchemes = downloadSchemes;
}
@Override
- public GeneralPreferencesInfo apply(AccountResource rsrc, GeneralPreferencesInfo i)
+ public GeneralPreferencesInfo apply(AccountResource rsrc, GeneralPreferencesInfo input)
throws AuthException, BadRequestException, IOException, ConfigInvalidException,
- PermissionBackendException {
+ PermissionBackendException, OrmException {
if (self.get() != rsrc.getUser()) {
permissionBackend.user(self).check(GlobalPermission.MODIFY_ACCOUNT);
}
- checkDownloadScheme(i.downloadScheme);
+ checkDownloadScheme(input.downloadScheme);
+ PreferencesConfig.validateMy(input.my);
Account.Id id = rsrc.getUser().getAccountId();
- GeneralPreferencesInfo n = loader.merge(id, i);
- n.changeTable = i.changeTable;
- n.my = i.my;
- n.urlAliases = i.urlAliases;
-
- writeToGit(id, n);
-
- return cache.get(id).getAccount().getGeneralPreferencesInfo();
- }
-
- private void writeToGit(Account.Id id, GeneralPreferencesInfo i)
- throws RepositoryNotFoundException, IOException, ConfigInvalidException, BadRequestException {
- VersionedAccountPreferences prefs;
- try (MetaDataUpdate md = metaDataUpdateFactory.get().create(allUsersName)) {
- prefs = VersionedAccountPreferences.forUser(id);
- prefs.load(md);
-
- storeSection(
- prefs.getConfig(),
- UserConfigSections.GENERAL,
- null,
- i,
- loader.readDefaultsFromGit(md.getRepository(), null));
-
- storeMyChangeTableColumns(prefs, i.changeTable);
- storeMyMenus(prefs, i.my);
- storeUrlAliases(prefs, i.urlAliases);
- prefs.commit(md);
- cache.evict(id);
- }
- }
-
- public static void storeMyMenus(VersionedAccountPreferences prefs, List<MenuItem> my)
- throws BadRequestException {
- Config cfg = prefs.getConfig();
- if (my != null) {
- unsetSection(cfg, UserConfigSections.MY);
- for (MenuItem item : my) {
- checkRequiredMenuItemField(item.name, "name");
- checkRequiredMenuItemField(item.url, "URL");
-
- set(cfg, item.name, KEY_URL, item.url);
- set(cfg, item.name, KEY_TARGET, item.target);
- set(cfg, item.name, KEY_ID, item.id);
- }
- }
- }
-
- public static void storeMyChangeTableColumns(
- VersionedAccountPreferences prefs, List<String> changeTable) {
- Config cfg = prefs.getConfig();
- if (changeTable != null) {
- unsetSection(cfg, UserConfigSections.CHANGE_TABLE);
- cfg.setStringList(UserConfigSections.CHANGE_TABLE, null, CHANGE_TABLE_COLUMN, changeTable);
- }
- }
-
- private static void set(Config cfg, String section, String key, @Nullable String val) {
- if (val == null || val.trim().isEmpty()) {
- cfg.unset(UserConfigSections.MY, section.trim(), key);
- } else {
- cfg.setString(UserConfigSections.MY, section.trim(), key, val.trim());
- }
- }
-
- private static void unsetSection(Config cfg, String section) {
- cfg.unsetSection(section, null);
- for (String subsection : cfg.getSubsections(section)) {
- cfg.unsetSection(section, subsection);
- }
- }
-
- public static void storeUrlAliases(
- VersionedAccountPreferences prefs, Map<String, String> urlAliases) {
- if (urlAliases != null) {
- Config cfg = prefs.getConfig();
- for (String subsection : cfg.getSubsections(URL_ALIAS)) {
- cfg.unsetSection(URL_ALIAS, subsection);
- }
-
- int i = 1;
- for (Entry<String, String> e : urlAliases.entrySet()) {
- cfg.setString(URL_ALIAS, URL_ALIAS + i, KEY_MATCH, e.getKey());
- cfg.setString(URL_ALIAS, URL_ALIAS + i, KEY_TOKEN, e.getValue());
- i++;
- }
- }
- }
-
- private static void checkRequiredMenuItemField(String value, String name)
- throws BadRequestException {
- if (value == null || value.trim().isEmpty()) {
- throw new BadRequestException(name + " for menu item is required");
- }
+ accountsUpdate
+ .create()
+ .update("Set Preferences via API", id, u -> u.setGeneralPreferences(input));
+ return cache.get(id).getGeneralPreferences();
}
private void checkDownloadScheme(String downloadScheme) throws BadRequestException {
diff --git a/java/com/google/gerrit/server/restapi/change/CherryPick.java b/java/com/google/gerrit/server/restapi/change/CherryPick.java
index c1479b7..2de5ba46 100644
--- a/java/com/google/gerrit/server/restapi/change/CherryPick.java
+++ b/java/com/google/gerrit/server/restapi/change/CherryPick.java
@@ -37,6 +37,7 @@
import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.NoSuchProjectException;
+import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.update.BatchUpdate;
import com.google.gerrit.server.update.RetryHelper;
import com.google.gerrit.server.update.RetryingRestModifyView;
@@ -47,16 +48,20 @@
import com.google.inject.Singleton;
import java.io.IOException;
import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
@Singleton
public class CherryPick
extends RetryingRestModifyView<RevisionResource, CherryPickInput, ChangeInfo>
implements UiAction<RevisionResource> {
+ private static final Logger log = LoggerFactory.getLogger(CherryPick.class);
private final PermissionBackend permissionBackend;
private final Provider<CurrentUser> user;
private final CherryPickChange cherryPickChange;
private final ChangeJson.Factory json;
private final ContributorAgreementsChecker contributorAgreements;
+ private final ProjectCache projectCache;
@Inject
CherryPick(
@@ -65,13 +70,15 @@
RetryHelper retryHelper,
CherryPickChange cherryPickChange,
ChangeJson.Factory json,
- ContributorAgreementsChecker contributorAgreements) {
+ ContributorAgreementsChecker contributorAgreements,
+ ProjectCache projectCache) {
super(retryHelper);
this.permissionBackend = permissionBackend;
this.user = user;
this.cherryPickChange = cherryPickChange;
this.json = json;
this.contributorAgreements = contributorAgreements;
+ this.projectCache = projectCache;
}
@Override
@@ -94,6 +101,7 @@
.project(rsrc.getChange().getProject())
.ref(refName)
.check(RefPermission.CREATE_CHANGE);
+ projectCache.checkedGet(rsrc.getProject()).checkStatePermitsWrite();
try {
Change.Id cherryPickedChangeId =
@@ -113,12 +121,18 @@
@Override
public UiAction.Description getDescription(RevisionResource rsrc) {
+ boolean projectStatePermitsWrite = false;
+ try {
+ projectStatePermitsWrite = projectCache.checkedGet(rsrc.getProject()).statePermitsWrite();
+ } catch (IOException e) {
+ log.error("Failed to check if project state permits write: " + rsrc.getProject(), e);
+ }
return new UiAction.Description()
.setLabel("Cherry Pick")
.setTitle("Cherry pick change to a different branch")
.setVisible(
and(
- rsrc.isCurrent(),
+ rsrc.isCurrent() && projectStatePermitsWrite,
permissionBackend
.user(user)
.project(rsrc.getProject())
diff --git a/java/com/google/gerrit/server/restapi/change/CherryPickCommit.java b/java/com/google/gerrit/server/restapi/change/CherryPickCommit.java
index 039c3ca6..7c10086 100644
--- a/java/com/google/gerrit/server/restapi/change/CherryPickCommit.java
+++ b/java/com/google/gerrit/server/restapi/change/CherryPickCommit.java
@@ -94,6 +94,7 @@
.project(projectName)
.ref(refName)
.check(RefPermission.CREATE_CHANGE);
+ rsrc.getProjectState().checkStatePermitsWrite();
try {
Change.Id cherryPickedChangeId =
diff --git a/java/com/google/gerrit/server/restapi/change/CreateChange.java b/java/com/google/gerrit/server/restapi/change/CreateChange.java
index d4e1c40..9aa1957 100644
--- a/java/com/google/gerrit/server/restapi/change/CreateChange.java
+++ b/java/com/google/gerrit/server/restapi/change/CreateChange.java
@@ -28,6 +28,7 @@
import com.google.gerrit.extensions.common.MergeInput;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
@@ -114,6 +115,7 @@
private final SubmitType submitType;
private final NotifyUtil notifyUtil;
private final ContributorAgreementsChecker contributorAgreements;
+ private final boolean disablePrivateChanges;
@Inject
CreateChange(
@@ -152,6 +154,7 @@
this.changeFinder = changeFinder;
this.psUtil = psUtil;
this.submitType = config.getEnum("project", null, "submitType", SubmitType.MERGE_IF_NECESSARY);
+ this.disablePrivateChanges = config.getBoolean("change", null, "disablePrivateChanges", false);
this.mergeUtilFactory = mergeUtilFactory;
this.notifyUtil = notifyUtil;
this.contributorAgreements = contributorAgreements;
@@ -181,11 +184,19 @@
}
ProjectResource rsrc = projectsCollection.parse(input.project);
+ boolean privateByDefault = rsrc.getProjectState().is(BooleanProjectConfig.PRIVATE_BY_DEFAULT);
+ boolean isPrivate = input.isPrivate == null ? privateByDefault : input.isPrivate;
+
+ if (isPrivate && disablePrivateChanges) {
+ throw new MethodNotAllowedException("private changes are disabled");
+ }
+
contributorAgreements.check(rsrc.getNameKey(), rsrc.getUser());
Project.NameKey project = rsrc.getNameKey();
String refName = RefNames.fullName(input.branch);
permissionBackend.user(user).project(project).ref(refName).check(RefPermission.CREATE_CHANGE);
+ rsrc.getProjectState().checkStatePermitsWrite();
try (Repository git = gitManager.openRepository(project);
ObjectInserter oi = git.newObjectInserter();
@@ -231,7 +242,7 @@
IdentifiedUser me = user.get().asIdentifiedUser();
PersonIdent author = me.newCommitterIdent(now, serverTimeZone);
AccountState account = accountCache.get(me.getAccountId());
- GeneralPreferencesInfo info = account.getAccount().getGeneralPreferencesInfo();
+ GeneralPreferencesInfo info = account.getGeneralPreferences();
ObjectId treeId = mergeTip == null ? emptyTreeId(oi) : mergeTip.getTree();
ObjectId id = ChangeIdUtil.computeChangeId(treeId, mergeTip, author, author, input.subject);
@@ -257,7 +268,6 @@
c = newCommit(oi, rw, author, mergeTip, commitMessage);
}
- boolean privateByDefault = rsrc.getProjectState().is(BooleanProjectConfig.PRIVATE_BY_DEFAULT);
Change.Id changeId = new Change.Id(seq.nextChangeId());
ChangeInserter ins = changeInserterFactory.create(changeId, c, refName);
ins.setMessage(String.format("Uploaded patch set %s.", ins.getPatchSetId().get()));
@@ -266,7 +276,7 @@
topic = Strings.emptyToNull(topic.trim());
}
ins.setTopic(topic);
- ins.setPrivate(input.isPrivate == null ? privateByDefault : input.isPrivate);
+ ins.setPrivate(isPrivate);
ins.setWorkInProgress(input.workInProgress != null && input.workInProgress);
ins.setGroups(groups);
ins.setNotify(input.notify);
diff --git a/java/com/google/gerrit/server/restapi/change/CreateMergePatchSet.java b/java/com/google/gerrit/server/restapi/change/CreateMergePatchSet.java
index 33a7453..dcaba77 100644
--- a/java/com/google/gerrit/server/restapi/change/CreateMergePatchSet.java
+++ b/java/com/google/gerrit/server/restapi/change/CreateMergePatchSet.java
@@ -130,6 +130,9 @@
UpdateException, PermissionBackendException {
rsrc.permissions().database(db).check(ChangePermission.ADD_PATCH_SET);
+ ProjectState projectState = projectCache.checkedGet(rsrc.getProject());
+ projectState.checkStatePermitsWrite();
+
MergeInput merge = in.merge;
if (merge == null || Strings.isNullOrEmpty(merge.source)) {
throw new BadRequestException("merge.source must be non-empty");
@@ -137,7 +140,6 @@
in.baseChange = Strings.nullToEmpty(in.baseChange).trim();
PatchSet ps = psUtil.current(db.get(), rsrc.getNotes());
- ProjectState projectState = projectCache.checkedGet(rsrc.getProject());
Change change = rsrc.getChange();
Project.NameKey project = change.getProject();
Branch.NameKey dest = change.getDest();
diff --git a/java/com/google/gerrit/server/restapi/change/Move.java b/java/com/google/gerrit/server/restapi/change/Move.java
index 2607f9c..2ad954d 100644
--- a/java/com/google/gerrit/server/restapi/change/Move.java
+++ b/java/com/google/gerrit/server/restapi/change/Move.java
@@ -70,10 +70,14 @@
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;
@Singleton
public class Move extends RetryingRestModifyView<ChangeResource, MoveInput, ChangeInfo>
implements UiAction<ChangeResource> {
+ private static final Logger log = LoggerFactory.getLogger(Move.class);
+
private final PermissionBackend permissionBackend;
private final Provider<ReviewDb> dbProvider;
private final ChangeJson.Factory json;
@@ -114,7 +118,8 @@
@Override
protected ChangeInfo applyImpl(
BatchUpdate.Factory updateFactory, ChangeResource rsrc, MoveInput input)
- throws RestApiException, OrmException, UpdateException, PermissionBackendException {
+ throws RestApiException, OrmException, UpdateException, PermissionBackendException,
+ IOException {
Change change = rsrc.getChange();
Project.NameKey project = rsrc.getProject();
IdentifiedUser caller = rsrc.getUser().asIdentifiedUser();
@@ -136,6 +141,7 @@
} catch (AuthException denied) {
throw new AuthException("move not permitted", denied);
}
+ projectCache.checkedGet(project).checkStatePermitsWrite();
try (BatchUpdate u =
updateFactory.create(dbProvider.get(), project, caller, TimeUtil.nowTs())) {
@@ -274,12 +280,18 @@
@Override
public UiAction.Description getDescription(ChangeResource rsrc) {
Change change = rsrc.getChange();
+ boolean projectStatePermitsWrite = false;
+ try {
+ projectStatePermitsWrite = projectCache.checkedGet(rsrc.getProject()).statePermitsWrite();
+ } catch (IOException e) {
+ log.error("Failed to check if project state permits write: " + rsrc.getProject(), e);
+ }
return new UiAction.Description()
.setLabel("Move Change")
.setTitle("Move change to a different branch")
.setVisible(
and(
- change.getStatus().isOpen(),
+ change.getStatus().isOpen() && projectStatePermitsWrite,
and(
permissionBackend
.user(rsrc.getUser())
diff --git a/java/com/google/gerrit/server/restapi/change/PostPrivate.java b/java/com/google/gerrit/server/restapi/change/PostPrivate.java
index 9f02fa8..5a13346 100644
--- a/java/com/google/gerrit/server/restapi/change/PostPrivate.java
+++ b/java/com/google/gerrit/server/restapi/change/PostPrivate.java
@@ -20,6 +20,7 @@
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.extensions.conditions.BooleanCondition;
import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.webui.UiAction;
@@ -27,6 +28,7 @@
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.change.ChangeResource;
+import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.update.BatchUpdate;
@@ -36,6 +38,7 @@
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
+import org.eclipse.jgit.lib.Config;
@Singleton
public class PostPrivate
@@ -45,6 +48,7 @@
private final Provider<ReviewDb> dbProvider;
private final PermissionBackend permissionBackend;
private final SetPrivateOp.Factory setPrivateOpFactory;
+ private final boolean disablePrivateChanges;
@Inject
PostPrivate(
@@ -52,18 +56,24 @@
RetryHelper retryHelper,
ChangeMessagesUtil cmUtil,
PermissionBackend permissionBackend,
- SetPrivateOp.Factory setPrivateOpFactory) {
+ SetPrivateOp.Factory setPrivateOpFactory,
+ @GerritServerConfig Config config) {
super(retryHelper);
this.dbProvider = dbProvider;
this.cmUtil = cmUtil;
this.permissionBackend = permissionBackend;
this.setPrivateOpFactory = setPrivateOpFactory;
+ this.disablePrivateChanges = config.getBoolean("change", null, "disablePrivateChanges", false);
}
@Override
public Response<String> applyImpl(
BatchUpdate.Factory updateFactory, ChangeResource rsrc, SetPrivateOp.Input input)
throws RestApiException, UpdateException {
+ if (disablePrivateChanges) {
+ throw new MethodNotAllowedException("private changes are disabled");
+ }
+
if (!canSetPrivate(rsrc).value()) {
throw new AuthException("not allowed to mark private");
}
@@ -88,7 +98,7 @@
return new UiAction.Description()
.setLabel("Mark private")
.setTitle("Mark change as private")
- .setVisible(and(!change.isPrivate(), canSetPrivate(rsrc)));
+ .setVisible(and(!disablePrivateChanges && !change.isPrivate(), canSetPrivate(rsrc)));
}
private BooleanCondition canSetPrivate(ChangeResource rsrc) {
diff --git a/java/com/google/gerrit/server/restapi/change/PostReview.java b/java/com/google/gerrit/server/restapi/change/PostReview.java
index 7507666..c2c5dce 100644
--- a/java/com/google/gerrit/server/restapi/change/PostReview.java
+++ b/java/com/google/gerrit/server/restapi/change/PostReview.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.restapi.change;
+import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.gerrit.server.CommentsUtil.setCommentRevId;
@@ -27,7 +28,6 @@
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import com.google.auto.value.AutoValue;
-import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
@@ -232,10 +232,9 @@
}
ProjectState projectState = projectCache.checkedGet(revision.getProject());
LabelTypes labelTypes = projectState.getLabelTypes(revision.getNotes(), revision.getUser());
+ input.drafts = firstNonNull(input.drafts, DraftHandling.KEEP);
if (input.onBehalfOf != null) {
revision = onBehalfOf(revision, labelTypes, input);
- } else if (input.drafts == null) {
- input.drafts = DraftHandling.DELETE;
}
if (input.labels != null) {
checkLabels(revision, labelTypes, input.labels);
@@ -435,9 +434,6 @@
throw new AuthException(
String.format("label required to post review on behalf of \"%s\"", in.onBehalfOf));
}
- if (in.drafts == null) {
- in.drafts = DraftHandling.KEEP;
- }
if (in.drafts != DraftHandling.KEEP) {
throw new AuthException("not allowed to modify other user's drafts");
}
@@ -900,7 +896,6 @@
}
}
- List<Comment> toDel = new ArrayList<>();
List<Comment> toPublish = new ArrayList<>();
Set<CommentSetEntry> existingIds =
@@ -931,23 +926,19 @@
}
switch (in.drafts) {
- case KEEP:
- default:
- break;
- case DELETE:
- toDel.addAll(drafts.values());
- break;
case PUBLISH:
case PUBLISH_ALL_REVISIONS:
commentsUtil.publish(ctx, psId, drafts.values(), in.tag);
comments.addAll(drafts.values());
break;
+ case KEEP:
+ default:
+ break;
}
ChangeUpdate u = ctx.getUpdate(psId);
- commentsUtil.deleteComments(ctx.getDb(), u, toDel);
commentsUtil.putComments(ctx.getDb(), u, Status.PUBLISHED, toPublish);
comments.addAll(toPublish);
- return !toDel.isEmpty() || !toPublish.isEmpty();
+ return !toPublish.isEmpty();
}
private boolean insertRobotComments(ChangeContext ctx) throws OrmException {
@@ -1118,8 +1109,7 @@
private boolean updateLabels(ProjectState projectState, ChangeContext ctx)
throws OrmException, ResourceConflictException, IOException {
- Map<String, Short> inLabels =
- MoreObjects.firstNonNull(in.labels, Collections.<String, Short>emptyMap());
+ Map<String, Short> inLabels = firstNonNull(in.labels, Collections.emptyMap());
// If no labels were modified and change is closed, abort early.
// This avoids trying to record a modified label caused by a user
diff --git a/java/com/google/gerrit/server/restapi/change/PutMessage.java b/java/com/google/gerrit/server/restapi/change/PutMessage.java
index f277d2c..c9c43cb 100644
--- a/java/com/google/gerrit/server/restapi/change/PutMessage.java
+++ b/java/com/google/gerrit/server/restapi/change/PutMessage.java
@@ -179,7 +179,7 @@
}
private void ensureCanEditCommitMessage(ChangeNotes changeNotes)
- throws AuthException, PermissionBackendException {
+ throws AuthException, PermissionBackendException, IOException, ResourceConflictException {
if (!currentUserProvider.get().isIdentifiedUser()) {
throw new AuthException("Authentication required");
}
@@ -189,6 +189,7 @@
.database(db.get())
.change(changeNotes)
.check(ChangePermission.ADD_PATCH_SET);
+ projectCache.checkedGet(changeNotes.getProjectName()).checkStatePermitsWrite();
} catch (AuthException denied) {
throw new AuthException("modifying commit message not permitted", denied);
}
diff --git a/java/com/google/gerrit/server/restapi/change/Rebase.java b/java/com/google/gerrit/server/restapi/change/Rebase.java
index 7ee5709..767ef7b 100644
--- a/java/com/google/gerrit/server/restapi/change/Rebase.java
+++ b/java/com/google/gerrit/server/restapi/change/Rebase.java
@@ -47,6 +47,7 @@
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.update.BatchUpdate;
import com.google.gerrit.server.update.RetryHelper;
import com.google.gerrit.server.update.RetryingRestModifyView;
@@ -79,6 +80,7 @@
private final ChangeJson.Factory json;
private final Provider<ReviewDb> dbProvider;
private final PermissionBackend permissionBackend;
+ private final ProjectCache projectCache;
@Inject
public Rebase(
@@ -88,7 +90,8 @@
RebaseUtil rebaseUtil,
ChangeJson.Factory json,
Provider<ReviewDb> dbProvider,
- PermissionBackend permissionBackend) {
+ PermissionBackend permissionBackend,
+ ProjectCache projectCache) {
super(retryHelper);
this.repoManager = repoManager;
this.rebaseFactory = rebaseFactory;
@@ -96,6 +99,7 @@
this.json = json;
this.dbProvider = dbProvider;
this.permissionBackend = permissionBackend;
+ this.projectCache = projectCache;
}
@Override
@@ -104,6 +108,7 @@
throws EmailException, OrmException, UpdateException, RestApiException, IOException,
NoSuchChangeException, PermissionBackendException {
rsrc.permissions().database(dbProvider).check(ChangePermission.REBASE);
+ projectCache.checkedGet(rsrc.getProject()).checkStatePermitsWrite();
Change change = rsrc.getChange();
try (Repository repo = repoManager.openRepository(change.getProject());
@@ -205,6 +210,13 @@
boolean visible = change.getStatus().isOpen() && resource.isCurrent();
boolean enabled = false;
+ try {
+ visible &= projectCache.checkedGet(resource.getProject()).statePermitsWrite();
+ } catch (IOException e) {
+ log.error("Failed to check if project state permits write: " + resource.getProject(), e);
+ visible = false;
+ }
+
if (visible) {
try (Repository repo = repoManager.openRepository(dest.getParentKey());
RevWalk rw = new RevWalk(repo)) {
diff --git a/java/com/google/gerrit/server/restapi/change/Restore.java b/java/com/google/gerrit/server/restapi/change/Restore.java
index 4bf1254..642c35a 100644
--- a/java/com/google/gerrit/server/restapi/change/Restore.java
+++ b/java/com/google/gerrit/server/restapi/change/Restore.java
@@ -39,6 +39,7 @@
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.permissions.ChangePermission;
import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.update.BatchUpdate;
import com.google.gerrit.server.update.BatchUpdateOp;
import com.google.gerrit.server.update.ChangeContext;
@@ -50,6 +51,7 @@
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
+import java.io.IOException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -64,6 +66,7 @@
private final ChangeMessagesUtil cmUtil;
private final PatchSetUtil psUtil;
private final ChangeRestored changeRestored;
+ private final ProjectCache projectCache;
@Inject
Restore(
@@ -73,7 +76,8 @@
ChangeMessagesUtil cmUtil,
PatchSetUtil psUtil,
RetryHelper retryHelper,
- ChangeRestored changeRestored) {
+ ChangeRestored changeRestored,
+ ProjectCache projectCache) {
super(retryHelper);
this.restoredSenderFactory = restoredSenderFactory;
this.dbProvider = dbProvider;
@@ -81,13 +85,16 @@
this.cmUtil = cmUtil;
this.psUtil = psUtil;
this.changeRestored = changeRestored;
+ this.projectCache = projectCache;
}
@Override
protected ChangeInfo applyImpl(
BatchUpdate.Factory updateFactory, ChangeResource req, RestoreInput input)
- throws RestApiException, UpdateException, OrmException, PermissionBackendException {
+ throws RestApiException, UpdateException, OrmException, PermissionBackendException,
+ IOException {
req.permissions().database(dbProvider).check(ChangePermission.RESTORE);
+ projectCache.checkedGet(req.getProject()).checkStatePermitsWrite();
Op op = new Op(input);
try (BatchUpdate u =
@@ -154,12 +161,18 @@
@Override
public UiAction.Description getDescription(ChangeResource rsrc) {
+ boolean projectStatePermitsWrite = false;
+ try {
+ projectStatePermitsWrite = projectCache.checkedGet(rsrc.getProject()).statePermitsWrite();
+ } catch (IOException e) {
+ log.error("Failed to check if project state permits write: " + rsrc.getProject(), e);
+ }
return new UiAction.Description()
.setLabel("Restore")
.setTitle("Restore the change")
.setVisible(
and(
- rsrc.getChange().getStatus() == Status.ABANDONED,
+ rsrc.getChange().getStatus() == Status.ABANDONED && projectStatePermitsWrite,
rsrc.permissions().database(dbProvider).testCond(ChangePermission.RESTORE)));
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/Revert.java b/java/com/google/gerrit/server/restapi/change/Revert.java
index bdab012..b55ca5e 100644
--- a/java/com/google/gerrit/server/restapi/change/Revert.java
+++ b/java/com/google/gerrit/server/restapi/change/Revert.java
@@ -53,6 +53,7 @@
import com.google.gerrit.server.project.ContributorAgreementsChecker;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.NoSuchProjectException;
+import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.update.BatchUpdate;
import com.google.gerrit.server.update.BatchUpdateOp;
import com.google.gerrit.server.update.ChangeContext;
@@ -100,6 +101,7 @@
private final ApprovalsUtil approvalsUtil;
private final ChangeReverted changeReverted;
private final ContributorAgreementsChecker contributorAgreements;
+ private final ProjectCache projectCache;
@Inject
Revert(
@@ -116,7 +118,8 @@
@GerritPersonIdent PersonIdent serverIdent,
ApprovalsUtil approvalsUtil,
ChangeReverted changeReverted,
- ContributorAgreementsChecker contributorAgreements) {
+ ContributorAgreementsChecker contributorAgreements,
+ ProjectCache projectCache) {
super(retryHelper);
this.db = db;
this.permissionBackend = permissionBackend;
@@ -131,6 +134,7 @@
this.approvalsUtil = approvalsUtil;
this.changeReverted = changeReverted;
this.contributorAgreements = contributorAgreements;
+ this.projectCache = projectCache;
}
@Override
@@ -145,6 +149,7 @@
contributorAgreements.check(rsrc.getProject(), rsrc.getUser());
permissionBackend.user(rsrc.getUser()).ref(change.getDest()).check(CREATE_CHANGE);
+ projectCache.checkedGet(rsrc.getProject()).checkStatePermitsWrite();
Change.Id revertId =
revert(updateFactory, rsrc.getNotes(), rsrc.getUser(), Strings.emptyToNull(input.message));
@@ -243,12 +248,18 @@
@Override
public UiAction.Description getDescription(ChangeResource rsrc) {
Change change = rsrc.getChange();
+ boolean projectStatePermitsWrite = false;
+ try {
+ projectStatePermitsWrite = projectCache.checkedGet(rsrc.getProject()).statePermitsWrite();
+ } catch (IOException e) {
+ log.error("Failed to check if project state permits write: " + rsrc.getProject(), e);
+ }
return new UiAction.Description()
.setLabel("Revert")
.setTitle("Revert the change")
.setVisible(
and(
- change.getStatus() == Change.Status.MERGED,
+ change.getStatus() == Change.Status.MERGED && projectStatePermitsWrite,
permissionBackend
.user(rsrc.getUser())
.ref(change.getDest())
diff --git a/java/com/google/gerrit/server/restapi/change/ReviewersUtil.java b/java/com/google/gerrit/server/restapi/change/ReviewersUtil.java
index 70ebbc1..7a2a148 100644
--- a/java/com/google/gerrit/server/restapi/change/ReviewersUtil.java
+++ b/java/com/google/gerrit/server/restapi/change/ReviewersUtil.java
@@ -36,6 +36,7 @@
import com.google.gerrit.metrics.Timer0;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.AccountControl;
import com.google.gerrit.server.account.AccountDirectory.FillOptions;
import com.google.gerrit.server.account.AccountLoader;
@@ -44,6 +45,9 @@
import com.google.gerrit.server.index.account.AccountField;
import com.google.gerrit.server.index.account.AccountIndexCollection;
import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.query.account.AccountPredicates;
@@ -51,6 +55,7 @@
import com.google.gwtorm.server.OrmException;
import com.google.gwtorm.server.ResultSet;
import com.google.inject.Inject;
+import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
import java.util.ArrayList;
@@ -109,7 +114,7 @@
// give the ranking algorithm a good set of candidates it can work with
private static final int CANDIDATE_LIST_MULTIPLIER = 2;
- private final AccountLoader accountLoader;
+ private final AccountLoader.Factory accountLoaderFactory;
private final AccountQueryBuilder accountQueryBuilder;
private final GroupBackend groupBackend;
private final GroupMembers groupMembers;
@@ -118,6 +123,8 @@
private final AccountIndexCollection accountIndexes;
private final IndexConfig indexConfig;
private final AccountControl.Factory accountControlFactory;
+ private final Provider<CurrentUser> self;
+ private final PermissionBackend permissionBackend;
@Inject
ReviewersUtil(
@@ -129,10 +136,10 @@
Metrics metrics,
AccountIndexCollection accountIndexes,
IndexConfig indexConfig,
- AccountControl.Factory accountControlFactory) {
- Set<FillOptions> fillOptions = EnumSet.of(FillOptions.SECONDARY_EMAILS);
- fillOptions.addAll(AccountLoader.DETAILED_OPTIONS);
- this.accountLoader = accountLoaderFactory.create(fillOptions);
+ AccountControl.Factory accountControlFactory,
+ Provider<CurrentUser> self,
+ PermissionBackend permissionBackend) {
+ this.accountLoaderFactory = accountLoaderFactory;
this.accountQueryBuilder = accountQueryBuilder;
this.groupBackend = groupBackend;
this.groupMembers = groupMembers;
@@ -141,6 +148,8 @@
this.accountIndexes = accountIndexes;
this.indexConfig = indexConfig;
this.accountControlFactory = accountControlFactory;
+ this.self = self;
+ this.permissionBackend = permissionBackend;
}
public interface VisibilityControl {
@@ -153,7 +162,7 @@
ProjectState projectState,
VisibilityControl visibilityControl,
boolean excludeGroups)
- throws IOException, OrmException, ConfigInvalidException {
+ throws IOException, OrmException, ConfigInvalidException, PermissionBackendException {
String query = suggestReviewers.getQuery();
int limit = suggestReviewers.getLimit();
@@ -242,7 +251,14 @@
}
private List<SuggestedReviewerInfo> loadAccounts(List<Account.Id> accountIds)
- throws OrmException {
+ throws OrmException, PermissionBackendException {
+ Set<FillOptions> fillOptions =
+ permissionBackend.user(self).test(GlobalPermission.MODIFY_ACCOUNT)
+ ? EnumSet.of(FillOptions.SECONDARY_EMAILS)
+ : EnumSet.noneOf(FillOptions.class);
+ fillOptions.addAll(AccountLoader.DETAILED_OPTIONS);
+ AccountLoader accountLoader = accountLoaderFactory.create(fillOptions);
+
try (Timer0.Context ctx = metrics.loadAccountsLatency.start()) {
List<SuggestedReviewerInfo> reviewer =
accountIds
diff --git a/java/com/google/gerrit/server/restapi/change/Submit.java b/java/com/google/gerrit/server/restapi/change/Submit.java
index 04aafff..264a0fb 100644
--- a/java/com/google/gerrit/server/restapi/change/Submit.java
+++ b/java/com/google/gerrit/server/restapi/change/Submit.java
@@ -57,6 +57,7 @@
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.NoSuchChangeException;
+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.restapi.account.AccountsCollection;
@@ -132,6 +133,7 @@
private final boolean submitWholeTopic;
private final Provider<InternalChangeQuery> queryProvider;
private final PatchSetUtil psUtil;
+ private final ProjectCache projectCache;
@Inject
Submit(
@@ -146,7 +148,8 @@
AccountsCollection accounts,
@GerritServerConfig Config cfg,
Provider<InternalChangeQuery> queryProvider,
- PatchSetUtil psUtil) {
+ PatchSetUtil psUtil,
+ ProjectCache projectCache) {
this.dbProvider = dbProvider;
this.repoManager = repoManager;
this.permissionBackend = permissionBackend;
@@ -183,6 +186,7 @@
cfg.getString("change", null, "submitTopicTooltip"), DEFAULT_TOPIC_TOOLTIP));
this.queryProvider = queryProvider;
this.psUtil = psUtil;
+ this.projectCache = projectCache;
}
@Override
@@ -197,6 +201,7 @@
rsrc.permissions().check(ChangePermission.SUBMIT);
submitter = rsrc.getUser().asIdentifiedUser();
}
+ projectCache.checkedGet(rsrc.getProject()).checkStatePermitsWrite();
return new Output(mergeChange(rsrc, submitter, input));
}
@@ -303,6 +308,15 @@
return null; // submit not visible
}
+ try {
+ if (!projectCache.checkedGet(resource.getProject()).statePermitsWrite()) {
+ return null; // submit not visible
+ }
+ } catch (IOException e) {
+ log.error("Error checking if change is submittable", e);
+ throw new OrmRuntimeException("Could not determine problems for the change", e);
+ }
+
ReviewDb db = dbProvider.get();
ChangeData cd = changeDataFactory.create(db, resource.getNotes());
try {
diff --git a/java/com/google/gerrit/server/restapi/change/SuggestChangeReviewers.java b/java/com/google/gerrit/server/restapi/change/SuggestChangeReviewers.java
index b8792c4..4dc5b06 100644
--- a/java/com/google/gerrit/server/restapi/change/SuggestChangeReviewers.java
+++ b/java/com/google/gerrit/server/restapi/change/SuggestChangeReviewers.java
@@ -19,15 +19,14 @@
import com.google.gerrit.extensions.restapi.AuthException;
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.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.IdentifiedUser.GenericFactory;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.permissions.PermissionBackend;
-import com.google.gerrit.server.permissions.RefPermission;
+import com.google.gerrit.server.permissions.ChangePermission;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.restapi.change.ReviewersUtil.VisibilityControl;
import com.google.gwtorm.server.OrmException;
@@ -49,7 +48,6 @@
)
boolean excludeGroups;
- private final PermissionBackend permissionBackend;
private final Provider<CurrentUser> self;
private final ProjectCache projectCache;
@@ -58,20 +56,19 @@
AccountVisibility av,
GenericFactory identifiedUserFactory,
Provider<ReviewDb> dbProvider,
- PermissionBackend permissionBackend,
Provider<CurrentUser> self,
@GerritServerConfig Config cfg,
ReviewersUtil reviewersUtil,
ProjectCache projectCache) {
super(av, identifiedUserFactory, dbProvider, cfg, reviewersUtil);
- this.permissionBackend = permissionBackend;
this.self = self;
this.projectCache = projectCache;
}
@Override
public List<SuggestedReviewerInfo> apply(ChangeResource rsrc)
- throws AuthException, BadRequestException, OrmException, IOException, ConfigInvalidException {
+ throws AuthException, BadRequestException, OrmException, IOException, ConfigInvalidException,
+ PermissionBackendException {
if (!self.get().isIdentifiedUser()) {
throw new AuthException("Authentication required");
}
@@ -86,14 +83,9 @@
private VisibilityControl getVisibility(ChangeResource rsrc) {
// Use the destination reference, not the change, as drafts may deny
// anyone who is not already a reviewer.
- // TODO(hiesel) Replace this with a check on the change resource once support for drafts was removed
- PermissionBackend.ForRef perm = permissionBackend.user(self).ref(rsrc.getChange().getDest());
- return new VisibilityControl() {
- @Override
- public boolean isVisibleTo(Account.Id account) throws OrmException {
- IdentifiedUser who = identifiedUserFactory.create(account);
- return perm.user(who).testOrFalse(RefPermission.READ);
- }
+ return account -> {
+ IdentifiedUser who = identifiedUserFactory.create(account);
+ return rsrc.permissions().user(who).testOrFalse(ChangePermission.READ);
};
}
}
diff --git a/java/com/google/gerrit/server/restapi/config/GetPreferences.java b/java/com/google/gerrit/server/restapi/config/GetPreferences.java
index c8a173f..3c7453c 100644
--- a/java/com/google/gerrit/server/restapi/config/GetPreferences.java
+++ b/java/com/google/gerrit/server/restapi/config/GetPreferences.java
@@ -14,33 +14,25 @@
package com.google.gerrit.server.restapi.config;
-import static com.google.gerrit.server.config.ConfigUtil.loadSection;
-
import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.server.account.GeneralPreferencesLoader;
-import com.google.gerrit.server.account.VersionedAccountPreferences;
+import com.google.gerrit.server.account.PreferencesConfig;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.ConfigResource;
import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.UserConfigSections;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.IOException;
import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Repository;
@Singleton
public class GetPreferences implements RestReadView<ConfigResource> {
- private final GeneralPreferencesLoader loader;
private final GitRepositoryManager gitMgr;
private final AllUsersName allUsersName;
@Inject
- public GetPreferences(
- GeneralPreferencesLoader loader, GitRepositoryManager gitMgr, AllUsersName allUsersName) {
- this.loader = loader;
+ public GetPreferences(GitRepositoryManager gitMgr, AllUsersName allUsersName) {
this.gitMgr = gitMgr;
this.allUsersName = allUsersName;
}
@@ -48,30 +40,8 @@
@Override
public GeneralPreferencesInfo apply(ConfigResource rsrc)
throws IOException, ConfigInvalidException {
- return readFromGit(gitMgr, loader, allUsersName, null);
- }
-
- static GeneralPreferencesInfo readFromGit(
- GitRepositoryManager gitMgr,
- GeneralPreferencesLoader loader,
- AllUsersName allUsersName,
- GeneralPreferencesInfo in)
- throws IOException, ConfigInvalidException, RepositoryNotFoundException {
try (Repository git = gitMgr.openRepository(allUsersName)) {
- VersionedAccountPreferences p = VersionedAccountPreferences.forDefault();
- p.load(git);
-
- GeneralPreferencesInfo r =
- loadSection(
- p.getConfig(),
- UserConfigSections.GENERAL,
- null,
- new GeneralPreferencesInfo(),
- GeneralPreferencesInfo.defaults(),
- in);
-
- // TODO(davido): Maintain cache of default values in AllUsers repository
- return loader.loadMyMenusAndUrlAliases(r, p, null);
+ return PreferencesConfig.readDefaultPreferences(git);
}
}
}
diff --git a/java/com/google/gerrit/server/restapi/config/GetServerInfo.java b/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
index 4c23f59..f31277d 100644
--- a/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
+++ b/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
@@ -246,6 +246,8 @@
info.updateDelay =
(int) ConfigUtil.getTimeUnit(cfg, "change", null, "updateDelay", 300, TimeUnit.SECONDS);
info.submitWholeTopic = MergeSuperSet.wholeTopicEnabled(cfg);
+ info.disablePrivateChanges =
+ toBoolean(config.getBoolean("change", null, "disablePrivateChanges", false));
return info;
}
diff --git a/java/com/google/gerrit/server/restapi/config/SetPreferences.java b/java/com/google/gerrit/server/restapi/config/SetPreferences.java
index 17908c3..be990e2 100644
--- a/java/com/google/gerrit/server/restapi/config/SetPreferences.java
+++ b/java/com/google/gerrit/server/restapi/config/SetPreferences.java
@@ -14,10 +14,7 @@
package com.google.gerrit.server.restapi.config;
-import static com.google.gerrit.server.config.ConfigUtil.loadSection;
import static com.google.gerrit.server.config.ConfigUtil.skipField;
-import static com.google.gerrit.server.config.ConfigUtil.storeSection;
-import static com.google.gerrit.server.restapi.config.GetPreferences.readFromGit;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability;
@@ -25,20 +22,16 @@
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.server.account.AccountCache;
-import com.google.gerrit.server.account.GeneralPreferencesLoader;
-import com.google.gerrit.server.account.VersionedAccountPreferences;
+import com.google.gerrit.server.account.PreferencesConfig;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.config.ConfigResource;
-import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MetaDataUpdate;
-import com.google.gerrit.server.git.UserConfigSections;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
import java.lang.reflect.Field;
import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -47,57 +40,31 @@
public class SetPreferences implements RestModifyView<ConfigResource, GeneralPreferencesInfo> {
private static final Logger log = LoggerFactory.getLogger(SetPreferences.class);
- private final GeneralPreferencesLoader loader;
- private final GitRepositoryManager gitManager;
private final Provider<MetaDataUpdate.User> metaDataUpdateFactory;
private final AllUsersName allUsersName;
private final AccountCache accountCache;
@Inject
SetPreferences(
- GeneralPreferencesLoader loader,
- GitRepositoryManager gitManager,
Provider<MetaDataUpdate.User> metaDataUpdateFactory,
AllUsersName allUsersName,
AccountCache accountCache) {
- this.loader = loader;
- this.gitManager = gitManager;
this.metaDataUpdateFactory = metaDataUpdateFactory;
this.allUsersName = allUsersName;
this.accountCache = accountCache;
}
@Override
- public GeneralPreferencesInfo apply(ConfigResource rsrc, GeneralPreferencesInfo i)
+ public GeneralPreferencesInfo apply(ConfigResource rsrc, GeneralPreferencesInfo input)
throws BadRequestException, IOException, ConfigInvalidException {
- if (!hasSetFields(i)) {
+ if (!hasSetFields(input)) {
throw new BadRequestException("unsupported option");
}
- return writeToGit(readFromGit(gitManager, loader, allUsersName, i));
- }
-
- private GeneralPreferencesInfo writeToGit(GeneralPreferencesInfo i)
- throws RepositoryNotFoundException, IOException, ConfigInvalidException, BadRequestException {
+ PreferencesConfig.validateMy(input.my);
try (MetaDataUpdate md = metaDataUpdateFactory.get().create(allUsersName)) {
- VersionedAccountPreferences p = VersionedAccountPreferences.forDefault();
- p.load(md);
- storeSection(
- p.getConfig(), UserConfigSections.GENERAL, null, i, GeneralPreferencesInfo.defaults());
- com.google.gerrit.server.restapi.account.SetPreferences.storeMyMenus(p, i.my);
- com.google.gerrit.server.restapi.account.SetPreferences.storeUrlAliases(p, i.urlAliases);
- p.commit(md);
-
+ GeneralPreferencesInfo updatedPrefs = PreferencesConfig.updateDefaultPreferences(md, input);
accountCache.evictAllNoReindex();
-
- GeneralPreferencesInfo r =
- loadSection(
- p.getConfig(),
- UserConfigSections.GENERAL,
- null,
- new GeneralPreferencesInfo(),
- GeneralPreferencesInfo.defaults(),
- null);
- return loader.loadMyMenusAndUrlAliases(r, p, null);
+ return updatedPrefs;
}
}
diff --git a/java/com/google/gerrit/server/restapi/project/ConfigInfoImpl.java b/java/com/google/gerrit/server/restapi/project/ConfigInfoImpl.java
index 70dc317..0d52090 100644
--- a/java/com/google/gerrit/server/restapi/project/ConfigInfoImpl.java
+++ b/java/com/google/gerrit/server/restapi/project/ConfigInfoImpl.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.restapi.project;
+import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
import com.google.gerrit.extensions.api.projects.CommentLinkInfo;
@@ -42,6 +43,7 @@
import java.util.TreeMap;
public class ConfigInfoImpl extends ConfigInfo {
+ @SuppressWarnings("deprecation")
public ConfigInfoImpl(
boolean serverEnableSignedPush,
ProjectState projectState,
@@ -79,7 +81,18 @@
maxObjectSizeLimit.inheritedValue = config.getFormattedMaxObjectSizeLimit();
this.maxObjectSizeLimit = maxObjectSizeLimit;
- this.submitType = p.getSubmitType();
+ this.defaultSubmitType = new SubmitTypeInfo();
+ this.defaultSubmitType.value = projectState.getSubmitType();
+ this.defaultSubmitType.configuredValue =
+ MoreObjects.firstNonNull(
+ projectState.getConfig().getProject().getConfiguredSubmitType(),
+ Project.DEFAULT_SUBMIT_TYPE);
+ ProjectState parent =
+ projectState.isAllProjects() ? projectState : projectState.parents().get(0);
+ this.defaultSubmitType.inheritedValue = parent.getSubmitType();
+
+ this.submitType = this.defaultSubmitType.value;
+
this.state =
p.getState() != com.google.gerrit.extensions.client.ProjectState.ACTIVE
? p.getState()
diff --git a/java/com/google/gerrit/server/restapi/project/CreateAccessChange.java b/java/com/google/gerrit/server/restapi/project/CreateAccessChange.java
index a06c8c5..44005c0 100644
--- a/java/com/google/gerrit/server/restapi/project/CreateAccessChange.java
+++ b/java/com/google/gerrit/server/restapi/project/CreateAccessChange.java
@@ -40,6 +40,7 @@
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.permissions.ProjectPermission;
import com.google.gerrit.server.permissions.RefPermission;
+import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectResource;
import com.google.gerrit.server.update.BatchUpdate;
import com.google.gerrit.server.update.UpdateException;
@@ -65,6 +66,7 @@
private final Provider<ReviewDb> db;
private final SetAccessUtil setAccess;
private final ChangeJson.Factory jsonFactory;
+ private final ProjectCache projectCache;
@Inject
CreateAccessChange(
@@ -75,7 +77,8 @@
Provider<MetaDataUpdate.User> metaDataUpdateFactory,
Provider<ReviewDb> db,
SetAccessUtil accessUtil,
- ChangeJson.Factory jsonFactory) {
+ ChangeJson.Factory jsonFactory,
+ ProjectCache projectCache) {
this.permissionBackend = permissionBackend;
this.seq = seq;
this.changeInserterFactory = changeInserterFactory;
@@ -84,6 +87,7 @@
this.db = db;
this.setAccess = accessUtil;
this.jsonFactory = jsonFactory;
+ this.projectCache = projectCache;
}
@Override
@@ -103,6 +107,7 @@
throw new PermissionDeniedException("cannot create change for " + RefNames.REFS_CONFIG);
}
}
+ projectCache.checkedGet(rsrc.getNameKey()).checkStatePermitsWrite();
MetaDataUpdate.User metaDataUpdateUser = metaDataUpdateFactory.get();
List<AccessSection> removals = setAccess.getAccessSections(input.remove);
diff --git a/java/com/google/gerrit/server/restapi/project/CreateBranch.java b/java/com/google/gerrit/server/restapi/project/CreateBranch.java
index 0b62c15..38bc982 100644
--- a/java/com/google/gerrit/server/restapi/project/CreateBranch.java
+++ b/java/com/google/gerrit/server/restapi/project/CreateBranch.java
@@ -183,6 +183,7 @@
info.revision = revid.getName();
info.canDelete =
permissionBackend.user(identifiedUser).ref(name).testOrFalse(RefPermission.DELETE)
+ && rsrc.getProjectState().statePermitsWrite()
? true
: null;
return info;
diff --git a/java/com/google/gerrit/server/restapi/project/CreateProject.java b/java/com/google/gerrit/server/restapi/project/CreateProject.java
index 56c004a..976ab09 100644
--- a/java/com/google/gerrit/server/restapi/project/CreateProject.java
+++ b/java/com/google/gerrit/server/restapi/project/CreateProject.java
@@ -197,6 +197,8 @@
input.createNewChangeForAllNotInTarget, InheritableBoolean.INHERIT);
args.changeIdRequired =
MoreObjects.firstNonNull(input.requireChangeId, InheritableBoolean.INHERIT);
+ args.rejectEmptyCommit =
+ MoreObjects.firstNonNull(input.rejectEmptyCommit, InheritableBoolean.INHERIT);
try {
args.maxObjectSizeLimit = ProjectConfig.validMaxObjectSizeLimit(input.maxObjectSizeLimit);
} catch (ConfigInvalidException e) {
@@ -287,6 +289,7 @@
BooleanProjectConfig.CREATE_NEW_CHANGE_FOR_ALL_NOT_IN_TARGET,
args.newChangeForAllNotInTarget);
newProject.setBooleanConfig(BooleanProjectConfig.REQUIRE_CHANGE_ID, args.changeIdRequired);
+ newProject.setBooleanConfig(BooleanProjectConfig.REJECT_EMPTY_COMMIT, args.rejectEmptyCommit);
newProject.setMaxObjectSizeLimit(args.maxObjectSizeLimit);
if (args.newParent != null) {
newProject.setParentName(args.newParent);
diff --git a/java/com/google/gerrit/server/restapi/project/CreateTag.java b/java/com/google/gerrit/server/restapi/project/CreateTag.java
index 0b0ce10..f501faf 100644
--- a/java/com/google/gerrit/server/restapi/project/CreateTag.java
+++ b/java/com/google/gerrit/server/restapi/project/CreateTag.java
@@ -18,7 +18,6 @@
import com.google.common.base.Strings;
import com.google.gerrit.common.TimeUtil;
-import com.google.gerrit.common.data.Permission;
import com.google.gerrit.extensions.api.projects.TagInfo;
import com.google.gerrit.extensions.api.projects.TagInput;
import com.google.gerrit.extensions.restapi.AuthException;
@@ -36,9 +35,7 @@
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.permissions.RefPermission;
import com.google.gerrit.server.project.NoSuchProjectException;
-import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.ProjectResource;
-import com.google.gerrit.server.project.RefControl;
import com.google.gerrit.server.project.RefUtil;
import com.google.gerrit.server.project.RefUtil.InvalidRevisionException;
import com.google.inject.Inject;
@@ -71,7 +68,6 @@
private final TagCache tagCache;
private final GitReferenceUpdated referenceUpdated;
private final WebLinks links;
- private final ProjectControl.GenericFactory projectControlFactory;
private String ref;
@Inject
@@ -82,7 +78,6 @@
TagCache tagCache,
GitReferenceUpdated referenceUpdated,
WebLinks webLinks,
- ProjectControl.GenericFactory projectControlFactory,
@Assisted String ref) {
this.permissionBackend = permissionBackend;
this.identifiedUser = identifiedUser;
@@ -90,7 +85,6 @@
this.tagCache = tagCache;
this.referenceUpdated = referenceUpdated;
this.links = webLinks;
- this.projectControlFactory = projectControlFactory;
this.ref = ref;
}
@@ -108,12 +102,6 @@
}
ref = RefUtil.normalizeTagRef(ref);
-
- // TODO(hiesel): Remove dependency on RefControl
- RefControl refControl =
- projectControlFactory
- .controlFor(resource.getNameKey(), resource.getUser())
- .controlForRef(ref);
PermissionBackend.ForRef perm =
permissionBackend.user(identifiedUser).project(resource.getNameKey()).ref(ref);
@@ -126,7 +114,7 @@
boolean isSigned = isAnnotated && input.message.contains("-----BEGIN PGP SIGNATURE-----\n");
if (isSigned) {
throw new MethodNotAllowedException("Cannot create signed tag \"" + ref + "\"");
- } else if (isAnnotated && !refControl.canPerform(Permission.CREATE_TAG)) {
+ } else if (isAnnotated && !check(perm, RefPermission.CREATE_TAG)) {
throw new AuthException("Cannot create annotated tag \"" + ref + "\"");
} else {
perm.check(RefPermission.CREATE);
@@ -159,7 +147,7 @@
result.getObjectId(),
identifiedUser.get().getAccount());
try (RevWalk w = new RevWalk(repo)) {
- return ListTags.createTagInfo(perm, result, w, resource.getNameKey(), links);
+ return ListTags.createTagInfo(perm, result, w, resource.getProjectState(), links);
}
}
} catch (InvalidRevisionException e) {
@@ -169,4 +157,14 @@
throw new IOException(e);
}
}
+
+ private static boolean check(PermissionBackend.ForRef perm, RefPermission permission)
+ throws PermissionBackendException {
+ try {
+ perm.check(permission);
+ return true;
+ } catch (AuthException e) {
+ return false;
+ }
+ }
}
diff --git a/java/com/google/gerrit/server/restapi/project/DeleteBranch.java b/java/com/google/gerrit/server/restapi/project/DeleteBranch.java
index 09bbca9..3114f8a 100644
--- a/java/com/google/gerrit/server/restapi/project/DeleteBranch.java
+++ b/java/com/google/gerrit/server/restapi/project/DeleteBranch.java
@@ -57,6 +57,7 @@
public Response<?> apply(BranchResource rsrc, Input input)
throws RestApiException, OrmException, IOException, PermissionBackendException {
permissionBackend.user(user).ref(rsrc.getBranchKey()).check(RefPermission.DELETE);
+ rsrc.getProjectState().checkStatePermitsWrite();
if (!queryProvider.get().setLimit(1).byBranchOpen(rsrc.getBranchKey()).isEmpty()) {
throw new ResourceConflictException("branch " + rsrc.getBranchKey() + " has open changes");
diff --git a/java/com/google/gerrit/server/restapi/project/DeleteRef.java b/java/com/google/gerrit/server/restapi/project/DeleteRef.java
index b1b575b..7bd1c4a 100644
--- a/java/com/google/gerrit/server/restapi/project/DeleteRef.java
+++ b/java/com/google/gerrit/server/restapi/project/DeleteRef.java
@@ -235,6 +235,10 @@
"it doesn't exist or you do not have permission to delete it");
}
+ if (!project.getProjectState().statePermitsWrite()) {
+ command.setResult(Result.REJECTED_OTHER_REASON, "project state does not permit write");
+ }
+
if (!refName.startsWith(R_TAGS)) {
Branch.NameKey branchKey = new Branch.NameKey(project.getNameKey(), ref.getName());
if (!queryProvider.get().setLimit(1).byBranchOpen(branchKey).isEmpty()) {
diff --git a/java/com/google/gerrit/server/restapi/project/DeleteTag.java b/java/com/google/gerrit/server/restapi/project/DeleteTag.java
index cce7103..f432129 100644
--- a/java/com/google/gerrit/server/restapi/project/DeleteTag.java
+++ b/java/com/google/gerrit/server/restapi/project/DeleteTag.java
@@ -56,6 +56,7 @@
.project(resource.getNameKey())
.ref(tag)
.check(RefPermission.DELETE);
+ resource.getProjectState().checkStatePermitsWrite();
deleteRefFactory.create(resource).ref(tag).delete();
return Response.none();
}
diff --git a/java/com/google/gerrit/server/restapi/project/GetAccess.java b/java/com/google/gerrit/server/restapi/project/GetAccess.java
index 1568a4c..520d74c 100644
--- a/java/com/google/gerrit/server/restapi/project/GetAccess.java
+++ b/java/com/google/gerrit/server/restapi/project/GetAccess.java
@@ -251,8 +251,10 @@
info.isOwner = toBoolean(canWriteConfig);
info.canUpload =
toBoolean(
- canWriteConfig
- || (canReadConfig && perm.ref(RefNames.REFS_CONFIG).testOrFalse(CREATE_CHANGE)));
+ projectState.statePermitsWrite()
+ && (canWriteConfig
+ || (canReadConfig
+ && perm.ref(RefNames.REFS_CONFIG).testOrFalse(CREATE_CHANGE))));
info.canAdd = toBoolean(perm.testOrFalse(CREATE_REF));
info.configVisible = canReadConfig || canWriteConfig;
diff --git a/java/com/google/gerrit/server/restapi/project/ListBranches.java b/java/com/google/gerrit/server/restapi/project/ListBranches.java
index 5675be1..416a5c2 100644
--- a/java/com/google/gerrit/server/restapi/project/ListBranches.java
+++ b/java/com/google/gerrit/server/restapi/project/ListBranches.java
@@ -204,7 +204,11 @@
branches.add(b);
if (!Constants.HEAD.equals(ref.getName())) {
- b.canDelete = perm.ref(ref.getName()).testOrFalse(RefPermission.DELETE) ? true : null;
+ b.canDelete =
+ perm.ref(ref.getName()).testOrFalse(RefPermission.DELETE)
+ && rsrc.getProjectState().statePermitsWrite()
+ ? true
+ : null;
}
continue;
}
@@ -248,7 +252,11 @@
info.ref = ref.getName();
info.revision = ref.getObjectId() != null ? ref.getObjectId().name() : null;
info.canDelete =
- !targets.contains(ref.getName()) && perm.testOrFalse(RefPermission.DELETE) ? true : null;
+ !targets.contains(ref.getName())
+ && perm.testOrFalse(RefPermission.DELETE)
+ && projectState.statePermitsWrite()
+ ? true
+ : null;
BranchResource rsrc = new BranchResource(projectState, user, ref);
for (UiAction.Description d : uiActions.from(branchViews, rsrc)) {
diff --git a/java/com/google/gerrit/server/restapi/project/ListProjects.java b/java/com/google/gerrit/server/restapi/project/ListProjects.java
index d2aecca..6eb5c88 100644
--- a/java/com/google/gerrit/server/restapi/project/ListProjects.java
+++ b/java/com/google/gerrit/server/restapi/project/ListProjects.java
@@ -21,7 +21,6 @@
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.errors.NoSuchGroupException;
@@ -47,7 +46,6 @@
import com.google.gerrit.server.permissions.ProjectPermission;
import com.google.gerrit.server.permissions.RefPermission;
import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.project.ProjectNode;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.restapi.group.GroupsCollection;
import com.google.gerrit.server.util.RegexListSearcher;
@@ -65,16 +63,16 @@
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
-import java.util.Set;
+import java.util.Objects;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
+import java.util.stream.Stream;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Ref;
@@ -516,31 +514,37 @@
private Collection<Project.NameKey> filter(PermissionBackend.WithUser perm)
throws BadRequestException, PermissionBackendException {
- Collection<Project.NameKey> matches = Lists.newArrayList(scan());
+ Stream<Project.NameKey> matches = scan();
if (type == FilterType.PARENT_CANDIDATES) {
matches = parentsOf(matches);
}
- return perm.filter(ProjectPermission.ACCESS, matches).stream().sorted().collect(toList());
+ // TODO(dborowitz): Streamified PermissionBackend#filter.
+ return perm.filter(ProjectPermission.ACCESS, matches.collect(toList()))
+ .stream()
+ .sorted()
+ .collect(toList());
}
- private Collection<Project.NameKey> parentsOf(Collection<Project.NameKey> matches) {
- Set<Project.NameKey> parents = new HashSet<>();
- for (Project.NameKey p : matches) {
- ProjectState ps = projectCache.get(p);
- if (ps != null) {
- Project.NameKey parent = ps.getProject().getParent();
- if (parent != null) {
- if (projectCache.get(parent) != null) {
- parents.add(parent);
- } else {
- log.warn(
- String.format(
- "parent project %s of project %s not found", parent.get(), ps.getName()));
- }
- }
- }
- }
- return parents;
+ private Stream<Project.NameKey> parentsOf(Stream<Project.NameKey> matches) {
+ return matches
+ .map(
+ p -> {
+ ProjectState ps = projectCache.get(p);
+ if (ps != null) {
+ Project.NameKey parent = ps.getProject().getParent();
+ if (parent != null) {
+ if (projectCache.get(parent) != null) {
+ return parent;
+ }
+ log.warn(
+ String.format(
+ "parent project %s of project %s not found", parent.get(), ps.getName()));
+ }
+ }
+ return null;
+ })
+ .filter(Objects::nonNull)
+ .distinct();
}
private boolean isParentAccessible(
@@ -560,32 +564,28 @@
return b;
}
- private Iterable<Project.NameKey> scan() throws BadRequestException {
+ private Stream<Project.NameKey> scan() throws BadRequestException {
if (matchPrefix != null) {
checkMatchOptions(matchSubstring == null && matchRegex == null);
- return projectCache.byName(matchPrefix);
+ return projectCache.byName(matchPrefix).stream();
} else if (matchSubstring != null) {
checkMatchOptions(matchPrefix == null && matchRegex == null);
- return Iterables.filter(
- projectCache.all(),
- p -> p.get().toLowerCase(Locale.US).contains(matchSubstring.toLowerCase(Locale.US)));
+ return projectCache
+ .all()
+ .stream()
+ .filter(
+ p -> p.get().toLowerCase(Locale.US).contains(matchSubstring.toLowerCase(Locale.US)));
} else if (matchRegex != null) {
checkMatchOptions(matchPrefix == null && matchSubstring == null);
RegexListSearcher<Project.NameKey> searcher;
try {
- searcher =
- new RegexListSearcher<Project.NameKey>(matchRegex) {
- @Override
- public String apply(Project.NameKey in) {
- return in.get();
- }
- };
+ searcher = new RegexListSearcher<>(matchRegex, Project.NameKey::get);
} catch (IllegalArgumentException e) {
throw new BadRequestException(e.getMessage());
}
return searcher.search(ImmutableList.copyOf(projectCache.all()));
} else {
- return projectCache.all();
+ return projectCache.all().stream();
}
}
diff --git a/java/com/google/gerrit/server/restapi/project/ListTags.java b/java/com/google/gerrit/server/restapi/project/ListTags.java
index a2b7082..af4586c 100644
--- a/java/com/google/gerrit/server/restapi/project/ListTags.java
+++ b/java/com/google/gerrit/server/restapi/project/ListTags.java
@@ -140,7 +140,8 @@
visibleTags(
resource.getProjectState(), repo, repo.getRefDatabase().getRefs(Constants.R_TAGS));
for (Ref ref : all.values()) {
- tags.add(createTagInfo(perm.ref(ref.getName()), ref, rw, resource.getNameKey(), links));
+ tags.add(
+ createTagInfo(perm.ref(ref.getName()), ref, rw, resource.getProjectState(), links));
}
}
@@ -180,7 +181,7 @@
.ref(ref.getName()),
ref,
rw,
- resource.getNameKey(),
+ resource.getProjectState(),
links);
}
}
@@ -188,15 +189,12 @@
}
public static TagInfo createTagInfo(
- PermissionBackend.ForRef perm,
- Ref ref,
- RevWalk rw,
- Project.NameKey projectName,
- WebLinks links)
+ PermissionBackend.ForRef perm, Ref ref, RevWalk rw, ProjectState projectState, WebLinks links)
throws MissingObjectException, IOException {
RevObject object = rw.parseAny(ref.getObjectId());
- Boolean canDelete = perm.testOrFalse(RefPermission.DELETE) ? true : null;
- List<WebLinkInfo> webLinks = links.getTagLinks(projectName.get(), ref.getName());
+ Boolean canDelete =
+ perm.testOrFalse(RefPermission.DELETE) && projectState.statePermitsWrite() ? true : null;
+ List<WebLinkInfo> webLinks = links.getTagLinks(projectState.getName(), ref.getName());
if (object instanceof RevTag) {
// Annotated or signed tag
RevTag tag = (RevTag) object;
diff --git a/java/com/google/gerrit/server/restapi/project/Module.java b/java/com/google/gerrit/server/restapi/project/Module.java
index b74b640..67380dc 100644
--- a/java/com/google/gerrit/server/restapi/project/Module.java
+++ b/java/com/google/gerrit/server/restapi/project/Module.java
@@ -100,5 +100,6 @@
put(PROJECT_KIND, "config").to(PutConfig.class);
factory(DeleteRef.Factory.class);
+ factory(ProjectNode.Factory.class);
}
}
diff --git a/java/com/google/gerrit/server/project/ProjectNode.java b/java/com/google/gerrit/server/restapi/project/ProjectNode.java
similarity index 87%
rename from java/com/google/gerrit/server/project/ProjectNode.java
rename to java/com/google/gerrit/server/restapi/project/ProjectNode.java
index e1ba692..54f7574 100644
--- a/java/com/google/gerrit/server/project/ProjectNode.java
+++ b/java/com/google/gerrit/server/restapi/project/ProjectNode.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.project;
+package com.google.gerrit.server.restapi.project;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.config.AllProjectsName;
@@ -23,8 +23,8 @@
import java.util.TreeSet;
/** Node of a Project in a tree formatted by {@link ListProjects}. */
-public class ProjectNode implements TreeNode, Comparable<ProjectNode> {
- public interface Factory {
+class ProjectNode implements TreeNode, Comparable<ProjectNode> {
+ interface Factory {
ProjectNode create(Project project, boolean isVisible);
}
@@ -49,15 +49,15 @@
*
* @return Project parent name, {@code null} for the 'All-Projects' root project
*/
- public Project.NameKey getParentName() {
+ Project.NameKey getParentName() {
return project.getParent(allProjectsName);
}
- public boolean isAllProjects() {
+ boolean isAllProjects() {
return allProjectsName.equals(project.getNameKey());
}
- public Project getProject() {
+ Project getProject() {
return project;
}
@@ -76,7 +76,7 @@
return children;
}
- public void addChild(ProjectNode child) {
+ void addChild(ProjectNode child) {
children.add(child);
}
diff --git a/java/com/google/gerrit/server/restapi/project/SetAccessUtil.java b/java/com/google/gerrit/server/restapi/project/SetAccessUtil.java
index 3fb9bb9..8cefd66 100644
--- a/java/com/google/gerrit/server/restapi/project/SetAccessUtil.java
+++ b/java/com/google/gerrit/server/restapi/project/SetAccessUtil.java
@@ -101,7 +101,9 @@
if (pri.min != null) {
r.setMin(pri.min);
}
- r.setAction(GetAccess.ACTION_TYPE.inverse().get(pri.action));
+ if (pri.action != null) {
+ r.setAction(GetAccess.ACTION_TYPE.inverse().get(pri.action));
+ }
if (pri.force != null) {
r.setForce(pri.force);
}
diff --git a/java/com/google/gerrit/server/schema/SchemaCreator.java b/java/com/google/gerrit/server/schema/SchemaCreator.java
index 2004c98..895e75c 100644
--- a/java/com/google/gerrit/server/schema/SchemaCreator.java
+++ b/java/com/google/gerrit/server/schema/SchemaCreator.java
@@ -240,7 +240,7 @@
AccountGroup.NameKey groupName = groupUpdate.getName().orElseGet(groupCreation::getNameKey);
GroupNameNotes groupNameNotes =
- GroupNameNotes.loadForNewGroup(allUsersRepo, groupCreation.getGroupUUID(), groupName);
+ GroupNameNotes.forNewGroup(allUsersRepo, groupCreation.getGroupUUID(), groupName);
commit(allUsersRepo, groupConfig, groupNameNotes);
diff --git a/java/com/google/gerrit/server/schema/Schema_139.java b/java/com/google/gerrit/server/schema/Schema_139.java
index 4dfc41a..f2c30df 100644
--- a/java/com/google/gerrit/server/schema/Schema_139.java
+++ b/java/com/google/gerrit/server/schema/Schema_139.java
@@ -1,16 +1,16 @@
-//Copyright (C) 2016 The Android Open Source Project
+// 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
+// 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
+// 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.
+// 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.schema;
@@ -24,7 +24,8 @@
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.account.WatchConfig;
+import com.google.gerrit.server.account.AccountConfig;
+import com.google.gerrit.server.account.InternalAccountUpdate;
import com.google.gerrit.server.account.WatchConfig.NotifyType;
import com.google.gerrit.server.account.WatchConfig.ProjectWatchKey;
import com.google.gerrit.server.config.AllUsersName;
@@ -147,10 +148,14 @@
md.getCommitBuilder().setCommitter(serverUser);
md.setMessage(MSG);
- WatchConfig watchConfig = new WatchConfig(e.getKey());
- watchConfig.load(md);
- watchConfig.setProjectWatches(projectWatches);
- watchConfig.commit(md);
+ AccountConfig accountConfig = new AccountConfig(e.getKey(), git);
+ accountConfig.load(md);
+ accountConfig.setAccountUpdate(
+ InternalAccountUpdate.builder()
+ .deleteProjectWatches(accountConfig.getProjectWatches().keySet())
+ .updateProjectWatches(projectWatches)
+ .build());
+ accountConfig.commit(md);
}
}
bru.execute(rw, NullProgressMonitor.INSTANCE);
diff --git a/java/com/google/gerrit/server/schema/Schema_147.java b/java/com/google/gerrit/server/schema/Schema_147.java
index 29ae7d5..fd85463 100644
--- a/java/com/google/gerrit/server/schema/Schema_147.java
+++ b/java/com/google/gerrit/server/schema/Schema_147.java
@@ -19,10 +19,7 @@
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.GerritPersonIdent;
-import com.google.gerrit.server.account.AccountsUpdate;
import com.google.gerrit.server.config.AllUsersName;
-import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -34,25 +31,23 @@
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
-import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.RefUpdate.Result;
import org.eclipse.jgit.lib.Repository;
/** Delete user branches for which no account exists. */
public class Schema_147 extends SchemaVersion {
private final GitRepositoryManager repoManager;
private final AllUsersName allUsersName;
- private final PersonIdent serverIdent;
@Inject
Schema_147(
- Provider<Schema_146> prior,
- GitRepositoryManager repoManager,
- AllUsersName allUsersName,
- @GerritPersonIdent PersonIdent serverIdent) {
+ Provider<Schema_146> prior, GitRepositoryManager repoManager, AllUsersName allUsersName) {
super(prior);
this.repoManager = repoManager;
this.allUsersName = allUsersName;
- this.serverIdent = serverIdent;
}
@Override
@@ -69,8 +64,7 @@
.collect(toSet());
accountIdsFromUserBranches.removeAll(accountIdsFromReviewDb);
for (Account.Id accountId : accountIdsFromUserBranches) {
- AccountsUpdate.deleteUserBranch(
- repo, allUsersName, GitReferenceUpdated.DISABLED, null, serverIdent, accountId);
+ deleteUserBranch(repo, accountId);
}
} catch (IOException e) {
throw new OrmException("Failed to delete user branches for non-existing accounts.", e);
@@ -87,4 +81,21 @@
return ids;
}
}
+
+ private void deleteUserBranch(Repository allUsersRepo, Account.Id accountId) throws IOException {
+ String refName = RefNames.refsUsers(accountId);
+ Ref ref = allUsersRepo.exactRef(refName);
+ if (ref == null) {
+ return;
+ }
+
+ RefUpdate ru = allUsersRepo.updateRef(refName);
+ ru.setExpectedOldObjectId(ref.getObjectId());
+ ru.setNewObjectId(ObjectId.zeroId());
+ ru.setForceUpdate(true);
+ Result result = ru.delete();
+ if (result != Result.FORCED) {
+ throw new IOException(String.format("Failed to delete ref %s: %s", refName, result.name()));
+ }
+ }
}
diff --git a/java/com/google/gerrit/server/schema/Schema_154.java b/java/com/google/gerrit/server/schema/Schema_154.java
index 8e05d38..0e5c110 100644
--- a/java/com/google/gerrit/server/schema/Schema_154.java
+++ b/java/com/google/gerrit/server/schema/Schema_154.java
@@ -139,10 +139,7 @@
PersonIdent ident = serverIdent.get();
md.getCommitBuilder().setAuthor(ident);
md.getCommitBuilder().setCommitter(ident);
- AccountConfig accountConfig = new AccountConfig(null, account.getId());
- accountConfig.load(allUsersRepo);
- accountConfig.setAccount(account);
- accountConfig.commit(md);
+ new AccountConfig(account.getId(), allUsersRepo).load().setAccount(account).commit(md);
}
@FunctionalInterface
diff --git a/java/com/google/gerrit/server/util/RegexListSearcher.java b/java/com/google/gerrit/server/util/RegexListSearcher.java
index 91cb709..11543bb 100644
--- a/java/com/google/gerrit/server/util/RegexListSearcher.java
+++ b/java/com/google/gerrit/server/util/RegexListSearcher.java
@@ -16,9 +16,6 @@
import static com.google.common.base.Preconditions.checkNotNull;
-import com.google.common.base.Function;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.primitives.Chars;
import dk.brics.automaton.Automaton;
@@ -26,26 +23,26 @@
import dk.brics.automaton.RunAutomaton;
import java.util.Collections;
import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Stream;
-/** Helper to search sorted lists for elements matching a regex. */
-public abstract class RegexListSearcher<T> implements Function<T, String> {
+/** Helper to search sorted lists for elements matching a {@link RegExp}. */
+public final class RegexListSearcher<T> {
public static RegexListSearcher<String> ofStrings(String re) {
- return new RegexListSearcher<String>(re) {
- @Override
- public String apply(String in) {
- return in;
- }
- };
+ return new RegexListSearcher<>(re, in -> in);
}
private final RunAutomaton pattern;
+ private final Function<T, String> toStringFunc;
private final String prefixBegin;
private final String prefixEnd;
private final int prefixLen;
private final boolean prefixOnly;
- public RegexListSearcher(String re) {
+ public RegexListSearcher(String re, Function<T, String> toStringFunc) {
+ this.toStringFunc = checkNotNull(toStringFunc);
+
if (re.startsWith("^")) {
re = re.substring(1);
}
@@ -70,34 +67,34 @@
pattern = prefixOnly ? null : new RunAutomaton(automaton);
}
- public Iterable<T> search(List<T> list) {
+ public Stream<T> search(List<T> list) {
checkNotNull(list);
int begin;
int end;
if (0 < prefixLen) {
- // Assumes many consecutive elements may have the same prefix, so the cost
- // of two binary searches is less than iterating to find the endpoints.
- begin = find(list, prefixBegin);
- end = find(list, prefixEnd);
+ // Assumes many consecutive elements may have the same prefix, so the cost of two binary
+ // searches is less than iterating linearly and running the regexp find the endpoints.
+ List<String> strings = Lists.transform(list, toStringFunc::apply);
+ begin = find(strings, prefixBegin);
+ end = find(strings, prefixEnd);
} else {
begin = 0;
end = list.size();
}
-
- if (prefixOnly) {
- return begin < end ? list.subList(begin, end) : ImmutableList.<T>of();
+ if (begin >= end) {
+ return Stream.empty();
}
- return Iterables.filter(list.subList(begin, end), x -> pattern.run(apply(x)));
+ Stream<T> result = list.subList(begin, end).stream();
+ if (!prefixOnly) {
+ result = result.filter(x -> pattern.run(toStringFunc.apply(x)));
+ }
+ return result;
}
- public boolean hasMatch(List<T> list) {
- return !Iterables.isEmpty(search(list));
- }
-
- private int find(List<T> list, String p) {
- int r = Collections.binarySearch(Lists.transform(list, this), p);
+ private static int find(List<String> list, String p) {
+ int r = Collections.binarySearch(list, p);
return r < 0 ? -(r + 1) : r;
}
}
diff --git a/java/com/google/gerrit/sshd/BaseCommand.java b/java/com/google/gerrit/sshd/BaseCommand.java
index fa3a0f5..cabc21d 100644
--- a/java/com/google/gerrit/sshd/BaseCommand.java
+++ b/java/com/google/gerrit/sshd/BaseCommand.java
@@ -370,6 +370,22 @@
}
}
+ protected String getTaskDescription() {
+ StringBuilder m = new StringBuilder();
+ m.append(context.getCommandLine());
+ return m.toString();
+ }
+
+ private String getTaskName() {
+ StringBuilder m = new StringBuilder();
+ m.append(getTaskDescription());
+ if (user.isIdentifiedUser()) {
+ IdentifiedUser u = user.asIdentifiedUser();
+ m.append(" (").append(u.getAccount().getUserName()).append(")");
+ }
+ return m.toString();
+ }
+
private final class TaskThunk implements CancelableRunnable, ProjectRunnable {
private final CommandRunnable thunk;
private final String taskName;
@@ -377,14 +393,7 @@
private TaskThunk(CommandRunnable thunk) {
this.thunk = thunk;
-
- StringBuilder m = new StringBuilder();
- m.append(context.getCommandLine());
- if (user.isIdentifiedUser()) {
- IdentifiedUser u = user.asIdentifiedUser();
- m.append(" (").append(u.getAccount().getUserName()).append(")");
- }
- this.taskName = m.toString();
+ this.taskName = getTaskName();
}
@Override
diff --git a/java/com/google/gerrit/sshd/HostKeyProvider.java b/java/com/google/gerrit/sshd/HostKeyProvider.java
index c0b6d5a..bffcfcd 100644
--- a/java/com/google/gerrit/sshd/HostKeyProvider.java
+++ b/java/com/google/gerrit/sshd/HostKeyProvider.java
@@ -39,7 +39,6 @@
public KeyPairProvider get() {
Path objKey = site.ssh_key;
Path rsaKey = site.ssh_rsa;
- Path dsaKey = site.ssh_dsa;
Path ecdsaKey_256 = site.ssh_ecdsa_256;
Path ecdsaKey_384 = site.ssh_ecdsa_384;
Path ecdsaKey_521 = site.ssh_ecdsa_521;
@@ -49,9 +48,6 @@
if (Files.exists(rsaKey)) {
stdKeys.add(rsaKey.toAbsolutePath().toFile());
}
- if (Files.exists(dsaKey)) {
- stdKeys.add(dsaKey.toAbsolutePath().toFile());
- }
if (Files.exists(ecdsaKey_256)) {
stdKeys.add(ecdsaKey_256.toAbsolutePath().toFile());
}
diff --git a/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java b/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java
index d6ecb0a..2051a00 100644
--- a/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java
+++ b/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java
@@ -100,6 +100,9 @@
@Option(name = "--change-id", usage = "if change-id is required")
private InheritableBoolean requireChangeID = InheritableBoolean.INHERIT;
+ @Option(name = "--reject-empty-commit", usage = "if empty commits should be rejected on submit")
+ private InheritableBoolean rejectEmptyCommit = InheritableBoolean.INHERIT;
+
@Option(
name = "--new-change-for-all-not-in-target",
usage = "if a new change will be created for every commit not in target branch"
@@ -201,6 +204,7 @@
input.branches = branch;
input.createEmptyCommit = createEmptyCommit;
input.maxObjectSizeLimit = maxObjectSizeLimit;
+ input.rejectEmptyCommit = rejectEmptyCommit;
if (pluginConfigValues != null) {
input.pluginConfigValues = parsePluginConfigValues(pluginConfigValues);
}
diff --git a/java/com/google/gerrit/sshd/commands/ReviewCommand.java b/java/com/google/gerrit/sshd/commands/ReviewCommand.java
index 1d764b9..1be32a8 100644
--- a/java/com/google/gerrit/sshd/commands/ReviewCommand.java
+++ b/java/com/google/gerrit/sshd/commands/ReviewCommand.java
@@ -240,6 +240,11 @@
}
}
+ @Override
+ protected String getTaskDescription() {
+ return "gerrit review";
+ }
+
private void applyReview(PatchSet patchSet, ReviewInput review) throws RestApiException {
gApi.changes()
.id(patchSet.getId().getParentKey().get())
diff --git a/java/com/google/gerrit/sshd/commands/UploadArchive.java b/java/com/google/gerrit/sshd/commands/UploadArchive.java
index 7518cbb..95dbb40 100644
--- a/java/com/google/gerrit/sshd/commands/UploadArchive.java
+++ b/java/com/google/gerrit/sshd/commands/UploadArchive.java
@@ -140,7 +140,7 @@
break;
}
if (!s.startsWith(argCmd)) {
- throw new Failure(1, "fatal: 'argument' token or flush expected");
+ throw new Failure(1, "fatal: 'argument' token or flush expected, got " + s);
}
String[] parts = s.substring(argCmd.length()).split("=", 2);
for (String p : parts) {
@@ -173,18 +173,18 @@
ArchiveFormat f = allowedFormats.getExtensions().get("." + options.format);
if (f == null) {
- throw new Failure(3, "fatal: upload-archive not permitted");
+ throw new Failure(3, "fatal: upload-archive not permitted for format " + options.format);
}
// Find out the object to get from the specified reference and paths
ObjectId treeId = repo.resolve(options.treeIsh);
if (treeId == null) {
- throw new Failure(4, "fatal: reference not found");
+ throw new Failure(4, "fatal: reference not found: " + options.treeIsh);
}
// Verify the user has permissions to read the specified tree.
if (!canRead(treeId)) {
- throw new Failure(5, "fatal: cannot perform upload-archive operation");
+ throw new Failure(5, "fatal: no permission to read tree" + options.treeIsh);
}
// The archive is sent in DATA sideband channel
diff --git a/java/com/google/gerrit/testing/FakeAccountCache.java b/java/com/google/gerrit/testing/FakeAccountCache.java
index 8256765..7668912 100644
--- a/java/com/google/gerrit/testing/FakeAccountCache.java
+++ b/java/com/google/gerrit/testing/FakeAccountCache.java
@@ -17,6 +17,7 @@
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountState;
@@ -79,6 +80,7 @@
new AllUsersName(AllUsersNameProvider.DEFAULT),
account,
ImmutableSet.of(),
- new HashMap<>());
+ new HashMap<>(),
+ GeneralPreferencesInfo.defaults());
}
}
diff --git a/java/com/google/gerrit/truth/ListSubject.java b/java/com/google/gerrit/truth/ListSubject.java
index bcd8dcf..cccf51b 100644
--- a/java/com/google/gerrit/truth/ListSubject.java
+++ b/java/com/google/gerrit/truth/ListSubject.java
@@ -30,8 +30,7 @@
@SuppressWarnings("unchecked")
public static <S extends Subject<S, E>, E> ListSubject<S, E> assertThat(
List<E> list, Function<E, S> elementAssertThatFunction) {
- // The ListSubjectFactory always returns ListSubjects.
- // -> Casting is appropriate.
+ // The ListSubjectFactory always returns ListSubjects. -> Casting is appropriate.
return (ListSubject<S, E>)
assertAbout(new ListSubjectFactory<>(elementAssertThatFunction)).that(list);
}
@@ -44,11 +43,8 @@
public S element(int index) {
checkArgument(index >= 0, "index(%s) must be >= 0", index);
- // The constructor only accepts lists.
- // -> Casting is appropriate.
- @SuppressWarnings("unchecked")
- List<E> list = (List<E>) actual();
isNotNull();
+ List<E> list = getActualList();
if (index >= list.size()) {
fail("has an element at index " + index);
}
@@ -61,11 +57,23 @@
return element(0);
}
+ public S lastElement() {
+ isNotNull();
+ isNotEmpty();
+ List<E> list = getActualList();
+ return element(list.size() - 1);
+ }
+
+ @SuppressWarnings("unchecked")
+ private List<E> getActualList() {
+ // The constructor only accepts lists. -> Casting is appropriate.
+ return (List<E>) actual();
+ }
+
@SuppressWarnings("unchecked")
@Override
public ListSubject<S, E> named(String s, Object... objects) {
- // This object is returned which is of type ListSubject.
- // -> Casting is appropriate.
+ // This object is returned which is of type ListSubject. -> Casting is appropriate.
return (ListSubject<S, E>) super.named(s, objects);
}
@@ -81,8 +89,7 @@
@SuppressWarnings("unchecked")
@Override
public ListSubject<S, T> createSubject(FailureMetadata failureMetadata, Iterable<?> objects) {
- // The constructor of ListSubject only accepts lists.
- // -> Casting is appropriate.
+ // The constructor of ListSubject only accepts lists. -> Casting is appropriate.
return new ListSubject<>(failureMetadata, (List<T>) objects, elementAssertThatFunction);
}
}
diff --git a/java/gerrit/PRED_project_default_submit_type_1.java b/java/gerrit/PRED_project_default_submit_type_1.java
index 91db57e..d70a9e4 100644
--- a/java/gerrit/PRED_project_default_submit_type_1.java
+++ b/java/gerrit/PRED_project_default_submit_type_1.java
@@ -47,7 +47,7 @@
Term a1 = arg1.dereference();
ProjectState projectState = StoredValues.PROJECT_STATE.get(engine);
- SubmitType submitType = projectState.getProject().getSubmitType();
+ SubmitType submitType = projectState.getSubmitType();
if (!a1.unify(term[submitType.ordinal()], engine.trail)) {
return engine.fail();
}
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
index 4b34ade..4b1b5d0 100644
--- a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
@@ -104,7 +104,6 @@
import com.google.gerrit.server.index.account.AccountIndexer;
import com.google.gerrit.server.index.account.StalenessChecker;
import com.google.gerrit.server.mail.Address;
-import com.google.gerrit.server.mail.send.OutgoingEmailValidator;
import com.google.gerrit.server.notedb.rebuild.ChangeRebuilderImpl;
import com.google.gerrit.server.project.RefPattern;
import com.google.gerrit.server.query.account.InternalAccountQuery;
@@ -191,8 +190,6 @@
@Inject private AccountIndexer accountIndexer;
- @Inject private OutgoingEmailValidator emailValidator;
-
@Inject private GitReferenceUpdated gitReferenceUpdated;
@Inject private RetryHelper.Metrics retryMetrics;
@@ -716,14 +713,24 @@
}
@Test
- public void getEmailsOfOtherAccount() throws Exception {
+ public void cannotGetEmailsOfOtherAccountWithoutModifyAccount() throws Exception {
String email = "preferred2@example.com";
- String secondaryEmail = "secondary2@example.com";
+ TestAccount foo = accountCreator.create(name("foo"), email, "Foo");
+
+ setApiUser(user);
+ exception.expect(AuthException.class);
+ exception.expectMessage("modify account not permitted");
+ gApi.accounts().id(foo.id.get()).getEmails();
+ }
+
+ @Test
+ public void getEmailsOfOtherAccount() throws Exception {
+ String email = "preferred3@example.com";
+ String secondaryEmail = "secondary3@example.com";
TestAccount foo = accountCreator.create(name("foo"), email, "Foo");
EmailInput input = newEmailInput(secondaryEmail);
gApi.accounts().id(foo.id.hashCode()).addEmail(input);
- setApiUser(user);
assertThat(
gApi.accounts()
.id(foo.id.get())
@@ -1338,7 +1345,7 @@
WatchConfig.WATCH_CONFIG,
wc.toText());
PushOneCommit.Result r = push.to(RefNames.REFS_USERS_SELF);
- r.assertErrorStatus("invalid watch configuration");
+ r.assertErrorStatus("invalid account configuration");
r.assertMessage(
String.format(
"%s: Invalid project watch of account %d for project %s: %s",
@@ -1928,7 +1935,8 @@
@Test
public void checkMetaId() throws Exception {
// metaId is set when account is loaded
- assertThat(accounts.get(admin.getId()).getMetaId()).isEqualTo(getMetaId(admin.getId()));
+ assertThat(accounts.get(admin.getId()).getAccount().getMetaId())
+ .isEqualTo(getMetaId(admin.getId()));
// metaId is set when account is created
AccountsUpdate au = accountsUpdate.create();
@@ -2013,7 +2021,6 @@
gitReferenceUpdated,
null,
allUsers,
- emailValidator,
metaDataUpdateInternalFactory,
new RetryHelper(
cfg,
@@ -2062,7 +2069,6 @@
gitReferenceUpdated,
null,
allUsers,
- emailValidator,
metaDataUpdateInternalFactory,
new RetryHelper(
cfg,
@@ -2101,7 +2107,7 @@
}
assertThat(bgCounter.get()).isEqualTo(status.size());
- Account updatedAccount = accounts.get(admin.id);
+ Account updatedAccount = accounts.get(admin.id).getAccount();
assertThat(updatedAccount.getStatus()).isEqualTo(Iterables.getLast(status));
assertThat(updatedAccount.getFullName()).isEqualTo(admin.fullName);
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/DiffPreferencesIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/DiffPreferencesIT.java
index 40bb08a..ba340eb 100644
--- a/javatests/com/google/gerrit/acceptance/api/accounts/DiffPreferencesIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/DiffPreferencesIT.java
@@ -16,51 +16,16 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.AssertUtil.assertPrefs;
-import static com.google.gerrit.acceptance.GitUtil.fetch;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
-import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
import com.google.gerrit.extensions.client.Theme;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.server.account.VersionedAccountPreferences;
-import org.eclipse.jgit.api.errors.TransportException;
-import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
-import org.eclipse.jgit.junit.TestRepository;
-import org.junit.After;
import org.junit.Test;
@NoHttpd
public class DiffPreferencesIT extends AbstractDaemonTest {
- @After
- public void cleanUp() throws Exception {
- gApi.accounts().id(admin.getId().toString()).setDiffPreferences(DiffPreferencesInfo.defaults());
-
- TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
- try {
- fetch(allUsersRepo, RefNames.REFS_USERS_DEFAULT + ":defaults");
- } catch (TransportException e) {
- if (e.getMessage()
- .equals(
- "Remote does not have " + RefNames.REFS_USERS_DEFAULT + " available for fetch.")) {
- return;
- }
- throw e;
- }
- allUsersRepo.reset("defaults");
- PushOneCommit push =
- pushFactory.create(
- db,
- admin.getIdent(),
- allUsersRepo,
- "Delete default preferences",
- VersionedAccountPreferences.PREFERENCES,
- "");
- push.rm(RefNames.REFS_USERS_DEFAULT).assertOkStatus();
- }
-
@Test
public void getDiffPreferences() throws Exception {
DiffPreferencesInfo d = DiffPreferencesInfo.defaults();
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/GeneralPreferencesIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/GeneralPreferencesIT.java
index 3cc040c..946e15c 100644
--- a/javatests/com/google/gerrit/acceptance/api/accounts/GeneralPreferencesIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/GeneralPreferencesIT.java
@@ -31,12 +31,8 @@
import com.google.gerrit.extensions.client.GeneralPreferencesInfo.TimeFormat;
import com.google.gerrit.extensions.client.MenuItem;
import com.google.gerrit.extensions.restapi.BadRequestException;
-import com.google.gerrit.reviewdb.client.RefNames;
import java.util.ArrayList;
import java.util.HashMap;
-import org.eclipse.jgit.lib.RefUpdate;
-import org.eclipse.jgit.lib.Repository;
-import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -50,20 +46,6 @@
user42 = accountCreator.create(name, name + "@example.com", "User 42");
}
- @After
- public void cleanUp() throws Exception {
- gApi.accounts().id(user42.getId().toString()).setPreferences(GeneralPreferencesInfo.defaults());
-
- try (Repository git = repoManager.openRepository(allUsers)) {
- if (git.exactRef(RefNames.REFS_USERS_DEFAULT) != null) {
- RefUpdate u = git.updateRef(RefNames.REFS_USERS_DEFAULT);
- u.setForceUpdate(true);
- assertThat(u.delete()).isEqualTo(RefUpdate.Result.FORCED);
- }
- }
- accountCache.evictAllNoReindex();
- }
-
@Test
public void getAndSetPreferences() throws Exception {
GeneralPreferencesInfo o = gApi.accounts().id(user42.id.toString()).getPreferences();
diff --git a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
index fa683cf..b770064 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -91,6 +91,7 @@
import com.google.gerrit.extensions.client.ChangeStatus;
import com.google.gerrit.extensions.client.Comment.Range;
import com.google.gerrit.extensions.client.InheritableBoolean;
+import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.client.ReviewerState;
import com.google.gerrit.extensions.client.Side;
import com.google.gerrit.extensions.client.SubmitType;
@@ -237,6 +238,15 @@
}
@Test
+ public void skipMergeable() throws Exception {
+ PushOneCommit.Result r = createChange();
+ String triplet = project.get() + "~master~" + r.getChangeId();
+ ChangeInfo c =
+ gApi.changes().id(triplet).get(ImmutableList.of(ListChangesOption.SKIP_MERGEABLE));
+ assertThat(c.mergeable).isNull();
+ }
+
+ @Test
public void setPrivateByOwner() throws Exception {
TestRepository<InMemoryRepository> userRepo = cloneProject(project, user);
PushOneCommit.Result result =
diff --git a/javatests/com/google/gerrit/acceptance/api/change/DisablePrivateChangesIT.java b/javatests/com/google/gerrit/acceptance/api/change/DisablePrivateChangesIT.java
new file mode 100644
index 0000000..287434d
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/api/change/DisablePrivateChangesIT.java
@@ -0,0 +1,118 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.api.change;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.GerritConfig;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.extensions.common.ChangeInput;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.Test;
+
+public class DisablePrivateChangesIT extends AbstractDaemonTest {
+
+ @Test
+ @GerritConfig(name = "change.disablePrivateChanges", value = "true")
+ public void createPrivateChangeWithDisablePrivateChangesTrue() throws Exception {
+ ChangeInput input = new ChangeInput(project.get(), "master", "empty change");
+ input.isPrivate = true;
+ exception.expect(MethodNotAllowedException.class);
+ exception.expectMessage("private changes are disabled");
+ gApi.changes().create(input);
+ }
+
+ @Test
+ @GerritConfig(name = "change.disablePrivateChanges", value = "true")
+ public void createNonPrivateChangeWithDisablePrivateChangesTrue() throws Exception {
+ ChangeInput input = new ChangeInput(project.get(), "master", "empty change");
+ assertThat(gApi.changes().create(input).get().isPrivate).isNull();
+ }
+
+ @Test
+ public void createPrivateChangeWithDisablePrivateChangesFalse() throws Exception {
+ ChangeInput input = new ChangeInput(project.get(), "master", "empty change");
+ input.isPrivate = true;
+ assertThat(gApi.changes().create(input).get().isPrivate).isTrue();
+ }
+
+ @Test
+ @GerritConfig(name = "change.disablePrivateChanges", value = "true")
+ public void pushPrivatesWithDisablePrivateChangesTrue() throws Exception {
+ PushOneCommit.Result result =
+ pushFactory.create(db, admin.getIdent(), testRepo).to("refs/for/master%private");
+ result.assertErrorStatus();
+ }
+
+ @Test
+ @GerritConfig(name = "change.disablePrivateChanges", value = "true")
+ public void pushDraftsWithDisablePrivateChangesTrue() throws Exception {
+ RevCommit initialHead = getRemoteHead();
+ PushOneCommit.Result result =
+ pushFactory.create(db, admin.getIdent(), testRepo).to("refs/for/master%draft");
+ result.assertErrorStatus();
+
+ testRepo.reset(initialHead);
+ result = pushFactory.create(db, admin.getIdent(), testRepo).to("refs/drafts/master");
+ result.assertErrorStatus();
+ }
+
+ @Test
+ @GerritConfig(name = "change.disablePrivateChanges", value = "true")
+ public void pushWithDisablePrivateChangesTrue() throws Exception {
+ PushOneCommit.Result result =
+ pushFactory.create(db, admin.getIdent(), testRepo).to("refs/for/master");
+ result.assertOkStatus();
+ assertThat(result.getChange().change().isPrivate()).isFalse();
+ }
+
+ @Test
+ public void pushPrivatesWithDisablePrivateChangesFalse() throws Exception {
+ PushOneCommit.Result result =
+ pushFactory.create(db, admin.getIdent(), testRepo).to("refs/for/master%private");
+ assertThat(result.getChange().change().isPrivate()).isTrue();
+ }
+
+ @Test
+ public void pushDraftsWithDisablePrivateChangesFalse() throws Exception {
+ RevCommit initialHead = getRemoteHead();
+ PushOneCommit.Result result =
+ pushFactory.create(db, admin.getIdent(), testRepo).to("refs/for/master%draft");
+ assertThat(result.getChange().change().isPrivate()).isTrue();
+
+ testRepo.reset(initialHead);
+ result = pushFactory.create(db, admin.getIdent(), testRepo).to("refs/drafts/master");
+ assertThat(result.getChange().change().isPrivate()).isTrue();
+ }
+
+ @Test
+ @GerritConfig(name = "change.disablePrivateChanges", value = "true")
+ public void setPrivateWithDisablePrivateChangesTrue() throws Exception {
+ PushOneCommit.Result result = createChange();
+
+ exception.expect(MethodNotAllowedException.class);
+ exception.expectMessage("private changes are disabled");
+ gApi.changes().id(result.getChangeId()).setPrivate(true, "set private");
+ }
+
+ @Test
+ public void setPrivateWithDisablePrivateChangesFalse() throws Exception {
+ PushOneCommit.Result result = createChange();
+ gApi.changes().id(result.getChangeId()).setPrivate(true, "set private");
+ assertThat(gApi.changes().id(result.getChangeId()).get().isPrivate).isTrue();
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/api/config/GeneralPreferencesIT.java b/javatests/com/google/gerrit/acceptance/api/config/GeneralPreferencesIT.java
index 23041de..c606982 100644
--- a/javatests/com/google/gerrit/acceptance/api/config/GeneralPreferencesIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/config/GeneralPreferencesIT.java
@@ -20,30 +20,14 @@
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
-import com.google.gerrit.reviewdb.client.RefNames;
-import org.eclipse.jgit.lib.RefUpdate;
-import org.eclipse.jgit.lib.Repository;
-import org.junit.After;
import org.junit.Test;
@NoHttpd
public class GeneralPreferencesIT extends AbstractDaemonTest {
- @After
- public void cleanUp() throws Exception {
- try (Repository git = repoManager.openRepository(allUsers)) {
- if (git.exactRef(RefNames.REFS_USERS_DEFAULT) != null) {
- RefUpdate u = git.updateRef(RefNames.REFS_USERS_DEFAULT);
- u.setForceUpdate(true);
- assertThat(u.delete()).isEqualTo(RefUpdate.Result.FORCED);
- }
- }
- accountCache.evictAllNoReindex();
- }
-
@Test
public void getGeneralPreferences() throws Exception {
GeneralPreferencesInfo result = gApi.config().server().getDefaultPreferences();
- assertPrefs(result, GeneralPreferencesInfo.defaults(), "my");
+ assertPrefs(result, GeneralPreferencesInfo.defaults(), "changeTable", "my");
}
@Test
@@ -57,6 +41,6 @@
result = gApi.config().server().getDefaultPreferences();
GeneralPreferencesInfo expected = GeneralPreferencesInfo.defaults();
expected.signedOffBy = newSignedOffBy;
- assertPrefs(result, expected, "my");
+ assertPrefs(result, expected, "changeTable", "my");
}
}
diff --git a/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java b/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java
index aba5c7d..c986c5e 100644
--- a/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java
@@ -219,13 +219,13 @@
RevCommit initialHead = getRemoteHead(project, RefNames.REFS_CONFIG);
ConfigInfo info = gApi.projects().name(project.get()).config();
- assertThat(info.submitType).isEqualTo(SubmitType.MERGE_IF_NECESSARY);
+ assertThat(info.defaultSubmitType.value).isEqualTo(SubmitType.MERGE_IF_NECESSARY);
ConfigInput input = new ConfigInput();
input.submitType = SubmitType.CHERRY_PICK;
info = gApi.projects().name(project.get()).config(input);
- assertThat(info.submitType).isEqualTo(SubmitType.CHERRY_PICK);
+ assertThat(info.defaultSubmitType.value).isEqualTo(SubmitType.CHERRY_PICK);
info = gApi.projects().name(project.get()).config();
- assertThat(info.submitType).isEqualTo(SubmitType.CHERRY_PICK);
+ assertThat(info.defaultSubmitType.value).isEqualTo(SubmitType.CHERRY_PICK);
RevCommit updatedHead = getRemoteHead(project, RefNames.REFS_CONFIG);
eventRecorder.assertRefUpdatedEvents(
@@ -233,6 +233,7 @@
}
@Test
+ @SuppressWarnings("deprecation")
public void setConfig() throws Exception {
ConfigInput input = createTestConfigInput();
ConfigInfo info = gApi.projects().name(project.get()).config(input);
@@ -250,9 +251,13 @@
.isEqualTo(input.createNewChangeForAllNotInTarget);
assertThat(info.maxObjectSizeLimit.configuredValue).isEqualTo(input.maxObjectSizeLimit);
assertThat(info.submitType).isEqualTo(input.submitType);
+ assertThat(info.defaultSubmitType.value).isEqualTo(input.submitType);
+ assertThat(info.defaultSubmitType.inheritedValue).isEqualTo(SubmitType.MERGE_IF_NECESSARY);
+ assertThat(info.defaultSubmitType.configuredValue).isEqualTo(input.submitType);
assertThat(info.state).isEqualTo(input.state);
}
+ @SuppressWarnings("deprecation")
@Test
public void setPartialConfig() throws Exception {
ConfigInput input = createTestConfigInput();
@@ -276,6 +281,9 @@
.isEqualTo(input.createNewChangeForAllNotInTarget);
assertThat(info.maxObjectSizeLimit.configuredValue).isEqualTo(input.maxObjectSizeLimit);
assertThat(info.submitType).isEqualTo(input.submitType);
+ assertThat(info.defaultSubmitType.value).isEqualTo(input.submitType);
+ assertThat(info.defaultSubmitType.inheritedValue).isEqualTo(SubmitType.MERGE_IF_NECESSARY);
+ assertThat(info.defaultSubmitType.configuredValue).isEqualTo(input.submitType);
assertThat(info.state).isEqualTo(input.state);
}
@@ -323,6 +331,24 @@
gApi.projects().name(project.get()).head("test");
}
+ @Test
+ public void nonActiveProjectCanBeMadeActive() throws Exception {
+ for (ProjectState nonActiveState :
+ ImmutableList.of(ProjectState.READ_ONLY, ProjectState.HIDDEN)) {
+ // ACTIVE => NON_ACTIVE
+ ConfigInput ci1 = new ConfigInput();
+ ci1.state = nonActiveState;
+ gApi.projects().name(project.get()).config(ci1);
+ assertThat(gApi.projects().name(project.get()).config().state).isEqualTo(nonActiveState);
+ // NON_ACTIVE => ACTIVE
+ ConfigInput ci2 = new ConfigInput();
+ ci2.state = ProjectState.ACTIVE;
+ gApi.projects().name(project.get()).config(ci2);
+ // ACTIVE is represented as null in the API
+ assertThat(gApi.projects().name(project.get()).config().state).isNull();
+ }
+ }
+
private ConfigInput createTestConfigInput() {
ConfigInput input = new ConfigInput();
input.description = "some description";
diff --git a/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java b/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
index df274ee..f7ca2f2 100644
--- a/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
+++ b/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
@@ -75,9 +75,11 @@
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.git.receive.ReceiveConstants;
+import com.google.gerrit.server.git.validators.CommitValidators.ChangeIdValidator;
import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.gerrit.server.mail.Address;
import com.google.gerrit.server.project.testing.Util;
@@ -1321,6 +1323,28 @@
}
@Test
+ public void testPushWithChangedChangeId() throws Exception {
+ PushOneCommit.Result r = pushTo("refs/for/master");
+ r.assertOkStatus();
+ PushOneCommit push =
+ pushFactory.create(
+ db,
+ admin.getIdent(),
+ testRepo,
+ PushOneCommit.SUBJECT
+ + "\n\n"
+ + "Change-Id: I55eab7c7a76e95005fa9cc469aa8f9fc16da9eba\n",
+ "b.txt",
+ "anotherContent",
+ r.getChangeId());
+ r = push.to("refs/changes/" + r.getChange().change().getId().get());
+ r.assertErrorStatus(
+ String.format(
+ ChangeIdValidator.CHANGE_ID_MISMATCH_MSG,
+ r.getCommit().abbreviate(RevId.ABBREV_LEN).name()));
+ }
+
+ @Test
public void pushWithMultipleChangeIds() throws Exception {
testPushWithMultipleChangeIds();
}
@@ -1923,8 +1947,8 @@
assertThat(info1.status).isEqualTo(ChangeStatus.NEW);
assertThat(info2.status).isEqualTo(ChangeStatus.NEW);
- assertThat(info1.isPrivate).isEqualTo(true);
- assertThat(info2.isPrivate).isEqualTo(true);
+ assertThat(info1.isPrivate).isTrue();
+ assertThat(info2.isPrivate).isTrue();
assertThat(info1.revisions).hasSize(1);
assertThat(info2.revisions).hasSize(1);
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java b/javatests/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java
index 9ccc138..2aa8d58 100644
--- a/javatests/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/account/ExternalIdIT.java
@@ -26,7 +26,6 @@
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
import static org.eclipse.jgit.lib.Constants.OBJ_TREE;
-import com.github.rholder.retry.StopStrategies;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
@@ -41,20 +40,15 @@
import com.google.gerrit.extensions.common.AccountExternalIdInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.metrics.MetricMaker;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.account.AccountsUpdate;
-import com.google.gerrit.server.account.externalids.DisabledExternalIdCache;
import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.server.account.externalids.ExternalIdNotes;
import com.google.gerrit.server.account.externalids.ExternalIdReader;
import com.google.gerrit.server.account.externalids.ExternalIds;
-import com.google.gerrit.server.account.externalids.ExternalIdsUpdate;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
-import com.google.gerrit.server.git.LockFailureException;
import com.google.gerrit.server.git.MetaDataUpdate;
-import com.google.gerrit.server.update.RetryHelper;
import com.google.gson.reflect.TypeToken;
import com.google.gwtorm.server.OrmDuplicateKeyException;
import com.google.inject.Inject;
@@ -67,8 +61,6 @@
import java.util.List;
import java.util.Locale;
import java.util.Set;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jgit.api.errors.TransportException;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
@@ -92,8 +84,6 @@
@Inject private AccountsUpdate.Server accountsUpdate;
@Inject private ExternalIds externalIds;
@Inject private ExternalIdReader externalIdReader;
- @Inject private MetricMaker metricMaker;
- @Inject private RetryHelper.Metrics retryMetrics;
@Inject private ExternalIdNotes.Factory externalIdNotesFactory;
@Test
@@ -715,91 +705,6 @@
}
@Test
- public void retryOnLockFailure() throws Exception {
- ExternalId.Key fooId = ExternalId.Key.create("foo", "foo");
- ExternalId.Key barId = ExternalId.Key.create("bar", "bar");
-
- final AtomicBoolean doneBgUpdate = new AtomicBoolean(false);
- ExternalIdsUpdate update =
- new ExternalIdsUpdate(
- repoManager,
- () -> metaDataUpdateFactory.create(allUsers),
- accountCache,
- allUsers,
- metricMaker,
- externalIds,
- new DisabledExternalIdCache(),
- new RetryHelper(
- cfg,
- retryMetrics,
- null,
- null,
- null,
- r -> r.withBlockStrategy(noSleepBlockStrategy)),
- () -> {
- if (!doneBgUpdate.getAndSet(true)) {
- try {
- insertExtId(ExternalId.create(barId, admin.id));
- } catch (Exception e) {
- // Ignore, the successful insertion of the external ID is asserted later
- }
- }
- });
- assertThat(doneBgUpdate.get()).isFalse();
- update.insert(ExternalId.create(fooId, admin.id));
- assertThat(doneBgUpdate.get()).isTrue();
-
- assertThat(externalIds.get(fooId)).isNotNull();
- assertThat(externalIds.get(barId)).isNotNull();
- }
-
- @Test
- public void failAfterRetryerGivesUp() throws Exception {
- ExternalId.Key[] extIdsKeys = {
- ExternalId.Key.create("foo", "foo"),
- ExternalId.Key.create("bar", "bar"),
- ExternalId.Key.create("baz", "baz")
- };
- final AtomicInteger bgCounter = new AtomicInteger(0);
- ExternalIdsUpdate update =
- new ExternalIdsUpdate(
- repoManager,
- () -> metaDataUpdateFactory.create(allUsers),
- accountCache,
- allUsers,
- metricMaker,
- externalIds,
- new DisabledExternalIdCache(),
- new RetryHelper(
- cfg,
- retryMetrics,
- null,
- null,
- null,
- r ->
- r.withStopStrategy(StopStrategies.stopAfterAttempt(extIdsKeys.length))
- .withBlockStrategy(noSleepBlockStrategy)),
- () -> {
- try {
- insertExtId(ExternalId.create(extIdsKeys[bgCounter.getAndAdd(1)], admin.id));
- } catch (Exception e) {
- // Ignore, the successful insertion of the external ID is asserted later
- }
- });
- assertThat(bgCounter.get()).isEqualTo(0);
- try {
- update.insert(ExternalId.create(ExternalId.Key.create("abc", "abc"), admin.id));
- fail("expected LockFailureException");
- } catch (LockFailureException e) {
- // Ignore, expected
- }
- assertThat(bgCounter.get()).isEqualTo(extIdsKeys.length);
- for (ExternalId.Key extIdKey : extIdsKeys) {
- assertThat(externalIds.get(extIdKey)).isNotNull();
- }
- }
-
- @Test
public void readExternalIdWithAccountIdThatCanBeExpressedInKiB() throws Exception {
ExternalId.Key extIdKey = ExternalId.Key.parse("foo:bar");
Account.Id accountId = new Account.Id(1024 * 100);
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java b/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
index 67307e2..e45f271 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
@@ -38,6 +38,7 @@
import com.google.gerrit.acceptance.TestProjectInput;
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.extensions.api.changes.ChangeApi;
import com.google.gerrit.extensions.api.changes.SubmitInput;
import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.api.projects.ConfigInput;
@@ -47,6 +48,7 @@
import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.common.ChangeInput;
import com.google.gerrit.extensions.common.LabelInfo;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.registration.RegistrationHandle;
@@ -236,6 +238,7 @@
break;
case MERGE_ALWAYS:
case MERGE_IF_NECESSARY:
+ case INHERIT:
assertThat(e.getMessage())
.isEqualTo(
"Failed to submit 3 changes due to the following problems:\n"
@@ -988,6 +991,78 @@
assertAuthorAndCommitDateEquals(getRemoteHead());
}
+ @Test
+ @TestProjectInput(rejectEmptyCommit = InheritableBoolean.FALSE)
+ public void submitEmptyCommitPatchSetCanNotFastForward_emptyCommitAllowed() throws Exception {
+ assume().that(getSubmitType()).isNotEqualTo(SubmitType.FAST_FORWARD_ONLY);
+
+ PushOneCommit.Result change = createChange("Change 1", "a.txt", "content");
+ submit(change.getChangeId());
+
+ ChangeApi revert1 = gApi.changes().id(change.getChangeId()).revert();
+ approve(revert1.id());
+ revert1.current().submit();
+
+ ChangeApi revert2 = gApi.changes().id(change.getChangeId()).revert();
+ approve(revert2.id());
+ revert2.current().submit();
+ }
+
+ @Test
+ @TestProjectInput(rejectEmptyCommit = InheritableBoolean.TRUE)
+ public void submitEmptyCommitPatchSetCanNotFastForward_emptyCommitNotAllowed() throws Exception {
+ assume().that(getSubmitType()).isNotEqualTo(SubmitType.FAST_FORWARD_ONLY);
+
+ PushOneCommit.Result change = createChange("Change 1", "a.txt", "content");
+ submit(change.getChangeId());
+
+ ChangeApi revert1 = gApi.changes().id(change.getChangeId()).revert();
+ approve(revert1.id());
+ revert1.current().submit();
+
+ ChangeApi revert2 = gApi.changes().id(change.getChangeId()).revert();
+ approve(revert2.id());
+
+ exception.expect(ResourceConflictException.class);
+ exception.expectMessage(
+ "Change "
+ + revert2.get()._number
+ + ": Change could not be merged because the commit is empty. "
+ + "Project policy requires all commits to contain modifications to at least one file.");
+ revert2.current().submit();
+ }
+
+ @Test
+ @TestProjectInput(rejectEmptyCommit = InheritableBoolean.FALSE)
+ public void submitEmptyCommitPatchSetCanFastForward_emptyCommitAllowed() throws Exception {
+ ChangeInput ci = new ChangeInput();
+ ci.subject = "Empty change";
+ ci.project = project.get();
+ ci.branch = "master";
+ ChangeApi change = gApi.changes().create(ci);
+ approve(change.id());
+ change.current().submit();
+ }
+
+ @Test
+ @TestProjectInput(rejectEmptyCommit = InheritableBoolean.TRUE)
+ public void submitEmptyCommitPatchSetCanFastForward_emptyCommitNotAllowed() throws Exception {
+ ChangeInput ci = new ChangeInput();
+ ci.subject = "Empty change";
+ ci.project = project.get();
+ ci.branch = "master";
+ ChangeApi change = gApi.changes().create(ci);
+ approve(change.id());
+
+ exception.expect(ResourceConflictException.class);
+ exception.expectMessage(
+ "Change "
+ + change.get()._number
+ + ": Change could not be merged because the commit is empty. "
+ + "Project policy requires all commits to contain modifications to at least one file.");
+ change.current().submit();
+ }
+
private void setChangeStatusToNew(PushOneCommit.Result... changes) throws Exception {
for (PushOneCommit.Result change : changes) {
try (BatchUpdate bu =
@@ -1084,9 +1159,7 @@
}
protected void assertSubmittable(String changeId) throws Exception {
- assertThat(get(changeId, SUBMITTABLE).submittable)
- .named("submit bit on ChangeInfo")
- .isEqualTo(true);
+ assertThat(get(changeId, SUBMITTABLE).submittable).named("submit bit on ChangeInfo").isTrue();
RevisionResource rsrc = parseCurrentRevisionResource(changeId);
UiAction.Description desc = submitHandler.getDescription(rsrc);
assertThat(desc.isVisible()).named("visible bit on submit action").isTrue();
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/PrivateByDefaultIT.java b/javatests/com/google/gerrit/acceptance/rest/change/PrivateByDefaultIT.java
index a10062c..0ece00a 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/PrivateByDefaultIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/PrivateByDefaultIT.java
@@ -17,14 +17,17 @@
import static com.google.common.truth.Truth.assertThat;
import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.GerritConfig;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.extensions.api.projects.ConfigInput;
import com.google.gerrit.extensions.client.InheritableBoolean;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ChangeInput;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.reviewdb.client.Project;
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Before;
import org.junit.Test;
@@ -43,7 +46,7 @@
public void createChangeWithPrivateByDefaultEnabled() throws Exception {
setPrivateByDefault(project2, InheritableBoolean.TRUE);
ChangeInput input = new ChangeInput(project2.get(), "master", "empty change");
- assertThat(gApi.changes().create(input).get().isPrivate).isEqualTo(true);
+ assertThat(gApi.changes().create(input).get().isPrivate).isTrue();
}
@Test
@@ -70,9 +73,20 @@
}
@Test
+ @GerritConfig(name = "change.disablePrivateChanges", value = "true")
+ public void createChangeWithPrivateByDefaultAndDisablePrivateChangesTrue() throws Exception {
+ setPrivateByDefault(project2, InheritableBoolean.TRUE);
+
+ ChangeInput input = new ChangeInput(project2.get(), "master", "empty change");
+ exception.expect(MethodNotAllowedException.class);
+ exception.expectMessage("private changes are disabled");
+ gApi.changes().create(input);
+ }
+
+ @Test
public void pushWithPrivateByDefaultEnabled() throws Exception {
setPrivateByDefault(project2, InheritableBoolean.TRUE);
- assertThat(createChange(project2).getChange().change().isPrivate()).isEqualTo(true);
+ assertThat(createChange(project2).getChange().change().isPrivate()).isTrue();
}
@Test
@@ -83,18 +97,45 @@
.getChange()
.change()
.isPrivate())
- .isEqualTo(false);
+ .isFalse();
}
@Test
public void pushWithPrivateByDefaultDisabled() throws Exception {
- assertThat(createChange(project2).getChange().change().isPrivate()).isEqualTo(false);
+ assertThat(createChange(project2).getChange().change().isPrivate()).isFalse();
}
@Test
public void pushBypassPrivateByDefaultInherited() throws Exception {
setPrivateByDefault(project1, InheritableBoolean.TRUE);
- assertThat(createChange(project2).getChange().change().isPrivate()).isEqualTo(true);
+ assertThat(createChange(project2).getChange().change().isPrivate()).isTrue();
+ }
+
+ @Test
+ @GerritConfig(name = "change.disablePrivateChanges", value = "true")
+ public void pushPrivatesWithPrivateByDefaultAndDisablePrivateChangesTrue() throws Exception {
+ setPrivateByDefault(project2, InheritableBoolean.TRUE);
+
+ TestRepository<InMemoryRepository> testRepo = cloneProject(project2);
+ PushOneCommit.Result result =
+ pushFactory.create(db, admin.getIdent(), testRepo).to("refs/for/master%private");
+ result.assertErrorStatus();
+ }
+
+ @Test
+ @GerritConfig(name = "change.disablePrivateChanges", value = "true")
+ public void pushDraftsWithPrivateByDefaultAndDisablePrivateChangesTrue() throws Exception {
+ setPrivateByDefault(project2, InheritableBoolean.TRUE);
+
+ RevCommit initialHead = getRemoteHead();
+ TestRepository<InMemoryRepository> testRepo = cloneProject(project2);
+ PushOneCommit.Result result =
+ pushFactory.create(db, admin.getIdent(), testRepo).to("refs/for/master%draft");
+ result.assertErrorStatus();
+
+ testRepo.reset(initialHead);
+ result = pushFactory.create(db, admin.getIdent(), testRepo).to("refs/drafts/master");
+ result.assertErrorStatus();
}
private void setPrivateByDefault(Project.NameKey proj, InheritableBoolean value)
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/SuggestReviewersIT.java b/javatests/com/google/gerrit/acceptance/rest/change/SuggestReviewersIT.java
index 21a1e76..c188d63 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/SuggestReviewersIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/SuggestReviewersIT.java
@@ -24,6 +24,7 @@
import com.google.gerrit.acceptance.GerritConfig;
import com.google.gerrit.acceptance.TestAccount;
import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.extensions.api.accounts.EmailInput;
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.common.ChangeInput;
import com.google.gerrit.extensions.common.GroupInfo;
@@ -413,6 +414,60 @@
assertReviewers(suggestReviewers(changeId, name), ImmutableList.of(foo1), ImmutableList.of());
}
+ @Test
+ public void suggestBySecondaryEmailWithModifyAccount() throws Exception {
+ String secondaryEmail = "foo.secondary@example.com";
+ TestAccount foo = createAccountWithSecondaryEmail("foo", secondaryEmail);
+
+ List<SuggestedReviewerInfo> reviewers =
+ suggestReviewers(createChange().getChangeId(), secondaryEmail, 4);
+ assertReviewers(reviewers, ImmutableList.of(foo), ImmutableList.of());
+
+ reviewers = suggestReviewers(createChange().getChangeId(), "secondary", 4);
+ assertReviewers(reviewers, ImmutableList.of(foo), ImmutableList.of());
+ }
+
+ @Test
+ public void cannotSuggestBySecondaryEmailWithoutModifyAccount() throws Exception {
+ String secondaryEmail = "foo.secondary@example.com";
+ createAccountWithSecondaryEmail("foo", secondaryEmail);
+
+ setApiUser(user);
+ List<SuggestedReviewerInfo> reviewers =
+ suggestReviewers(createChange().getChangeId(), secondaryEmail, 4);
+ assertThat(reviewers).isEmpty();
+
+ reviewers = suggestReviewers(createChange().getChangeId(), "secondary2", 4);
+ assertThat(reviewers).isEmpty();
+ }
+
+ @Test
+ public void secondaryEmailsInSuggestions() throws Exception {
+ String secondaryEmail = "foo.secondary@example.com";
+ TestAccount foo = createAccountWithSecondaryEmail("foo", secondaryEmail);
+
+ List<SuggestedReviewerInfo> reviewers =
+ suggestReviewers(createChange().getChangeId(), "foo", 4);
+ assertReviewers(reviewers, ImmutableList.of(foo), ImmutableList.of());
+ assertThat(Iterables.getOnlyElement(reviewers).account.secondaryEmails)
+ .containsExactly(secondaryEmail);
+
+ setApiUser(user);
+ reviewers = suggestReviewers(createChange().getChangeId(), "foo", 4);
+ assertReviewers(reviewers, ImmutableList.of(foo), ImmutableList.of());
+ assertThat(Iterables.getOnlyElement(reviewers).account.secondaryEmails).isNull();
+ }
+
+ private TestAccount createAccountWithSecondaryEmail(String name, String secondaryEmail)
+ throws Exception {
+ TestAccount foo = accountCreator.create(name(name), "foo.primary@example.com", "Foo");
+ EmailInput input = new EmailInput();
+ input.email = secondaryEmail;
+ input.noConfirmation = true;
+ gApi.accounts().id(foo.id.get()).addEmail(input);
+ return foo;
+ }
+
private List<SuggestedReviewerInfo> suggestReviewers(String changeId, String query)
throws Exception {
return gApi.changes().id(changeId).suggestReviewers(query).get();
diff --git a/javatests/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java b/javatests/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java
index 38462c0..8fc5312 100644
--- a/javatests/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java
@@ -61,6 +61,7 @@
@GerritConfig(name = "change.replyTooltip", value = "Publish votes and draft comments")
@GerritConfig(name = "change.replyLabel", value = "Vote")
@GerritConfig(name = "change.updateDelay", value = "50s")
+ @GerritConfig(name = "change.disablePrivateChanges", value = "true")
// download
@GerritConfig(
@@ -105,6 +106,7 @@
assertThat(i.change.replyTooltip).startsWith("Publish votes and draft comments");
assertThat(i.change.replyLabel).isEqualTo("Vote\u2026");
assertThat(i.change.updateDelay).isEqualTo(50);
+ assertThat(i.change.disablePrivateChanges).isTrue();
// download
assertThat(i.download.archives).containsExactly("tar", "tbz2", "tgz", "txz");
@@ -178,6 +180,7 @@
assertThat(i.change.replyTooltip).startsWith("Reply and score");
assertThat(i.change.replyLabel).isEqualTo("Reply\u2026");
assertThat(i.change.updateDelay).isEqualTo(300);
+ assertThat(i.change.disablePrivateChanges).isNull();
// download
assertThat(i.download.archives).containsExactly("tar", "tbz2", "tgz", "txz");
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java b/javatests/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java
index 9d811b2..8cbe1e7 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java
@@ -24,9 +24,12 @@
import com.google.common.collect.Sets;
import com.google.common.net.HttpHeaders;
import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.GerritConfig;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.acceptance.UseLocalDisk;
import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.extensions.api.projects.ConfigInfo;
+import com.google.gerrit.extensions.api.projects.ConfigInput;
import com.google.gerrit.extensions.api.projects.ProjectInput;
import com.google.gerrit.extensions.client.InheritableBoolean;
import com.google.gerrit.extensions.client.SubmitType;
@@ -181,7 +184,7 @@
Project project = projectCache.get(new Project.NameKey(newProjectName)).getProject();
assertProjectInfo(project, p);
assertThat(project.getDescription()).isEqualTo(in.description);
- assertThat(project.getSubmitType()).isEqualTo(in.submitType);
+ assertThat(project.getConfiguredSubmitType()).isEqualTo(in.submitType);
assertThat(project.getBooleanConfig(BooleanProjectConfig.USE_CONTRIBUTOR_AGREEMENTS))
.isEqualTo(in.useContributorAgreements);
assertThat(project.getBooleanConfig(BooleanProjectConfig.USE_SIGNED_OFF_BY))
@@ -331,6 +334,84 @@
}
}
+ @SuppressWarnings("deprecation")
+ @Test
+ public void createProjectWithDefaultInheritedSubmitType() throws Exception {
+ String parent = name("parent");
+ ProjectInput pin = new ProjectInput();
+ pin.name = parent;
+ ConfigInfo cfg = gApi.projects().create(pin).config();
+ assertThat(cfg.submitType).isEqualTo(SubmitType.MERGE_IF_NECESSARY);
+ assertThat(cfg.defaultSubmitType.value).isEqualTo(SubmitType.MERGE_IF_NECESSARY);
+ assertThat(cfg.defaultSubmitType.configuredValue).isEqualTo(SubmitType.MERGE_IF_NECESSARY);
+ assertThat(cfg.defaultSubmitType.inheritedValue).isEqualTo(SubmitType.MERGE_IF_NECESSARY);
+
+ ConfigInput cin = new ConfigInput();
+ cin.submitType = SubmitType.CHERRY_PICK;
+ gApi.projects().name(parent).config(cin);
+ cfg = gApi.projects().name(parent).config();
+ assertThat(cfg.submitType).isEqualTo(SubmitType.CHERRY_PICK);
+ assertThat(cfg.defaultSubmitType.value).isEqualTo(SubmitType.CHERRY_PICK);
+ assertThat(cfg.defaultSubmitType.configuredValue).isEqualTo(SubmitType.CHERRY_PICK);
+ assertThat(cfg.defaultSubmitType.inheritedValue).isEqualTo(SubmitType.MERGE_IF_NECESSARY);
+
+ String child = name("child");
+ pin = new ProjectInput();
+ pin.submitType = SubmitType.INHERIT;
+ pin.parent = parent;
+ pin.name = child;
+ cfg = gApi.projects().create(pin).config();
+ assertThat(cfg.submitType).isEqualTo(SubmitType.CHERRY_PICK);
+ assertThat(cfg.defaultSubmitType.value).isEqualTo(SubmitType.CHERRY_PICK);
+ assertThat(cfg.defaultSubmitType.configuredValue).isEqualTo(SubmitType.INHERIT);
+ assertThat(cfg.defaultSubmitType.inheritedValue).isEqualTo(SubmitType.CHERRY_PICK);
+
+ cin = new ConfigInput();
+ cin.submitType = SubmitType.REBASE_IF_NECESSARY;
+ gApi.projects().name(parent).config(cin);
+ cfg = gApi.projects().name(parent).config();
+ assertThat(cfg.submitType).isEqualTo(SubmitType.REBASE_IF_NECESSARY);
+ assertThat(cfg.defaultSubmitType.value).isEqualTo(SubmitType.REBASE_IF_NECESSARY);
+ assertThat(cfg.defaultSubmitType.configuredValue).isEqualTo(SubmitType.REBASE_IF_NECESSARY);
+ assertThat(cfg.defaultSubmitType.inheritedValue).isEqualTo(SubmitType.MERGE_IF_NECESSARY);
+
+ cfg = gApi.projects().name(child).config();
+ assertThat(cfg.submitType).isEqualTo(SubmitType.REBASE_IF_NECESSARY);
+ assertThat(cfg.defaultSubmitType.value).isEqualTo(SubmitType.REBASE_IF_NECESSARY);
+ assertThat(cfg.defaultSubmitType.configuredValue).isEqualTo(SubmitType.INHERIT);
+ assertThat(cfg.defaultSubmitType.inheritedValue).isEqualTo(SubmitType.REBASE_IF_NECESSARY);
+ }
+
+ @SuppressWarnings("deprecation")
+ @Test
+ @GerritConfig(
+ name = "repository.testinheritedsubmittype/*.defaultSubmitType",
+ value = "CHERRY_PICK"
+ )
+ public void repositoryConfigTakesPrecedenceOverInheritedSubmitType() throws Exception {
+ // Can't use name() since we need to specify this project name in gerrit.config prior to
+ // startup. Pick something reasonably unique instead.
+ String parent = "testinheritedsubmittype";
+ ProjectInput pin = new ProjectInput();
+ pin.name = parent;
+ pin.submitType = SubmitType.MERGE_ALWAYS;
+ ConfigInfo cfg = gApi.projects().create(pin).config();
+ assertThat(cfg.submitType).isEqualTo(SubmitType.MERGE_ALWAYS);
+ assertThat(cfg.defaultSubmitType.value).isEqualTo(SubmitType.MERGE_ALWAYS);
+ assertThat(cfg.defaultSubmitType.configuredValue).isEqualTo(SubmitType.MERGE_ALWAYS);
+ assertThat(cfg.defaultSubmitType.inheritedValue).isEqualTo(SubmitType.MERGE_IF_NECESSARY);
+
+ String child = parent + "/child";
+ pin = new ProjectInput();
+ pin.parent = parent;
+ pin.name = child;
+ cfg = gApi.projects().create(pin).config();
+ assertThat(cfg.submitType).isEqualTo(SubmitType.CHERRY_PICK);
+ assertThat(cfg.defaultSubmitType.value).isEqualTo(SubmitType.CHERRY_PICK);
+ assertThat(cfg.defaultSubmitType.configuredValue).isEqualTo(SubmitType.CHERRY_PICK);
+ assertThat(cfg.defaultSubmitType.inheritedValue).isEqualTo(SubmitType.MERGE_ALWAYS);
+ }
+
private void assertHead(String projectName, String expectedRef) throws Exception {
try (Repository repo = repoManager.openRepository(new Project.NameKey(projectName))) {
assertThat(repo.exactRef(Constants.HEAD).getTarget().getName()).isEqualTo(expectedRef);
diff --git a/javatests/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java b/javatests/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java
index f1385dd..f8b7652 100644
--- a/javatests/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java
@@ -41,7 +41,6 @@
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.Sequences;
-import com.google.gerrit.server.account.AccountsUpdate;
import com.google.gerrit.server.change.ChangeInserter;
import com.google.gerrit.server.change.ConsistencyChecker;
import com.google.gerrit.server.change.PatchSetInserter;
@@ -67,7 +66,10 @@
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.RefUpdate.Result;
+import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Before;
import org.junit.Test;
@@ -90,8 +92,6 @@
@Inject private Sequences sequences;
- @Inject private AccountsUpdate.Server accountsUpdate;
-
private RevCommit tip;
private Account.Id adminId;
private ConsistencyChecker checker;
@@ -126,7 +126,7 @@
public void missingOwner() throws Exception {
TestAccount owner = accountCreator.create("missing");
ChangeNotes notes = insertChange(owner);
- accountsUpdate.create().deleteByKey(owner.getId());
+ deleteUserBranch(owner.getId());
assertProblems(notes, null, problem("Missing change owner: " + owner.getId()));
}
@@ -958,4 +958,23 @@
private void assertNoProblems(ChangeNotes notes, @Nullable FixInput fix) throws Exception {
assertThat(checker.check(notes, fix).problems()).isEmpty();
}
+
+ private void deleteUserBranch(Account.Id accountId) throws IOException {
+ try (Repository repo = repoManager.openRepository(allUsers)) {
+ String refName = RefNames.refsUsers(accountId);
+ Ref ref = repo.exactRef(refName);
+ if (ref == null) {
+ return;
+ }
+
+ RefUpdate ru = repo.updateRef(refName);
+ ru.setExpectedOldObjectId(ref.getObjectId());
+ ru.setNewObjectId(ObjectId.zeroId());
+ ru.setForceUpdate(true);
+ Result result = ru.delete();
+ if (result != Result.FORCED) {
+ throw new IOException(String.format("Failed to delete ref %s: %s", refName, result.name()));
+ }
+ }
+ }
}
diff --git a/javatests/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java b/javatests/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java
index 79fed8b..c0dcc9c 100644
--- a/javatests/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java
@@ -1195,7 +1195,7 @@
Map<String, List<CommentInfo>> comments = gApi.changes().id(id.get()).current().drafts();
for (List<CommentInfo> cList : comments.values()) {
for (CommentInfo ci : cList) {
- assertThat(ci.unresolved).isEqualTo(true);
+ assertThat(ci.unresolved).isTrue();
}
}
}
diff --git a/javatests/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java b/javatests/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java
index 228beef..1236826 100644
--- a/javatests/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/project/ProjectWatchIT.java
@@ -26,30 +26,21 @@
import com.google.gerrit.extensions.api.changes.ReviewInput;
import com.google.gerrit.extensions.api.changes.StarsInput;
import com.google.gerrit.extensions.common.GroupInfo;
-import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.account.WatchConfig;
import com.google.gerrit.server.account.WatchConfig.NotifyType;
-import com.google.gerrit.server.account.WatchConfig.ProjectWatchKey;
import com.google.gerrit.server.git.NotifyConfig;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.mail.Address;
import com.google.gerrit.testing.FakeEmailSender.Message;
-import com.google.inject.Inject;
import java.util.EnumSet;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
-import java.util.Set;
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
import org.eclipse.jgit.junit.TestRepository;
import org.junit.Test;
@NoHttpd
public class ProjectWatchIT extends AbstractDaemonTest {
- @Inject private WatchConfig.Accessor watchConfig;
-
@Test
public void newPatchSetsNotifyConfig() throws Exception {
Address addr = new Address("Watcher", "watcher@example.com");
@@ -494,34 +485,6 @@
}
@Test
- public void deleteAllProjectWatches() throws Exception {
- Map<ProjectWatchKey, Set<NotifyType>> watches = new HashMap<>();
- watches.put(ProjectWatchKey.create(project, "*"), ImmutableSet.of(NotifyType.ALL));
- watchConfig.upsertProjectWatches(admin.getId(), watches);
- assertThat(watchConfig.getProjectWatches(admin.getId())).isNotEmpty();
-
- watchConfig.deleteAllProjectWatches(admin.getId());
- assertThat(watchConfig.getProjectWatches(admin.getId())).isEmpty();
- }
-
- @Test
- public void deleteAllProjectWatchesIfWatchConfigIsTheOnlyFileInUserBranch() throws Exception {
- // Create account that has no files in its refs/users/ branch.
- Account.Id id = accountCreator.create().id;
-
- // Add a project watch so that a watch.config file in the refs/users/ branch is created.
- Map<ProjectWatchKey, Set<NotifyType>> watches = new HashMap<>();
- watches.put(ProjectWatchKey.create(project, "*"), ImmutableSet.of(NotifyType.ALL));
- watchConfig.upsertProjectWatches(id, watches);
- assertThat(watchConfig.getProjectWatches(id)).isNotEmpty();
-
- // Delete all project watches so that the watch.config file in the refs/users/ branch is
- // deleted.
- watchConfig.deleteAllProjectWatches(id);
- assertThat(watchConfig.getProjectWatches(id)).isEmpty();
- }
-
- @Test
public void watchProjectNoNotificationForPrivateChange() throws Exception {
// watch project
String watchedProject = createProject("watchedProject").get();
diff --git a/javatests/com/google/gerrit/acceptance/ssh/UploadArchiveIT.java b/javatests/com/google/gerrit/acceptance/ssh/UploadArchiveIT.java
index a64818d..f4f81f8 100644
--- a/javatests/com/google/gerrit/acceptance/ssh/UploadArchiveIT.java
+++ b/javatests/com/google/gerrit/acceptance/ssh/UploadArchiveIT.java
@@ -127,7 +127,7 @@
tmp = in.readString();
tmp = in.readString();
tmp = tmp.substring(1);
- assertThat(tmp).isEqualTo("fatal: upload-archive not permitted");
+ assertThat(tmp).isEqualTo("fatal: upload-archive not permitted for format zip");
}
private InputStream argumentsToInputStream(String c) throws Exception {
diff --git a/javatests/com/google/gerrit/server/group/db/BUILD b/javatests/com/google/gerrit/server/group/db/BUILD
index 1ba0ce9..9816603 100644
--- a/javatests/com/google/gerrit/server/group/db/BUILD
+++ b/javatests/com/google/gerrit/server/group/db/BUILD
@@ -6,12 +6,14 @@
srcs = glob(["*.java"]),
deps = [
"//java/com/google/gerrit/common:server",
+ "//java/com/google/gerrit/common/data/testing:common-data-test-util",
"//java/com/google/gerrit/extensions:api",
"//java/com/google/gerrit/extensions/common/testing:common-test-util",
"//java/com/google/gerrit/reviewdb:server",
"//java/com/google/gerrit/server",
"//java/com/google/gerrit/server/group/db/testing",
"//java/com/google/gerrit/testing:gerrit-test-util",
+ "//java/com/google/gerrit/truth",
"//lib:gwtorm",
"//lib:truth",
"//lib/jgit/org.eclipse.jgit:jgit",
diff --git a/javatests/com/google/gerrit/server/group/db/GroupNameNotesTest.java b/javatests/com/google/gerrit/server/group/db/GroupNameNotesTest.java
index d4ddbcf..c059da9 100644
--- a/javatests/com/google/gerrit/server/group/db/GroupNameNotesTest.java
+++ b/javatests/com/google/gerrit/server/group/db/GroupNameNotesTest.java
@@ -18,24 +18,38 @@
import static com.google.common.truth.Truth.assert_;
import static com.google.gerrit.extensions.common.testing.CommitInfoSubject.assertThat;
import static com.google.gerrit.reviewdb.client.RefNames.REFS_GROUPNAMES;
+import static com.google.gerrit.truth.OptionalSubject.assertThat;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.common.data.testing.GroupReferenceSubject;
import com.google.gerrit.extensions.common.CommitInfo;
+import com.google.gerrit.extensions.common.testing.CommitInfoSubject;
import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.config.AllUsersNameProvider;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.group.db.testing.GroupTestUtil;
import com.google.gerrit.server.update.RefUpdateUtil;
-import com.google.gerrit.testing.GerritBaseTests;
import com.google.gerrit.testing.TestTimeUtil;
+import com.google.gerrit.truth.ListSubject;
+import com.google.gerrit.truth.OptionalSubject;
+import com.google.gwtorm.client.KeyUtil;
+import com.google.gwtorm.server.OrmDuplicateKeyException;
+import com.google.gwtorm.server.StandardKeyEncoder;
+import java.io.IOException;
import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
+import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
import org.eclipse.jgit.junit.TestRepository;
@@ -47,16 +61,28 @@
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.notes.NoteMap;
+import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ExpectedException;
-public class GroupNameNotesTest extends GerritBaseTests {
+public class GroupNameNotesTest {
+ static {
+ KeyUtil.setEncoderImpl(new StandardKeyEncoder());
+ }
+
private static final String SERVER_NAME = "Gerrit Server";
private static final String SERVER_EMAIL = "noreply@gerritcodereview.com";
private static final TimeZone TZ = TimeZone.getTimeZone("America/Los_Angeles");
+ @Rule public ExpectedException expectedException = ExpectedException.none();
+
+ private final AccountGroup.UUID groupUuid = new AccountGroup.UUID("users-XYZ");
+ private final AccountGroup.NameKey groupName = new AccountGroup.NameKey("users");
+
private AtomicInteger idCounter;
private Repository repo;
@@ -73,12 +99,311 @@
}
@Test
+ public void newGroupCanBeCreated() throws Exception {
+ createGroup(groupUuid, groupName);
+
+ Optional<GroupReference> groupReference = loadGroup(groupName);
+ assertThatGroup(groupReference).value().groupUuid().isEqualTo(groupUuid);
+ assertThatGroup(groupReference).value().name().isEqualTo(groupName.get());
+ }
+
+ @Test
+ public void uuidOfNewGroupMustNotBeNull() throws Exception {
+ expectedException.expect(NullPointerException.class);
+ GroupNameNotes.forNewGroup(repo, null, groupName);
+ }
+
+ @Test
+ public void nameOfNewGroupMustNotBeNull() throws Exception {
+ expectedException.expect(NullPointerException.class);
+ GroupNameNotes.forNewGroup(repo, groupUuid, null);
+ }
+
+ @Test
+ public void nameOfNewGroupMayBeEmpty() throws Exception {
+ AccountGroup.NameKey emptyName = new AccountGroup.NameKey("");
+ createGroup(groupUuid, emptyName);
+
+ Optional<GroupReference> groupReference = loadGroup(emptyName);
+ assertThatGroup(groupReference).value().name().isEqualTo("");
+ }
+
+ @Test
+ public void newGroupMustNotReuseNameOfAnotherGroup() throws Exception {
+ createGroup(groupUuid, groupName);
+
+ AccountGroup.UUID anotherGroupUuid = new AccountGroup.UUID("AnotherGroup");
+ expectedException.expect(OrmDuplicateKeyException.class);
+ expectedException.expectMessage(groupName.get());
+ GroupNameNotes.forNewGroup(repo, anotherGroupUuid, groupName);
+ }
+
+ @Test
+ public void newGroupMayReuseUuidOfAnotherGroup() throws Exception {
+ createGroup(groupUuid, groupName);
+
+ AccountGroup.NameKey anotherName = new AccountGroup.NameKey("admins");
+ createGroup(groupUuid, anotherName);
+
+ Optional<GroupReference> group1 = loadGroup(groupName);
+ assertThatGroup(group1).value().groupUuid().isEqualTo(groupUuid);
+ Optional<GroupReference> group2 = loadGroup(anotherName);
+ assertThatGroup(group2).value().groupUuid().isEqualTo(groupUuid);
+ }
+
+ @Test
+ public void groupCanBeRenamed() throws Exception {
+ createGroup(groupUuid, groupName);
+
+ AccountGroup.NameKey anotherName = new AccountGroup.NameKey("admins");
+ renameGroup(groupUuid, groupName, anotherName);
+
+ Optional<GroupReference> groupReference = loadGroup(anotherName);
+ assertThatGroup(groupReference).value().groupUuid().isEqualTo(groupUuid);
+ assertThatGroup(groupReference).value().name().isEqualTo(anotherName.get());
+ }
+
+ @Test
+ public void previousNameOfGroupCannotBeUsedAfterRename() throws Exception {
+ createGroup(groupUuid, groupName);
+
+ AccountGroup.NameKey anotherName = new AccountGroup.NameKey("admins");
+ renameGroup(groupUuid, groupName, anotherName);
+
+ Optional<GroupReference> group = loadGroup(groupName);
+ assertThatGroup(group).isAbsent();
+ }
+
+ @Test
+ public void groupCannotBeRenamedToNull() throws Exception {
+ createGroup(groupUuid, groupName);
+
+ expectedException.expect(NullPointerException.class);
+ GroupNameNotes.forRename(repo, groupUuid, groupName, null);
+ }
+
+ @Test
+ public void oldNameOfGroupMustBeSpecifiedForRename() throws Exception {
+ createGroup(groupUuid, groupName);
+
+ AccountGroup.NameKey anotherName = new AccountGroup.NameKey("admins");
+ expectedException.expect(NullPointerException.class);
+ GroupNameNotes.forRename(repo, groupUuid, null, anotherName);
+ }
+
+ @Test
+ public void groupCannotBeRenamedWhenOldNameIsWrong() throws Exception {
+ createGroup(groupUuid, groupName);
+
+ AccountGroup.NameKey anotherOldName = new AccountGroup.NameKey("contributors");
+ AccountGroup.NameKey anotherName = new AccountGroup.NameKey("admins");
+ expectedException.expect(ConfigInvalidException.class);
+ expectedException.expectMessage(anotherOldName.get());
+ GroupNameNotes.forRename(repo, groupUuid, anotherOldName, anotherName);
+ }
+
+ @Test
+ public void groupCannotBeRenamedToNameOfAnotherGroup() throws Exception {
+ createGroup(groupUuid, groupName);
+ AccountGroup.UUID anotherGroupUuid = new AccountGroup.UUID("admins-ABC");
+ AccountGroup.NameKey anotherGroupName = new AccountGroup.NameKey("admins");
+ createGroup(anotherGroupUuid, anotherGroupName);
+
+ expectedException.expect(OrmDuplicateKeyException.class);
+ expectedException.expectMessage(anotherGroupName.get());
+ GroupNameNotes.forRename(repo, groupUuid, groupName, anotherGroupName);
+ }
+
+ @Test
+ public void groupCannotBeRenamedWithoutSpecifiedUuid() throws Exception {
+ createGroup(groupUuid, groupName);
+
+ AccountGroup.NameKey anotherName = new AccountGroup.NameKey("admins");
+ expectedException.expect(NullPointerException.class);
+ GroupNameNotes.forRename(repo, null, groupName, anotherName);
+ }
+
+ @Test
+ public void groupCannotBeRenamedWhenUuidIsWrong() throws Exception {
+ createGroup(groupUuid, groupName);
+
+ AccountGroup.UUID anotherGroupUuid = new AccountGroup.UUID("admins-ABC");
+ AccountGroup.NameKey anotherName = new AccountGroup.NameKey("admins");
+ expectedException.expect(ConfigInvalidException.class);
+ expectedException.expectMessage(groupUuid.get());
+ GroupNameNotes.forRename(repo, anotherGroupUuid, groupName, anotherName);
+ }
+
+ @Test
+ public void firstGroupCreationCreatesARootCommit() throws Exception {
+ createGroup(groupUuid, groupName);
+
+ Ref ref = repo.exactRef(RefNames.REFS_GROUPNAMES);
+ assertThat(ref.getObjectId()).isNotNull();
+
+ try (RevWalk revWalk = new RevWalk(repo)) {
+ RevCommit revCommit = revWalk.parseCommit(ref.getObjectId());
+ assertThat(revCommit.getParentCount()).isEqualTo(0);
+ }
+ }
+
+ @Test
+ public void furtherGroupCreationAppendsACommit() throws Exception {
+ createGroup(groupUuid, groupName);
+ ImmutableList<CommitInfo> commitsAfterCreation = log();
+
+ AccountGroup.UUID anotherGroupUuid = new AccountGroup.UUID("admins-ABC");
+ AccountGroup.NameKey anotherName = new AccountGroup.NameKey("admins");
+ createGroup(anotherGroupUuid, anotherName);
+
+ ImmutableList<CommitInfo> commitsAfterFurtherGroup = log();
+ assertThatCommits(commitsAfterFurtherGroup).containsAllIn(commitsAfterCreation);
+ assertThatCommits(commitsAfterFurtherGroup).lastElement().isNotIn(commitsAfterCreation);
+ }
+
+ @Test
+ public void groupRenamingAppendsACommit() throws Exception {
+ createGroup(groupUuid, groupName);
+ ImmutableList<CommitInfo> commitsAfterCreation = log();
+
+ AccountGroup.NameKey anotherName = new AccountGroup.NameKey("admins");
+ renameGroup(groupUuid, groupName, anotherName);
+
+ ImmutableList<CommitInfo> commitsAfterRename = log();
+ assertThatCommits(commitsAfterRename).containsAllIn(commitsAfterCreation);
+ assertThatCommits(commitsAfterRename).lastElement().isNotIn(commitsAfterCreation);
+ }
+
+ @Test
+ public void newCommitIsNotCreatedForRedundantNameUpdate() throws Exception {
+ createGroup(groupUuid, groupName);
+ ImmutableList<CommitInfo> commitsAfterCreation = log();
+
+ renameGroup(groupUuid, groupName, groupName);
+
+ ImmutableList<CommitInfo> commitsAfterRename = log();
+ assertThatCommits(commitsAfterRename).isEqualTo(commitsAfterCreation);
+ }
+
+ @Test
+ public void newCommitIsNotCreatedWhenCommittingGroupCreationTwice() throws Exception {
+ GroupNameNotes groupNameNotes = GroupNameNotes.forNewGroup(repo, groupUuid, groupName);
+
+ commit(groupNameNotes);
+ ImmutableList<CommitInfo> commitsAfterFirstCommit = log();
+ commit(groupNameNotes);
+ ImmutableList<CommitInfo> commitsAfterSecondCommit = log();
+
+ assertThatCommits(commitsAfterSecondCommit).isEqualTo(commitsAfterFirstCommit);
+ }
+
+ @Test
+ public void newCommitIsNotCreatedWhenCommittingGroupRenamingTwice() throws Exception {
+ createGroup(groupUuid, groupName);
+
+ AccountGroup.NameKey anotherName = new AccountGroup.NameKey("admins");
+ GroupNameNotes groupNameNotes =
+ GroupNameNotes.forRename(repo, groupUuid, groupName, anotherName);
+
+ commit(groupNameNotes);
+ ImmutableList<CommitInfo> commitsAfterFirstCommit = log();
+ commit(groupNameNotes);
+ ImmutableList<CommitInfo> commitsAfterSecondCommit = log();
+
+ assertThatCommits(commitsAfterSecondCommit).isEqualTo(commitsAfterFirstCommit);
+ }
+
+ @Test
+ public void commitMessageMentionsGroupCreation() throws Exception {
+ createGroup(groupUuid, groupName);
+
+ ImmutableList<CommitInfo> commits = log();
+ assertThatCommits(commits).lastElement().message().contains("Create");
+ assertThatCommits(commits).lastElement().message().contains(groupName.get());
+ }
+
+ @Test
+ public void commitMessageMentionsGroupRenaming() throws Exception {
+ createGroup(groupUuid, groupName);
+
+ AccountGroup.NameKey anotherName = new AccountGroup.NameKey("admins");
+ renameGroup(groupUuid, groupName, anotherName);
+
+ ImmutableList<CommitInfo> commits = log();
+ assertThatCommits(commits).lastElement().message().contains("Rename");
+ assertThatCommits(commits).lastElement().message().contains(groupName.get());
+ assertThatCommits(commits).lastElement().message().contains(anotherName.get());
+ }
+
+ @Test
+ public void nonExistentNotesRefIsEquivalentToNonExistentGroup() throws Exception {
+ Optional<GroupReference> group = loadGroup(groupName);
+
+ assertThatGroup(group).isAbsent();
+ }
+
+ @Test
+ public void nonExistentGroupCannotBeLoaded() throws Exception {
+ createGroup(new AccountGroup.UUID("contributors-MN"), new AccountGroup.NameKey("contributors"));
+ createGroup(groupUuid, groupName);
+
+ Optional<GroupReference> group = loadGroup(new AccountGroup.NameKey("admins"));
+ assertThatGroup(group).isAbsent();
+ }
+
+ @Test
+ public void specificGroupCanBeLoaded() throws Exception {
+ createGroup(new AccountGroup.UUID("contributors-MN"), new AccountGroup.NameKey("contributors"));
+ createGroup(groupUuid, groupName);
+ createGroup(new AccountGroup.UUID("admins-ABC"), new AccountGroup.NameKey("admins"));
+
+ Optional<GroupReference> group = loadGroup(groupName);
+ assertThatGroup(group).value().groupUuid().isEqualTo(groupUuid);
+ }
+
+ @Test
+ public void nonExistentNotesRefIsEquivalentToNotAnyExistingGroups() throws Exception {
+ ImmutableList<GroupReference> allGroups = GroupNameNotes.loadAllGroups(repo);
+
+ assertThat(allGroups).isEmpty();
+ }
+
+ @Test
+ public void allGroupsCanBeLoaded() throws Exception {
+ AccountGroup.UUID groupUuid1 = new AccountGroup.UUID("contributors-MN");
+ AccountGroup.NameKey groupName1 = new AccountGroup.NameKey("contributors");
+ createGroup(groupUuid1, groupName1);
+ AccountGroup.UUID groupUuid2 = new AccountGroup.UUID("admins-ABC");
+ AccountGroup.NameKey groupName2 = new AccountGroup.NameKey("admins");
+ createGroup(groupUuid2, groupName2);
+
+ ImmutableList<GroupReference> allGroups = GroupNameNotes.loadAllGroups(repo);
+
+ GroupReference group1 = new GroupReference(groupUuid1, groupName1.get());
+ GroupReference group2 = new GroupReference(groupUuid2, groupName2.get());
+ assertThat(allGroups).containsExactly(group1, group2);
+ }
+
+ @Test
+ public void loadedGroupsContainGroupsWithDuplicateGroupUuids() throws Exception {
+ createGroup(groupUuid, groupName);
+ AccountGroup.NameKey anotherGroupName = new AccountGroup.NameKey("admins");
+ createGroup(groupUuid, anotherGroupName);
+
+ ImmutableList<GroupReference> allGroups = GroupNameNotes.loadAllGroups(repo);
+
+ GroupReference group1 = new GroupReference(groupUuid, groupName.get());
+ GroupReference group2 = new GroupReference(groupUuid, anotherGroupName.get());
+ assertThat(allGroups).containsExactly(group1, group2);
+ }
+
+ @Test
public void updateGroupNames() throws Exception {
GroupReference g1 = newGroup("a");
GroupReference g2 = newGroup("b");
PersonIdent ident = newPersonIdent();
- updateGroupNames(ident, g1, g2);
+ updateAllGroups(ident, g1, g2);
ImmutableList<CommitInfo> log = log();
assertThat(log).hasSize(1);
@@ -87,11 +412,11 @@
assertThat(log.get(0)).author().matches(ident);
assertThat(log.get(0)).committer().matches(ident);
- assertThat(GroupTestUtil.readNameToUuidMap(repo)).containsExactly("a", "a-1", "b", "b-2");
+ assertThat(GroupNameNotes.loadAllGroups(repo)).containsExactly(g1, g2);
// Updating the same set of names is a no-op.
String commit = log.get(0).commit;
- updateGroupNames(newPersonIdent(), g1, g2);
+ updateAllGroups(newPersonIdent(), g1, g2);
log = log();
assertThat(log).hasSize(1);
assertThat(log.get(0)).commit().isEqualTo(commit);
@@ -120,9 +445,9 @@
.copy();
ident = newPersonIdent();
- updateGroupNames(ident, g1, g2);
+ updateAllGroups(ident, g1, g2);
- assertThat(GroupTestUtil.readNameToUuidMap(repo)).containsExactly("a", "a-1", "b", "b-2");
+ assertThat(GroupNameNotes.loadAllGroups(repo)).containsExactly(g1, g2);
ImmutableList<CommitInfo> log = log();
assertThat(log).hasSize(2);
@@ -142,13 +467,13 @@
GroupReference g2 = newGroup("b");
PersonIdent ident = newPersonIdent();
- updateGroupNames(ident, g1, g2);
+ updateAllGroups(ident, g1, g2);
- assertThat(GroupTestUtil.readNameToUuidMap(repo)).containsExactly("a", "a-1", "b", "b-2");
+ assertThat(GroupNameNotes.loadAllGroups(repo)).containsExactly(g1, g2);
- updateGroupNames(ident);
+ updateAllGroups(ident);
- assertThat(GroupTestUtil.readNameToUuidMap(repo)).isEmpty();
+ assertThat(GroupNameNotes.loadAllGroups(repo)).isEmpty();
ImmutableList<CommitInfo> log = log();
assertThat(log).hasSize(2);
@@ -171,12 +496,46 @@
@Test
public void emptyGroupName() throws Exception {
GroupReference g = newGroup("");
- updateGroupNames(newPersonIdent(), g);
+ updateAllGroups(newPersonIdent(), g);
- assertThat(GroupTestUtil.readNameToUuidMap(repo)).containsExactly("", "-1");
+ assertThat(GroupNameNotes.loadAllGroups(repo)).containsExactly(g);
assertThat(readNameNote(g)).isEqualTo("[group]\n\tuuid = -1\n\tname = \n");
}
+ private void createGroup(AccountGroup.UUID groupUuid, AccountGroup.NameKey groupName)
+ throws Exception {
+ GroupNameNotes groupNameNotes = GroupNameNotes.forNewGroup(repo, groupUuid, groupName);
+ commit(groupNameNotes);
+ }
+
+ private void renameGroup(
+ AccountGroup.UUID groupUuid, AccountGroup.NameKey oldName, AccountGroup.NameKey newName)
+ throws Exception {
+ GroupNameNotes groupNameNotes = GroupNameNotes.forRename(repo, groupUuid, oldName, newName);
+ commit(groupNameNotes);
+ }
+
+ private Optional<GroupReference> loadGroup(AccountGroup.NameKey groupName) throws Exception {
+ return GroupNameNotes.loadGroup(repo, groupName);
+ }
+
+ private void commit(GroupNameNotes groupNameNotes) throws IOException {
+ try (MetaDataUpdate metaDataUpdate = createMetaDataUpdate()) {
+ groupNameNotes.commit(metaDataUpdate);
+ }
+ }
+
+ private MetaDataUpdate createMetaDataUpdate() {
+ PersonIdent serverIdent = newPersonIdent();
+
+ MetaDataUpdate metaDataUpdate =
+ new MetaDataUpdate(
+ GitReferenceUpdated.DISABLED, new Project.NameKey("Test Repository"), repo);
+ metaDataUpdate.getCommitBuilder().setCommitter(serverIdent);
+ metaDataUpdate.getCommitBuilder().setAuthor(serverIdent);
+ return metaDataUpdate;
+ }
+
private GroupReference newGroup(String name) {
int id = idCounter.incrementAndGet();
return new GroupReference(new AccountGroup.UUID(name + "-" + id), name);
@@ -190,10 +549,10 @@
return GroupNameNotes.getNoteKey(new AccountGroup.NameKey(g.getName()));
}
- private void updateGroupNames(PersonIdent ident, GroupReference... groupRefs) throws Exception {
+ private void updateAllGroups(PersonIdent ident, GroupReference... groupRefs) throws Exception {
try (ObjectInserter inserter = repo.newObjectInserter()) {
BatchRefUpdate bru = repo.getRefDatabase().newBatchUpdate();
- GroupNameNotes.updateGroupNames(repo, inserter, bru, Arrays.asList(groupRefs), ident);
+ GroupNameNotes.updateAllGroups(repo, inserter, bru, Arrays.asList(groupRefs), ident);
inserter.flush();
RefUpdateUtil.executeChecked(bru, repo);
}
@@ -204,7 +563,7 @@
BatchRefUpdate bru = repo.getRefDatabase().newBatchUpdate();
PersonIdent ident = newPersonIdent();
try {
- GroupNameNotes.updateGroupNames(repo, inserter, bru, Arrays.asList(groupRefs), ident);
+ GroupNameNotes.updateAllGroups(repo, inserter, bru, Arrays.asList(groupRefs), ident);
assert_().fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException e) {
assertThat(e).hasMessageThat().isEqualTo(GroupNameNotes.UNIQUE_REF_ERROR);
@@ -225,4 +584,14 @@
return new String(reader.open(noteMap.get(k), OBJ_BLOB).getCachedBytes(), UTF_8);
}
}
+
+ private static OptionalSubject<GroupReferenceSubject, GroupReference> assertThatGroup(
+ Optional<GroupReference> group) {
+ return assertThat(group, GroupReferenceSubject::assertThat);
+ }
+
+ private static ListSubject<CommitInfoSubject, CommitInfo> assertThatCommits(
+ List<CommitInfo> commits) {
+ return ListSubject.assertThat(commits, CommitInfoSubject::assertThat);
+ }
}
diff --git a/javatests/com/google/gerrit/server/group/db/GroupRebuilderTest.java b/javatests/com/google/gerrit/server/group/db/GroupRebuilderTest.java
index 51cf987..048eaba 100644
--- a/javatests/com/google/gerrit/server/group/db/GroupRebuilderTest.java
+++ b/javatests/com/google/gerrit/server/group/db/GroupRebuilderTest.java
@@ -536,7 +536,7 @@
try (ObjectInserter inserter = repo.newObjectInserter()) {
ImmutableList<GroupReference> refs =
ImmutableList.of(GroupReference.forGroup(g1), GroupReference.forGroup(g2));
- GroupNameNotes.updateGroupNames(repo, inserter, bru, refs, newPersonIdent());
+ GroupNameNotes.updateAllGroups(repo, inserter, bru, refs, newPersonIdent());
inserter.flush();
}
@@ -552,7 +552,9 @@
assertMigratedCleanly(reload(g1), b1);
assertMigratedCleanly(reload(g2), b2);
- assertThat(GroupTestUtil.readNameToUuidMap(repo)).containsExactly("a", "a-1", "b", "b-2");
+ GroupReference group1 = GroupReference.forGroup(g1);
+ GroupReference group2 = GroupReference.forGroup(g2);
+ assertThat(GroupNameNotes.loadAllGroups(repo)).containsExactly(group1, group2);
}
@Test
diff --git a/javatests/com/google/gerrit/server/group/db/GroupsNoteDbConsistencyCheckerTest.java b/javatests/com/google/gerrit/server/group/db/GroupsNoteDbConsistencyCheckerTest.java
index eff755f..a5b04ee 100644
--- a/javatests/com/google/gerrit/server/group/db/GroupsNoteDbConsistencyCheckerTest.java
+++ b/javatests/com/google/gerrit/server/group/db/GroupsNoteDbConsistencyCheckerTest.java
@@ -30,7 +30,7 @@
public void groupNamesRefIsMissing() throws Exception {
List<ConsistencyProblemInfo> problems =
GroupsNoteDbConsistencyChecker.checkWithGroupNameNotes(
- allUsersRepo, "g-1", new AccountGroup.UUID("uuid-1"));
+ allUsersRepo, new AccountGroup.NameKey("g-1"), new AccountGroup.UUID("uuid-1"));
assertThat(problems)
.containsExactly(warning("Group with name 'g-1' doesn't exist in the list of all names"));
}
@@ -40,7 +40,7 @@
updateGroupNamesRef("g-2", "[group]\n\tuuid = uuid-2\n\tname = g-2\n");
List<ConsistencyProblemInfo> problems =
GroupsNoteDbConsistencyChecker.checkWithGroupNameNotes(
- allUsersRepo, "g-1", new AccountGroup.UUID("uuid-1"));
+ allUsersRepo, new AccountGroup.NameKey("g-1"), new AccountGroup.UUID("uuid-1"));
assertThat(problems)
.containsExactly(warning("Group with name 'g-1' doesn't exist in the list of all names"));
}
@@ -50,7 +50,7 @@
updateGroupNamesRef("g-1", "[group]\n\tuuid = uuid-1\n\tname = g-1\n");
List<ConsistencyProblemInfo> problems =
GroupsNoteDbConsistencyChecker.checkWithGroupNameNotes(
- allUsersRepo, "g-1", new AccountGroup.UUID("uuid-1"));
+ allUsersRepo, new AccountGroup.NameKey("g-1"), new AccountGroup.UUID("uuid-1"));
assertThat(problems).isEmpty();
}
@@ -59,7 +59,7 @@
updateGroupNamesRef("g-1", "[group]\n\tuuid = uuid-2\n\tname = g-1\n");
List<ConsistencyProblemInfo> problems =
GroupsNoteDbConsistencyChecker.checkWithGroupNameNotes(
- allUsersRepo, "g-1", new AccountGroup.UUID("uuid-1"));
+ allUsersRepo, new AccountGroup.NameKey("g-1"), new AccountGroup.UUID("uuid-1"));
assertThat(problems)
.containsExactly(
warning(
@@ -72,7 +72,7 @@
updateGroupNamesRef("g-1", "[group]\n\tuuid = uuid-1\n\tname = g-2\n");
List<ConsistencyProblemInfo> problems =
GroupsNoteDbConsistencyChecker.checkWithGroupNameNotes(
- allUsersRepo, "g-1", new AccountGroup.UUID("uuid-1"));
+ allUsersRepo, new AccountGroup.NameKey("g-1"), new AccountGroup.UUID("uuid-1"));
assertThat(problems)
.containsExactly(warning("group note of name 'g-1' claims to represent name of 'g-2'"));
}
@@ -82,7 +82,7 @@
updateGroupNamesRef("g-1", "[group]\n\tuuid = uuid-2\n\tname = g-2\n");
List<ConsistencyProblemInfo> problems =
GroupsNoteDbConsistencyChecker.checkWithGroupNameNotes(
- allUsersRepo, "g-1", new AccountGroup.UUID("uuid-1"));
+ allUsersRepo, new AccountGroup.NameKey("g-1"), new AccountGroup.UUID("uuid-1"));
assertThat(problems)
.containsExactly(
warning(
@@ -97,7 +97,7 @@
updateGroupNamesRef("g-1", "[invalid");
List<ConsistencyProblemInfo> problems =
GroupsNoteDbConsistencyChecker.checkWithGroupNameNotes(
- allUsersRepo, "g-1", new AccountGroup.UUID("uuid-1"));
+ allUsersRepo, new AccountGroup.NameKey("g-1"), new AccountGroup.UUID("uuid-1"));
assertThat(problems)
.containsExactly(
warning(
diff --git a/javatests/com/google/gerrit/server/index/account/AccountFieldTest.java b/javatests/com/google/gerrit/server/index/account/AccountFieldTest.java
index 6bba51b..1542fe5 100644
--- a/javatests/com/google/gerrit/server/index/account/AccountFieldTest.java
+++ b/javatests/com/google/gerrit/server/index/account/AccountFieldTest.java
@@ -23,6 +23,7 @@
import com.google.common.collect.Iterables;
import com.google.common.collect.Streams;
import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.account.AccountState;
@@ -44,7 +45,12 @@
List<String> values =
toStrings(
AccountField.REF_STATE.get(
- new AccountState(allUsersName, account, ImmutableSet.of(), ImmutableMap.of())));
+ new AccountState(
+ allUsersName,
+ account,
+ ImmutableSet.of(),
+ ImmutableMap.of(),
+ GeneralPreferencesInfo.defaults())));
assertThat(values).hasSize(1);
String expectedValue =
allUsersName.get() + ":" + RefNames.refsUsers(account.getId()) + ":" + metaId;
@@ -73,7 +79,11 @@
toStrings(
AccountField.EXTERNAL_ID_STATE.get(
new AccountState(
- null, account, ImmutableSet.of(extId1, extId2), ImmutableMap.of())));
+ null,
+ account,
+ ImmutableSet.of(extId1, extId2),
+ ImmutableMap.of(),
+ GeneralPreferencesInfo.defaults())));
String expectedValue1 = extId1.key().sha1().name() + ":" + extId1.blobId().name();
String expectedValue2 = extId2.key().sha1().name() + ":" + extId2.blobId().name();
assertThat(values).containsExactly(expectedValue1, expectedValue2);
diff --git a/javatests/com/google/gerrit/server/mail/send/FromAddressGeneratorProviderTest.java b/javatests/com/google/gerrit/server/mail/send/FromAddressGeneratorProviderTest.java
index d65dd47..01e8225 100644
--- a/javatests/com/google/gerrit/server/mail/send/FromAddressGeneratorProviderTest.java
+++ b/javatests/com/google/gerrit/server/mail/send/FromAddressGeneratorProviderTest.java
@@ -22,6 +22,7 @@
import static org.easymock.EasyMock.verify;
import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountState;
@@ -388,6 +389,7 @@
new AllUsersName(AllUsersNameProvider.DEFAULT),
account,
Collections.emptySet(),
- new HashMap<>());
+ new HashMap<>(),
+ GeneralPreferencesInfo.defaults());
}
}
diff --git a/javatests/com/google/gerrit/server/project/RefControlTest.java b/javatests/com/google/gerrit/server/project/RefControlTest.java
index 9f70e3d..8892a50 100644
--- a/javatests/com/google/gerrit/server/project/RefControlTest.java
+++ b/javatests/com/google/gerrit/server/project/RefControlTest.java
@@ -34,6 +34,7 @@
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
+import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Lists;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.PermissionRange;
@@ -207,6 +208,7 @@
@Inject private SingleVersionListener singleVersionListener;
@Inject private InMemoryDatabase schemaFactory;
@Inject private ThreadLocalRequestContext requestContext;
+ @Inject private ProjectControl.Factory projectControlFactory;
@Before
public void setUp() throws Exception {
@@ -235,13 +237,16 @@
public void remove(Project p) {}
@Override
- public Iterable<Project.NameKey> all() {
- return Collections.emptySet();
+ public void remove(Project.NameKey name) {}
+
+ @Override
+ public ImmutableSortedSet<Project.NameKey> all() {
+ return ImmutableSortedSet.of();
}
@Override
- public Iterable<Project.NameKey> byName(String prefix) {
- return Collections.emptySet();
+ public ImmutableSortedSet<Project.NameKey> byName(String prefix) {
+ return ImmutableSortedSet.of();
}
@Override
@@ -831,7 +836,6 @@
private InMemoryRepository add(ProjectConfig pc) {
PrologEnvironment.Factory envFactory = null;
- ProjectControl.AssistedFactory projectControlFactory = null;
RulesCache rulesCache = null;
SitePaths sitePaths = null;
List<CommentLinkInfo> commentLinks = null;
@@ -871,7 +875,6 @@
Collections.<AccountGroup.UUID>emptySet(),
Collections.<AccountGroup.UUID>emptySet(),
sectionSorter,
- null, // commitsCollection
changeControlFactory,
permissionBackend,
new MockUser(name, memberOf),
diff --git a/javatests/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java b/javatests/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java
index 77c139e..262701c 100644
--- a/javatests/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java
+++ b/javatests/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java
@@ -26,10 +26,14 @@
import com.google.gerrit.extensions.client.ProjectWatchInfo;
import com.google.gerrit.extensions.common.AccountExternalIdInfo;
import com.google.gerrit.extensions.common.AccountInfo;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.index.FieldDef;
import com.google.gerrit.index.IndexConfig;
import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.Schema;
import com.google.gerrit.index.query.FieldBundle;
import com.google.gerrit.lifecycle.LifecycleManager;
import com.google.gerrit.reviewdb.client.Account;
@@ -121,7 +125,7 @@
protected Injector injector;
protected ReviewDb db;
protected AccountInfo currentUserInfo;
- protected CurrentUser user;
+ protected CurrentUser admin;
protected abstract Injector createInjector();
@@ -145,10 +149,10 @@
db = schemaFactory.open();
schemaCreator.create(db);
- Account.Id userId = createAccount("user", "User", "user@example.com", true);
- user = userFactory.create(userId);
- requestContext.setContext(newRequestContext(userId));
- currentUserInfo = gApi.accounts().id(userId.get()).get();
+ Account.Id adminId = createAccount("admin", "Administrator", "admin@example.com", true);
+ admin = userFactory.create(adminId);
+ requestContext.setContext(newRequestContext(adminId));
+ currentUserInfo = gApi.accounts().id(adminId.get()).get();
}
protected RequestContext newRequestContext(Account.Id requestUserId) {
@@ -238,6 +242,52 @@
}
@Test
+ public void bySecondaryEmail() throws Exception {
+ String prefix = name("secondary");
+ String domain = name("test.com");
+ String secondaryEmail = prefix + "@" + domain;
+ AccountInfo user1 = newAccountWithEmail("user1", name("user1@example.com"));
+ addEmails(user1, secondaryEmail);
+
+ AccountInfo user2 = newAccountWithEmail("user2", name("user2@example.com"));
+ addEmails(user2, name("other@" + domain));
+
+ assertQuery(secondaryEmail, user1);
+ assertQuery("email:" + secondaryEmail, user1);
+ assertQuery("email:" + prefix, user1);
+ assertQuery(domain, user1, user2);
+ }
+
+ @Test
+ public void byEmailWithoutModifyAccountCapability() throws Exception {
+ String preferredEmail = name("primary@test.com");
+ String secondaryEmail = name("secondary@test.com");
+ AccountInfo user1 = newAccountWithEmail("user1", preferredEmail);
+ addEmails(user1, secondaryEmail);
+
+ AccountInfo user2 = newAccount("user");
+ requestContext.setContext(newRequestContext(new Account.Id(user2._accountId)));
+
+ if (getSchemaVersion() < 5) {
+ assertMissingField(AccountField.PREFERRED_EMAIL);
+ assertFailingQuery("email:foo", "'email' operator is not supported by account index version");
+ return;
+ }
+
+ // This at least needs the PREFERRED_EMAIL field which is available from schema version 5.
+ if (getSchemaVersion() >= 5) {
+ assertQuery(preferredEmail, user1);
+ } else {
+ assertQuery(preferredEmail);
+ }
+
+ assertQuery(secondaryEmail);
+
+ assertQuery("email:" + preferredEmail, user1);
+ assertQuery("email:" + secondaryEmail);
+ }
+
+ @Test
public void byUsername() throws Exception {
AccountInfo user1 = newAccount("myuser");
@@ -298,6 +348,49 @@
}
@Test
+ public void byNameWithoutModifyAccountCapability() throws Exception {
+ AccountInfo user1 = newAccountWithFullName("jdoe", "John Doe");
+ AccountInfo user2 = newAccountWithFullName("jroe", "Jane Roe");
+
+ AccountInfo user3 = newAccount("user");
+ requestContext.setContext(newRequestContext(new Account.Id(user3._accountId)));
+
+ assertQuery("notexisting");
+ assertQuery("Not Existing");
+
+ // by full name works with any index version
+ assertQuery(quote(user1.name), user1);
+ assertQuery("name:" + quote(user1.name), user1);
+ assertQuery(quote(user2.name), user2);
+ assertQuery("name:" + quote(user2.name), user2);
+
+ // by self/me works with any index version
+ assertQuery("self", user3);
+ assertQuery("me", user3);
+
+ if (getSchemaVersion() < 8) {
+ assertMissingField(AccountField.NAME_PART_NO_SECONDARY_EMAIL);
+
+ // prefix queries only work if the NAME_PART_NO_SECONDARY_EMAIL field is available
+ assertQuery("john");
+ return;
+ }
+
+ assertQuery("John", user1);
+ assertQuery("john", user1);
+ assertQuery("Doe", user1);
+ assertQuery("doe", user1);
+ assertQuery("DOE", user1);
+ assertQuery("Jo Do", user1);
+ assertQuery("jo do", user1);
+ assertQuery("name:John", user1);
+ assertQuery("name:john", user1);
+ assertQuery("name:Doe", user1);
+ assertQuery("name:doe", user1);
+ assertQuery("name:DOE", user1);
+ }
+
+ @Test
public void byWatchedProject() throws Exception {
Project.NameKey p = createProject(name("p"));
Project.NameKey p2 = createProject(name("p2"));
@@ -375,6 +468,11 @@
List<AccountInfo> result = assertQuery(user1.username, user1);
assertThat(result.get(0).secondaryEmails).isNull();
+ result = assertQuery(newQuery(user1.username).withSuggest(true), user1);
+ assertThat(result.get(0).secondaryEmails)
+ .containsExactlyElementsIn(Arrays.asList(secondaryEmails))
+ .inOrder();
+
result = assertQuery(newQuery(user1.username).withOption(ListAccountsOption.DETAILS), user1);
assertThat(result.get(0).secondaryEmails).isNull();
@@ -394,6 +492,21 @@
}
@Test
+ public void withSecondaryEmailsWithoutModifyAccountCapability() throws Exception {
+ AccountInfo user = newAccount("myuser", "My User", "abc@example.com", true);
+ String[] secondaryEmails = new String[] {"dfg@example.com", "hij@example.com"};
+ addEmails(user, secondaryEmails);
+
+ requestContext.setContext(newRequestContext(new Account.Id(user._accountId)));
+
+ List<AccountInfo> result = newQuery(user.username).withSuggest(true).get();
+ assertThat(result.get(0).secondaryEmails).isNull();
+
+ exception.expect(AuthException.class);
+ newQuery(user.username).withOption(ListAccountsOption.ALL_EMAILS).get();
+ }
+
+ @Test
public void asAnonymous() throws Exception {
AccountInfo user1 = newAccount("user1");
@@ -416,10 +529,10 @@
PersonIdent ident = serverIdent.get();
md.getCommitBuilder().setAuthor(ident);
md.getCommitBuilder().setCommitter(ident);
- AccountConfig accountConfig = new AccountConfig(null, accountId);
- accountConfig.load(repo);
- accountConfig.setAccountUpdate(InternalAccountUpdate.builder().setFullName(newName).build());
- accountConfig.commit(md);
+ new AccountConfig(accountId, repo)
+ .load()
+ .setAccountUpdate(InternalAccountUpdate.builder().setFullName(newName).build())
+ .commit(md);
}
assertQuery("name:" + quote(user1.name), user1);
@@ -432,7 +545,7 @@
@Test
public void rawDocument() throws Exception {
- AccountInfo userInfo = gApi.accounts().id(user.getAccountId().get()).get();
+ AccountInfo userInfo = gApi.accounts().id(admin.getAccountId().get()).get();
Optional<FieldBundle> rawFields =
indexes
@@ -633,6 +746,29 @@
return accounts.stream().map(a -> a._accountId).collect(toList());
}
+ protected void assertMissingField(FieldDef<AccountState, ?> field) {
+ assertThat(getSchema().hasField(field))
+ .named("schema %s has field %s", getSchemaVersion(), field.getName())
+ .isFalse();
+ }
+
+ protected void assertFailingQuery(String query, String expectedMessage) throws Exception {
+ try {
+ assertQuery(query);
+ fail("expected BadRequestException for query '" + query + "'");
+ } catch (BadRequestException e) {
+ assertThat(e.getMessage()).isEqualTo(expectedMessage);
+ }
+ }
+
+ protected int getSchemaVersion() {
+ return getSchema().getVersion();
+ }
+
+ protected Schema<AccountState> getSchema() {
+ return indexes.getSearchIndex().getSchema();
+ }
+
/** Boiler plate code to check two byte arrays for equality */
private static class ByteArrayWrapper {
private byte[] arr;
@@ -654,8 +790,4 @@
return Arrays.hashCode(arr);
}
}
-
- protected int getSchemaVersion() {
- return indexes.getSearchIndex().getSchema().getVersion();
- }
}
diff --git a/javatests/com/google/gerrit/server/schema/Schema_159_to_160_Test.java b/javatests/com/google/gerrit/server/schema/Schema_159_to_160_Test.java
index 0bf9399..9b86c0e 100644
--- a/javatests/com/google/gerrit/server/schema/Schema_159_to_160_Test.java
+++ b/javatests/com/google/gerrit/server/schema/Schema_159_to_160_Test.java
@@ -106,7 +106,7 @@
assertThat(myMenusFromApi(accountId).keySet()).containsExactlyElementsIn(newNames).inOrder();
}
- // Raw config values, bypassing the defaults set by GeneralPreferencesLoader.
+ // Raw config values, bypassing the defaults set by PreferencesConfig.
private ImmutableMap<String, String> myMenusFromNoteDb(Account.Id id) throws Exception {
try (Repository repo = repoManager.openRepository(allUsersName)) {
VersionedAccountPreferences prefs = VersionedAccountPreferences.forUser(id);
diff --git a/javatests/com/google/gerrit/server/util/RegexListSearcherTest.java b/javatests/com/google/gerrit/server/util/RegexListSearcherTest.java
index dc8c0d8..01964a8 100644
--- a/javatests/com/google/gerrit/server/util/RegexListSearcherTest.java
+++ b/javatests/com/google/gerrit/server/util/RegexListSearcherTest.java
@@ -14,12 +14,10 @@
package com.google.gerrit.server.util;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth8.assertThat;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Ordering;
import java.util.List;
import org.junit.Test;
@@ -32,13 +30,6 @@
}
@Test
- public void hasMatch() {
- List<String> list = ImmutableList.of("bar", "foo", "quux");
- assertTrue(RegexListSearcher.ofStrings("foo").hasMatch(list));
- assertFalse(RegexListSearcher.ofStrings("xyz").hasMatch(list));
- }
-
- @Test
public void anchors() {
List<String> list = ImmutableList.of("foo");
assertSearchReturns(list, "^f.*", list);
@@ -66,7 +57,9 @@
}
private void assertSearchReturns(List<?> expected, String re, List<String> inputs) {
- assertTrue(Ordering.natural().isOrdered(inputs));
- assertEquals(expected, ImmutableList.copyOf(RegexListSearcher.ofStrings(re).search(inputs)));
+ assertThat(inputs).isOrdered();
+ assertThat(RegexListSearcher.ofStrings(re).search(inputs))
+ .containsExactlyElementsIn(expected)
+ .inOrder();
}
}
diff --git a/lib/js/bower_archives.bzl b/lib/js/bower_archives.bzl
index b914b63..c035793 100644
--- a/lib/js/bower_archives.bzl
+++ b/lib/js/bower_archives.bzl
@@ -54,7 +54,7 @@
sha1 = "01c485fbf898307029bbb72ac7e132db1570a842")
bower_archive(
name = "iron-flex-layout",
- package = "polymerelements/iron-flex-layout",
+ package = "PolymerElements/iron-flex-layout",
version = "1.3.7",
sha1 = "4d4cf3232cf750a17a7df0a37476117f831ac633")
bower_archive(
@@ -99,17 +99,12 @@
sha1 = "588d289f779d02b21ce5b676e257bbd6155649e8")
bower_archive(
name = "paper-behaviors",
- package = "polymerelements/paper-behaviors",
+ package = "PolymerElements/paper-behaviors",
version = "1.0.13",
sha1 = "a81eab28a952e124c208430e17508d9a1aae4ee7")
bower_archive(
- name = "paper-material",
- package = "polymerelements/paper-material",
- version = "1.0.7",
- sha1 = "159b7fb6b13b181c4276b25f9c6adbeaacb0d42b")
- bower_archive(
name = "paper-ripple",
- package = "polymerelements/paper-ripple",
+ package = "PolymerElements/paper-ripple",
version = "1.0.10",
sha1 = "21199db50d02b842da54bd6f4f1d1b10b474e893")
bower_archive(
diff --git a/lib/js/bower_components.bzl b/lib/js/bower_components.bzl
index 79f40bb..fb40855 100644
--- a/lib/js/bower_components.bzl
+++ b/lib/js/bower_components.bzl
@@ -223,8 +223,7 @@
deps = [
":iron-flex-layout",
":paper-behaviors",
- ":paper-material",
- ":paper-ripple",
+ ":paper-styles",
":polymer",
],
seed = True,
@@ -266,14 +265,6 @@
seed = True,
)
bower_component(
- name = "paper-material",
- license = "//lib:LICENSE-polymer",
- deps = [
- ":paper-styles",
- ":polymer",
- ],
- )
- bower_component(
name = "paper-ripple",
license = "//lib:LICENSE-polymer",
deps = [
diff --git a/plugins/codemirror-editor b/plugins/codemirror-editor
index 27f2c73..b33196a 160000
--- a/plugins/codemirror-editor
+++ b/plugins/codemirror-editor
@@ -1 +1 @@
-Subproject commit 27f2c73897da46d22b73c12cfdad282edb4e687c
+Subproject commit b33196a3da70e75ad00b5ac787620b29d20fed65
diff --git a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.html b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.html
index b36733d..4792c30 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.html
+++ b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.html
@@ -69,6 +69,7 @@
<input
is="iron-input"
id="tagNameInput"
+ maxlength="1024"
bind-value="{{topic}}">
</section>
<section>
diff --git a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.html b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.html
index c38a62c..4013b37 100644
--- a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.html
+++ b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.html
@@ -16,6 +16,7 @@
<link rel="import" href="../../../bower_components/polymer/polymer.html">
<link rel="import" href="../../../behaviors/gr-access-behavior/gr-access-behavior.html">
+<link rel="import" href="../../../bower_components/paper-toggle-button/paper-toggle-button.html">
<link rel="import" href="../../../styles/gr-form-styles.html">
<link rel="import" href="../../../styles/gr-menu-page-styles.html">
<link rel="import" href="../../../styles/shared-styles.html">
@@ -37,19 +38,14 @@
justify-content: space-between;
margin: .3em .7em;
}
- #deletedContainer {
- border: 1px solid #d1d2d3;
- padding: .7em;
- }
.rules {
background: #fafafa;
border: 1px solid #d1d2d3;
border-bottom: 0;
}
- /* TODO @beckysiegel add back */
- /* .editing .rules {
+ .editing .rules {
border-bottom: 1px solid #d1d2d3;
- } */
+ }
.title {
margin-bottom: .3em;
}
@@ -57,19 +53,29 @@
#removeBtn {
display: none;
}
- /* TODO @beckysiegel add back */
- /* .editing #removeBtn {
+ .right {
+ display: flex;
+ align-items: center;
+ }
+ .editing #removeBtn {
display: block;
+ margin-left: 1.5em;
}
.editing #addRule {
display: block;
padding: .7em;
- } */
+ }
#deletedContainer,
.deleted #mainContainer {
display: none;
}
- .deleted #deletedContainer,
+ .deleted #deletedContainer {
+ align-items: baseline;
+ border: 1px solid #d1d2d3;
+ display: flex;
+ justify-content: space-between;
+ padding: .7em;
+ }
#mainContainer {
display: block;
}
@@ -82,9 +88,17 @@
<div id="mainContainer">
<div class="header">
<span class="title">[[name]]</span>
- <gr-button
- id="removeBtn"
- on-tap="_handleRemovePermission">Remove</gr-button>
+ <div class="right">
+ <paper-toggle-button
+ id="exclusiveToggle"
+ checked="{{permission.value.exclusive}}"
+ on-change="_handleValueChange"
+ disabled$="[[!editing]]"></paper-toggle-button>Exclusive
+ <gr-button
+ link
+ id="removeBtn"
+ on-tap="_handleRemovePermission">Remove</gr-button>
+ </div>
</div><!-- end header -->
<div class="rules">
<template
@@ -112,8 +126,9 @@
</div> <!-- end rules -->
</div><!-- end mainContainer -->
<div id="deletedContainer">
- [[name]] was deleted
+ <span>[[name]] was deleted</span>
<gr-button
+ link
id="undoRemoveBtn"
on-tap="_handleUndoRemove">Undo</gr-button>
</div><!-- end deletedContainer -->
diff --git a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.js b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.js
index 2ba1eed..f9c04e60 100644
--- a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.js
+++ b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.js
@@ -16,6 +16,12 @@
const MAX_AUTOCOMPLETE_RESULTS = 20;
+ /**
+ * Fired when the permission has been modified or removed.
+ *
+ * @event access-modified
+ */
+
Polymer({
is: 'gr-permission',
@@ -33,6 +39,7 @@
editing: {
type: Boolean,
value: false,
+ observer: '_handleEditingChanged',
},
_label: {
type: Object,
@@ -51,6 +58,7 @@
type: Boolean,
value: false,
},
+ _originalExclusiveValue: Boolean,
},
behaviors: [
@@ -61,6 +69,53 @@
'_handleRulesChanged(_rules.splices)',
],
+ listeners: {
+ 'access-saved': '_handleAccessSaved',
+ },
+
+ ready() {
+ this._setupValues();
+ },
+
+ _setupValues() {
+ if (!this.permission) { return; }
+ this._originalExclusiveValue = !!this.permission.value.exclusive;
+ Polymer.dom.flush();
+ },
+
+ _handleAccessSaved() {
+ // Set a new 'original' value to keep track of after the value has been
+ // saved.
+ this._setupValues();
+ },
+
+ _handleEditingChanged(editing, editingOld) {
+ // Ignore when editing gets set initially.
+ if (!editingOld) { return; }
+ // Restore original values if no longer editing.
+ if (!editing) {
+ this._deleted = false;
+ this._groupFilter = '';
+ this._rules = this._rules.filter(rule => !rule.value.added);
+
+ // Restore exclusive bit to original.
+ this.set(['permission', 'value', 'exclusive'],
+ this._originalExclusiveValue);
+ }
+ },
+
+ _handleValueChange() {
+ this.permission.value.modified = true;
+ // Allows overall access page to know a change has been made.
+ this.dispatchEvent(new CustomEvent('access-modified', {bubbles: true}));
+ },
+
+ _handleRemovePermission() {
+ this._deleted = true;
+ this.permission.value.deleted = true;
+ this.dispatchEvent(new CustomEvent('access-modified', {bubbles: true}));
+ },
+
_handleRulesChanged(changeRecord) {
// Update the groups to exclude in the autocomplete.
this._groupsWithRules = this._computeGroupsWithRules(this._rules);
@@ -70,11 +125,6 @@
this._rules = this.toSortedArray(permission.value.rules);
},
- _handleRemovePermission() {
- this._deleted = true;
- this.set('permission.value.deleted', true);
- },
-
_computeSectionClass(editing, deleted) {
const classList = [];
if (editing) {
@@ -164,21 +214,26 @@
* gr-rule-editor handles setting the default values.
*/
_handleAddRuleItem(e) {
- this.set(['permission', 'value', 'rules', e.detail.value.id], {});
+ // The group id is encoded, but have to decode in order for the access
+ // API to work as expected.
+ const groupId = decodeURIComponent(e.detail.value.id);
+ this.set(['permission', 'value', 'rules', groupId], {});
// Purposely don't recompute sorted array so that the newly added rule
// is the last item of the array.
this.push('_rules', {
- id: e.detail.value.id,
+ id: groupId,
});
- // Wait for new rule to get value populated via gr-rule editor, and then
+ // Wait for new rule to get value populated via gr-rule-editor, and then
// add to permission values as well, so that the change gets propogated
// back to the section. Since the rule is inside a dom-repeat, a flush
// is needed.
Polymer.dom.flush();
- this.set(['permission', 'value', 'rules', e.detail.value.id],
- this._rules[this._rules.length - 1].value);
+ const value = this._rules[this._rules.length - 1].value;
+ value.added = true;
+ this.set(['permission', 'value', 'rules', groupId], value);
+ this.dispatchEvent(new CustomEvent('access-modified', {bubbles: true}));
},
});
})();
\ No newline at end of file
diff --git a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_test.html b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_test.html
index 179d221..b67d705 100644
--- a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission_test.html
@@ -288,6 +288,7 @@
},
},
};
+ element._setupValues();
flushAsynchronousOperations();
});
@@ -309,7 +310,7 @@
assert.equal(element._rules.length, 3);
assert.equal(Object.keys(element._groupsWithRules).length, 3);
assert.deepEqual(element.permission.value.rules['newUserGroupId'],
- {action: 'ALLOW', min: -2, max: 2});
+ {action: 'ALLOW', min: -2, max: 2, added: true});
});
test('removing the permission', () => {
@@ -326,6 +327,32 @@
assert.isFalse(element.$.permission.classList.contains('deleted'));
assert.isFalse(element._deleted);
});
+
+ test('modify a permission', () => {
+ element.editing = true;
+ element.name = 'Priority';
+ element.section = 'refs/*';
+
+ assert.isFalse(element._originalExclusiveValue);
+ assert.isNotOk(element.permission.value.modified);
+ MockInteractions.tap(element.$.exclusiveToggle);
+ flushAsynchronousOperations();
+ assert.isTrue(element.permission.value.exclusive);
+ assert.isTrue(element.permission.value.modified);
+ assert.isFalse(element._originalExclusiveValue);
+ element.editing = false;
+ assert.isFalse(element.permission.value.exclusive);
+ });
+
+ test('_handleValueChange', () => {
+ const modifiedHandler = sandbox.stub();
+ element.permission = {value: {rules: {}}};
+ element.addEventListener('access-modified', modifiedHandler);
+ assert.isNotOk(element.permission.value.modified);
+ element._handleValueChange();
+ assert.isTrue(element.permission.value.modified);
+ assert.isTrue(modifiedHandler.called);
+ });
});
});
</script>
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.html b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.html
index 6f63843..0494e2c 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.html
@@ -50,8 +50,9 @@
</style>
<style include="gr-menu-page-styles"></style>
<main class$="[[_computeAdminClass(_isAdmin)]]">
- <div class="gwtLink">This is currently in read only mode. To modify content, go to the
+ <div class="gwtLink">Editing access in the new UI is a work in progress. Visit the
<a href$="[[computeGwtUrl(path)]]" rel="external">Old UI</a>
+ if you need a feature that is not yet supported.
</div>
<template is="dom-if" if="[[_inheritsFrom]]">
<h3 id="inheritsFrom">Rights Inherit From
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.js b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.js
index 2110aaa..d903404 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access.js
@@ -156,7 +156,14 @@
for (const item of path) {
if (!curPos[item]) {
if (item === path[path.length - 1] && type === 'remove') {
- curPos[item] = null;
+ // TODO(beckysiegel) This if statement should be removed when
+ // https://gerrit-review.googlesource.com/c/gerrit/+/150851
+ // is live.
+ if (path[path.length - 2] === 'permissions') {
+ curPos[item] = {rules: {}};
+ } else {
+ curPos[item] = null;
+ }
} else if (item === path[path.length - 1] && type === 'add') {
curPos[item] = opt_value;
} else {
@@ -181,6 +188,9 @@
path.concat(k), 'remove');
this._updateAddRemoveObj(addRemoveObj,
path.concat(k), 'add', obj[k]);
+ } else if (obj[k].added) {
+ this._updateAddRemoveObj(addRemoveObj,
+ path.concat(k), 'add', obj[k]);
}
this._recursivelyUpdateAddRemoveObj(obj[k], addRemoveObj,
path.concat(k));
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.html b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.html
index cfa20d9..b26121c 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-access/gr-repo-access_test.html
@@ -76,46 +76,6 @@
'Code-Review': {},
},
};
- const repoAccessInput = {
- add: {
- 'refs/*': {
- permissions: {
- owner: {
- rules: {
- 123: {action: 'DENY', modified: true},
- },
- },
- },
- },
- },
- remove: {
- 'refs/*': {
- permissions: {
- owner: {
- rules: {
- 123: null,
- },
- },
- },
- },
- },
- };
-
- const repoAccessInputRemoved = {
- add: {},
- remove: {
- 'refs/*': {
- permissions: {
- owner: {
- rules: {
- 123: null,
- },
- },
- },
- },
- },
- };
-
setup(() => {
sandbox = sinon.sandbox.create();
element = fixture('basic');
@@ -142,14 +102,13 @@
name: 'Create Account',
},
};
-
const accessStub = sandbox.stub(element.$.restAPI,
'getRepoAccessRights');
-
- accessStub.withArgs('New Repo').returns(Promise.resolve(accessRes));
+ accessStub.withArgs('New Repo').returns(
+ Promise.resolve(JSON.parse(JSON.stringify(accessRes))));
accessStub.withArgs('Another New Repo')
- .returns(Promise.resolve(accessRes2));
+ .returns(Promise.resolve(JSON.parse(JSON.stringify(accessRes2))));
const capabilitiesStub = sandbox.stub(element.$.restAPI,
'getCapabilities');
capabilitiesStub.returns(Promise.resolve(capabilitiesRes));
@@ -184,26 +143,13 @@
name: 'Access Database',
},
};
- const accessRes = {
- local: {
- GLOBAL_CAPABILITIES: {
- permissions: {
- accessDatabase: {
- rules: {
- 123: {},
- },
- },
- },
- },
- },
- };
const repoRes = {
labels: {
'Code-Review': {},
},
};
- const accessStub = sandbox.stub(element.$.restAPI,
- 'getRepoAccessRights').returns(Promise.resolve(accessRes));
+ const accessStub = sandbox.stub(element.$.restAPI, 'getRepoAccessRights')
+ .returns(Promise.resolve(JSON.parse(JSON.stringify(accessRes2))));
const capabilitiesStub = sandbox.stub(element.$.restAPI,
'getCapabilities').returns(Promise.resolve(capabilitiesRes));
const repoStub = sandbox.stub(element.$.restAPI, 'getRepo').returns(
@@ -244,7 +190,8 @@
suite('with defined sections', () => {
setup(() => {
- element._sections = element.toSortedArray(accessRes.local);
+ element._sections =
+ element.toSortedArray(JSON.parse(JSON.stringify(accessRes.local)));
flushAsynchronousOperations();
});
@@ -280,20 +227,201 @@
assert.isTrue(element._handleAccessModified.called);
});
- test('_computeAddAndRemove', () => {
- // With nothing modified
- element._local = accessRes.local;
+ test('_computeAddAndRemove rules', () => {
+ element._local = JSON.parse(JSON.stringify(accessRes.local));
assert.deepEqual(element._computeAddAndRemove(), {add: {}, remove: {}});
element._local['refs/*'].permissions.owner.rules[123].deleted = true;
- assert.deepEqual(element._computeAddAndRemove(), repoAccessInputRemoved);
+ let expectedInput = {
+ add: {},
+ remove: {
+ 'refs/*': {
+ permissions: {
+ owner: {
+ rules: {
+ 123: null,
+ },
+ },
+ },
+ },
+ },
+ };
+ assert.deepEqual(element._computeAddAndRemove(), expectedInput);
delete element._local['refs/*'].permissions.owner.rules[123].deleted;
element._local['refs/*'].permissions.owner.rules[123].modified = true;
- assert.deepEqual(element._computeAddAndRemove(), repoAccessInput);
+ expectedInput = {
+ add: {
+ 'refs/*': {
+ permissions: {
+ owner: {
+ rules: {
+ 123: {action: 'DENY', modified: true},
+ },
+ },
+ },
+ },
+ },
+ remove: {
+ 'refs/*': {
+ permissions: {
+ owner: {
+ rules: {
+ 123: null,
+ },
+ },
+ },
+ },
+ },
+ };
+ assert.deepEqual(element._computeAddAndRemove(), expectedInput);
+ });
+
+ test('_computeAddAndRemove permissions', () => {
+ element._local = JSON.parse(JSON.stringify(accessRes.local));
+ assert.deepEqual(element._computeAddAndRemove(), {add: {}, remove: {}});
+ element._local['refs/*'].permissions.owner.deleted = true;
+ let expectedInput = {
+ add: {},
+ remove: {
+ 'refs/*': {
+ permissions: {
+ owner: {rules: {}},
+ },
+ },
+ },
+ };
+ assert.deepEqual(element._computeAddAndRemove(), expectedInput);
+ delete element._local['refs/*'].permissions.owner.deleted;
+ element._local['refs/*'].permissions.owner.modified = true;
+ expectedInput = {
+ add: {
+ 'refs/*': {
+ permissions: {
+ owner: {
+ modified: true,
+ rules: {
+ 234: {action: 'ALLOW'},
+ 123: {action: 'DENY'},
+ },
+ },
+ },
+ },
+ },
+ remove: {
+ 'refs/*': {
+ permissions: {
+ owner: {rules: {}},
+ },
+ },
+ },
+ };
+ assert.deepEqual(element._computeAddAndRemove(), expectedInput);
+ });
+
+ test('_computeAddAndRemove combinations', () => {
+ // Modify rule and delete permission that it is inside of.
+ element._local = JSON.parse(JSON.stringify(accessRes.local));
+ element._local['refs/*'].permissions.owner.rules[123].modified = true;
+ element._local['refs/*'].permissions.owner.deleted = true;
+ let expectedInput = {
+ add: {},
+ remove: {
+ 'refs/*': {
+ permissions: {
+ owner: {rules: {}},
+ },
+ },
+ },
+ };
+ assert.deepEqual(element._computeAddAndRemove(), expectedInput);
+ // Delete rule and delete permission that it is inside of.
+ element._local['refs/*'].permissions.owner.rules[123].modified = false;
+ element._local['refs/*'].permissions.owner.rules[123].deleted = true;
+ assert.deepEqual(element._computeAddAndRemove(), expectedInput);
+
+ // Also modify a different rule inside of another permission.
+ element._local['refs/*'].permissions.read.modified = true;
+ expectedInput = {
+ add: {
+ 'refs/*': {
+ permissions: {
+ read: {
+ modified: true,
+ rules: {
+ 234: {action: 'ALLOW'},
+ },
+ },
+ },
+ },
+ },
+ remove: {
+ 'refs/*': {
+ permissions: {
+ owner: {rules: {}},
+ read: {rules: {}},
+ },
+ },
+ },
+ };
+ assert.deepEqual(element._computeAddAndRemove(), expectedInput);
+ // Modify both permissions with an exclusive bit. Owner is still
+ // deleted.
+ element._local['refs/*'].permissions.owner.exclusive = true;
+ element._local['refs/*'].permissions.owner.modified = true;
+ element._local['refs/*'].permissions.read.exclusive = true;
+ element._local['refs/*'].permissions.read.modified = true;
+ expectedInput = {
+ add: {
+ 'refs/*': {
+ permissions: {
+ read: {
+ exclusive: true,
+ modified: true,
+ rules: {
+ 234: {action: 'ALLOW'},
+ },
+ },
+ },
+ },
+ },
+ remove: {
+ 'refs/*': {
+ permissions: {
+ owner: {rules: {}},
+ read: {rules: {}},
+ },
+ },
+ },
+ };
+ assert.deepEqual(element._computeAddAndRemove(), expectedInput);
});
test('_handleSaveForReview', done => {
- sandbox.stub(element.$.restAPI, 'getRepoAccessRights')
- .returns(Promise.resolve(accessRes));
+ const repoAccessInput = {
+ add: {
+ 'refs/*': {
+ permissions: {
+ owner: {
+ rules: {
+ 123: {action: 'DENY', modified: true},
+ },
+ },
+ },
+ },
+ },
+ remove: {
+ 'refs/*': {
+ permissions: {
+ owner: {
+ rules: {
+ 123: null,
+ },
+ },
+ },
+ },
+ },
+ };
+ sandbox.stub(element.$.restAPI, 'getRepoAccessRights').returns(
+ Promise.resolve(JSON.parse(JSON.stringify(accessRes))));
sandbox.stub(element.$.restAPI, 'getRepo')
.returns(Promise.resolve({}));
sandbox.stub(Gerrit.Nav, 'navigateToChange');
@@ -302,8 +430,7 @@
.returns(Promise.resolve({_number: 1}));
element.repo = 'test-repo';
- sandbox.stub(element, '_computeAddAndRemove')
- .returns(repoAccessInput);
+ sandbox.stub(element, '_computeAddAndRemove').returns(repoAccessInput);
element._handleSaveForReview().then(() => {
assert.isTrue(saveForReviewStub.called);
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.js b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.js
index 96f229d..05d5d5c 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.js
@@ -100,7 +100,7 @@
}
});
- this.detailType = params.detailType;
+ this.detailType = params.detail;
this._filter = this.getFilterValue(params);
this._offset = this.getOffsetValue(params);
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.html b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.html
index be6861c..695ecc7 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.html
@@ -98,7 +98,7 @@
const params = {
repo: 'test',
- detailType: 'branches',
+ detail: 'branches',
};
element._paramsChanged(params).then(() => { flush(done); });
@@ -281,7 +281,7 @@
const params = {
repo: 'test',
- detailType: 'branches',
+ detail: 'branches',
};
element._paramsChanged(params).then(() => { flush(done); });
@@ -298,7 +298,7 @@
return Promise.resolve(branches);
});
const params = {
- detailType: 'branches',
+ detail: 'branches',
repo: 'test',
filter: 'test',
offset: 25,
@@ -341,7 +341,7 @@
const params = {
repo: 'test',
- detailType: 'tags',
+ detail: 'tags',
};
element._paramsChanged(params).then(() => { flush(done); });
@@ -416,7 +416,7 @@
const params = {
repo: 'test',
- detailType: 'tags',
+ detail: 'tags',
};
element._paramsChanged(params).then(() => { flush(done); });
@@ -434,7 +434,7 @@
});
const params = {
repo: 'test',
- detailType: 'tags',
+ detail: 'tags',
filter: 'test',
offset: 25,
};
diff --git a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.html b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.html
index ea0c0a4..b1964ff 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.html
@@ -103,7 +103,8 @@
id="submitTypeSelect"
bind-value="{{_repoConfig.submit_type}}">
<select disabled$="[[_readOnly]]">
- <template is="dom-repeat" items="[[_submitTypes]]">
+ <template is="dom-repeat"
+ items="[[_formatSubmitTypeSelect(_repoConfig)]]">
<option value="[[item.value]]">[[item.label]]</option>
</template>
</select>
@@ -265,6 +266,21 @@
</gr-select>
</span>
</section>
+ <section>
+ <span class="title">Reject empty commit upon submit</span>
+ <span class="value">
+ <gr-select
+ id="rejectEmptyCommitSelect"
+ bind-value="{{_repoConfig.reject_empty_commit.configured_value}}">
+ <select disabled$="[[_readOnly]]">
+ <template is="dom-repeat"
+ items="[[_formatBooleanSelect(_repoConfig.reject_empty_commit)]]">
+ <option value="[[item.value]]">[[item.label]]</option>
+ </template>
+ </select>
+ </gr-select>
+ </span>
+ </section>
</fieldset>
<h3 id="Options">Contributor Agreements</h3>
<fieldset id="agreements">
diff --git a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.js b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.js
index 5019f45..2febb25 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.js
@@ -21,6 +21,7 @@
};
const SUBMIT_TYPES = {
+ // Exclude INHERIT, which is handled specially.
mergeIfNecessary: {
value: 'MERGE_IF_NECESSARY',
label: 'Merge if necessary',
@@ -129,6 +130,15 @@
promises.push(this.$.restAPI.getProjectConfig(this.repo).then(
config => {
+ if (config.default_submit_type) {
+ // The gr-select is bound to submit_type, which needs to be the
+ // *configured* submit type. When default_submit_type is
+ // present, the server reports the *effective* submit type in
+ // submit_type, so we need to overwrite it before storing the
+ // config in this.
+ config.submit_type =
+ config.default_submit_type.configured_value;
+ }
if (!config.state) {
config.state = STATES.active.value;
}
@@ -183,6 +193,36 @@
];
},
+ _formatSubmitTypeSelect(projectConfig) {
+ if (!projectConfig) { return; }
+ const allValues = Object.values(SUBMIT_TYPES);
+ const type = projectConfig.default_submit_type;
+ if (!type) {
+ // Server is too old to report default_submit_type, so assume INHERIT
+ // is not a valid value.
+ return allValues;
+ }
+
+ let inheritLabel = 'Inherit';
+ if (type.inherited_value) {
+ let inherited = type.inherited_value;
+ for (const val of allValues) {
+ if (val.value === type.inherited_value) {
+ inherited = val.label;
+ break;
+ }
+ }
+ inheritLabel = `Inherit (${inherited})`;
+ }
+ return [
+ {
+ label: inheritLabel,
+ value: 'INHERIT',
+ },
+ ...allValues,
+ ];
+ },
+
_isLoading() {
return this._loading || this._loading === undefined;
},
@@ -195,6 +235,12 @@
const configInputObj = {};
for (const key in p) {
if (p.hasOwnProperty(key)) {
+ if (key === 'default_submit_type') {
+ // default_submit_type is not in the input type, and the
+ // configured value was already copied to submit_type by
+ // _loadProject. Omit this property when saving.
+ continue;
+ }
if (typeof p[key] === 'object') {
configInputObj[key] = p[key].configured_value;
} else {
diff --git a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo_test.html b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo_test.html
index f44beae..9ea0177 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo_test.html
@@ -93,12 +93,21 @@
value: false,
configured_value: 'FALSE',
},
+ reject_empty_commit: {
+ value: false,
+ configured_value: 'FALSE',
+ },
enable_reviewer_by_email: {
value: false,
configured_value: 'FALSE',
},
max_object_size_limit: {},
submit_type: 'MERGE_IF_NECESSARY',
+ default_submit_type: {
+ value: 'MERGE_IF_NECESSARY',
+ configured_value: 'INHERIT',
+ inherited_value: 'MERGE_IF_NECESSARY',
+ },
});
},
getConfig() {
@@ -252,7 +261,16 @@
});
});
- test('fields update and save correctly', done => {
+ test('inherited submit type value is calculated correctly', () => {
+ return element._loadRepo().then(() => {
+ const sel = element.$.submitTypeSelect;
+ assert.equal(sel.bindValue, 'INHERIT');
+ assert.equal(
+ sel.nativeSelect.options[0].text, 'Inherit (Merge if necessary)');
+ });
+ });
+
+ test('fields update and save correctly', () => {
// test notedb
element._noteDbEnabled = false;
@@ -276,6 +294,7 @@
reject_implicit_merges: 'TRUE',
private_by_default: 'TRUE',
match_author_to_committer_date: 'TRUE',
+ reject_empty_commit: 'TRUE',
max_object_size_limit: 10,
submit_type: 'FAST_FORWARD_ONLY',
state: 'READ_ONLY',
@@ -289,7 +308,7 @@
const button = Polymer.dom(element.root).querySelector('gr-button');
- element._loadRepo().then(() => {
+ return element._loadRepo().then(() => {
assert.isTrue(button.hasAttribute('disabled'));
assert.isFalse(element.$.Title.classList.contains('edited'));
element.$.descriptionInput.bindValue = configInputObj.description;
@@ -317,6 +336,8 @@
configInputObj.use_contributor_agreements;
element.$.useSignedOffBySelect.bindValue =
configInputObj.use_signed_off_by;
+ element.$.rejectEmptyCommitSelect.bindValue =
+ configInputObj.reject_empty_commit;
element.$.unRegisteredCcSelect.bindValue =
configInputObj.enable_reviewer_by_email;
@@ -327,12 +348,11 @@
element._formatRepoConfigForSave(element._repoConfig);
assert.deepEqual(formattedObj, configInputObj);
- element._handleSaveRepoConfig().then(() => {
+ return element._handleSaveRepoConfig().then(() => {
assert.isTrue(button.hasAttribute('disabled'));
assert.isFalse(element.$.Title.classList.contains('edited'));
assert.isTrue(saveStub.lastCall.calledWithExactly(REPO,
configInputObj));
- done();
});
});
});
diff --git a/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.js b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.js
index de33fa1..a3b0272 100644
--- a/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.js
+++ b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor.js
@@ -196,6 +196,9 @@
},
_handleUndoChange() {
+ // gr-permission will take care of removing rules that were added but
+ // unsaved. We need to keep the added bit for the filter.
+ if (this.rule.value.added) { return; }
this.set('rule.value', Object.assign({}, this._originalRuleValues));
this._deleted = false;
delete this.rule.value.deleted;
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 b076c83..66ab290 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
@@ -28,7 +28,9 @@
<link rel="import" href="../../shared/gr-editable-label/gr-editable-label.html">
<link rel="import" href="../../shared/gr-label/gr-label.html">
<link rel="import" href="../../shared/gr-linked-chip/gr-linked-chip.html">
+<link rel="import" href="../../shared/gr-tooltip-content/gr-tooltip-content.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
+<link rel="import" href="../gr-commit-info/gr-commit-info.html">
<link rel="import" href="../gr-reviewer-list/gr-reviewer-list.html">
<dom-module id="gr-change-metadata">
@@ -127,6 +129,20 @@
#externalStyle {
display: block;
}
+ .parentList.merge {
+ list-style-type: decimal;
+ padding-left: 1em;
+ }
+ .parentList gr-commit-info {
+ display: inline-block;
+ }
+ #parentNotCurrentMessage {
+ display: none;
+ }
+ .parentList.notCurrent.nonMerge #parentNotCurrentMessage {
+ --arrow-color: #ffa62f;
+ display: inline-block;
+ }
@media screen and (max-width: 50em), screen and (min-width: 75em) {
:host {
display: table;
@@ -225,6 +241,26 @@
<a href$="[[_computeBranchURL(change.project, change.branch)]]">[[change.branch]]</a>
</span>
</section>
+ <section>
+ <span class="title">[[_computeParentsLabel(_currentParents)]]</span>
+ <span class="value">
+ <ol class$="[[_computeParentListClass(_currentParents, parentIsCurrent)]]">
+ <template is="dom-repeat" items="[[_currentParents]]" as="parent">
+ <li>
+ <gr-commit-info
+ change="[[change]]"
+ commit-info="[[parent]]"
+ server-config="[[serverConfig]]"></gr-commit-info>
+ <gr-tooltip-content
+ id="parentNotCurrentMessage"
+ has-tooltip
+ show-icon
+ title$="[[_notCurrentMessage]]"></gr-tooltip-content>
+ </li>
+ </template>
+ </ol>
+ </span>
+ </section>
<section class="topic">
<span class="title">Topic</span>
<span class="value">
@@ -244,6 +280,7 @@
<gr-editable-label
label-text="Add a topic"
value="[[change.topic]]"
+ max-length="1024"
placeholder="[[_computeTopicPlaceholder(_topicReadOnly)]]"
read-only="[[_topicReadOnly]]"
on-changed="_handleTopicChanged"></gr-editable-label>
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
index fb5771c..ddee577 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
@@ -25,6 +25,8 @@
CHERRY_PICK: 'Cherry Pick',
};
+ const NOT_CURRENT_MESSAGE = 'Not current - rebase possible';
+
Polymer({
is: 'gr-change-metadata',
@@ -46,6 +48,12 @@
* @type {{ note_db_enabled: string }}
*/
serverConfig: Object,
+ parentIsCurrent: Boolean,
+ _notCurrentMessage: {
+ type: String,
+ value: NOT_CURRENT_MESSAGE,
+ readOnly: true,
+ },
_topicReadOnly: {
type: Boolean,
computed: '_computeTopicReadOnly(mutable, change)',
@@ -74,6 +82,11 @@
type: Boolean,
value: false,
},
+
+ _currentParents: {
+ type: Array,
+ computed: '_computeParents(change)',
+ },
},
behaviors: [
@@ -406,5 +419,26 @@
return rev.uploader;
},
+
+ _computeParents(change) {
+ if (!change.current_revision ||
+ !change.revisions[change.current_revision] ||
+ !change.revisions[change.current_revision].commit) {
+ return undefined;
+ }
+ return change.revisions[change.current_revision].commit.parents;
+ },
+
+ _computeParentsLabel(parents) {
+ return parents.length > 1 ? 'Parents' : 'Parent';
+ },
+
+ _computeParentListClass(parents, parentIsCurrent) {
+ return [
+ 'parentList',
+ parents.length > 1 ? 'merge' : 'nonMerge',
+ parentIsCurrent ? 'current' : 'notCurrent',
+ ].join(' ');
+ },
});
})();
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 53e896b..9ee09ea 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
@@ -318,6 +318,36 @@
assert.equal(actual, '');
});
+ test('_computeParents', () => {
+ const parents = [{commit: '123', subject: 'abc'}];
+ assert.isUndefined(element._computeParents(
+ {revisions: {456: {commit: {parents}}}}));
+ assert.isUndefined(element._computeParents(
+ {current_revision: '789', revisions: {456: {commit: {parents}}}}));
+ assert.equal(element._computeParents(
+ {current_revision: '456', revisions: {456: {commit: {parents}}}}),
+ parents);
+ });
+
+ test('_computeParentsLabel', () => {
+ const parent = {commit: 'abc123', subject: 'My parent commit'};
+ assert.equal(element._computeParentsLabel([parent]), 'Parent');
+ assert.equal(element._computeParentsLabel([parent, parent]),
+ 'Parents');
+ });
+
+ test('_computeParentListClass', () => {
+ const parent = {commit: 'abc123', subject: 'My parent commit'};
+ assert.equal(element._computeParentListClass([parent], true),
+ 'parentList nonMerge current');
+ assert.equal(element._computeParentListClass([parent], false),
+ 'parentList nonMerge notCurrent');
+ assert.equal(element._computeParentListClass([parent, parent], false),
+ 'parentList merge notCurrent');
+ assert.equal(element._computeParentListClass([parent, parent], true),
+ 'parentList merge current');
+ });
+
test('_showAddTopic', () => {
assert.isTrue(element._showAddTopic(null, false));
assert.isTrue(element._showAddTopic({base: {topic: null}}, false));
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 3bfde5c..7e661c7 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
@@ -104,7 +104,7 @@
https://github.com/Polymer/polymer/issues/2531 */
.container section.changeInfo {
display: flex;
- padding: 1em var(--default-horizontal-margin);
+ padding: 0 var(--default-horizontal-margin);
}
.changeId {
color: #666;
@@ -116,7 +116,9 @@
padding-right: 1em;
}
.changeMetadata {
+ border-right: 1px solid #ddd;
font-size: .95em;
+ padding: 1em 0;
}
/* Prevent plugin text from overflowing. */
#change_plugins {
@@ -164,6 +166,7 @@
.relatedChanges {
flex: 1 1 auto;
overflow: hidden;
+ padding: 1em 0;
}
.mobile {
display: none;
@@ -193,6 +196,7 @@
display: flex;
flex-direction: column;
flex-shrink: 0;
+ margin: 1em 0;
}
.collapseToggleContainer {
display: flex;
@@ -366,6 +370,7 @@
server-config="[[_serverConfig]]"
missing-labels="[[_missingLabels]]"
mutable="[[_loggedIn]]"
+ parent-is-current="[[!_rebaseOriginallyEnabled]]"
on-show-reply-dialog="_handleShowReplyDialog">
</gr-change-metadata>
<!-- Plugins insert content into following container.
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
index 9cf029b..fd9a490 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
@@ -224,6 +224,7 @@
value: false,
observer: '_updateToggleContainerClass',
},
+ _rebaseOriginallyEnabled: Boolean,
},
behaviors: [
@@ -937,6 +938,7 @@
if (revisionActions && revisionActions.rebase) {
revisionActions.rebase.rebaseOnCurrent =
!!revisionActions.rebase.enabled;
+ this._rebaseOriginallyEnabled = !!revisionActions.rebase.enabled;
revisionActions.rebase.enabled = true;
}
return revisionActions;
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.html b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.html
index 3189cd4..c92919d 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.html
@@ -118,15 +118,6 @@
.fileViewActions > *:not(:last-child) {
margin-right: 5px;
}
- .separator {
- background-color: rgba(0, 0, 0, .3);
- height: 20px;
- margin: 0 8px;
- width: 1px;
- }
- .separator.transparent {
- background-color: transparent;
- }
.editLoaded .hideOnEdit {
display: none;
}
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.html b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.html
index 3c09797..df9ce54 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.html
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.html
@@ -15,6 +15,7 @@
-->
<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../../bower_components/paper-toggle-button/paper-toggle-button.html">
<link rel="import" href="../../core/gr-reporting/gr-reporting.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../gr-message/gr-message.html">
@@ -57,15 +58,6 @@
#messageControlsContainer gr-button {
padding: 0.4em 0;
}
- .separator {
- background-color: rgba(0, 0, 0, .3);
- height: 1.5em;
- margin: 0 .6em;
- width: 1px;
- }
- .separator.transparent {
- background-color: transparent;
- }
.container {
align-items: center;
display: flex;
@@ -78,12 +70,9 @@
id="automatedMessageToggleContainer"
class="container"
hidden$="[[!_hasAutomatedMessages(messages)]]">
- <gr-button
+ <paper-toggle-button
id="automatedMessageToggle"
- link
- on-tap="_handleAutomatedMessageToggleTap">
- [[_computeAutomatedToggleText(_hideAutomated)]]
- </gr-button>
+ checked="{{_hideAutomated}}"></paper-toggle-button>Only comments
<span class="transparent separator"></span>
</span>
<gr-button
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js
index e2c62f3..9ccadf7 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js
@@ -175,12 +175,6 @@
this.handleExpandCollapse(!this._expanded);
},
- _handleAutomatedMessageToggleTap(e) {
- e.preventDefault();
-
- this._hideAutomated = !this._hideAutomated;
- },
-
_handleScrollTo(e) {
this.scrollToMessage(e.detail.message.id);
},
@@ -199,10 +193,6 @@
return expanded ? 'Collapse all' : 'Expand all';
},
- _computeAutomatedToggleText(hideAutomated) {
- return hideAutomated ? 'Show all messages' : 'Show comments only';
- },
-
/**
* Computes message author's file comments for change's message.
* Method uses this.messages to find next message and relies on messages
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 d0ed22f..453d97a 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
@@ -484,7 +484,7 @@
assert.isFalse(!!allHiddenMessageEls.length);
});
- test('autogenerated messages hidden after hide button tap', () => {
+ test('autogenerated messages hidden after comments only toggle', () => {
let allHiddenMessageEls = getHiddenMessages();
element._hideAutomated = false;
@@ -497,16 +497,17 @@
assert.equal(allHiddenMessageEls.length, allMessageEls.length);
});
- test('autogenerated messages not hidden after show button tap', () => {
- let allHiddenMessageEls = getHiddenMessages();
+ test('autogenerated messages not hidden after comments only toggle',
+ () => {
+ let allHiddenMessageEls = getHiddenMessages();
- element._hideAutomated = true;
- MockInteractions.tap(element.$.automatedMessageToggle);
- allHiddenMessageEls = getHiddenMessages();
+ element._hideAutomated = true;
+ MockInteractions.tap(element.$.automatedMessageToggle);
+ allHiddenMessageEls = getHiddenMessages();
- // Autogenerated messages are now hidden.
- assert.isFalse(!!allHiddenMessageEls.length);
- });
+ // Autogenerated messages are now hidden.
+ assert.isFalse(!!allHiddenMessageEls.length);
+ });
test('_getDelta', () => {
let messages = [randomMessage()];
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
index 42c6147..46b803f 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
@@ -266,7 +266,7 @@
<span
id="notLatestLabel"
hidden$="[[!_isState(knownLatestState, 'not-latest')]]">
- Patch [[patchNum]] is not latest.
+ [[_computePatchSetWarning(patchNum, _labelsChanged)]]
<gr-button link on-tap="_reload">Reload</gr-button>
</span>
</div>
@@ -279,7 +279,7 @@
<gr-button
link
primary
- disabled="[[_computeSendButtonDisabled(knownLatestState, _sendButtonLabel, diffDrafts, draft, _reviewersMutated, _labelsChanged, _includeComments)]]"
+ disabled="[[_computeSendButtonDisabled(_sendButtonLabel, diffDrafts, draft, _reviewersMutated, _labelsChanged, _includeComments)]]"
class="action send"
has-tooltip
title$="[[_computeSendButtonTooltip(canBeStarted)]]"
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
index 1a14ae1..35bdfb0 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
@@ -406,12 +406,6 @@
},
send(includeComments, startReview) {
- if (this.knownLatestState === 'not-latest') {
- this.fire('show-alert',
- {message: 'Cannot reply to non-latest patch.'});
- return Promise.resolve({});
- }
-
const labels = this.$.labelScores.getLabelValues();
const obj = {
@@ -835,16 +829,21 @@
return savingComments ? 'saving' : '';
},
- _computeSendButtonDisabled(knownLatestState, buttonLabel, drafts, text,
- reviewersMutated, labelsChanged, includeComments) {
- if (this._isState(knownLatestState, LatestPatchState.NOT_LATEST)) {
- return true;
- }
+ _computeSendButtonDisabled(buttonLabel, drafts, text, reviewersMutated,
+ labelsChanged, includeComments) {
if (buttonLabel === ButtonLabels.START_REVIEW) {
return false;
}
const hasDrafts = includeComments && Object.keys(drafts).length;
return !hasDrafts && !text.length && !reviewersMutated && !labelsChanged;
},
+
+ _computePatchSetWarning(patchNum, labelsChanged) {
+ let str = `Patch ${patchNum} is not latest.`;
+ if (labelsChanged) {
+ str += ' Voting on a non-latest patch will have no effect.';
+ }
+ return str;
+ },
});
})();
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 1b88ec0..8d91ef8 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
@@ -1083,21 +1083,20 @@
test('_computeSendButtonDisabled', () => {
const fn = element._computeSendButtonDisabled.bind(element);
- assert.isTrue(fn('not-latest'));
- assert.isFalse(fn('latest', 'Start review'));
- assert.isTrue(fn('latest', 'Send', {}, '', false, false, false));
+ assert.isFalse(fn('Start review'));
+ assert.isTrue(fn('Send', {}, '', false, false, false));
// Mock nonempty comment draft array, with seding comments.
- assert.isFalse(fn('latest', 'Send', {file: ['draft']}, '', false, false,
+ assert.isFalse(fn('Send', {file: ['draft']}, '', false, false,
true));
// Mock nonempty comment draft array, without seding comments.
- assert.isTrue(fn('latest', 'Send', {file: ['draft']}, '', false, false,
+ assert.isTrue(fn('Send', {file: ['draft']}, '', false, false,
false));
// Mock nonempty change message.
- assert.isFalse(fn('latest', 'Send', {}, 'test', false, false, false));
+ assert.isFalse(fn('Send', {}, 'test', false, false, false));
// Mock reviewers mutated.
- assert.isFalse(fn('latest', 'Send', {}, '', true, false, false));
+ assert.isFalse(fn('Send', {}, '', true, false, false));
// Mock labels changed.
- assert.isFalse(fn('latest', 'Send', {}, '', false, true, false));
+ assert.isFalse(fn('Send', {}, '', false, true, false));
});
});
</script>
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router.js b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
index af2b9e6..dc264f2 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.js
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
@@ -21,7 +21,8 @@
CUSTOM_DASHBOARD: /^\/dashboard\/?$/,
PROJECT_DASHBOARD: /^\/p\/(.+)\/\+\/dashboard\/(.+)/,
- AGREEMENTS: /^\/settings\/(agreements|new-agreement)/,
+ AGREEMENTS: /^\/settings\/agreements\/?/,
+ NEW_AGREEMENTS: /^\/settings\/new-agreement\/?/,
REGISTER: /^\/register(\/.*)?$/,
// Pattern for login and logout URLs intended to be passed-through. May
@@ -771,6 +772,9 @@
this._mapRoute(RoutePattern.AGREEMENTS, '_handleAgreementsRoute', true);
+ this._mapRoute(RoutePattern.NEW_AGREEMENTS, '_handleNewAgreementsRoute',
+ true);
+
this._mapRoute(RoutePattern.SETTINGS_LEGACY,
'_handleSettingsLegacyRoute', true);
@@ -1032,7 +1036,6 @@
this._setParams({
view: Gerrit.Nav.View.REPO,
detail: Gerrit.Nav.RepoDetailView.BRANCHES,
- detailType: 'branches',
repo: data.params[0],
offset: data.params[2] || 0,
filter: null,
@@ -1043,7 +1046,6 @@
this._setParams({
view: Gerrit.Nav.View.REPO,
detail: Gerrit.Nav.RepoDetailView.BRANCHES,
- detailType: 'branches',
repo: data.params.repo,
offset: data.params.offset,
filter: data.params.filter,
@@ -1054,7 +1056,6 @@
this._setParams({
view: Gerrit.Nav.View.REPO,
detail: Gerrit.Nav.RepoDetailView.BRANCHES,
- detailType: 'branches',
repo: data.params.repo,
filter: data.params.filter || null,
});
@@ -1064,7 +1065,6 @@
this._setParams({
view: Gerrit.Nav.View.REPO,
detail: Gerrit.Nav.RepoDetailView.TAGS,
- detailType: 'tags',
repo: data.params[0],
offset: data.params[2] || 0,
filter: null,
@@ -1075,7 +1075,6 @@
this._setParams({
view: Gerrit.Nav.View.REPO,
detail: Gerrit.Nav.RepoDetailView.TAGS,
- detailType: 'tags',
repo: data.params.repo,
offset: data.params.offset,
filter: data.params.filter,
@@ -1086,7 +1085,6 @@
this._setParams({
view: Gerrit.Nav.View.REPO,
detail: Gerrit.Nav.RepoDetailView.TAGS,
- detailType: 'tags',
repo: data.params.repo,
filter: data.params.filter || null,
});
@@ -1272,7 +1270,13 @@
}
},
+ // TODO fix this so it properly redirects
+ // to /settings#Agreements (Scrolls down)
_handleAgreementsRoute(data) {
+ this._redirect('/settings/#Agreements');
+ },
+
+ _handleNewAgreementsRoute(data) {
data.params.view = Gerrit.Nav.View.AGREEMENTS;
this._setParams(data.params);
},
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
index ae002af..7011c65 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
@@ -134,6 +134,7 @@
'_handleGroupListOffsetRoute',
'_handleGroupMembersRoute',
'_handleGroupRoute',
+ '_handleNewAgreementsRoute',
'_handlePluginListFilterOffsetRoute',
'_handlePluginListFilterRoute',
'_handlePluginListOffsetRoute',
@@ -532,7 +533,14 @@
});
test('_handleAgreementsRoute', () => {
- element._handleAgreementsRoute({params: {}});
+ const data = {params: {}};
+ element._handleAgreementsRoute(data);
+ assert.isTrue(redirectStub.calledOnce);
+ assert.equal(redirectStub.lastCall.args[0], '/settings/#Agreements');
+ });
+
+ test('_handleNewAgreementsRoute', () => {
+ element._handleNewAgreementsRoute({params: {}});
assert.isTrue(setParamsStub.calledOnce);
assert.equal(setParamsStub.lastCall.args[0].view,
Gerrit.Nav.View.AGREEMENTS);
@@ -956,7 +964,6 @@
assertDataToParams(data, '_handleBranchListOffsetRoute', {
view: Gerrit.Nav.View.REPO,
detail: Gerrit.Nav.RepoDetailView.BRANCHES,
- detailType: 'branches',
repo: 4321,
offset: 0,
filter: null,
@@ -966,7 +973,6 @@
assertDataToParams(data, '_handleBranchListOffsetRoute', {
view: Gerrit.Nav.View.REPO,
detail: Gerrit.Nav.RepoDetailView.BRANCHES,
- detailType: 'branches',
repo: 4321,
offset: 42,
filter: null,
@@ -978,7 +984,6 @@
assertDataToParams(data, '_handleBranchListFilterOffsetRoute', {
view: Gerrit.Nav.View.REPO,
detail: Gerrit.Nav.RepoDetailView.BRANCHES,
- detailType: 'branches',
repo: 4321,
offset: 42,
filter: 'foo',
@@ -990,7 +995,6 @@
assertDataToParams(data, '_handleBranchListFilterRoute', {
view: Gerrit.Nav.View.REPO,
detail: Gerrit.Nav.RepoDetailView.BRANCHES,
- detailType: 'branches',
repo: 4321,
filter: 'foo',
});
@@ -1003,7 +1007,6 @@
assertDataToParams(data, '_handleTagListOffsetRoute', {
view: Gerrit.Nav.View.REPO,
detail: Gerrit.Nav.RepoDetailView.TAGS,
- detailType: 'tags',
repo: 4321,
offset: 0,
filter: null,
@@ -1015,7 +1018,6 @@
assertDataToParams(data, '_handleTagListFilterOffsetRoute', {
view: Gerrit.Nav.View.REPO,
detail: Gerrit.Nav.RepoDetailView.TAGS,
- detailType: 'tags',
repo: 4321,
offset: 42,
filter: 'foo',
@@ -1027,7 +1029,6 @@
assertDataToParams(data, '_handleTagListFilterRoute', {
view: Gerrit.Nav.View.REPO,
detail: Gerrit.Nav.RepoDetailView.TAGS,
- detailType: 'tags',
repo: 4321,
filter: null,
});
@@ -1036,7 +1037,6 @@
assertDataToParams(data, '_handleTagListFilterRoute', {
view: Gerrit.Nav.View.REPO,
detail: Gerrit.Nav.RepoDetailView.TAGS,
- detailType: 'tags',
repo: 4321,
filter: 'foo',
});
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js
index 1ca745e..a6f27489 100644
--- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js
+++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js
@@ -19,6 +19,7 @@
'added:',
'age:',
'age:1week', // Give an example age
+ 'assignee:',
'author:',
'branch:',
'bug:',
@@ -44,6 +45,7 @@
'intopic:',
'is:',
'is:abandoned',
+ 'is:assigned',
'is:closed',
'is:ignored',
'is:mergeable',
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences.html b/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences.html
index 7e6d54d..ab2077d 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences.html
@@ -148,7 +148,7 @@
<gr-button id="saveButton" link primary on-tap="_handleSave">
Save</gr-button>
</div>
- </overlay>
+ </gr-overlay>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
<gr-storage id="storage"></gr-storage>
</template>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
index e5f525e..0952ebd 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
@@ -73,8 +73,13 @@
color: #999;
}
.navLinks {
+ align-items: center;
+ display: flex;
white-space: nowrap;
}
+ .navLink {
+ padding: 0 .25em;
+ }
.reviewed {
display: inline-block;
margin: 0 .25em;
@@ -107,9 +112,6 @@
.prefsButton {
text-align: right;
}
- .separator {
- margin: 0 .25em;
- }
.noOverflow {
display: block;
overflow: auto;
@@ -120,8 +122,12 @@
.blameLoader {
display: none;
}
- .blameLoader.show {
- display: inline;
+ .blameLoader.show,
+ .download,
+ .preferences,
+ .rightControls {
+ align-items: center;
+ display: flex;
}
gr-dropdown-list {
--trigger-style: {
@@ -211,11 +217,11 @@
<a class="navLink"
href$="[[_computeNavLinkURL(_change, _path, _fileList, -1, 1)]]">
Prev</a>
- /
+ <span class="separator"></span>
<a class="navLink"
href$="[[_computeChangePath(_change, _patchRange.*, _change.revisions)]]">
Up</a>
- /
+ <span class="separator"></span>
<a class="navLink"
href$="[[_computeNavLinkURL(_change, _path, _fileList, 1, 1)]]">
Next</a>
@@ -236,7 +242,7 @@
on-patch-range-change="_handlePatchChange">
</gr-patch-range-select>
<span class="download desktop">
- <span class="separator">/</span>
+ <span class="separator"></span>
<a
class="downloadLink"
download
@@ -245,7 +251,7 @@
</a>
</span>
</div>
- <div>
+ <div class="rightControls">
<gr-select
id="modeSelect"
bind-value="{{changeViewState.diffMode}}"
@@ -258,15 +264,14 @@
<span id="diffPrefsContainer"
hidden$="[[_computePrefsButtonHidden(_prefs, _loggedIn)]]" hidden>
<span class="preferences desktop">
- <span
- hidden$="[[_computeModeSelectHidden(_isImageDiff)]]">/</span>
+ <span class="separator" hidden$="[[_computeModeSelectHidden(_isImageDiff)]]"></span>
<gr-button link
class="prefsButton"
on-tap="_handlePrefsTap">Preferences</gr-button>
</span>
</span>
<span class$="blameLoader [[_computeBlameLoaderClass(_isImageDiff, _isBlameSupported)]]">
- <span class="separator">/</span>
+ <span class="separator"></span>
<gr-button
link
disabled="[[_isBlameLoading]]"
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
index 8c2954b..de9905a 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
@@ -307,17 +307,30 @@
},
_moveToPreviousFileWithComment() {
- if (this._commentSkips && this._commentSkips.previous) {
- Gerrit.Nav.navigateToDiff(this._change, this._commentSkips.previous,
- this._patchRange.patchNum, this._patchRange.basePatchNum);
+ if (!this._commentSkips) { return; }
+
+ // If there is no previous diff with comments, then return to the change
+ // view.
+ if (!this._commentSkips.previous) {
+ this._navToChangeView();
+ return;
}
+
+ Gerrit.Nav.navigateToDiff(this._change, this._commentSkips.previous,
+ this._patchRange.patchNum, this._patchRange.basePatchNum);
},
_moveToNextFileWithComment() {
- if (this._commentSkips && this._commentSkips.next) {
- Gerrit.Nav.navigateToDiff(this._change, this._commentSkips.next,
- this._patchRange.patchNum, this._patchRange.basePatchNum);
+ if (!this._commentSkips) { return; }
+
+ // If there is no next diff with comments, then return to the change view.
+ if (!this._commentSkips.next) {
+ this._navToChangeView();
+ return;
}
+
+ Gerrit.Nav.navigateToDiff(this._change, this._commentSkips.next,
+ this._patchRange.patchNum, this._patchRange.basePatchNum);
},
_handleCKey(e) {
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 8fe8c40..979f253 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
@@ -769,6 +769,88 @@
assert.equal(result.previous, fileList[1]);
assert.isNull(result.next);
});
+
+ suite('skip next/previous', () => {
+ let navToChangeStub;
+ let navToDiffStub;
+
+ setup(() => {
+ navToChangeStub = sandbox.stub(element, '_navToChangeView');
+ navToDiffStub = sandbox.stub(Gerrit.Nav, 'navigateToDiff');
+ element._fileList = [
+ 'path/one.jpg', 'path/two.m4v', 'path/three.wav',
+ ];
+ element._patchRange = {patchNum: '2', basePatchNum: '1'};
+ });
+
+ suite('_moveToPreviousFileWithComment', () => {
+ test('no skips', () => {
+ element._moveToPreviousFileWithComment();
+ assert.isFalse(navToChangeStub.called);
+ assert.isFalse(navToDiffStub.called);
+ });
+
+ test('no previous', () => {
+ const commentMap = {};
+ commentMap[element._fileList[0]] = false;
+ commentMap[element._fileList[1]] = false;
+ commentMap[element._fileList[2]] = true;
+ element._commentMap = commentMap;
+ element._path = element._fileList[1];
+
+ element._moveToPreviousFileWithComment();
+ assert.isTrue(navToChangeStub.calledOnce);
+ assert.isFalse(navToDiffStub.called);
+ });
+
+ test('w/ previous', () => {
+ const commentMap = {};
+ commentMap[element._fileList[0]] = true;
+ commentMap[element._fileList[1]] = false;
+ commentMap[element._fileList[2]] = true;
+ element._commentMap = commentMap;
+ element._path = element._fileList[1];
+
+ element._moveToPreviousFileWithComment();
+ assert.isFalse(navToChangeStub.called);
+ assert.isTrue(navToDiffStub.calledOnce);
+ });
+ });
+
+ suite('_moveToNextFileWithComment', () => {
+ test('no skips', () => {
+ element._moveToNextFileWithComment();
+ assert.isFalse(navToChangeStub.called);
+ assert.isFalse(navToDiffStub.called);
+ });
+
+ test('no previous', () => {
+ const commentMap = {};
+ commentMap[element._fileList[0]] = true;
+ commentMap[element._fileList[1]] = false;
+ commentMap[element._fileList[2]] = false;
+ element._commentMap = commentMap;
+ element._path = element._fileList[1];
+
+ element._moveToNextFileWithComment();
+ assert.isTrue(navToChangeStub.calledOnce);
+ assert.isFalse(navToDiffStub.called);
+ });
+
+ test('w/ previous', () => {
+ const commentMap = {};
+ commentMap[element._fileList[0]] = true;
+ commentMap[element._fileList[1]] = false;
+ commentMap[element._fileList[2]] = true;
+ element._commentMap = commentMap;
+ element._path = element._fileList[1];
+
+ element._moveToNextFileWithComment();
+ assert.isFalse(navToChangeStub.called);
+ assert.isTrue(navToDiffStub.calledOnce);
+ });
+ });
+ });
});
test('_computeEditLoaded', () => {
diff --git a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.js b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.js
index cca723e..ce0cd21 100644
--- a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.js
+++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.js
@@ -141,6 +141,7 @@
this._newContent).then(res => {
this._saving = false;
this._showAlert(res.ok ? SAVED_MESSAGE : SAVE_FAILED_MSG);
+ if (res.ok) { this._content = this._newContent; }
});
},
diff --git a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.html b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.html
index d00dcc3..3cd5608 100644
--- a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.html
+++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.html
@@ -167,6 +167,7 @@
[mockParams.changeNum, mockParams.path, newText]);
assert.isFalse(navigateStub.called);
assert.isFalse(element.$.save.hasAttribute('disabled'));
+ assert.notEqual(element._content, element._newContent);
});
});
@@ -191,7 +192,8 @@
assert.isFalse(element._saving);
assert.equal(alertStub.lastCall.args[0], 'All changes saved');
assert.isFalse(navigateStub.called);
- assert.isFalse(element.$.save.hasAttribute('disabled'));
+ assert.isTrue(element.$.save.hasAttribute('disabled'));
+ assert.equal(element._content, element._newContent);
});
});
diff --git a/polygerrit-ui/app/elements/gr-app.html b/polygerrit-ui/app/elements/gr-app.html
index 95cddab..ef93794 100644
--- a/polygerrit-ui/app/elements/gr-app.html
+++ b/polygerrit-ui/app/elements/gr-app.html
@@ -179,7 +179,7 @@
</gr-endpoint-decorator>
</template>
<template is="dom-if" if="[[_showCLAView]]" restamp="true">
- <gr-cla-view path="[[_path]]"></gr-cla-view>
+ <gr-cla-view></gr-cla-view>
</template>
<div id="errorView" class="errorView">
<div class="errorEmoji">[[_lastError.emoji]]</div>
@@ -211,6 +211,7 @@
</gr-overlay>
<gr-overlay id="registration" with-backdrop>
<gr-registration-dialog
+ settings-url="[[_settingsUrl]]"
on-account-detail-update="_handleAccountDetailUpdate"
on-close="_handleRegistrationDialogClose">
</gr-registration-dialog>
diff --git a/polygerrit-ui/app/elements/gr-app.js b/polygerrit-ui/app/elements/gr-app.js
index ad5c62e..e98aaac 100644
--- a/polygerrit-ui/app/elements/gr-app.js
+++ b/polygerrit-ui/app/elements/gr-app.js
@@ -84,6 +84,7 @@
type: String,
computed: '_computePluginScreenName(params)',
},
+ _settingsUrl: String,
},
listeners: {
@@ -122,6 +123,10 @@
this._version = version;
});
+ // Note: this is evaluated here to ensure that it only happens after the
+ // router has been initialized. @see Issue 7837
+ this._settingsUrl = Gerrit.Nav.getUrlForSettings();
+
this.$.reporting.appStarted();
this._viewState = {
changeView: {
@@ -150,6 +155,7 @@
// Preferences are cached when a user is logged in; warm them.
this.$.restAPI.getPreferences();
this.$.restAPI.getDiffPreferences();
+ this.$.restAPI.getEditPreferences();
this.$.errorManager.knownAccountId =
this._account && this._account._account_id || null;
},
diff --git a/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.html b/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.html
index c665df4..307a2b4 100644
--- a/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.html
+++ b/polygerrit-ui/app/elements/settings/gr-agreements-list/gr-agreements-list.html
@@ -53,8 +53,7 @@
</template>
</tbody>
</table>
- <!-- TODO: Renable this when supported in polygerrit -->
- <!-- <a href$="[[getUrl()]]">New Contributor Agreement</a> -->
+ <a href$="[[getUrl()]]">New Contributor Agreement</a>
</div>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
</template>
diff --git a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.html b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.html
index b667d66..a1f5dc5 100644
--- a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.html
+++ b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.html
@@ -1,5 +1,5 @@
<!--
-Copyright (C) 2017 The Android Open Source Project
+Copyright (C) 2018 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.
@@ -14,12 +14,94 @@
limitations under the License.
-->
+<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
+<link rel="import" href="../../../bower_components/iron-input/iron-input.html">
<link rel="import" href="../../../bower_components/polymer/polymer.html">
-<link rel="import" href="../../shared/gr-placeholder/gr-placeholder.html">
+<link rel="import" href="../../../styles/gr-form-styles.html">
+<link rel="import" href="../../../styles/shared-styles.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-cla-view">
<template>
- <gr-placeholder title="Agreements" path="[[path]]"></gr-placeholder>
+ <style include="shared-styles">
+ h1 {
+ margin-bottom: .6em;
+ }
+ h3 {
+ margin-bottom: .5em;
+ }
+ .agreementsUrl {
+ border: 0.1em solid #b0bdcc;
+ margin-bottom: 1.25em;
+ margin-left: 1.25em;
+ margin-right: 1.25em;
+ padding: 0.3em;
+ }
+ #claNewAgreementsLabel {
+ font-family: var(--font-family-bold);
+ }
+ #claNewAgreement {
+ display: none;
+ }
+ #claNewAgreement.show {
+ display: block;
+ }
+ .contributorAgreementButton {
+ font-family: var(--font-family-bold);
+ }
+ .contributorAgreementAlreadySubmitted {
+ color: red;
+ margin: 0 2em;
+ padding: .5em;
+ }
+ .agreementsSubmitted,
+ .hideAgreementsTextBox {
+ display: none;
+ }
+ main {
+ margin: 2em auto;
+ max-width: 50em;
+ }
+ </style>
+ <style include="gr-form-styles"></style>
+ <main>
+ <h1>New Contributor Agreement</h1>
+ <h3>Select an agreement type:</h3>
+ <template is="dom-repeat" items="[[_serverConfig.auth.contributor_agreements]]">
+ <span class="contributorAgreementButton">
+ <input id$="claNewAgreementsInput[[item.name]]"
+ name="claNewAgreementsRadio"
+ type="radio"
+ data-name$="[[item.name]]"
+ data-url$="[[item.url]]"
+ on-tap="_handleShowAgreement"
+ disabled$="[[_disableAggreements(item, _groups)]]">
+ <label id="claNewAgreementsLabel">[[item.name]]</label>
+ </span>
+ <div class$="contributorAgreementAlreadySubmitted [[_hideAggreements(item, _groups)]]">
+ Agreement already submitted.
+ </div>
+ <div class="agreementsUrl">
+ [[item.description]]
+ </div>
+ </template>
+ <div id="claNewAgreement" class$="[[_computeShowAgreementsClass(_showAgreements)]]">
+ <h3 class="smallHeading">Review the agreement:</h3>
+ <div id="agreementsUrl" class="agreementsUrl">
+ <a href$="[[_agreementsUrl]]" target="blank" rel="noopener">
+ Please review the agreement.</a>
+ </div>
+ <div class$="agreementsTextBox [[_computeHideAgreementClass(_agreementName, _serverConfig.auth.contributor_agreements)]]">
+ <h3 class="smallHeading">Complete the agreement:</h3>
+ <input id="input-agreements" is="iron-input" bind-value="{{_agreementsText}}" placeholder="Enter 'I agree' here" />
+ <gr-button on-tap="_handleSaveAgreements" disabled="[[_disableAgreementsText(_agreementsText)]]">
+ Submit
+ </gr-button>
+ </div>
+ </div>
+ </main>
+ <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
</template>
<script src="gr-cla-view.js"></script>
-</dom-module>
+</dom-module>
\ No newline at end of file
diff --git a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.js b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.js
index 71dc71b..39400c64 100644
--- a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.js
+++ b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.js
@@ -1,4 +1,4 @@
-// Copyright (C) 2017 The Android Open Source Project
+// Copyright (C) 2018 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.
@@ -18,7 +18,121 @@
is: 'gr-cla-view',
properties: {
- path: String,
+ _groups: Object,
+ /** @type {?} */
+ _serverConfig: Object,
+ _agreementsText: String,
+ _agreementName: String,
+ _showAgreements: {
+ type: Boolean,
+ value: false,
+ },
+ _agreementsUrl: String,
+ },
+
+ behaviors: [
+ Gerrit.BaseUrlBehavior,
+ ],
+
+ attached() {
+ this.loadData();
+
+ this.fire('title-change', {title: 'New Contributor Agreement'});
+ },
+
+ loadData() {
+ const promises = [];
+ promises.push(this.$.restAPI.getConfig(true).then(config => {
+ this._serverConfig = config;
+ }));
+
+ promises.push(this.$.restAPI.getAccountGroups().then(groups => {
+ this._groups = groups.sort((a, b) => {
+ return a.name.localeCompare(b.name);
+ });
+ }));
+
+ return Promise.all(promises);
+ },
+
+ _getAgreementsUrl(configUrl) {
+ let url;
+ if (!configUrl) { return ''; }
+ if (configUrl.startsWith('http:') || configUrl.startsWith('https:')) {
+ url = configUrl;
+ } else {
+ url = this.getBaseUrl() + '/' + configUrl;
+ }
+
+ return url;
+ },
+
+ _handleShowAgreement(e) {
+ this._agreementName = e.target.getAttribute('data-name');
+ this._agreementsUrl =
+ this._getAgreementsUrl(e.target.getAttribute('data-url'));
+ this._showAgreements = true;
+ },
+
+ _handleSaveAgreements(e) {
+ this._createToast('Agreement saving...');
+
+ const name = this._agreementName;
+ return this.$.restAPI.saveAccountAgreement({name}).then(res => {
+ let message = 'Agreement failed to be submitted, please try again';
+ if (res.status === 200) {
+ message = 'Agreement has been successfully submited.';
+ }
+ this._createToast(message);
+ this.loadData();
+ this._agreementsText = '';
+ this._showAgreements = false;
+ });
+ },
+
+ _createToast(message) {
+ this.dispatchEvent(new CustomEvent('show-alert',
+ {detail: {message}, bubbles: true}));
+ },
+
+ _computeShowAgreementsClass(agreements) {
+ return agreements ? 'show' : '';
+ },
+
+ _disableAggreements(item, groups) {
+ for (const value of groups) {
+ if (item && item.auto_verify_group &&
+ item.auto_verify_group.name === value.name) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ _hideAggreements(item, groups) {
+ return this._disableAggreements(item, groups) ?
+ '' : 'agreementsSubmitted';
+ },
+
+ _disableAgreementsText(text) {
+ return text.toLowerCase() === 'i agree' ? false : true;
+ },
+
+ // This checks for auto_verify_group,
+ // if specified it returns 'hideAgreementsTextBox' which
+ // then hides the text box and submit button.
+ _computeHideAgreementClass(name, config) {
+ for (const key in config) {
+ if (!config.hasOwnProperty(key)) { return; }
+ for (const prop in config[key]) {
+ if (!config[key].hasOwnProperty(prop)) { return; }
+ if (name === config[key].name &&
+ !config[key].auto_verify_group) {
+ return 'hideAgreementsTextBox';
+ }
+ }
+ }
},
});
})();
diff --git a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_test.html b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_test.html
new file mode 100644
index 0000000..985fbfa
--- /dev/null
+++ b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view_test.html
@@ -0,0 +1,183 @@
+<!DOCTYPE html>
+<!--
+Copyright (C) 2018 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-cla-view</title>
+
+<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
+<link rel="import" href="gr-cla-view.html">
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+ <template>
+ <gr-cla-view></gr-cla-view>
+ </template>
+</test-fixture>
+
+<script>
+ suite('gr-cla-view tests', () => {
+ let element;
+ let agreements;
+ const auth = {
+ name: 'Individual',
+ description: 'test-description',
+ url: 'static/cla_individual.html',
+ auto_verify_group: {
+ url: '#/admin/groups/uuid-bc53f2738ef8ad0b3a4f53846ff59b05822caecb',
+ options: {
+ visible_to_all: true,
+ },
+ group_id: 20,
+ owner: 'CLA Accepted - Individual',
+ owner_id: 'bc53f2738ef8ad0b3a4f53846ff59b05822caecb',
+ created_on: '2017-07-31 15:11:04.000000000',
+ id: 'bc53f2738ef8ad0b3a4f53846ff59b05822caecb',
+ name: 'CLA Accepted - Individual',
+ },
+ };
+ const auth2 = {
+ name: 'Individual2',
+ description: 'test-description2',
+ url: 'static/cla_individual2.html',
+ auto_verify_group: {
+ url: '#/admin/groups/uuid-e9aaddc47f305be7661ad4db9b66f9b707bd19a0',
+ options: {},
+ group_id: 21,
+ owner: 'CLA Accepted - Individual2',
+ owner_id: 'e9aaddc47f305be7661ad4db9b66f9b707bd19a0',
+ created_on: '2017-07-31 15:25:42.000000000',
+ id: 'e9aaddc47f305be7661ad4db9b66f9b707bd19a0',
+ name: 'CLA Accepted - Individual2',
+ },
+ };
+ const config = {
+ auth: {
+ use_contributor_agreements: true,
+ contributor_agreements: [
+ {
+ name: 'Individual',
+ description: 'test-description',
+ url: 'static/cla_individual.html',
+ },
+ ],
+ },
+ };
+ const config2 = {
+ auth: {
+ use_contributor_agreements: true,
+ contributor_agreements: [
+ {
+ name: 'Individual2',
+ description: 'test-description2',
+ url: 'static/cla_individual2.html',
+ },
+ ],
+ },
+ };
+ const groups = [
+ {
+ url: 'some url',
+ options: {},
+ description: 'Group 1 description',
+ group_id: 1,
+ owner: 'Administrators',
+ owner_id: '123',
+ id: 'abc',
+ name: 'Individual',
+ },
+ {
+ options: {visible_to_all: true},
+ id: '456',
+ group_id: 2,
+ name: 'Individual 2',
+ },
+ {
+ options: {visible_to_all: true},
+ id: '457',
+ group_id: 3,
+ name: 'CLA Accepted - Individual',
+ },
+ ];
+
+ setup(done => {
+ agreements = [{
+ url: 'test-agreements.html',
+ description: 'Agreements 1 description',
+ name: 'Agreements 1',
+ }];
+
+ stub('gr-rest-api-interface', {
+ getAccountGroups() { return Promise.resolve(agreements); },
+ });
+
+ element = fixture('basic');
+
+ element.loadData().then(() => { flush(done); });
+ });
+
+ test('_disableAggreements equals true', () => {
+ assert.isTrue(element._disableAggreements(auth, groups));
+ });
+
+ test('_disableAggreements equals false', () => {
+ assert.isFalse(element._disableAggreements(auth2, groups));
+ });
+
+ test('_hideAggreements equals string', () => {
+ assert.equal(element._hideAggreements(auth, groups), '');
+ });
+
+ test('_hideAggreements equals agreementsSubmitted', () => {
+ assert.equal(element._hideAggreements(auth2, groups),
+ 'agreementsSubmitted');
+ });
+
+ test('_disableAgreementsText equals true', () => {
+ assert.isFalse(element._disableAgreementsText('I AGREE'));
+ });
+
+ test('_disableAgreementsText equals true', () => {
+ assert.isTrue(element._disableAgreementsText('I DO NOT AGREE'));
+ });
+
+ test('_computeHideAgreementClass returns true', () => {
+ assert.equal(
+ element._computeHideAgreementClass(
+ auth.name, config.auth.contributor_agreements),
+ 'hideAgreementsTextBox');
+ });
+
+ test('_computeHideAgreementClass returns undefined', () => {
+ assert.isUndefined(
+ element._computeHideAgreementClass(
+ auth.name, config2.auth.contributor_agreements));
+ });
+
+ test('_getAgreementsUrl has http', () => {
+ assert.equal(element._getAgreementsUrl(
+ 'http://test.org/test.html'), 'http://test.org/test.html');
+ });
+
+ test('_getAgreementsUrl does not have http://', () => {
+ assert.equal(element._getAgreementsUrl(
+ 'test_cla.html'), '/test_cla.html');
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences.html b/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences.html
new file mode 100644
index 0000000..bbd7396
--- /dev/null
+++ b/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences.html
@@ -0,0 +1,170 @@
+<!--
+Copyright (C) 2018 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../../styles/gr-form-styles.html">
+<link rel="import" href="../../../styles/shared-styles.html">
+<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
+<link rel="import" href="../../shared/gr-select/gr-select.html">
+
+<dom-module id="gr-edit-preferences">
+ <template>
+ <style include="shared-styles"></style>
+ <style include="gr-form-styles"></style>
+ <div id="editPreferences" class="gr-form-styles">
+ <section>
+ <span class="title">Tab width</span>
+ <span class="value">
+ <input
+ is="iron-input"
+ type="number"
+ prevent-invalid-input
+ allowed-pattern="[0-9]"
+ bind-value="{{editPrefs.tab_size}}"
+ on-change="_handleEditPrefsChanged">
+ </span>
+ </section>
+ <section>
+ <span class="title">Columns</span>
+ <span class="value">
+ <input
+ is="iron-input"
+ type="number"
+ prevent-invalid-input
+ allowed-pattern="[0-9]"
+ bind-value="{{editPrefs.line_length}}"
+ on-change="_handleEditPrefsChanged">
+ </span>
+ </section>
+ <section>
+ <span class="title">Indent unit</span>
+ <span class="value">
+ <input
+ is="iron-input"
+ type="number"
+ prevent-invalid-input
+ allowed-pattern="[0-9]"
+ bind-value="{{editPrefs.indent_unit}}"
+ on-change="_handleEditPrefsChanged">
+ </span>
+ </section>
+ <section>
+ <span class="title">Cursor blink rate</span>
+ <span class="value">
+ <input
+ is="iron-input"
+ type="number"
+ prevent-invalid-input
+ allowed-pattern="[0-9]"
+ bind-value="{{editPrefs.cursor_blink_rate}}"
+ on-change="_handleEditPrefsChanged">
+ </span>
+ </section>
+ <section>
+ <span class="title">Top menu</span>
+ <span class="value">
+ <input
+ id="showTopMenu"
+ type="checkbox"
+ checked$="[[editPrefs.hide_top_menu]]"
+ on-change="_handleTopMenuChanged">
+ </span>
+ </section>
+ <section>
+ <span class="title">Syntax highlighting</span>
+ <span class="value">
+ <input
+ id="editSyntaxHighlighting"
+ type="checkbox"
+ checked$="[[editPrefs.syntax_highlighting]]"
+ on-change="_handleEditSyntaxHighlightingChanged">
+ </span>
+ </section>
+ <section>
+ <span class="title">Show tabs</span>
+ <span class="value">
+ <input
+ id="editShowTabs"
+ type="checkbox"
+ checked$="[[editPrefs.show_tabs]]"
+ on-change="_handleEditShowTabsChanged">
+ </span>
+ </section>
+ <section>
+ <span class="title">Whitespace errors</span>
+ <span class="value">
+ <input
+ id="whitespaceErrors"
+ type="checkbox"
+ checked$="[[editPrefs.show_whitespace_errors]]"
+ on-change="_handleWhitespaceErrorsChanged">
+ </span>
+ </section>
+ <section>
+ <span class="title">Line numbers</span>
+ <span class="value">
+ <input
+ id="showLineNumbers"
+ type="checkbox"
+ checked$="[[editPrefs.hide_line_numbers]]"
+ on-change="_handleLineNumbersChanged">
+ </span>
+ </section>
+ <section>
+ <span class="title">Match brackets</span>
+ <span class="value">
+ <input
+ id="showMatchBrackets"
+ type="checkbox"
+ checked$="[[editPrefs.match_brackets]]"
+ on-change="_handleMatchBracketsChanged">
+ </span>
+ </section>
+ <section>
+ <span class="title">Line wrapping</span>
+ <span class="value">
+ <input
+ id="editShowLineWrapping"
+ type="checkbox"
+ checked$="[[editPrefs.line_wrapping]]"
+ on-change="_handleEditLineWrappingChanged">
+ </span>
+ </section>
+ <section>
+ <span class="title">Indent with tabs</span>
+ <span class="value">
+ <input
+ id="showIndentWithTabs"
+ type="checkbox"
+ checked$="[[editPrefs.indent_with_tabs]]"
+ on-change="_handleIndentWithTabsChanged">
+ </span>
+ </section>
+ <section>
+ <span class="title">Auto close brackets</span>
+ <span class="value">
+ <input
+ id="showAutoCloseBrackets"
+ type="checkbox"
+ checked$="[[editPrefs.auto_close_brackets]]"
+ on-change="_handleAutoCloseBracketsChanged">
+ </span>
+ </section>
+ </div>
+ <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
+ </template>
+ <script src="gr-edit-preferences.js"></script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences.js b/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences.js
new file mode 100644
index 0000000..01b45f8
--- /dev/null
+++ b/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences.js
@@ -0,0 +1,105 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+(function() {
+ 'use strict';
+
+ Polymer({
+ is: 'gr-edit-preferences',
+
+ properties: {
+ hasUnsavedChanges: {
+ type: Boolean,
+ notify: true,
+ value: false,
+ },
+
+ /** @type {?} */
+ editPrefs: Object,
+ },
+
+ loadData() {
+ return this.$.restAPI.getEditPreferences().then(prefs => {
+ this.editPrefs = prefs;
+ });
+ },
+
+ _handleEditPrefsChanged() {
+ this.hasUnsavedChanges = true;
+ },
+
+ _handleTopMenuChanged() {
+ this.set('editPrefs.hide_top_menu', this.$.showTopMenu.checked);
+ this._handleEditPrefsChanged();
+ },
+
+ _handleEditSyntaxHighlightingChanged() {
+ this.set('editPrefs.syntax_highlighting',
+ this.$.editSyntaxHighlighting.checked);
+ this._handleEditPrefsChanged();
+ },
+
+ _handleEditShowTabsChanged() {
+ this.set('editPrefs.show_tabs', this.$.editShowTabs.checked);
+ this._handleEditPrefsChanged();
+ },
+
+ _handleWhitespaceErrorsChanged() {
+ this.set('editPrefs.show_whitespace_errors',
+ this.$.whitespaceErrors.checked);
+ this._handleEditPrefsChanged();
+ },
+
+ _handleLineNumbersChanged() {
+ this.set('editPrefs.hide_line_numbers',
+ this.$.showLineNumbers.checked);
+ this._handleEditPrefsChanged();
+ },
+
+ _handleMatchBracketsChanged() {
+ this.set('editPrefs.match_brackets', this.$.showMatchBrackets.checked);
+ this._handleEditPrefsChanged();
+ },
+
+ _handleEditLineWrappingChanged() {
+ this.set('editPrefs.line_wrapping',
+ this.$.editShowLineWrapping.checked);
+ this._handleEditPrefsChanged();
+ },
+
+ _handleIndentWithTabsChanged() {
+ this.set('editPrefs.indent_with_tabs',
+ this.$.showIndentWithTabs.checked);
+ this._handleEditPrefsChanged();
+ },
+
+ _handleAutoCloseBracketsChanged() {
+ this.set('editPrefs.auto_close_brackets',
+ this.$.showAutoCloseBrackets.checked);
+ this._handleEditPrefsChanged();
+ },
+
+ _handleShowBaseVersionChanged() {
+ this.set('editPrefs.show_base',
+ this.$.showShowBaseVersion.checked);
+ this._handleEditPrefsChanged();
+ },
+
+ save() {
+ return this.$.restAPI.saveEditPreferences(this.editPrefs)
+ .then(() => {
+ this.hasUnsavedChanges = false;
+ });
+ },
+ });
+})();
diff --git a/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences_test.html b/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences_test.html
new file mode 100644
index 0000000..0305c84
--- /dev/null
+++ b/polygerrit-ui/app/elements/settings/gr-edit-preferences/gr-edit-preferences_test.html
@@ -0,0 +1,135 @@
+<!DOCTYPE html>
+<!--
+Copyright (C) 2018 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-edit-preferences</title>
+
+<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
+<link rel="import" href="gr-edit-preferences.html">
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+ <template>
+ <gr-edit-preferences></gr-edit-preferences>
+ </template>
+</test-fixture>
+
+<script>
+ suite('gr-edit-preferences tests', () => {
+ let element;
+ let editPreferences;
+
+ function valueOf(title, fieldsetid) {
+ const sections = element.$[fieldsetid].querySelectorAll('section');
+ let titleEl;
+ for (let i = 0; i < sections.length; i++) {
+ titleEl = sections[i].querySelector('.title');
+ if (titleEl.textContent.trim() === title) {
+ return sections[i].querySelector('.value');
+ }
+ }
+ }
+
+ setup(done => {
+ editPreferences = {
+ auto_close_brackets: false,
+ cursor_blink_rate: 0,
+ hide_line_numbers: false,
+ hide_top_menu: false,
+ indent_unit: 2,
+ indent_with_tabs: false,
+ key_map_type: 'DEFAULT',
+ line_length: 100,
+ line_wrapping: false,
+ match_brackets: true,
+ show_base: false,
+ show_tabs: true,
+ show_whitespace_errors: true,
+ syntax_highlighting: true,
+ tab_size: 8,
+ theme: 'DEFAULT',
+ };
+
+ stub('gr-rest-api-interface', {
+ getEditPreferences() {
+ return Promise.resolve(editPreferences);
+ },
+ });
+
+ element = fixture('basic');
+
+ element.loadData().then(done);
+ });
+
+ test('renders', () => {
+ // Rendered with the expected preferences selected.
+ assert.equal(valueOf('Tab width', 'editPreferences')
+ .firstElementChild.bindValue, editPreferences.tab_size);
+ assert.equal(valueOf('Columns', 'editPreferences')
+ .firstElementChild.bindValue, editPreferences.line_length);
+ assert.equal(valueOf('Indent unit', 'editPreferences')
+ .firstElementChild.bindValue, editPreferences.indent_unit);
+ assert.equal(valueOf('Cursor blink rate', 'editPreferences')
+ .firstElementChild.bindValue, editPreferences.cursor_blink_rate);
+ assert.equal(valueOf('Top menu', 'editPreferences')
+ .firstElementChild.checked, editPreferences.hide_top_menu);
+ assert.equal(valueOf('Syntax highlighting', 'editPreferences')
+ .firstElementChild.checked, editPreferences.syntax_highlighting);
+ assert.equal(valueOf('Show tabs', 'editPreferences')
+ .firstElementChild.checked, editPreferences.show_tabs);
+ assert.equal(valueOf('Whitespace errors', 'editPreferences')
+ .firstElementChild.checked, editPreferences.show_whitespace_errors);
+ assert.equal(valueOf('Line numbers', 'editPreferences')
+ .firstElementChild.checked, editPreferences.hide_line_numbers);
+ assert.equal(valueOf('Match brackets', 'editPreferences')
+ .firstElementChild.checked, editPreferences.match_brackets);
+ assert.equal(valueOf('Line wrapping', 'editPreferences')
+ .firstElementChild.checked, editPreferences.line_wrapping);
+ assert.equal(valueOf('Indent with tabs', 'editPreferences')
+ .firstElementChild.checked, editPreferences.indent_with_tabs);
+ assert.equal(valueOf('Auto close brackets', 'editPreferences')
+ .firstElementChild.checked, editPreferences.auto_close_brackets);
+
+ assert.isFalse(element.hasUnsavedChanges);
+ });
+
+ test('save changes', done => {
+ const showTabsCheckbox = valueOf('Show tabs', 'editPreferences')
+ .firstElementChild;
+ showTabsCheckbox.checked = false;
+ element._handleEditShowTabsChanged();
+
+ assert.isTrue(element.hasUnsavedChanges);
+
+ stub('gr-rest-api-interface', {
+ saveEditPreferences(prefs) {
+ assert.equal(prefs.show_tabs, false);
+ return Promise.resolve();
+ },
+ });
+
+ // Save the change.
+ element.save().then(() => {
+ assert.isFalse(element.hasUnsavedChanges);
+ done();
+ });
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.html b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.html
index 0cbd1f6..1b3d9d4 100644
--- a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.html
+++ b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.html
@@ -94,7 +94,7 @@
<hr>
<p>
More configuration options for Gerrit may be found in the
- <a on-tap="close" href$="[[_computeSettingsUrl(_account)]]">settings</a>.
+ <a on-tap="close" href$="[[settingsUrl]]">settings</a>.
</p>
</main>
<footer>
diff --git a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.js b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.js
index 406d16c..dace2ca 100644
--- a/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.js
+++ b/polygerrit-ui/app/elements/settings/gr-registration-dialog/gr-registration-dialog.js
@@ -30,6 +30,7 @@
*/
properties: {
+ settingsUrl: String,
/** @type {?} */
_account: {
type: Object,
@@ -89,9 +90,5 @@
_computeSaveDisabled(name, username, email, saving) {
return !name || !username || !email || saving;
},
-
- _computeSettingsUrl() {
- return Gerrit.Nav.getUrlForSettings();
- },
});
})();
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.html b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.html
index 8aae460..1195408 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.html
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.html
@@ -29,6 +29,7 @@
<link rel="import" href="../../shared/gr-select/gr-select.html">
<link rel="import" href="../gr-account-info/gr-account-info.html">
<link rel="import" href="../gr-agreements-list/gr-agreements-list.html">
+<link rel="import" href="../gr-edit-preferences/gr-edit-preferences.html">
<link rel="import" href="../gr-email-editor/gr-email-editor.html">
<link rel="import" href="../gr-group-list/gr-group-list.html">
<link rel="import" href="../gr-http-password/gr-http-password.html">
@@ -63,6 +64,7 @@
<li><a href="#Profile">Profile</a></li>
<li><a href="#Preferences">Preferences</a></li>
<li><a href="#DiffPreferences">Diff Preferences</a></li>
+ <li><a href="#EditPreferences">Edit Preferences</a></li>
<li><a href="#Menu">Menu</a></li>
<li><a href="#ChangeTableColumns">Change Table Columns</a></li>
<li><a href="#Notifications">Notifications</a></li>
@@ -237,10 +239,10 @@
<span class="title">Fit to screen</span>
<span class="value">
<input
- id="lineWrapping"
+ id="diffLineWrapping"
type="checkbox"
checked$="[[_diffPrefs.line_wrapping]]"
- on-change="_handleLineWrappingChanged">
+ on-change="_handleDiffLineWrappingChanged">
</span>
</section>
<section id="columnsPref" hidden$="[[_diffPrefs.line_wrapping]]">
@@ -280,10 +282,10 @@
<span class="title">Show tabs</span>
<span class="value">
<input
- id="showTabs"
+ id="diffShowTabs"
type="checkbox"
checked$="[[_diffPrefs.show_tabs]]"
- on-change="_handleShowTabsChanged">
+ on-change="_handleDiffShowTabsChanged">
</span>
</section>
<section>
@@ -300,10 +302,10 @@
<span class="title">Syntax highlighting</span>
<span class="value">
<input
- id="syntaxHighlighting"
+ id="diffSyntaxHighlighting"
type="checkbox"
checked$="[[_diffPrefs.syntax_highlighting]]"
- on-change="_handleSyntaxHighlightingChanged">
+ on-change="_handleDiffSyntaxHighlightingChanged">
</span>
</section>
<gr-button
@@ -311,6 +313,20 @@
on-tap="_handleSaveDiffPreferences"
disabled$="[[!_diffPrefsChanged]]">Save changes</gr-button>
</fieldset>
+ <h2
+ id="EditPreferences"
+ class$="[[_computeHeaderClass(_editPrefsChanged)]]">
+ Edit Preferences
+ </h2>
+ <fieldset id="editPreferences">
+ <gr-edit-preferences
+ id="editPrefs"
+ has-unsaved-changes="{{_editPrefsChanged}}"></gr-edit-preferences>
+ <gr-button
+ id="saveEditPrefs"
+ on-tap="_handleSaveEditPreferences"
+ disabled$="[[!_editPrefsChanged]]">Save changes</gr-button>
+ </fieldset>
<h2 id="Menu" class$="[[_computeHeaderClass(_menuChanged)]]">Menu</h2>
<fieldset id="menu">
<gr-menu-editor menu-items="{{_localMenu}}"></gr-menu-editor>
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.js b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.js
index 08c3d4c1..5836b0d 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.js
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.js
@@ -90,6 +90,8 @@
type: Boolean,
value: false,
},
+ /** @type {?} */
+ _editPrefsChanged: Boolean,
_menuChanged: {
type: Boolean,
value: false,
@@ -146,6 +148,7 @@
this.$.groupList.loadData(),
this.$.httpPass.loadData(),
this.$.identities.loadData(),
+ this.$.editPrefs.loadData(),
];
promises.push(this.$.restAPI.getPreferences().then(prefs => {
@@ -287,12 +290,12 @@
});
},
- _handleLineWrappingChanged() {
- this.set('_diffPrefs.line_wrapping', this.$.lineWrapping.checked);
+ _handleDiffLineWrappingChanged() {
+ this.set('_diffPrefs.line_wrapping', this.$.diffLineWrapping.checked);
},
- _handleShowTabsChanged() {
- this.set('_diffPrefs.show_tabs', this.$.showTabs.checked);
+ _handleDiffShowTabsChanged() {
+ this.set('_diffPrefs.show_tabs', this.$.diffShowTabs.checked);
},
_handleShowTrailingWhitespaceChanged() {
@@ -300,9 +303,9 @@
this.$.showTrailingWhitespace.checked);
},
- _handleSyntaxHighlightingChanged() {
+ _handleDiffSyntaxHighlightingChanged() {
this.set('_diffPrefs.syntax_highlighting',
- this.$.syntaxHighlighting.checked);
+ this.$.diffSyntaxHighlighting.checked);
},
_handleSaveChangeTable() {
@@ -321,6 +324,10 @@
});
},
+ _handleSaveEditPreferences() {
+ this.$.editPrefs.save();
+ },
+
_handleSaveMenu() {
this.set('prefs.my', this._localMenu);
this._cloneMenu();
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.html b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.html
index 868a9e3..9a20b6c 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view_test.html
@@ -254,7 +254,7 @@
const showTabsCheckbox = valueOf('Show tabs', 'diffPreferences')
.firstElementChild;
showTabsCheckbox.checked = false;
- element._handleShowTabsChanged();
+ element._handleDiffShowTabsChanged();
assert.isTrue(element._diffPrefsChanged);
@@ -275,10 +275,10 @@
test('columns input is hidden with fit to scsreen is selected', () => {
assert.isFalse(element.$.columnsPref.hidden);
- MockInteractions.tap(element.$.lineWrapping);
+ MockInteractions.tap(element.$.diffLineWrapping);
assert.isTrue(element.$.columnsPref.hidden);
- MockInteractions.tap(element.$.lineWrapping);
+ MockInteractions.tap(element.$.diffLineWrapping);
assert.isFalse(element.$.columnsPref.hidden);
});
diff --git a/polygerrit-ui/app/elements/shared/gr-button/gr-button.html b/polygerrit-ui/app/elements/shared/gr-button/gr-button.html
index 2d61d18..a065f7a 100644
--- a/polygerrit-ui/app/elements/shared/gr-button/gr-button.html
+++ b/polygerrit-ui/app/elements/shared/gr-button/gr-button.html
@@ -40,10 +40,14 @@
text-transform: none;
}
paper-button {
+ /* paper-button sets this to anti-aliased, which appears different than
+ roboto-medium elsewhere. */
+ -webkit-font-smoothing: initial;
align-items: center;
background-color: var(--background-color);
color: var(--button-color);
display: flex;
+ font-family: inherit;
justify-content: center;
margin: var(--margin, 0);
min-width: var(--border, 0);
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.html b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.html
index ef78f3a..07aa537 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.html
+++ b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.html
@@ -90,6 +90,7 @@
<paper-input
id="input"
label="[[labelText]]"
+ maxlength="[[maxLength]]"
value="{{_inputText}}"></paper-input>
<div class="buttons">
<gr-button link id="cancelBtn" on-tap="_cancel">cancel</gr-button>
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.js b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.js
index af06291..94a6b64 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.js
+++ b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.js
@@ -51,6 +51,7 @@
reflectToAttribute: true,
value: false,
},
+ maxLength: Number,
_inputText: String,
// This is used to push the iron-input element up on the page, so
// the input is placed in approximately the same position as the
diff --git a/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.js b/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.js
index eabe061..0e13563 100644
--- a/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.js
+++ b/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text.js
@@ -39,10 +39,18 @@
type: Boolean,
value: false,
},
+
+ /**
+ * The maximum number of characters to display in the tooltop.
+ */
+ tooltipLimit: {
+ type: Number,
+ value: 1024,
+ },
},
observers: [
- '_updateTitle(text, limit)',
+ '_updateTitle(text, limit, tooltipLimit)',
],
behaviors: [
@@ -53,10 +61,10 @@
* The text or limit have changed. Recompute whether a tooltip needs to be
* enabled.
*/
- _updateTitle(text, limit) {
+ _updateTitle(text, limit, tooltipLimit) {
this.hasTooltip = !!limit && !!text && text.length > limit;
if (this.hasTooltip) {
- this.setAttribute('title', text);
+ this.setAttribute('title', text.substr(0, tooltipLimit));
} else {
this.removeAttribute('title');
}
diff --git a/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text_test.html b/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text_test.html
index 9e00331..d0d5a33 100644
--- a/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-limited-text/gr-limited-text_test.html
@@ -66,15 +66,20 @@
assert.equal(element.getAttribute('title'), 'abc 123');
assert.isTrue(element.hasTooltip);
+ element.tooltipLimit = 3;
+ flushAsynchronousOperations();
+ assert.equal(element.getAttribute('title'), 'abc');
+
+ element.tooltipLimit = 1024;
element.limit = 100;
flushAsynchronousOperations();
- assert.equal(updateSpy.callCount, 4);
+ assert.equal(updateSpy.callCount, 6);
assert.isNotOk(element.getAttribute('title'));
assert.isFalse(element.hasTooltip);
element.limit = null;
flushAsynchronousOperations();
- assert.equal(updateSpy.callCount, 5);
+ assert.equal(updateSpy.callCount, 7);
assert.isNotOk(element.getAttribute('title'));
assert.isFalse(element.hasTooltip);
});
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.html b/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.html
index bd1b91b..cf0c243 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.html
+++ b/polygerrit-ui/app/elements/shared/gr-linked-chip/gr-linked-chip.html
@@ -68,7 +68,9 @@
</style>
<div class$="container [[_getBackgroundClass(transparentBackground)]]">
<a href$="[[href]]">
- <gr-limited-text limit="[[limit]]" text="[[text]]"></gr-limited-text>
+ <gr-limited-text
+ limit="[[limit]]"
+ text="[[text]]"></gr-limited-text>
</a>
<gr-button
id="remove"
diff --git a/polygerrit-ui/app/elements/shared/gr-placeholder/gr-placeholder.html b/polygerrit-ui/app/elements/shared/gr-placeholder/gr-placeholder.html
deleted file mode 100644
index 15f44cf..0000000
--- a/polygerrit-ui/app/elements/shared/gr-placeholder/gr-placeholder.html
+++ /dev/null
@@ -1,55 +0,0 @@
-<!--
-Copyright (C) 2017 The Android Open Source Project
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
--->
-
-<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
-<link rel="import" href="../../../styles/shared-styles.html">
-
-<dom-module id="gr-placeholder">
- <template>
- <style include="shared-styles">
- main {
- margin: 2em auto;
- max-width: 46em;
- }
- h1 {
- margin-bottom: .1em;
- }
- @media only screen and (max-width: 67em) {
- main {
- margin: 2em 0 2em 15em;
- }
- }
- @media only screen and (max-width: 53em) {
- .loading {
- padding: 0 var(--default-horizontal-margin);
- }
- main {
- margin: 2em 1em;
- }
- }
- </style>
- <main>
- <h1>[[title]]</h1>
- <section>
- This page is not yet implemented in PolyGerrit. View it in the
- <a id="gwtLink" href$="[[computeGwtUrl(path)]]" rel="external">
- Old UI</a>
- </section>
- </main>
- </template>
- <script src="gr-placeholder.js"></script>
-</dom-module>
diff --git a/polygerrit-ui/app/elements/shared/gr-placeholder/gr-placeholder.js b/polygerrit-ui/app/elements/shared/gr-placeholder/gr-placeholder.js
deleted file mode 100644
index 9b60061..0000000
--- a/polygerrit-ui/app/elements/shared/gr-placeholder/gr-placeholder.js
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (C) 2017 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-(function() {
- 'use strict';
-
- Polymer({
- is: 'gr-placeholder',
-
- properties: {
- path: String,
- title: String,
- },
-
- behaviors: [
- Gerrit.BaseUrlBehavior,
- ],
- });
-})();
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 a3d3d90..b5bb35a 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
@@ -230,8 +230,12 @@
return JSON.parse(source.substring(JSON_PREFIX.length));
},
- getConfig() {
- return this._fetchSharedCacheURL('/config/server/info');
+ getConfig(noCache) {
+ if (!noCache) {
+ return this._fetchSharedCacheURL('/config/server/info');
+ }
+
+ return this.fetchJSON('/config/server/info');
},
getRepo(repo) {
@@ -675,13 +679,17 @@
},
getAccountGroups() {
- return this._fetchSharedCacheURL('/accounts/self/groups');
+ return this.fetchJSON('/accounts/self/groups');
},
getAccountAgreements() {
return this._fetchSharedCacheURL('/accounts/self/agreements');
},
+ saveAccountAgreement(name) {
+ return this.send('PUT', '/accounts/self/agreements', name);
+ },
+
/**
* @param {string=} opt_params
*/
diff --git a/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content.html b/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content.html
index e80cbe5..68db696 100644
--- a/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content.html
+++ b/polygerrit-ui/app/elements/shared/gr-tooltip-content/gr-tooltip-content.html
@@ -19,6 +19,11 @@
<dom-module id="gr-tooltip-content">
<template>
+ <style>
+ .arrow {
+ color: var(--arrow-color);
+ }
+ </style>
<slot></slot><!--
--><span class="arrow" hidden$="[[!showIcon]]">ⓘ</span>
</template>
diff --git a/polygerrit-ui/app/styles/shared-styles.html b/polygerrit-ui/app/styles/shared-styles.html
index 31b1c6e..a3cf247 100644
--- a/polygerrit-ui/app/styles/shared-styles.html
+++ b/polygerrit-ui/app/styles/shared-styles.html
@@ -87,6 +87,19 @@
[hidden] {
display: none !important;
}
+ .separator {
+ background-color: rgba(0, 0, 0, .3);
+ height: 20px;
+ margin: 0 8px;
+ width: 1px;
+ }
+ .separator.transparent {
+ background-color: transparent;
+ }
+ paper-toggle-button {
+ --paper-toggle-button-checked-bar-color: var(--color-link);
+ --paper-toggle-button-checked-button-color: var(--color-link);
+ }
</style>
</template>
</dom-module>
diff --git a/polygerrit-ui/app/test/index.html b/polygerrit-ui/app/test/index.html
index ecbd86a..44a9296 100644
--- a/polygerrit-ui/app/test/index.html
+++ b/polygerrit-ui/app/test/index.html
@@ -119,6 +119,8 @@
'plugins/gr-settings-api/gr-settings-api_test.html',
'settings/gr-account-info/gr-account-info_test.html',
'settings/gr-change-table-editor/gr-change-table-editor_test.html',
+ 'settings/gr-cla-view/gr-cla-view_test.html',
+ 'settings/gr-edit-preferences/gr-edit-preferences_test.html',
'settings/gr-email-editor/gr-email-editor_test.html',
'settings/gr-group-list/gr-group-list_test.html',
'settings/gr-http-password/gr-http-password_test.html',
diff --git a/tools/eclipse/project.py b/tools/eclipse/project.py
index e63e739..448d940 100755
--- a/tools/eclipse/project.py
+++ b/tools/eclipse/project.py
@@ -51,10 +51,22 @@
action='store_true')
opts.add_option('--name', help='name of the generated project',
action='store', default='gerrit', dest='project_name')
+opts.add_option('-b', '--batch', action='store_true',
+ dest='batch', help='Bazel batch option')
args, _ = opts.parse_args()
+batch_option = '--batch' if args.batch else None
+
+def _build_bazel_cmd(*args):
+ cmd = ['bazel']
+ if batch_option:
+ cmd.append('--batch')
+ for arg in args:
+ cmd.append(arg)
+ return cmd
+
def retrieve_ext_location():
- return check_output(['bazel', 'info', 'output_base']).strip()
+ return check_output(_build_bazel_cmd('info', 'output_base')).strip()
def gen_bazel_path():
bazel = check_output(['which', 'bazel']).strip()
@@ -66,7 +78,7 @@
deps = []
t = cp_targets[target]
try:
- check_call(['bazel', 'build', t])
+ check_call(_build_bazel_cmd('build', t))
except CalledProcessError:
exit(1)
name = 'bazel-bin/tools/eclipse/' + t.split(':')[1] + '.runtime_classpath'
@@ -277,7 +289,7 @@
makedirs(path.join(ROOT, gwt_working_dir))
try:
- check_call(['bazel', 'build', MAIN, GWT, '//java/org/eclipse/jgit:libEdit-src.jar'])
+ check_call(_build_bazel_cmd('build', MAIN, GWT, '//java/org/eclipse/jgit:libEdit-src.jar'))
except CalledProcessError:
exit(1)
except KeyboardInterrupt:
diff --git a/version.bzl b/version.bzl
index 340ba87..62d841f 100644
--- a/version.bzl
+++ b/version.bzl
@@ -3,10 +3,3 @@
# when talking to the destination repository.
#
GERRIT_VERSION = "2.16-SNAPSHOT"
-
-def check_version(x):
- if native.bazel_version == "":
- # experimental / unreleased Bazel.
- return
- if native.bazel_version < x:
- fail("\nERROR: Current Bazel version is {}, expected at least {}\n".format(native.bazel_version, x))