Merge "Add icons for checks table for expanding/collapsing message"
diff --git a/Documentation/config-accounts.txt b/Documentation/config-accounts.txt
index e642425..4f4685b 100644
--- a/Documentation/config-accounts.txt
+++ b/Documentation/config-accounts.txt
@@ -185,8 +185,6 @@
link:user-review-ui.html#diff-preferences[diff] and edit preferences:
----
-[general]
- showSiteHeader = false
[diff]
hideTopMenu = true
[edit]
diff --git a/Documentation/dev-bazel.txt b/Documentation/dev-bazel.txt
index 0b6a218..1ac0fd8 100644
--- a/Documentation/dev-bazel.txt
+++ b/Documentation/dev-bazel.txt
@@ -8,7 +8,8 @@
* A Linux or macOS system (Windows is not supported at this time)
* A JDK for Java 8|9|10|11|...
* Python 2 or 3
-* Node.js (including npm)
+* link:https://github.com/nodesource/distributions/blob/master/README.md[Node.js (including npm)]
+* Bower (`sudo npm install -g bower`)
* link:https://www.bazel.io/versions/master/docs/install.html[Bazel]
* Maven
* zip, unzip
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index e292549..77ef60d 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -2600,10 +2600,8 @@
modifications, `NOT_READY`. Other statuses are available for particular cases.
A change can be submitted if all the plugins accept the change.
-Plugins may also decide not to vote on a given change by returning an empty
-Collection (ie: the plugin is not enabled for this repository), or to vote
-several times (ie: one SubmitRecord per project in the hierarchy).
-The results are handled as if multiple plugins voted for the change.
+Plugins may also decide not to vote on a given change by returning an
+`Optional.empty()` (ie: the plugin is not enabled for this repository).
If a plugin decides not to vote, it's name will not be displayed in the UI and
it will not be recoded in the database.
@@ -2638,20 +2636,20 @@
[source, java]
----
-import java.util.Collection;
+import java.util.Optional;
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.common.data.SubmitRecord.Status;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.rules.SubmitRule;
public class MyPluginRules implements SubmitRule {
- public Collection<SubmitRecord> evaluate(ChangeData changeData) {
+ public Optional<SubmitRecord> evaluate(ChangeData changeData) {
// Implement your submitability logic here
// Assuming we want to prevent this change from being submitted:
SubmitRecord record = new SubmitRecord();
record.status = Status.NOT_READY;
- return record;
+ return Optional.of(record);
}
}
----
diff --git a/Documentation/dev-release.txt b/Documentation/dev-release.txt
index cda7a49..96e8cb0 100644
--- a/Documentation/dev-release.txt
+++ b/Documentation/dev-release.txt
@@ -373,7 +373,7 @@
feature-deprecations that we were holding off on to have a stable release where
the feature is still contained, but marked as deprecated.
-See link:dev-contributing.html#deprecating-features[Deprecating features] for
+See link:dev-processes.html#deprecating-features[Deprecating features] for
details.
GERRIT
diff --git a/Documentation/intro-user.txt b/Documentation/intro-user.txt
index 17c9a61..e9f1861 100644
--- a/Documentation/intro-user.txt
+++ b/Documentation/intro-user.txt
@@ -694,36 +694,6 @@
The following preferences can be configured:
-- [[review-category]]`Display In Review Category`:
-+
-This setting controls how the values of the review labels in change
-lists and dashboards are visualized.
-+
-** `None`:
-+
-For each review label only the voting value is shown. Approvals are
-rendered as a green check mark icon, vetoes as a red X icon.
-+
-** `Show Name`:
-+
-For each review label the voting value is shown together with the full
-name of the voting user.
-+
-** `Show Email`:
-+
-For each review label the voting value is shown together with the email
-address of the voting user.
-+
-** `Show Username`:
-+
-For each review label the voting value is shown together with the
-username of the voting user.
-+
-** `Show Abbreviated Name`:
-+
-For each review label the voting value is shown together with the
-initials of the full name of the voting user.
-
- [[page-size]]`Maximum Page Size`:
+
The maximum number of entries that are shown on one page, e.g. used
diff --git a/Documentation/js-api.txt b/Documentation/js-api.txt
index 4ef2a6c..d909c00 100644
--- a/Documentation/js-api.txt
+++ b/Documentation/js-api.txt
@@ -713,10 +713,12 @@
[[Gerrit_css]]
=== Gerrit.css()
+[WARNING]
+This method is deprecated. It doesn't work with Shadow DOM and
+will be removed in the future. Please, use link:pg-plugin-dev.html#plugin-styles[plugin.styles] instead.
+
Creates a new unique CSS class and injects it into the document.
The name of the class is returned and can be used by the plugin.
-See link:#Gerrit_html[`Gerrit.html()`] for an easy way to use
-generated class names.
Classes created with this function should be created once at install
time and reused throughout the plugin. Repeatedly creating the same
@@ -814,112 +816,6 @@
the current browser window will navigate to the non-Gerrit URL.
The user can return to Gerrit with the back button.
-[[Gerrit_html]]
-=== Gerrit.html()
-Parses an HTML fragment after performing template replacements. If
-the HTML has a single root element or node that node is returned,
-otherwise it is wrapped inside a `<div>` and the div is returned.
-
-.Signature
-[source,javascript]
-----
-Gerrit.html(htmlText, options, wantElements);
-----
-
-* htmlText: string of HTML to be parsed. A new unattached `<div>` is
- created in the browser's document and the innerHTML property is
- assigned to the passed string, after performing replacements. If
- the div has exactly one child, that child will be returned instead
- of the div.
-
-* options: optional object reference supplying replacements for any
- `{name}` references in htmlText. Navigation through objects is
- supported permitting `{style.bar}` to be replaced with `"foo"` if
- options was `{style: {bar: "foo"}}`. Value replacements are HTML
- escaped before being inserted into the document fragment.
-
-* wantElements: if options is given and wantElements is also true
- an object consisting of `{root: parsedElement, elements: {...}}` is
- returned instead of the parsed element. The elements object contains
- a property for each element using `id={name}` in htmlText.
-
-.Example
-[source,javascript]
-----
-var style = {bar: Gerrit.css('background: yellow')};
-Gerrit.html(
- '<span class="{style.bar}">Hello {name}!</span>',
- {style: style, name: "World"});
-----
-
-Event handlers can be automatically attached to elements referenced
-through an attribute id. Object navigation is not supported for ids,
-and the parser strips the id attribute before returning the result.
-Handler functions must begin with `on` and be a function to be
-installed on the element. This approach is useful for onclick and
-other handlers that do not want to create circular references that
-will eventually leak browser memory.
-
-.Example
-[source,javascript]
-----
-var options = {
- link: {
- onclick: function(e) { window.close() },
- },
-};
-Gerrit.html('<a href="javascript:;" id="{link}">Close</a>', options);
-----
-
-When using options to install handlers care must be taken to not
-accidentally include the returned element into the event handler's
-closure. This is why options is built before calling `Gerrit.html()`
-and not inline as a shown above with "Hello World".
-
-DOM nodes can optionally be returned, allowing handlers to access the
-elements identified by `id={name}` at a later point in time.
-
-.Example
-[source,javascript]
-----
-var w = Gerrit.html(
- '<div>Name: <input type="text" id="{name}"></div>'
- + '<div>Age: <input type="text" id="{age}"></div>'
- + '<button id="{submit}"><div>Save</div></button>',
- {
- submit: {
- onclick: function(s) {
- var e = w.elements;
- window.alert(e.name.value + " is " + e.age.value);
- },
- },
- }, true);
-----
-
-To prevent memory leaks `w.root` and `w.elements` should be set to
-null when the elements are no longer necessary. Screens can use
-link:#screen_onUnload[screen.onUnload()] to define a callback function
-to perform this cleanup:
-
-[source,javascript]
-----
-var w = Gerrit.html(...);
-screen.body.appendElement(w.root);
-screen.onUnload(function() { w.clear() });
-----
-
-[[Gerrit_injectCss]]
-=== Gerrit.injectCss()
-Injects CSS rules into the document by appending onto the end of the
-existing rule list. CSS rules are global to the entire application
-and must be manually scoped by each plugin. For an automatic scoping
-alternative see link:#Gerrit_css[`css()`].
-
-[source,javascript]
-----
-Gerrit.injectCss('.myplugin_bg {background: #000}');
-----
-
[[Gerrit_install]]
=== Gerrit.install()
Registers a new plugin by invoking the supplied initialization
diff --git a/Documentation/pg-plugin-dev.txt b/Documentation/pg-plugin-dev.txt
index 8fb5655..d901851 100644
--- a/Documentation/pg-plugin-dev.txt
+++ b/Documentation/pg-plugin-dev.txt
@@ -360,6 +360,16 @@
Deprecated. Use link:#plugin-settings[`plugin.settings()`] instead.
+[[plugin-styles]]
+=== styles
+`plugin.styles()`
+
+.Params:
+- none
+
+.Returns:
+- Instance of link:pg-plugin-styles-api.html[GrStylesApi]
+
=== changeMetadata
`plugin.changeMetadata()`
@@ -372,6 +382,7 @@
=== theme
`plugin.theme()`
+
Note: TODO
=== url
diff --git a/Documentation/pg-plugin-style-object.txt b/Documentation/pg-plugin-style-object.txt
new file mode 100644
index 0000000..cdcfb55
--- /dev/null
+++ b/Documentation/pg-plugin-style-object.txt
@@ -0,0 +1,33 @@
+= Gerrit Code Review - GrStyleObject
+
+Store information about css style properties. You can't create this object
+directly. Instead you should use the link:pg-plugin-styles-api.html#css[css] method.
+This object allows to apply style correctly to elements within different shadow
+subtree.
+
+[[get-class-name]]
+== getClassName
+`styleObject.getClassName(element)`
+
+.Params
+- `element` - an HTMLElement.
+
+.Returns
+- `string` - class name. The class name is valid only within the shadow root of `element`.
+
+Creates a new unique CSS class and injects it into the appropriate place
+in DOM (it can be document or shadow root for element). This class can be later
+added to the element or to any other element in the same shadow root. It is guarantee,
+that method adds CSS class only once for each shadow root.
+
+== apply
+`styleObject.apply(element)`
+
+.Params
+- `element` - element to apply style.
+
+Create a new unique CSS class (see link:#get-class-name[getClassName]) and
+adds class to the element.
+
+
+
diff --git a/Documentation/pg-plugin-styles-api.txt b/Documentation/pg-plugin-styles-api.txt
new file mode 100644
index 0000000..a829325
--- /dev/null
+++ b/Documentation/pg-plugin-styles-api.txt
@@ -0,0 +1,29 @@
+= Gerrit Code Review - Plugin styles API
+
+This API is provided by link:pg-plugin-dev.html#plugin-styles[plugin.styles()]
+and provides a way to apply dynamically created styles to elements in a
+document.
+
+[[css]]
+== css
+`styles.css(rulesStr)`
+
+.Params
+- `*string* rulesStr` string with CSS styling declarations.
+
+Example:
+----
+const styleObject = plugin.styles().css('background: black; color: white;');
+...
+const className = styleObject.getClassName(element)
+...
+element.classList.add(className);
+...
+styleObject.apply(someOtherElement);
+----
+
+.Returns
+- Instance of link:pg-plugin-style-object.html[GrStyleObject].
+
+
+
diff --git a/Documentation/rest-api-accounts.txt b/Documentation/rest-api-accounts.txt
index 5d22659..145af0e 100644
--- a/Documentation/rest-api-accounts.txt
+++ b/Documentation/rest-api-accounts.txt
@@ -1250,14 +1250,11 @@
)]}'
{
"changes_per_page": 25,
- "show_site_header": true,
- "use_flash_clipboard": true,
"download_command": "CHECKOUT",
"date_format": "STD",
"time_format": "HHMM_12",
"diff_view": "SIDE_BY_SIDE",
"size_bar_in_change_table": true,
- "review_category_strategy": "ABBREV",
"mute_common_path_prefixes": true,
"publish_comments_on_push": true,
"work_in_progress_by_default": true,
@@ -1306,14 +1303,11 @@
{
"changes_per_page": 50,
- "show_site_header": true,
- "use_flash_clipboard": true,
"expand_inline_diffs": true,
"download_command": "CHECKOUT",
"date_format": "STD",
"time_format": "HHMM_12",
"size_bar_in_change_table": true,
- "review_category_strategy": "NAME",
"diff_view": "SIDE_BY_SIDE",
"mute_common_path_prefixes": true,
"my": [
@@ -1357,14 +1351,11 @@
)]}'
{
"changes_per_page": 50,
- "show_site_header": true,
- "use_flash_clipboard": true,
"expand_inline_diffs": true,
"download_command": "CHECKOUT",
"date_format": "STD",
"time_format": "HHMM_12",
"size_bar_in_change_table": true,
- "review_category_strategy": "NAME",
"diff_view": "SIDE_BY_SIDE",
"publish_comments_on_push": true,
"work_in_progress_by_default": true,
@@ -2722,10 +2713,6 @@
|`changes_per_page` ||
The number of changes to show on each page.
Allowed values are `10`, `25`, `50`, `100`.
-|`show_site_header` |not set if `false`|
-Whether the site header should be shown.
-|`use_flash_clipboard` |not set if `false`|
-Whether to use the flash clipboard widget.
|`expand_inline_diffs` |not set if `false`|
Whether to expand diffs inline instead of opening as separate page
(PolyGerrit only).
@@ -2750,9 +2737,6 @@
Whether to show the change sizes as colored bars in the change table.
|`legacycid_in_change_table` |not set if `false`|
Whether to show change number in the change table.
-|`review_category_strategy` ||
-The strategy used to displayed info in the review category column.
-Allowed values are `NONE`, `NAME`, `EMAIL`, `USERNAME`, `ABBREV`.
|`mute_common_path_prefixes` |not set if `false`|
Whether to mute common path prefixes in file names in the file table.
|`signed_off_by` |not set if `false`|
@@ -2796,10 +2780,6 @@
|`changes_per_page` |optional|
The number of changes to show on each page.
Allowed values are `10`, `25`, `50`, `100`.
-|`show_site_header` |optional|
-Whether the site header should be shown.
-|`use_flash_clipboard` |optional|
-Whether to use the flash clipboard widget.
|`expand_inline_diffs` |not set if `false`|
Whether to expand diffs inline instead of opening as separate page
(PolyGerrit only).
@@ -2822,9 +2802,6 @@
Whether to show the change sizes as colored bars in the change table.
|`legacycid_in_change_table` |optional|
Whether to show change number in the change table.
-|`review_category_strategy` |optional|
-The strategy used to displayed info in the review category column.
-Allowed values are `NONE`, `NAME`, `EMAIL`, `USERNAME`, `ABBREV`.
|`mute_common_path_prefixes` |optional|
Whether to mute common path prefixes in file names in the file table.
|`signed_off_by` |optional|
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index ec269e4..0df6aa5 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -2987,7 +2987,20 @@
Suggest the reviewers for a given query `q` and result limit `n`. If result
limit is not passed, then the default 10 is used.
-Groups can be excluded from the results by specifying 'e=f'.
+This REST endpoint only suggests accounts that
+
+* are active
+* can see the change
+* are visible to the calling user
+* are not already reviewer on the change
+* don't own the change
+
+Groups can be excluded from the results by specifying the 'exclude-groups'
+request parameter:
+
+--
+'GET /changes/link:#change-id[\{change-id\}]/suggest_reviewers?q=J&n=5&exclude-groups'
+--
As result a list of link:#suggested-reviewer-info[SuggestedReviewerInfo] entries is returned.
@@ -4345,8 +4358,10 @@
R = label('Any-Label-Name', reject(_)).
----
-The response is a list of link:#submit-record[SubmitRecord] entries
-describing the permutations that satisfy the tested submit rule.
+The response is a link:#submit-record[SubmitRecord] describing the
+permutations that satisfy the tested submit rule.
+
+If the submit rule was a np-op, the response is "`204 No Content`".
.Response
----
@@ -4355,14 +4370,12 @@
Content-Type: application/json; charset=UTF-8
)]}'
- [
- {
- "status": "NOT_READY",
- "reject": {
- "Any-Label-Name": {}
- }
+ {
+ "status": "NOT_READY",
+ "reject": {
+ "Any-Label-Name": {}
}
- ]
+ }
----
When testing with the `curl` command line client the
diff --git a/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt
index 96b376d..52c393f 100644
--- a/Documentation/rest-api-config.txt
+++ b/Documentation/rest-api-config.txt
@@ -1054,14 +1054,11 @@
)]}'
{
"changes_per_page": 25,
- "show_site_header": true,
- "use_flash_clipboard": true,
"download_command": "CHECKOUT",
"date_format": "STD",
"time_format": "HHMM_12",
"diff_view": "SIDE_BY_SIDE",
"size_bar_in_change_table": true,
- "review_category_strategy": "NONE",
"mute_common_path_prefixes": true,
"publish_comments_on_push": true,
"my": [
@@ -1133,14 +1130,11 @@
)]}'
{
"changes_per_page": 50,
- "show_site_header": true,
- "use_flash_clipboard": true,
"download_command": "CHECKOUT",
"date_format": "STD",
"time_format": "HHMM_12",
"diff_view": "SIDE_BY_SIDE",
"size_bar_in_change_table": true,
- "review_category_strategy": "NONE",
"mute_common_path_prefixes": true,
"publish_comments_on_push": true,
"my": [
@@ -1545,11 +1539,15 @@
|`update_delay` ||
link:config-gerrit.html#change.updateDelay[How often in seconds the web
interface should poll for updates to the currently open change].
-|`submit_whole_topic` ||
+|`submit_whole_topic` |not set if `false`|
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.
+|`exclude_mergeable_in_change_info` |not set if `false`|
+Value of the link:config-gerrit.html#change.api.excludeMergeableInChangeInfo[
+configuration parameter] that controls whether the mergeability bit in
+link:rest-api-changes.html#change-info[ChangeInfo] will never be set.
|=============================
[[check-account-external-ids-input]]
diff --git a/java/com/google/gerrit/extensions/api/changes/ChangeApi.java b/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
index 010ef6d..e3318ea 100644
--- a/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
+++ b/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
@@ -381,6 +381,7 @@
abstract class SuggestedReviewersRequest {
private String query;
private int limit;
+ private boolean excludeGroups;
public abstract List<SuggestedReviewerInfo> get() throws RestApiException;
@@ -394,6 +395,11 @@
return this;
}
+ public SuggestedReviewersRequest excludeGroups(boolean excludeGroups) {
+ this.excludeGroups = excludeGroups;
+ return this;
+ }
+
public String getQuery() {
return query;
}
@@ -401,6 +407,10 @@
public int getLimit() {
return limit;
}
+
+ public boolean getExcludeGroups() {
+ return excludeGroups;
+ }
}
/**
diff --git a/java/com/google/gerrit/extensions/api/changes/RevisionApi.java b/java/com/google/gerrit/extensions/api/changes/RevisionApi.java
index 7d356bf..f8404ce 100644
--- a/java/com/google/gerrit/extensions/api/changes/RevisionApi.java
+++ b/java/com/google/gerrit/extensions/api/changes/RevisionApi.java
@@ -146,7 +146,7 @@
SubmitType testSubmitType(TestSubmitRuleInput in) throws RestApiException;
- List<TestSubmitRuleInfo> testSubmitRule(TestSubmitRuleInput in) throws RestApiException;
+ TestSubmitRuleInfo testSubmitRule(TestSubmitRuleInput in) throws RestApiException;
MergeListRequest getMergeList() throws RestApiException;
@@ -351,7 +351,7 @@
}
@Override
- public List<TestSubmitRuleInfo> testSubmitRule(TestSubmitRuleInput in) throws RestApiException {
+ public TestSubmitRuleInfo testSubmitRule(TestSubmitRuleInput in) throws RestApiException {
throw new NotImplementedException();
}
diff --git a/java/com/google/gerrit/extensions/client/GeneralPreferencesInfo.java b/java/com/google/gerrit/extensions/client/GeneralPreferencesInfo.java
index fa95b8f..f5a740e 100644
--- a/java/com/google/gerrit/extensions/client/GeneralPreferencesInfo.java
+++ b/java/com/google/gerrit/extensions/client/GeneralPreferencesInfo.java
@@ -68,14 +68,6 @@
}
}
- public enum ReviewCategoryStrategy {
- NONE,
- NAME,
- EMAIL,
- USERNAME,
- ABBREV
- }
-
public enum DiffView {
SIDE_BY_SIDE,
UNIFIED_DIFF
@@ -131,10 +123,6 @@
/** Number of changes to show in a screen. */
public Integer changesPerPage;
- /** Should the site header be displayed when logged in ? */
- public Boolean showSiteHeader;
- /** Should the Flash helper movie be used to copy text to the clipboard? */
- public Boolean useFlashClipboard;
/** Type of download URL the user prefers to use. */
public String downloadScheme;
/** Type of download command the user prefers to use. */
@@ -148,7 +136,6 @@
public DiffView diffView;
public Boolean sizeBarInChangeTable;
public Boolean legacycidInChangeTable;
- public ReviewCategoryStrategy reviewCategoryStrategy;
public Boolean muteCommonPathPrefixes;
public Boolean signedOffBy;
public EmailStrategy emailStrategy;
@@ -160,10 +147,6 @@
public List<String> changeTable;
public Map<String, String> urlAliases;
- public boolean isShowInfoInReviewCategory() {
- return getReviewCategoryStrategy() != ReviewCategoryStrategy.NONE;
- }
-
public DateFormat getDateFormat() {
if (dateFormat == null) {
return DateFormat.STD;
@@ -178,13 +161,6 @@
return timeFormat;
}
- public ReviewCategoryStrategy getReviewCategoryStrategy() {
- if (reviewCategoryStrategy == null) {
- return ReviewCategoryStrategy.NONE;
- }
- return reviewCategoryStrategy;
- }
-
public DiffView getDiffView() {
if (diffView == null) {
return DiffView.SIDE_BY_SIDE;
@@ -209,8 +185,6 @@
public static GeneralPreferencesInfo defaults() {
GeneralPreferencesInfo p = new GeneralPreferencesInfo();
p.changesPerPage = DEFAULT_PAGESIZE;
- p.showSiteHeader = true;
- p.useFlashClipboard = true;
p.downloadScheme = null;
p.downloadCommand = DownloadCommand.CHECKOUT;
p.dateFormat = DateFormat.STD;
@@ -221,7 +195,6 @@
p.diffView = DiffView.SIDE_BY_SIDE;
p.sizeBarInChangeTable = true;
p.legacycidInChangeTable = false;
- p.reviewCategoryStrategy = ReviewCategoryStrategy.NONE;
p.muteCommonPathPrefixes = true;
p.signedOffBy = false;
p.emailStrategy = EmailStrategy.ENABLED;
diff --git a/java/com/google/gerrit/extensions/common/ChangeConfigInfo.java b/java/com/google/gerrit/extensions/common/ChangeConfigInfo.java
index 1e822e3..e8aeb40 100644
--- a/java/com/google/gerrit/extensions/common/ChangeConfigInfo.java
+++ b/java/com/google/gerrit/extensions/common/ChangeConfigInfo.java
@@ -23,4 +23,5 @@
public String replyTooltip;
public int updateDelay;
public Boolean submitWholeTopic;
+ public Boolean excludeMergeableInChangeInfo;
}
diff --git a/java/com/google/gerrit/extensions/restapi/UnprocessableEntityException.java b/java/com/google/gerrit/extensions/restapi/UnprocessableEntityException.java
index 9ed83b2..c1a9ad2 100644
--- a/java/com/google/gerrit/extensions/restapi/UnprocessableEntityException.java
+++ b/java/com/google/gerrit/extensions/restapi/UnprocessableEntityException.java
@@ -21,4 +21,8 @@
public UnprocessableEntityException(String msg) {
super(msg);
}
+
+ public UnprocessableEntityException(String msg, Throwable cause) {
+ super(msg, cause);
+ }
}
diff --git a/java/com/google/gerrit/server/account/Preferences.java b/java/com/google/gerrit/server/account/Preferences.java
index f33d8fe..bba5843 100644
--- a/java/com/google/gerrit/server/account/Preferences.java
+++ b/java/com/google/gerrit/server/account/Preferences.java
@@ -65,8 +65,6 @@
* <p>The config file has separate sections for general, diff and edit preferences:
*
* <pre>
- * [general]
- * showSiteHeader = false
* [diff]
* hideTopMenu = true
* [edit]
@@ -401,7 +399,7 @@
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));
+ my.add(new MenuItem("Groups", "#/settings/#Groups", null));
}
return my;
}
diff --git a/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java b/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
index 3cbcb73..04911eb 100644
--- a/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
+++ b/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
@@ -448,6 +448,7 @@
try {
suggestReviewers.setQuery(r.getQuery());
suggestReviewers.setLimit(r.getLimit());
+ suggestReviewers.setExcludeGroups(r.getExcludeGroups());
return suggestReviewers.apply(change).value();
} catch (Exception e) {
throw asRestApiException("Cannot retrieve suggested reviewers", e);
diff --git a/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java b/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
index cc0a2f1..f1bd690 100644
--- a/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
+++ b/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
@@ -547,7 +547,7 @@
}
@Override
- public List<TestSubmitRuleInfo> testSubmitRule(TestSubmitRuleInput in) throws RestApiException {
+ public TestSubmitRuleInfo testSubmitRule(TestSubmitRuleInput in) throws RestApiException {
try {
return testSubmitRule.get().apply(revision, in).value();
} catch (Exception e) {
diff --git a/java/com/google/gerrit/server/change/DeleteChangeOp.java b/java/com/google/gerrit/server/change/DeleteChangeOp.java
index 2449df2..37ac713 100644
--- a/java/com/google/gerrit/server/change/DeleteChangeOp.java
+++ b/java/com/google/gerrit/server/change/DeleteChangeOp.java
@@ -19,6 +19,7 @@
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.StarredChangesUtil;
import com.google.gerrit.server.extensions.events.ChangeDeleted;
@@ -114,7 +115,23 @@
String prefix = PatchSet.id(id, 1).toRefName();
prefix = prefix.substring(0, prefix.length() - 1);
for (Map.Entry<String, ObjectId> e : ctx.getRepoView().getRefs(prefix).entrySet()) {
- ctx.addRefUpdate(e.getValue(), ObjectId.zeroId(), prefix + e.getKey());
+ removeRef(ctx, e, prefix);
}
+ removeUserEdits(ctx);
+ }
+
+ private void removeUserEdits(RepoContext ctx) throws IOException {
+ String prefix = RefNames.REFS_USERS;
+ String editRef = String.format("/edit-%s/", id);
+ for (Map.Entry<String, ObjectId> e : ctx.getRepoView().getRefs(prefix).entrySet()) {
+ if (e.getKey().contains(editRef)) {
+ removeRef(ctx, e, prefix);
+ }
+ }
+ }
+
+ private void removeRef(RepoContext ctx, Map.Entry<String, ObjectId> entry, String prefix)
+ throws IOException {
+ ctx.addRefUpdate(entry.getValue(), ObjectId.zeroId(), prefix + entry.getKey());
}
}
diff --git a/java/com/google/gerrit/server/index/change/ChangeIndexer.java b/java/com/google/gerrit/server/index/change/ChangeIndexer.java
index 87ee27f..07bd963 100644
--- a/java/com/google/gerrit/server/index/change/ChangeIndexer.java
+++ b/java/com/google/gerrit/server/index/change/ChangeIndexer.java
@@ -190,6 +190,7 @@
"Replacing change in index",
Metadata.builder()
.changeId(cd.getId().get())
+ .patchSetId(cd.currentPatchSet().number())
.indexVersion(i.getSchema().getVersion())
.build())) {
i.replace(cd);
diff --git a/java/com/google/gerrit/server/logging/Metadata.java b/java/com/google/gerrit/server/logging/Metadata.java
index 7184a92..dae78c0 100644
--- a/java/com/google/gerrit/server/logging/Metadata.java
+++ b/java/com/google/gerrit/server/logging/Metadata.java
@@ -15,8 +15,14 @@
package com.google.gerrit.server.logging;
import com.google.auto.value.AutoValue;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.MoreObjects.ToStringHelper;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.common.Nullable;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
import java.util.Optional;
/** Metadata that is provided to {@link PerformanceLogger}s as context for performance records. */
@@ -134,6 +140,99 @@
// The username of an account.
public abstract Optional<String> username();
+ /**
+ * Returns a string representation of this instance that is suitable for logging.
+ *
+ * <p>{@link #toString()} formats the {@link Optional} fields as {@code key=Optional[value]} or
+ * {@code key=Optional.empty}. Since this class has many optional fields from which usually only a
+ * few are populated this leads to long string representations such as
+ *
+ * <pre>
+ * Metadata{accountId=Optional.empty, actionType=Optional.empty, authDomainName=Optional.empty,
+ * branchName=Optional.empty, cacheKey=Optional.empty, cacheName=Optional.empty,
+ * className=Optional.empty, changeId=Optional[9212550], changeIdType=Optional.empty,
+ * eventType=Optional.empty, exportValue=Optional.empty, filePath=Optional.empty,
+ * garbageCollectorName=Optional.empty, gitOperation=Optional.empty, groupId=Optional.empty,
+ * groupName=Optional.empty, groupUuid=Optional.empty, httpStatus=Optional.empty,
+ * indexName=Optional.empty, indexVersion=Optional[0], methodName=Optional.empty,
+ * multiple=Optional.empty, operationName=Optional.empty, partial=Optional.empty,
+ * noteDbFilePath=Optional.empty, noteDbRefName=Optional.empty,
+ * noteDbSequenceType=Optional.empty, noteDbTable=Optional.empty, patchSetId=Optional.empty,
+ * pluginMetadata=[], pluginName=Optional.empty, projectName=Optional.empty,
+ * pushType=Optional.empty, resourceCount=Optional.empty, restViewName=Optional.empty,
+ * revision=Optional.empty, username=Optional.empty}
+ * </pre>
+ *
+ * <p>That's hard to read in logs. This is why this method
+ *
+ * <ul>
+ * <li>drops fields which have {@code Optional.empty} as value and
+ * <li>reformats values that are {@code Optional[value]} to {@code value}.
+ * </ul>
+ *
+ * <p>For the example given above the formatted string would look like this:
+ *
+ * <pre>
+ * Metadata{changeId=9212550, indexVersion=0, pluginMetadata=[]}
+ * </pre>
+ *
+ * @return string representation of this instance that is suitable for logging
+ */
+ public String toStringForLogging() {
+ // Append class name.
+ String className = getClass().getSimpleName();
+ if (className.startsWith("AutoValue_")) {
+ className = className.substring(10);
+ }
+ ToStringHelper stringHelper = MoreObjects.toStringHelper(className);
+
+ // Append key-value pairs for field which are set.
+ Method[] methods = Metadata.class.getDeclaredMethods();
+ Arrays.<Method>sort(methods, (m1, m2) -> m1.getName().compareTo(m2.getName()));
+ for (Method method : methods) {
+ if (Modifier.isStatic(method.getModifiers())) {
+ // skip static method
+ continue;
+ }
+
+ if (method.getName().equals(Thread.currentThread().getStackTrace()[1].getMethodName())) {
+ // skip this method (toStringForLogging() method)
+ continue;
+ }
+
+ if (method.getReturnType().equals(Void.TYPE) || method.getParameterCount() > 0) {
+ // skip method since it's not a getter
+ continue;
+ }
+
+ method.setAccessible(true);
+
+ Object returnValue;
+ try {
+ returnValue = method.invoke(this);
+ } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) {
+ // should never happen
+ throw new IllegalStateException(e);
+ }
+
+ if (returnValue instanceof Optional) {
+ Optional<?> fieldValueOptional = (Optional<?>) returnValue;
+ if (!fieldValueOptional.isPresent()) {
+ // drop this key-value pair
+ continue;
+ }
+
+ // format as 'key=value' instead of 'key=Optional[value]'
+ stringHelper.add(method.getName(), fieldValueOptional.get());
+ } else {
+ // not an Optional value, keep as is
+ stringHelper.add(method.getName(), returnValue);
+ }
+ }
+
+ return stringHelper.toString();
+ }
+
public static Metadata.Builder builder() {
return new AutoValue_Metadata.Builder();
}
diff --git a/java/com/google/gerrit/server/logging/TraceContext.java b/java/com/google/gerrit/server/logging/TraceContext.java
index 06db7b4..b597a51 100644
--- a/java/com/google/gerrit/server/logging/TraceContext.java
+++ b/java/com/google/gerrit/server/logging/TraceContext.java
@@ -196,7 +196,8 @@
LoggingContext.getInstance()
.addPerformanceLogRecord(
() -> PerformanceLogRecord.create(operation, elapsedMs, metadata));
- logger.atFine().log("%s (%s) (%d ms)", operation, metadata, elapsedMs);
+ logger.atFine().log(
+ "%s (%s) (%d ms)", operation, metadata.toStringForLogging(), elapsedMs);
});
}
diff --git a/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java b/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java
index 32c8b06..5691a82 100644
--- a/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java
+++ b/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java
@@ -280,16 +280,6 @@
}
}
- public void flush() throws IOException {
- checkNotExecuted();
- if (changeRepo != null) {
- changeRepo.flush();
- }
- if (allUsersRepo != null) {
- allUsersRepo.flush();
- }
- }
-
@Nullable
public BatchRefUpdate execute() throws IOException {
return execute(false);
diff --git a/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java b/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java
index 36c6d36..7495957 100644
--- a/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java
+++ b/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java
@@ -31,9 +31,9 @@
import com.google.gerrit.server.rules.SubmitRule;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
-import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.Optional;
import java.util.stream.Collectors;
/**
@@ -83,15 +83,15 @@
this.opts = options;
}
- public static List<SubmitRecord> defaultRuleError() {
+ public static SubmitRecord defaultRuleError() {
return createRuleError(DEFAULT_MSG);
}
- public static List<SubmitRecord> createRuleError(String err) {
+ public static SubmitRecord createRuleError(String err) {
SubmitRecord rec = new SubmitRecord();
rec.status = SubmitRecord.Status.RULE_ERROR;
rec.errorMessage = err;
- return Collections.singletonList(rec);
+ return rec;
}
public static SubmitTypeRecord defaultTypeError() {
@@ -120,7 +120,7 @@
throw new NoSuchProjectException(cd.project());
}
} catch (StorageException | NoSuchProjectException e) {
- return ruleError("Error looking up change " + cd.getId(), e);
+ return Collections.singletonList(ruleError("Error looking up change " + cd.getId(), e));
}
if ((!opts.allowClosed() || OnlineReindexMode.isActive()) && change.isClosed()) {
@@ -133,12 +133,13 @@
// and then we collect the results in one list.
return Streams.stream(submitRules)
.map(c -> c.call(s -> s.evaluate(cd)))
- .flatMap(Collection::stream)
+ .filter(Optional::isPresent)
+ .map(Optional::get)
.collect(Collectors.toList());
}
}
- private List<SubmitRecord> ruleError(String err, Exception e) {
+ private SubmitRecord ruleError(String err, Exception e) {
logger.atSevere().withCause(e).log(err);
return defaultRuleError();
}
diff --git a/java/com/google/gerrit/server/restapi/change/Rebase.java b/java/com/google/gerrit/server/restapi/change/Rebase.java
index 5f0a56b..bd3742b 100644
--- a/java/com/google/gerrit/server/restapi/change/Rebase.java
+++ b/java/com/google/gerrit/server/restapi/change/Rebase.java
@@ -165,7 +165,7 @@
}
} catch (NoSuchChangeException e) {
throw new UnprocessableEntityException(
- String.format("Base change not found: %s", input.base));
+ String.format("Base change not found: %s", input.base), e);
}
PatchSet.Id baseId = base.patchSet().id();
diff --git a/java/com/google/gerrit/server/restapi/change/SuggestChangeReviewers.java b/java/com/google/gerrit/server/restapi/change/SuggestChangeReviewers.java
index 213bae9..28296dd 100644
--- a/java/com/google/gerrit/server/restapi/change/SuggestChangeReviewers.java
+++ b/java/com/google/gerrit/server/restapi/change/SuggestChangeReviewers.java
@@ -39,15 +39,20 @@
public class SuggestChangeReviewers extends SuggestReviewers
implements RestReadView<ChangeResource> {
+ private final PermissionBackend permissionBackend;
+ private final Provider<CurrentUser> self;
+ private final ProjectCache projectCache;
+
+ private boolean excludeGroups;
+
@Option(
name = "--exclude-groups",
aliases = {"-e"},
usage = "exclude groups from query")
- boolean excludeGroups;
-
- private final PermissionBackend permissionBackend;
- private final Provider<CurrentUser> self;
- private final ProjectCache projectCache;
+ public SuggestChangeReviewers setExcludeGroups(boolean excludeGroups) {
+ this.excludeGroups = excludeGroups;
+ return this;
+ }
@Inject
SuggestChangeReviewers(
diff --git a/java/com/google/gerrit/server/restapi/change/TestSubmitRule.java b/java/com/google/gerrit/server/restapi/change/TestSubmitRule.java
index d5ed9a4..bae2e52 100644
--- a/java/com/google/gerrit/server/restapi/change/TestSubmitRule.java
+++ b/java/com/google/gerrit/server/restapi/change/TestSubmitRule.java
@@ -15,8 +15,6 @@
package com.google.gerrit.server.restapi.change;
import com.google.common.base.MoreObjects;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.TestSubmitRuleInfo;
@@ -37,7 +35,6 @@
import com.google.gerrit.server.rules.RulesCache;
import com.google.inject.Inject;
import java.util.LinkedHashMap;
-import java.util.List;
import org.kohsuke.args4j.Option;
public class TestSubmitRule implements RestModifyView<RevisionResource, TestSubmitRuleInput> {
@@ -65,7 +62,7 @@
}
@Override
- public Response<List<TestSubmitRuleInfo>> apply(RevisionResource rsrc, TestSubmitRuleInput input)
+ public Response<TestSubmitRuleInfo> apply(RevisionResource rsrc, TestSubmitRuleInput input)
throws AuthException, PermissionBackendException, BadRequestException {
if (input == null) {
input = new TestSubmitRuleInput();
@@ -83,15 +80,12 @@
throw new BadRequestException("project not found");
}
ChangeData cd = changeDataFactory.create(rsrc.getNotes());
- List<SubmitRecord> records =
- ImmutableList.copyOf(
- prologRule.evaluate(
- cd, PrologOptions.dryRunOptions(input.rule, input.filters == Filters.SKIP)));
- List<TestSubmitRuleInfo> out = Lists.newArrayListWithCapacity(records.size());
+ SubmitRecord record =
+ prologRule.evaluate(
+ cd, PrologOptions.dryRunOptions(input.rule, input.filters == Filters.SKIP));
+
AccountLoader accounts = accountInfoFactory.create(true);
- for (SubmitRecord r : records) {
- out.add(newSubmitRuleInfo(r, accounts));
- }
+ TestSubmitRuleInfo out = newSubmitRuleInfo(record, accounts);
accounts.fill();
return Response.ok(out);
}
diff --git a/java/com/google/gerrit/server/restapi/config/GetServerInfo.java b/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
index 32d6f17..a36e75c 100644
--- a/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
+++ b/java/com/google/gerrit/server/restapi/config/GetServerInfo.java
@@ -234,7 +234,9 @@
+ "\u2026";
info.updateDelay =
(int) ConfigUtil.getTimeUnit(config, "change", null, "updateDelay", 300, TimeUnit.SECONDS);
- info.submitWholeTopic = MergeSuperSet.wholeTopicEnabled(config);
+ info.submitWholeTopic = toBoolean(MergeSuperSet.wholeTopicEnabled(config));
+ info.excludeMergeableInChangeInfo =
+ toBoolean(this.config.getBoolean("change", "api", "excludeMergeableInChangeInfo", false));
info.disablePrivateChanges =
toBoolean(this.config.getBoolean("change", null, "disablePrivateChanges", false));
return info;
diff --git a/java/com/google/gerrit/server/rules/DefaultSubmitRule.java b/java/com/google/gerrit/server/rules/DefaultSubmitRule.java
index ee997b2..0016789 100644
--- a/java/com/google/gerrit/server/rules/DefaultSubmitRule.java
+++ b/java/com/google/gerrit/server/rules/DefaultSubmitRule.java
@@ -31,8 +31,8 @@
import com.google.inject.Singleton;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
import java.util.List;
+import java.util.Optional;
/**
* Java implementation of Gerrit's default pre-submit rules behavior: check if the labels have the
@@ -62,13 +62,13 @@
}
@Override
- public Collection<SubmitRecord> evaluate(ChangeData cd) {
+ public Optional<SubmitRecord> evaluate(ChangeData cd) {
ProjectState projectState = projectCache.get(cd.project());
// In case at least one project has a rules.pl file, we let Prolog handle it.
// The Prolog rules engine will also handle the labels for us.
if (projectState == null || projectState.hasPrologRules()) {
- return Collections.emptyList();
+ return Optional.empty();
}
SubmitRecord submitRecord = new SubmitRecord();
@@ -85,7 +85,7 @@
submitRecord.errorMessage = "Unable to fetch labels and approvals for the change";
submitRecord.status = SubmitRecord.Status.RULE_ERROR;
- return Collections.singletonList(submitRecord);
+ return Optional.of(submitRecord);
}
submitRecord.labels = new ArrayList<>(labelTypes.size());
@@ -98,7 +98,7 @@
submitRecord.errorMessage = "Unable to find the LabelFunction for label " + t.getName();
submitRecord.status = SubmitRecord.Status.RULE_ERROR;
- return Collections.singletonList(submitRecord);
+ return Optional.of(submitRecord);
}
Collection<PatchSetApproval> approvalsForLabel = getApprovalsForLabel(approvals, t);
@@ -118,7 +118,7 @@
}
}
- return Collections.singletonList(submitRecord);
+ return Optional.of(submitRecord);
}
private static List<PatchSetApproval> getApprovalsForLabel(
diff --git a/java/com/google/gerrit/server/rules/IgnoreSelfApprovalRule.java b/java/com/google/gerrit/server/rules/IgnoreSelfApprovalRule.java
index ff5d99e..f64ab13 100644
--- a/java/com/google/gerrit/server/rules/IgnoreSelfApprovalRule.java
+++ b/java/com/google/gerrit/server/rules/IgnoreSelfApprovalRule.java
@@ -17,7 +17,6 @@
import static com.google.common.collect.ImmutableList.toImmutableList;
import com.google.common.annotations.VisibleForTesting;
-import com.google.common.collect.ImmutableList;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.data.LabelFunction;
import com.google.gerrit.common.data.LabelType;
@@ -34,6 +33,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
+import java.util.Optional;
/**
* Rule to require an approval from a user that did not upload the current patch set or block
@@ -59,7 +59,7 @@
IgnoreSelfApprovalRule() {}
@Override
- public Collection<SubmitRecord> evaluate(ChangeData cd) {
+ public Optional<SubmitRecord> evaluate(ChangeData cd) {
List<LabelType> labelTypes;
List<PatchSetApproval> approvals;
try {
@@ -67,13 +67,13 @@
approvals = cd.currentApprovals();
} catch (StorageException e) {
logger.atWarning().withCause(e).log(E_UNABLE_TO_FETCH_LABELS);
- return singletonRuleError(E_UNABLE_TO_FETCH_LABELS);
+ return ruleError(E_UNABLE_TO_FETCH_LABELS);
}
boolean shouldIgnoreSelfApproval = labelTypes.stream().anyMatch(LabelType::ignoreSelfApproval);
if (!shouldIgnoreSelfApproval) {
// Shortcut to avoid further processing if no label should ignore uploader approvals
- return ImmutableList.of();
+ return Optional.empty();
}
Account.Id uploader;
@@ -81,7 +81,7 @@
uploader = cd.currentPatchSet().uploader();
} catch (StorageException e) {
logger.atWarning().withCause(e).log(E_UNABLE_TO_FETCH_UPLOADER);
- return singletonRuleError(E_UNABLE_TO_FETCH_UPLOADER);
+ return ruleError(E_UNABLE_TO_FETCH_UPLOADER);
}
SubmitRecord submitRecord = new SubmitRecord();
@@ -123,10 +123,10 @@
}
if (submitRecord.labels.isEmpty()) {
- return ImmutableList.of();
+ return Optional.empty();
}
- return ImmutableList.of(submitRecord);
+ return Optional.of(submitRecord);
}
private static boolean labelCheckPassed(SubmitRecord.Label label) {
@@ -143,11 +143,11 @@
return false;
}
- private static Collection<SubmitRecord> singletonRuleError(String reason) {
+ private static Optional<SubmitRecord> ruleError(String reason) {
SubmitRecord submitRecord = new SubmitRecord();
submitRecord.errorMessage = reason;
submitRecord.status = SubmitRecord.Status.RULE_ERROR;
- return ImmutableList.of(submitRecord);
+ return Optional.of(submitRecord);
}
@VisibleForTesting
diff --git a/java/com/google/gerrit/server/rules/PrologRule.java b/java/com/google/gerrit/server/rules/PrologRule.java
index e15b4b5..bf1d545 100644
--- a/java/com/google/gerrit/server/rules/PrologRule.java
+++ b/java/com/google/gerrit/server/rules/PrologRule.java
@@ -21,8 +21,7 @@
import com.google.gerrit.server.query.change.ChangeData;
import com.google.inject.Inject;
import com.google.inject.Singleton;
-import java.util.Collection;
-import java.util.Collections;
+import java.util.Optional;
@Singleton
public class PrologRule implements SubmitRule {
@@ -36,17 +35,17 @@
}
@Override
- public Collection<SubmitRecord> evaluate(ChangeData cd) {
+ public Optional<SubmitRecord> evaluate(ChangeData cd) {
ProjectState projectState = projectCache.get(cd.project());
// We only want to run the Prolog engine if we have at least one rules.pl file to use.
if ((projectState == null || !projectState.hasPrologRules())) {
- return Collections.emptyList();
+ return Optional.empty();
}
- return evaluate(cd, PrologOptions.defaultOptions());
+ return Optional.of(evaluate(cd, PrologOptions.defaultOptions()));
}
- public Collection<SubmitRecord> evaluate(ChangeData cd, PrologOptions opts) {
+ public SubmitRecord evaluate(ChangeData cd, PrologOptions opts) {
return getEvaluator(cd, opts).evaluate();
}
diff --git a/java/com/google/gerrit/server/rules/PrologRuleEvaluator.java b/java/com/google/gerrit/server/rules/PrologRuleEvaluator.java
index 7f6450d..0b49ae5 100644
--- a/java/com/google/gerrit/server/rules/PrologRuleEvaluator.java
+++ b/java/com/google/gerrit/server/rules/PrologRuleEvaluator.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.rules;
+import static com.google.common.base.Preconditions.checkState;
import static com.google.gerrit.server.project.SubmitRuleEvaluator.createRuleError;
import static com.google.gerrit.server.project.SubmitRuleEvaluator.defaultRuleError;
import static com.google.gerrit.server.project.SubmitRuleEvaluator.defaultTypeError;
@@ -50,7 +51,6 @@
import com.googlecode.prolog_cafe.lang.VariableTerm;
import java.io.StringReader;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -140,10 +140,9 @@
/**
* Evaluate the submit rules.
*
- * @return List of {@link SubmitRecord} objects returned from the evaluated rules, including any
- * errors.
+ * @return {@link SubmitRecord} returned from the evaluated rules. Can include errors.
*/
- public Collection<SubmitRecord> evaluate() {
+ public SubmitRecord evaluate() {
Change change;
try {
change = cd.change();
@@ -193,28 +192,32 @@
* output. Later after the loop the out collection is reversed to restore it to the original
* ordering.
*/
- public List<SubmitRecord> resultsToSubmitRecord(Term submitRule, List<Term> results) {
- boolean foundOk = false;
- List<SubmitRecord> out = new ArrayList<>(results.size());
+ public SubmitRecord resultsToSubmitRecord(Term submitRule, List<Term> results) {
+ checkState(!results.isEmpty(), "the list of Prolog terms must not be empty");
+
+ SubmitRecord resultSubmitRecord = new SubmitRecord();
+ resultSubmitRecord.labels = new ArrayList<>();
for (int resultIdx = results.size() - 1; 0 <= resultIdx; resultIdx--) {
Term submitRecord = results.get(resultIdx);
- SubmitRecord rec = new SubmitRecord();
- out.add(rec);
if (!(submitRecord instanceof StructureTerm) || 1 != submitRecord.arity()) {
return invalidResult(submitRule, submitRecord);
}
- if ("ok".equals(submitRecord.name())) {
- rec.status = SubmitRecord.Status.OK;
-
- } else if ("not_ready".equals(submitRecord.name())) {
- rec.status = SubmitRecord.Status.NOT_READY;
-
- } else {
+ if (!"ok".equals(submitRecord.name()) && !"not_ready".equals(submitRecord.name())) {
return invalidResult(submitRule, submitRecord);
}
+ // This transformation is required to adapt Prolog's behavior to the way Gerrit handles
+ // SubmitRecords, as defined in the SubmitRecord#allRecordsOK method.
+ // When several rules are defined in Prolog, they are all matched to a SubmitRecord. We want
+ // the change to be submittable when at least one result is OK.
+ if ("ok".equals(submitRecord.name())) {
+ resultSubmitRecord.status = SubmitRecord.Status.OK;
+ } else if ("not_ready".equals(submitRecord.name()) && resultSubmitRecord.status == null) {
+ resultSubmitRecord.status = SubmitRecord.Status.NOT_READY;
+ }
+
// Unpack the one argument. This should also be a structure with one
// argument per label that needs to be reported on to the caller.
//
@@ -224,8 +227,6 @@
return invalidResult(submitRule, submitRecord);
}
- rec.labels = new ArrayList<>(submitRecord.arity());
-
for (Term state : ((StructureTerm) submitRecord).args()) {
if (!(state instanceof StructureTerm)
|| 2 != state.arity()
@@ -234,7 +235,7 @@
}
SubmitRecord.Label lbl = new SubmitRecord.Label();
- rec.labels.add(lbl);
+ resultSubmitRecord.labels.add(lbl);
lbl.label = checkLabelName(state.arg(0).name());
Term status = state.arg(1);
@@ -265,24 +266,12 @@
}
}
- if (rec.status == SubmitRecord.Status.OK) {
- foundOk = true;
+ if (resultSubmitRecord.status == SubmitRecord.Status.OK) {
break;
}
}
- Collections.reverse(out);
-
- // This transformation is required to adapt Prolog's behavior to the way Gerrit handles
- // SubmitRecords, as defined in the SubmitRecord#allRecordsOK method.
- // When several rules are defined in Prolog, they are all matched to a SubmitRecord. We want
- // the change to be submittable when at least one result is OK.
- if (foundOk) {
- for (SubmitRecord record : out) {
- record.status = SubmitRecord.Status.OK;
- }
- }
-
- return out;
+ Collections.reverse(resultSubmitRecord.labels);
+ return resultSubmitRecord;
}
@VisibleForTesting
@@ -299,7 +288,7 @@
return VALID_LABEL_MATCHER.retainFrom(name);
}
- private List<SubmitRecord> invalidResult(Term rule, Term record, String reason) {
+ private SubmitRecord invalidResult(Term rule, Term record, String reason) {
return ruleError(
String.format(
"Submit rule %s for change %s of %s output invalid result: %s%s",
@@ -310,15 +299,15 @@
(reason == null ? "" : ". Reason: " + reason)));
}
- private List<SubmitRecord> invalidResult(Term rule, Term record) {
+ private SubmitRecord invalidResult(Term rule, Term record) {
return invalidResult(rule, record, null);
}
- private List<SubmitRecord> ruleError(String err) {
+ private SubmitRecord ruleError(String err) {
return ruleError(err, null);
}
- private List<SubmitRecord> ruleError(String err, Exception e) {
+ private SubmitRecord ruleError(String err, Exception e) {
if (opts.logErrors()) {
logger.atSevere().withCause(e).log(err);
return defaultRuleError();
diff --git a/java/com/google/gerrit/server/rules/SubmitRule.java b/java/com/google/gerrit/server/rules/SubmitRule.java
index 20cb8fb..b221117 100644
--- a/java/com/google/gerrit/server/rules/SubmitRule.java
+++ b/java/com/google/gerrit/server/rules/SubmitRule.java
@@ -16,13 +16,13 @@
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.extensions.annotations.ExtensionPoint;
import com.google.gerrit.server.query.change.ChangeData;
-import java.util.Collection;
+import java.util.Optional;
/**
* Allows plugins to decide whether a change is ready to be submitted or not.
*
- * <p>For a given {@link ChangeData}, each plugin is called and returns a {@link Collection} of
- * {@link SubmitRecord}. This collection can be empty, or contain one or several values.
+ * <p>For a given {@link ChangeData}, each plugin is called and returns a {@link Optional} of {@link
+ * SubmitRecord}.
*
* <p>A Change can only be submitted if all the plugins give their consent.
*
@@ -38,6 +38,9 @@
*/
@ExtensionPoint
public interface SubmitRule {
- /** Returns a {@link Collection} of {@link SubmitRecord} status for the change. */
- Collection<SubmitRecord> evaluate(ChangeData changeData);
+ /**
+ * Returns a {@link Optional} of {@link SubmitRecord} status for the change. {@code
+ * Optional#empty()} if the SubmitRule was a no-op.
+ */
+ Optional<SubmitRecord> evaluate(ChangeData changeData);
}
diff --git a/java/com/google/gerrit/server/update/RetryHelper.java b/java/com/google/gerrit/server/update/RetryHelper.java
index c8b101c..d24832b 100644
--- a/java/com/google/gerrit/server/update/RetryHelper.java
+++ b/java/com/google/gerrit/server/update/RetryHelper.java
@@ -315,7 +315,7 @@
.addTag(RequestId.Type.TRACE_ID, "retry-on-failure-" + new RequestId())
.forceLogging();
logger.atFine().withCause(t).log(
- "%s failed, retry with tracing enabled", caller);
+ "AutoRetry: %s failed, retry with tracing enabled", caller);
metrics.autoRetryCount.increment(actionType, caller);
return true;
}
@@ -323,7 +323,8 @@
// A non-recoverable failure occurred. We retried the operation with tracing
// enabled and it failed again. Log the failure so that admin can see if it
// differs from the failure that triggered the retry.
- logger.atFine().withCause(t).log("auto-retry of %s has failed", caller);
+ logger.atFine().withCause(t).log(
+ "AutoRetry: auto-retry of %s has failed", caller);
metrics.failuresOnAutoRetryCount.increment(actionType, caller);
return false;
}
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/GeneralPreferencesIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/GeneralPreferencesIT.java
index 12266c9..f253533 100644
--- a/javatests/com/google/gerrit/acceptance/api/accounts/GeneralPreferencesIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/GeneralPreferencesIT.java
@@ -28,7 +28,6 @@
import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DownloadCommand;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo.EmailFormat;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo.EmailStrategy;
-import com.google.gerrit.extensions.client.GeneralPreferencesInfo.ReviewCategoryStrategy;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo.TimeFormat;
import com.google.gerrit.extensions.client.MenuItem;
import com.google.gerrit.extensions.config.DownloadScheme;
@@ -66,15 +65,13 @@
new MenuItem("Edits", "#/q/has:edit", null),
new MenuItem("Watched Changes", "#/q/is:watched+is:open", null),
new MenuItem("Starred Changes", "#/q/is:starred", null),
- new MenuItem("Groups", "#/groups/self", null));
+ new MenuItem("Groups", "#/settings/#Groups", null));
assertThat(o.changeTable).isEmpty();
GeneralPreferencesInfo i = GeneralPreferencesInfo.defaults();
// change all default values
i.changesPerPage *= -1;
- i.showSiteHeader ^= true;
- i.useFlashClipboard ^= true;
i.downloadCommand = DownloadCommand.REPO_DOWNLOAD;
i.dateFormat = DateFormat.US;
i.timeFormat = TimeFormat.HHMM_24;
@@ -88,7 +85,6 @@
i.legacycidInChangeTable ^= true;
i.muteCommonPathPrefixes ^= true;
i.signedOffBy ^= true;
- i.reviewCategoryStrategy = ReviewCategoryStrategy.ABBREV;
i.diffView = DiffView.UNIFIED_DIFF;
i.my = new ArrayList<>();
i.my.add(new MenuItem("name", "url"));
diff --git a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
index 918b62d..359ed1d 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -83,6 +83,7 @@
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.FooterConstants;
+import com.google.gerrit.common.RawInputUtil;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.data.LabelFunction;
import com.google.gerrit.common.data.LabelType;
@@ -116,6 +117,7 @@
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.ProjectState;
import com.google.gerrit.extensions.client.ReviewerState;
import com.google.gerrit.extensions.client.Side;
import com.google.gerrit.extensions.client.SubmitType;
@@ -864,6 +866,21 @@
assertThat(thrown).hasMessageThat().contains("Cannot revert initial commit");
}
+ @Test
+ public void cantCreateRevertWithoutProjectWritePermission() throws Exception {
+ PushOneCommit.Result r = createChange();
+ gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).review(ReviewInput.approve());
+ gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).submit();
+ projectCache.checkedGet(project).getProject().setState(ProjectState.READ_ONLY);
+
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class, () -> gApi.changes().id(r.getChangeId()).revert());
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("project state " + ProjectState.READ_ONLY + " does not permit write");
+ }
+
@FunctionalInterface
private interface Rebase {
void call(String id) throws RestApiException;
@@ -1400,6 +1417,47 @@
}
@Test
+ public void deleteChangeRemovesItsChangeEdit() throws Exception {
+ PushOneCommit.Result result = createChange();
+
+ requestScopeOperations.setApiUser(user.id());
+ String changeId = result.getChangeId();
+ gApi.changes().id(changeId).edit().create();
+ gApi.changes()
+ .id(changeId)
+ .edit()
+ .modifyFile(FILE_NAME, RawInputUtil.create("foo".getBytes(UTF_8)));
+
+ requestScopeOperations.setApiUser(admin.id());
+ try (Repository repo = repoManager.openRepository(project)) {
+ String expected =
+ RefNames.refsUsers(user.id()) + "/edit-" + result.getChange().getId() + "/1";
+ assertThat(repo.getRefDatabase().getRefsByPrefix(expected)).isNotEmpty();
+ gApi.changes().id(changeId).delete();
+ assertThat(repo.getRefDatabase().getRefsByPrefix(expected)).isEmpty();
+ }
+ }
+
+ @Test
+ public void deleteChangeDoesntRemoveOtherChangeEdits() throws Exception {
+ PushOneCommit.Result result = createChange();
+ PushOneCommit.Result irrelevantChangeResult = createChange();
+ requestScopeOperations.setApiUser(admin.id());
+ String changeId = result.getChangeId();
+ String irrelevantChangeId = irrelevantChangeResult.getChangeId();
+
+ gApi.changes().id(irrelevantChangeId).edit().create();
+ gApi.changes()
+ .id(irrelevantChangeId)
+ .edit()
+ .modifyFile(FILE_NAME, RawInputUtil.create("foo".getBytes(UTF_8)));
+
+ gApi.changes().id(changeId).delete();
+
+ assertThat(gApi.changes().id(irrelevantChangeId).edit().get()).isPresent();
+ }
+
+ @Test
public void rebaseUpToDateChange() throws Exception {
PushOneCommit.Result r = createChange();
ResourceConflictException thrown =
diff --git a/javatests/com/google/gerrit/acceptance/api/change/ChangeSubmitRequirementIT.java b/javatests/com/google/gerrit/acceptance/api/change/ChangeSubmitRequirementIT.java
index 1842a9ec..4d76074 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/ChangeSubmitRequirementIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/ChangeSubmitRequirementIT.java
@@ -30,7 +30,7 @@
import com.google.gerrit.server.rules.SubmitRule;
import com.google.inject.Module;
import java.util.ArrayList;
-import java.util.Collection;
+import java.util.Optional;
import org.junit.Test;
public class ChangeSubmitRequirementIT extends AbstractDaemonTest {
@@ -66,12 +66,12 @@
private static class CustomSubmitRule implements SubmitRule {
@Override
- public Collection<SubmitRecord> evaluate(ChangeData changeData) {
+ public Optional<SubmitRecord> evaluate(ChangeData changeData) {
SubmitRecord record = new SubmitRecord();
record.labels = new ArrayList<>();
record.status = SubmitRecord.Status.NOT_READY;
record.requirements = ImmutableList.of(req);
- return ImmutableList.of(record);
+ return Optional.of(record);
}
}
}
diff --git a/javatests/com/google/gerrit/acceptance/api/change/SubmitTypeRuleIT.java b/javatests/com/google/gerrit/acceptance/api/change/SubmitTypeRuleIT.java
index 62600b2..aa613fb 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/SubmitTypeRuleIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/SubmitTypeRuleIT.java
@@ -262,8 +262,8 @@
TestSubmitRuleInput in = new TestSubmitRuleInput();
in.rule = "invalid prolog rule";
// We have no rules.pl by default. The fact that the default rules are showing up here is a bug.
- List<TestSubmitRuleInfo> response = gApi.changes().id(changeId).current().testSubmitRule(in);
- assertThat(response).containsExactly(invalidPrologRuleInfo());
+ TestSubmitRuleInfo response = gApi.changes().id(changeId).current().testSubmitRule(in);
+ assertThat(response).isEqualTo(invalidPrologRuleInfo());
}
@Test
@@ -274,8 +274,8 @@
TestSubmitRuleInput in = new TestSubmitRuleInput();
in.rule = "invalid prolog rule";
- List<TestSubmitRuleInfo> response = gApi.changes().id(changeId).current().testSubmitRule(in);
- assertThat(response).containsExactly(invalidPrologRuleInfo());
+ TestSubmitRuleInfo response = gApi.changes().id(changeId).current().testSubmitRule(in);
+ assertThat(response).isEqualTo(invalidPrologRuleInfo());
}
private static TestSubmitRuleInfo invalidPrologRuleInfo() {
diff --git a/javatests/com/google/gerrit/acceptance/rest/TraceIT.java b/javatests/com/google/gerrit/acceptance/rest/TraceIT.java
index 15b9a93..ac37530 100644
--- a/javatests/com/google/gerrit/acceptance/rest/TraceIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/TraceIT.java
@@ -52,8 +52,8 @@
import com.google.gerrit.server.validators.ValidationException;
import com.google.inject.Inject;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.List;
+import java.util.Optional;
import java.util.SortedMap;
import java.util.SortedSet;
import org.apache.http.message.BasicHeader;
@@ -679,7 +679,7 @@
boolean failOnce;
@Override
- public Collection<SubmitRecord> evaluate(ChangeData changeData) {
+ public Optional<SubmitRecord> evaluate(ChangeData changeData) {
if (failOnce) {
failOnce = false;
throw new IllegalStateException("forced failure from test");
@@ -691,7 +691,7 @@
SubmitRecord submitRecord = new SubmitRecord();
submitRecord.status = SubmitRecord.Status.OK;
- return ImmutableList.of(submitRecord);
+ return Optional.of(submitRecord);
}
}
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/SuggestReviewersIT.java b/javatests/com/google/gerrit/acceptance/rest/change/SuggestReviewersIT.java
index 5401a2c..6cc2059 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/SuggestReviewersIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/SuggestReviewersIT.java
@@ -34,7 +34,9 @@
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.api.accounts.EmailInput;
+import com.google.gerrit.extensions.api.changes.AddReviewerInput;
import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.client.ReviewerState;
import com.google.gerrit.extensions.common.ChangeInput;
import com.google.gerrit.extensions.common.SuggestedReviewerInfo;
import com.google.gerrit.extensions.restapi.RestApiException;
@@ -134,6 +136,27 @@
}
@Test
+ public void suggestReviewersWithExcludeGroups() throws Exception {
+ String changeId = createChange().getChangeId();
+
+ // by default groups are included
+ List<SuggestedReviewerInfo> reviewers = suggestReviewers(changeId, name("user"));
+ assertReviewers(
+ reviewers, ImmutableList.of(user1, user2, user3), ImmutableList.of(group1, group2, group3));
+
+ // exclude groups
+ reviewers =
+ gApi.changes().id(changeId).suggestReviewers(name("user")).excludeGroups(true).get();
+ assertReviewers(reviewers, ImmutableList.of(user1, user2, user3), ImmutableList.of());
+
+ // explicitly include groups
+ reviewers =
+ gApi.changes().id(changeId).suggestReviewers(name("user")).excludeGroups(false).get();
+ assertReviewers(
+ reviewers, ImmutableList.of(user1, user2, user3), ImmutableList.of(group1, group2, group3));
+ }
+
+ @Test
@GerritConfig(name = "accounts.visibility", value = "SAME_GROUP")
public void suggestReviewersSameGroupVisibility() throws Exception {
String changeId = createChange().getChangeId();
@@ -493,6 +516,38 @@
}
@Test
+ public void suggestNoExistingReviewers() throws Exception {
+ String name = name("foo");
+ TestAccount foo1 = accountCreator.create(name + "-1");
+ TestAccount foo2 = accountCreator.create(name + "-2");
+
+ String changeId = createChange().getChangeId();
+ assertReviewers(
+ suggestReviewers(changeId, name), ImmutableList.of(foo1, foo2), ImmutableList.of());
+
+ gApi.changes().id(changeId).addReviewer(foo2.id().toString());
+ assertReviewers(suggestReviewers(changeId, name), ImmutableList.of(foo1), ImmutableList.of());
+ }
+
+ @Test
+ public void suggestCcAsReviewer() throws Exception {
+ String name = name("foo");
+ TestAccount foo1 = accountCreator.create(name + "-1");
+ TestAccount foo2 = accountCreator.create(name + "-2");
+
+ String changeId = createChange().getChangeId();
+ assertReviewers(
+ suggestReviewers(changeId, name), ImmutableList.of(foo1, foo2), ImmutableList.of());
+
+ AddReviewerInput reviewerInput = new AddReviewerInput();
+ reviewerInput.reviewer = foo2.id().toString();
+ reviewerInput.state = ReviewerState.CC;
+ gApi.changes().id(changeId).addReviewer(reviewerInput);
+ assertReviewers(
+ suggestReviewers(changeId, name), ImmutableList.of(foo1, foo2), ImmutableList.of());
+ }
+
+ @Test
public void suggestBySecondaryEmailWithModifyAccount() throws Exception {
String secondaryEmail = "foo.secondary@example.com";
TestAccount foo = createAccountWithSecondaryEmail("foo", secondaryEmail);
diff --git a/javatests/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java b/javatests/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java
index 4a74018..1d87ca1 100644
--- a/javatests/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java
@@ -168,6 +168,8 @@
assertThat(i.change.replyLabel).isEqualTo("Reply\u2026");
assertThat(i.change.updateDelay).isEqualTo(300);
assertThat(i.change.disablePrivateChanges).isNull();
+ assertThat(i.change.submitWholeTopic).isNull();
+ assertThat(i.change.excludeMergeableInChangeInfo).isNull();
// download
assertThat(i.download.archives).containsExactly("tar", "tbz2", "tgz", "txz");
@@ -190,4 +192,18 @@
// user
assertThat(i.user.anonymousCowardName).isEqualTo(AnonymousCowardNameProvider.DEFAULT);
}
+
+ @Test
+ @GerritConfig(name = "change.submitWholeTopic", value = "true")
+ public void serverConfigWithSubmitWholeTopic() throws Exception {
+ ServerInfo i = gApi.config().server().getInfo();
+ assertThat(i.change.submitWholeTopic).isTrue();
+ }
+
+ @Test
+ @GerritConfig(name = "change.api.excludeMergeableInChangeInfo", value = "true")
+ public void serverConfigWithExcludeMergeableInChangeInfo() throws Exception {
+ ServerInfo i = gApi.config().server().getInfo();
+ assertThat(i.change.excludeMergeableInChangeInfo).isTrue();
+ }
}
diff --git a/javatests/com/google/gerrit/acceptance/server/rules/IgnoreSelfApprovalRuleIT.java b/javatests/com/google/gerrit/acceptance/server/rules/IgnoreSelfApprovalRuleIT.java
index 37237c6..1c820af 100644
--- a/javatests/com/google/gerrit/acceptance/server/rules/IgnoreSelfApprovalRuleIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/rules/IgnoreSelfApprovalRuleIT.java
@@ -15,6 +15,7 @@
package com.google.gerrit.acceptance.server.rules;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth8.assertThat;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
@@ -24,8 +25,8 @@
import com.google.gerrit.common.data.SubmitRequirement;
import com.google.gerrit.server.rules.IgnoreSelfApprovalRule;
import com.google.inject.Inject;
-import java.util.Collection;
import java.util.Map;
+import java.util.Optional;
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
import org.eclipse.jgit.junit.TestRepository;
import org.junit.Test;
@@ -41,10 +42,10 @@
PushOneCommit.Result r = createChange();
approve(r.getChangeId());
- Collection<SubmitRecord> submitRecords = rule.evaluate(r.getChange());
+ Optional<SubmitRecord> submitRecord = rule.evaluate(r.getChange());
- assertThat(submitRecords).hasSize(1);
- SubmitRecord result = submitRecords.iterator().next();
+ assertThat(submitRecord).isPresent();
+ SubmitRecord result = submitRecord.get();
assertThat(result.status).isEqualTo(SubmitRecord.Status.NOT_READY);
assertThat(result.labels).isNotEmpty();
assertThat(result.requirements)
@@ -67,8 +68,8 @@
// Approve as admin
approve(r.getChangeId());
- Collection<SubmitRecord> submitRecords = rule.evaluate(r.getChange());
- assertThat(submitRecords).isEmpty();
+ Optional<SubmitRecord> submitRecord = rule.evaluate(r.getChange());
+ assertThat(submitRecord).isEmpty();
}
@Test
@@ -78,8 +79,8 @@
PushOneCommit.Result r = createChange();
approve(r.getChangeId());
- Collection<SubmitRecord> submitRecords = rule.evaluate(r.getChange());
- assertThat(submitRecords).isEmpty();
+ Optional<SubmitRecord> submitRecord = rule.evaluate(r.getChange());
+ assertThat(submitRecord).isEmpty();
}
private void enableRule(String labelName, boolean newState) throws Exception {
diff --git a/javatests/com/google/gerrit/acceptance/server/rules/PrologRuleEvaluatorIT.java b/javatests/com/google/gerrit/acceptance/server/rules/PrologRuleEvaluatorIT.java
index efc3b5b..de2b105 100644
--- a/javatests/com/google/gerrit/acceptance/server/rules/PrologRuleEvaluatorIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/rules/PrologRuleEvaluatorIT.java
@@ -15,6 +15,7 @@
package com.google.gerrit.acceptance.server.rules;
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.gerrit.acceptance.AbstractDaemonTest;
@@ -30,7 +31,6 @@
import com.googlecode.prolog_cafe.lang.StructureTerm;
import com.googlecode.prolog_cafe.lang.Term;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.List;
import org.eclipse.jgit.lib.ObjectId;
import org.junit.Test;
@@ -46,9 +46,9 @@
StructureTerm labels = new StructureTerm("label", verifiedLabel);
List<Term> terms = ImmutableList.of(makeTerm("ok", labels));
- Collection<SubmitRecord> records = evaluator.resultsToSubmitRecord(null, terms);
+ SubmitRecord record = evaluator.resultsToSubmitRecord(null, terms);
- assertThat(records).hasSize(1);
+ assertThat(record.status).isEqualTo(SubmitRecord.Status.OK);
}
/**
@@ -113,23 +113,16 @@
terms.add(makeTerm("not_ready", makeLabels(label3)));
// When
- List<SubmitRecord> records = evaluator.resultsToSubmitRecord(null, terms);
+ SubmitRecord record = evaluator.resultsToSubmitRecord(null, terms);
// assert that
- SubmitRecord record1Expected = new SubmitRecord();
- record1Expected.status = SubmitRecord.Status.OK;
- record1Expected.labels = new ArrayList<>();
- record1Expected.labels.add(submitRecordLabel2);
+ SubmitRecord expectedRecord = new SubmitRecord();
+ expectedRecord.status = SubmitRecord.Status.OK;
+ expectedRecord.labels = new ArrayList<>();
+ expectedRecord.labels.add(submitRecordLabel2);
+ expectedRecord.labels.add(submitRecordLabel3);
- SubmitRecord record2Expected = new SubmitRecord();
- record2Expected.status = SubmitRecord.Status.OK;
- record2Expected.labels = new ArrayList<>();
- record2Expected.labels.add(submitRecordLabel3);
-
- assertThat(records).hasSize(2);
-
- assertThat(records.get(0)).isEqualTo(record1Expected);
- assertThat(records.get(1)).isEqualTo(record2Expected);
+ assertThat(record).isEqualTo(expectedRecord);
}
private static Term makeTerm(String status, StructureTerm labels) {
diff --git a/javatests/com/google/gerrit/server/account/PreferencesTest.java b/javatests/com/google/gerrit/server/account/PreferencesTest.java
new file mode 100644
index 0000000..b1d31bf
--- /dev/null
+++ b/javatests/com/google/gerrit/server/account/PreferencesTest.java
@@ -0,0 +1,66 @@
+// Copyright (C) 2019 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.truth.Truth.assertThat;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.server.git.ValidationError;
+import org.eclipse.jgit.lib.Config;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+/** Tests for parsing user preferences from Git. */
+public class PreferencesTest {
+
+ enum Unknown {
+ STATE
+ }
+
+ @Test
+ public void ignoreUnknownAccountPreferencesWhenParsing() {
+ ValidationError.Sink errorSink = Mockito.mock(ValidationError.Sink.class);
+ Preferences preferences =
+ new Preferences(Account.id(1), configWithUnknownEntries(), new Config(), errorSink);
+ GeneralPreferencesInfo parsedPreferences = preferences.getGeneralPreferences();
+
+ assertThat(parsedPreferences).isNotNull();
+ assertThat(parsedPreferences.expandInlineDiffs).isTrue();
+ verifyNoMoreInteractions(errorSink);
+ }
+
+ @Test
+ public void ignoreUnknownDefaultAccountPreferencesWhenParsing() {
+ ValidationError.Sink errorSink = Mockito.mock(ValidationError.Sink.class);
+ Preferences preferences =
+ new Preferences(Account.id(1), new Config(), configWithUnknownEntries(), errorSink);
+ GeneralPreferencesInfo parsedPreferences = preferences.getGeneralPreferences();
+
+ assertThat(parsedPreferences).isNotNull();
+ assertThat(parsedPreferences.expandInlineDiffs).isTrue();
+ verifyNoMoreInteractions(errorSink);
+ }
+
+ private static Config configWithUnknownEntries() {
+ Config cfg = new Config();
+ cfg.setBoolean("general", null, "expandInlineDiffs", true);
+ cfg.setBoolean("general", null, "unknown", true);
+ cfg.setEnum("general", null, "unknownenum", Unknown.STATE);
+ cfg.setString("general", null, "unknownstring", "bla");
+ return cfg;
+ }
+}
diff --git a/javatests/com/google/gerrit/server/logging/MetadataTest.java b/javatests/com/google/gerrit/server/logging/MetadataTest.java
new file mode 100644
index 0000000..89e5690
--- /dev/null
+++ b/javatests/com/google/gerrit/server/logging/MetadataTest.java
@@ -0,0 +1,36 @@
+// Copyright (C) 2019 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.logging;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+
+public class MetadataTest {
+
+ @Test
+ public void stringForLoggingOmitsEmptyOptionalValuesAndReformatsOptionalValuesThatArePresent() {
+ Metadata metadata = Metadata.builder().accountId(1000001).branchName("refs/heads/foo").build();
+ assertThat(metadata.toStringForLogging())
+ .isEqualTo("Metadata{accountId=1000001, branchName=refs/heads/foo, pluginMetadata=[]}");
+ }
+
+ @Test
+ public void
+ stringForLoggingOmitsEmptyOptionalValuesAndReformatsOptionalValuesThatArePresentNoFieldsSet() {
+ Metadata metadata = Metadata.builder().build();
+ assertThat(metadata.toStringForLogging()).isEqualTo("Metadata{pluginMetadata=[]}");
+ }
+}
diff --git a/plugins/replication b/plugins/replication
index 90253f8..9b10716 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit 90253f88e42653a0c3af632beae507847f71adec
+Subproject commit 9b10716f0afaf58e29e591d22859120c4e35a702
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 8e57534..63f3eaf 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
@@ -329,8 +329,17 @@
name: 'ldap/tests tests'}});
assert.equal(element._rules.length, 3);
assert.equal(Object.keys(element._groupsWithRules).length, 3);
- assert.deepEqual(element.permission.value.rules['ldap:CN=test test'],
- {action: 'ALLOW', min: -2, max: 2, added: true});
+ if (Polymer.Element) {
+ // Under Polymer 2 gr-rule-editor.js#_handleValueChange get's
+ // fully loaded before this change, thus `modified: true` get's managed
+ // to be added. Under Polymer 1 it was a mix hence why it was not
+ // added in time for when this test ran.
+ assert.deepEqual(element.permission.value.rules['ldap:CN=test test'],
+ {action: 'ALLOW', min: -2, max: 2, modified: true, added: true});
+ } else {
+ assert.deepEqual(element.permission.value.rules['ldap:CN=test test'],
+ {action: 'ALLOW', min: -2, max: 2, added: true});
+ }
// New rule should be removed if cancel from editing.
element.editing = false;
assert.equal(element._rules.length, 2);
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 61362a8..5459749 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
@@ -344,6 +344,12 @@
} else if (obj[k].added) {
this._updateAddRemoveObj(addRemoveObj,
path.concat(ref), 'add', obj[k]);
+ /**
+ * As add / delete both can happen in the new section,
+ * so here to make sure it will remove the deleted ones.
+ * @see Issue 11339
+ */
+ this._recursivelyRemoveDeleted(addRemoveObj.add[k]);
continue;
}
this._recursivelyUpdateAddRemoveObj(obj[k], addRemoveObj,
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 abd11d6..8b1de6e 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
@@ -416,6 +416,72 @@
assert.deepEqual(obj, expectedResult);
});
+ test('_recursivelyUpdateAddRemoveObj on new added section', () => {
+ const obj = {
+ 'refs/for/*': {
+ permissions: {
+ 'label-Code-Review': {
+ rules: {
+ e798fed07afbc9173a587f876ef8760c78d240c1: {
+ min: -2,
+ max: 2,
+ action: 'ALLOW',
+ added: true,
+ },
+ },
+ added: true,
+ label: 'Code-Review',
+ },
+ 'labelAs-Code-Review': {
+ rules: {
+ 'ldap:gerritcodereview-eng': {
+ min: -2,
+ max: 2,
+ action: 'ALLOW',
+ added: true,
+ deleted: true,
+ },
+ },
+ added: true,
+ label: 'Code-Review',
+ },
+ },
+ added: true,
+ },
+ };
+
+ const expectedResult = {
+ add: {
+ 'refs/for/*': {
+ permissions: {
+ 'label-Code-Review': {
+ rules: {
+ e798fed07afbc9173a587f876ef8760c78d240c1: {
+ min: -2,
+ max: 2,
+ action: 'ALLOW',
+ added: true,
+ },
+ },
+ added: true,
+ label: 'Code-Review',
+ },
+ 'labelAs-Code-Review': {
+ rules: {},
+ added: true,
+ label: 'Code-Review',
+ },
+ },
+ added: true,
+ },
+ },
+ remove: {},
+ };
+ const updateObj = {add: {}, remove: {}};
+ element._recursivelyUpdateAddRemoveObj(obj, updateObj);
+ assert.deepEqual(updateObj, expectedResult);
+ });
+
test('_handleSaveForReview with no changes', () => {
assert.deepEqual(element._computeAddAndRemove(), {add: {}, remove: {}});
});
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
index 8176d80..cbb653b 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
@@ -330,6 +330,12 @@
-ms-user-select: text;
user-select: text;
}
+
+ .whitespace-change-only-message {
+ background-color: var(--diff-context-control-background-color);
+ border: 1px solid var(--diff-context-control-border-color);
+ text-align: center;
+ }
</style>
<style include="gr-syntax-theme"></style>
<style include="gr-ranged-comment-theme"></style>
@@ -365,6 +371,13 @@
id="diffTable"
class$="[[_diffTableClass]]"
role="presentation"></table>
+
+ <template is="dom-if" if="[[showNoChangeMessage(loading, prefs, _diffLength)]]">
+ <div class="whitespace-change-only-message">
+ This file only contains whitespace changes.
+ Modify the whitespace setting to see the changes.
+ </div>
+ </template>
</gr-diff-builder>
</gr-diff-highlight>
</gr-diff-selection>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
index c69b313..113e28f 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
@@ -296,6 +296,12 @@
this._unobserveNodes();
},
+ showNoChangeMessage(loading, prefs, diffLength) {
+ return !loading &&
+ prefs && prefs.ignore_whitespace !== 'IGNORE_NONE'
+ && diffLength === 0;
+ },
+
_enableSelectionObserver(loggedIn, isAttached) {
// Polymer 2: check for undefined
if ([loggedIn, isAttached].some(arg => arg === undefined)) {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
index a86f5c7..15deaff 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
@@ -1007,6 +1007,100 @@
});
});
});
+
+ suite('whitespace changes only message', () => {
+ const setupDiff = function(ignore_whitespace, diffContent) {
+ element = fixture('basic');
+ element.prefs = {
+ ignore_whitespace,
+ auto_hide_diff_table_header: true,
+ context: 10,
+ cursor_blink_rate: 0,
+ font_size: 12,
+ intraline_difference: true,
+ line_length: 100,
+ line_wrapping: false,
+ show_line_endings: true,
+ show_tabs: true,
+ show_whitespace_errors: true,
+ syntax_highlighting: true,
+ tab_size: 8,
+ theme: 'DEFAULT',
+ };
+
+ element.diff = {
+ intraline_status: 'OK',
+ change_type: 'MODIFIED',
+ diff_header: [
+ 'diff --git a/carrot.js b/carrot.js',
+ 'index 2adc47d..f9c2f2c 100644',
+ '--- a/carrot.js',
+ '+++ b/carrot.jjs',
+ 'file differ',
+ ],
+ content: diffContent,
+ binary: true,
+ };
+
+ element._renderDiffTable();
+ flushAsynchronousOperations();
+ };
+
+ test('show the message if ignore_whitespace is criteria matches', () => {
+ setupDiff('IGNORE_ALL', [{skip: 100}]);
+ assert.isTrue(element.showNoChangeMessage(
+ /* loading= */ false,
+ element.prefs,
+ element._diffLength
+ ));
+ });
+
+ test('do not show the message if still loading', () => {
+ setupDiff('IGNORE_ALL', [{skip: 100}]);
+ assert.isFalse(element.showNoChangeMessage(
+ /* loading= */ true,
+ element.prefs,
+ element._diffLength
+ ));
+ });
+
+ test('do not show the message if contains valid changes', () => {
+ const content = [{
+ a: ['all work and no play make andybons a dull boy'],
+ b: ['elgoog elgoog elgoog'],
+ }, {
+ ab: [
+ 'Non eram nescius, Brute, cum, quae summis ingeniis ',
+ 'exquisitaque doctrina philosophi Graeco sermone tractavissent',
+ ],
+ }];
+ setupDiff('IGNORE_ALL', content);
+ assert.equal(element._diffLength, 3);
+ assert.isFalse(element.showNoChangeMessage(
+ /* loading= */ false,
+ element.prefs,
+ element._diffLength
+ ));
+ });
+
+ test('do not show message if ignore whitespace is disabled', () => {
+ const content = [{
+ a: ['all work and no play make andybons a dull boy'],
+ b: ['elgoog elgoog elgoog'],
+ }, {
+ ab: [
+ 'Non eram nescius, Brute, cum, quae summis ingeniis ',
+ 'exquisitaque doctrina philosophi Graeco sermone tractavissent',
+ ],
+ }];
+ setupDiff('IGNORE_NONE', content);
+ assert.isFalse(element.showNoChangeMessage(
+ /* loading= */ false,
+ element.prefs,
+ element._diffLength
+ ));
+ });
+ });
});
a11ySuite('basic');
diff --git a/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api.html b/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api.html
new file mode 100644
index 0000000..74b87c8
--- /dev/null
+++ b/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api.html
@@ -0,0 +1,18 @@
+<!--
+@license
+Copyright (C) 2019 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.
+-->
+
+<script src="gr-styles-api.js"></script>
diff --git a/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api.js b/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api.js
new file mode 100644
index 0000000..879b392
--- /dev/null
+++ b/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api.js
@@ -0,0 +1,77 @@
+/**
+ * @license
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+(function(window) {
+ 'use strict';
+
+ // Prevent redefinition.
+ if (window.GrStylesApi) { return; }
+
+ let styleObjectCount = 0;
+
+ function GrStyleObject(rulesStr) {
+ this._rulesStr = rulesStr;
+ this._className = `__pg_js_api_class_${styleObjectCount}`;
+ styleObjectCount++;
+ }
+
+ /**
+ * Creates a new unique CSS class and injects it in a root node of the element
+ * if it hasn't been added yet. A root node is an document or is the
+ * associated shadowRoot. This class can be added to any element with the same
+ * root node.
+ * @param {HTMLElement} element The element to get class name for.
+ * @return {string} Appropriate class name for the element is returned
+ */
+ GrStyleObject.prototype.getClassName = function(element) {
+ const rootNode = Polymer.Settings.useShadow
+ ? element.getRootNode() : document.body;
+ if (!rootNode.__pg_js_api_style_tags) {
+ rootNode.__pg_js_api_style_tags = {};
+ }
+ if (!rootNode.__pg_js_api_style_tags[this._className]) {
+ const styleTag = document.createElement('style');
+ styleTag.innerHTML = `.${this._className} { ${this._rulesStr} }`;
+ rootNode.appendChild(styleTag);
+ rootNode.__pg_js_api_style_tags[this._className] = true;
+ }
+ return this._className;
+ };
+
+ /**
+ * Apply shared style to the element.
+ * @param {HTMLElement} element The element to apply style for
+ */
+ GrStyleObject.prototype.apply = function(element) {
+ element.classList.add(this.getClassName(element));
+ };
+
+
+ function GrStylesApi() {
+ }
+
+ /**
+ * Creates a new GrStyleObject with specified style properties.
+ * @param {string} String with style properties.
+ * @return {GrStyleObject}
+ */
+ GrStylesApi.prototype.css = function(ruleStr) {
+ return new GrStyleObject(ruleStr);
+ };
+
+
+ window.GrStylesApi = GrStylesApi;
+})(window);
diff --git a/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api_test.html b/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api_test.html
new file mode 100644
index 0000000..d67a309
--- /dev/null
+++ b/polygerrit-ui/app/elements/plugins/gr-styles-api/gr-styles-api_test.html
@@ -0,0 +1,182 @@
+<!DOCTYPE html>
+<!--
+@license
+Copyright (C) 2019 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-admin-api</title>
+<script src="/test/common-test-setup.js"></script>
+<script src="/bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
+
+<script src="/bower_components/webcomponentsjs/webcomponents-lite.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="../../shared/gr-js-api-interface/gr-js-api-interface.html">
+<link rel="import" href="gr-styles-api.html">
+
+<script>void(0);</script>
+
+<dom-module id="gr-style-test-element">
+ <template>
+ <div id="wrapper"></div>
+ </template>
+ <script>Polymer({is: 'gr-style-test-element'});</script>
+</dom-module>
+
+<script>
+ suite('gr-styles-api tests', () => {
+ let sandbox;
+ let stylesApi;
+
+ setup(() => {
+ sandbox = sinon.sandbox.create();
+ let plugin;
+ Gerrit.install(p => { plugin = p; }, '0.1',
+ 'http://test.com/plugins/testplugin/static/test.js');
+ sandbox.stub(Gerrit, '_arePluginsLoaded').returns(true);
+ stylesApi = plugin.styles();
+ });
+
+ teardown(() => {
+ stylesApi = null;
+ sandbox.restore();
+ });
+
+ test('exists', () => {
+ assert.isOk(stylesApi);
+ });
+
+ test('css', () => {
+ const styleObject = stylesApi.css('background: red');
+ assert.isDefined(styleObject);
+ });
+ });
+
+ suite('GrStyleObject tests', () => {
+ let sandbox;
+ let stylesApi;
+ let displayInlineStyle;
+ let displayNoneStyle;
+
+ setup(() => {
+ sandbox = sinon.sandbox.create();
+ let plugin;
+ Gerrit.install(p => { plugin = p; }, '0.1',
+ 'http://test.com/plugins/testplugin/static/test.js');
+ sandbox.stub(Gerrit, '_arePluginsLoaded').returns(true);
+ stylesApi = plugin.styles();
+ displayInlineStyle = stylesApi.css('display: inline');
+ displayNoneStyle = stylesApi.css('display: none');
+ });
+
+ teardown(() => {
+ displayInlineStyle = null;
+ displayNoneStyle = null;
+ stylesApi = null;
+ sandbox.restore();
+ });
+
+ function createNestedElements(parentElement) {
+ /* parentElement
+ * |--- element1
+ * |--- element2
+ * |--- element3
+ **/
+ const element1 = document.createElement('div');
+ const element2 = document.createElement('div');
+ const element3 = document.createElement('div');
+ Polymer.dom(parentElement).appendChild(element1);
+ Polymer.dom(parentElement).appendChild(element2);
+ Polymer.dom(element2).appendChild(element3);
+
+ return [element1, element2, element3];
+ }
+
+
+ test('getClassName - body level elements', () => {
+ const bodyLevelElements = createNestedElements(document.body);
+
+ testGetClassName(bodyLevelElements);
+ });
+
+ test('getClassName - elements inside polymer element', () => {
+ const polymerElement = document.createElement('gr-style-test-element');
+ Polymer.dom(document.body).appendChild(polymerElement);
+ const contentElements = createNestedElements(polymerElement.$.wrapper);
+
+ testGetClassName(contentElements);
+ });
+
+ function testGetClassName(elements) {
+ assertAllElementsHaveDefaultStyle(elements);
+
+ const className1 = displayInlineStyle.getClassName(elements[0]);
+ const className2 = displayNoneStyle.getClassName(elements[1]);
+ const className3 = displayInlineStyle.getClassName(elements[2]);
+
+ assert.notEqual(className2, className1);
+ assert.equal(className3, className1);
+
+ assertAllElementsHaveDefaultStyle(elements);
+
+ elements[0].classList.add(className1);
+ elements[1].classList.add(className2);
+ elements[2].classList.add(className1);
+
+ assertDisplayPropertyValues(elements, ['inline', 'none', 'inline']);
+ }
+
+ test('apply - body level elements', () => {
+ const bodyLevelElements = createNestedElements(document.body);
+
+ testApply(bodyLevelElements);
+ });
+
+ test('apply - elements inside polymer element', () => {
+ const polymerElement = document.createElement('gr-style-test-element');
+ Polymer.dom(document.body).appendChild(polymerElement);
+ const contentElements = createNestedElements(polymerElement.$.wrapper);
+
+ testApply(contentElements);
+ });
+
+ function testApply(elements) {
+ assertAllElementsHaveDefaultStyle(elements);
+ displayInlineStyle.apply(elements[0]);
+ displayNoneStyle.apply(elements[1]);
+ displayInlineStyle.apply(elements[2]);
+ assertDisplayPropertyValues(elements, ['inline', 'none', 'inline']);
+ }
+
+
+ function assertAllElementsHaveDefaultStyle(elements) {
+ for (const element of elements) {
+ assert.equal(getComputedStyle(element).getPropertyValue('display'),
+ 'block');
+ }
+ }
+
+ function assertDisplayPropertyValues(elements, expectedDisplayValues) {
+ for (const key in elements) {
+ if (elements.hasOwnProperty(key)) {
+ assert.equal(
+ getComputedStyle(elements[key]).getPropertyValue('display'),
+ expectedDisplayValues[key]);
+ }
+ }
+ }
+ });
+</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context.js
index 45f28d1..76cb51f 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context.js
@@ -43,25 +43,26 @@
* Method to add annotations to a content line.
* @param {number} offset The char offset where the update starts.
* @param {number} length The number of chars that the update covers.
- * @param {string} cssClass The name of a CSS class created using Gerrit.css.
+ * @param {GrStyleObject} styleObject The style object for the range.
* @param {string} side The side of the update. ('left' or 'right')
*/
GrAnnotationActionsContext.prototype.annotateRange = function(
- offset, length, cssClass, side) {
+ offset, length, styleObject, side) {
if (this._contentEl && this._contentEl.getAttribute('data-side') == side) {
- GrAnnotation.annotateElement(this._contentEl, offset, length, cssClass);
+ GrAnnotation.annotateElement(this._contentEl, offset, length,
+ styleObject.getClassName(this._contentEl));
}
};
/**
* Method to add a CSS class to the line number TD element.
- * @param {string} cssClass The name of a CSS class created using Gerrit.css.
+ * @param {GrStyleObject} styleObject The style object for the range.
* @param {string} side The side of the update. ('left' or 'right')
*/
GrAnnotationActionsContext.prototype.annotateLineNumber = function(
- cssClass, side) {
+ styleObject, side) {
if (this._lineNumberEl && this._lineNumberEl.classList.contains(side)) {
- this._lineNumberEl.classList.add(cssClass);
+ styleObject.apply(this._lineNumberEl);
}
};
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context_test.html
index 653999c..2a6487e 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-context_test.html
@@ -42,9 +42,13 @@
let sandbox;
let el;
let lineNumberEl;
+ let plugin;
setup(() => {
sandbox = sinon.sandbox.create();
+ Gerrit.install(p => { plugin = p; }, '0.1',
+ 'http://test.com/plugins/testplugin/static/test.js');
+
const str = 'lorem ipsum blah blah';
const line = {text: str};
el = document.createElement('div');
@@ -64,32 +68,34 @@
annotateElementSpy = sandbox.spy(GrAnnotation, 'annotateElement');
const start = 0;
const end = 100;
- const cssClass = Gerrit.css('background-color: #000000');
+ const cssStyleObject = plugin.styles().css('background-color: #000000');
// Assert annotateElement is not called when side is different.
- instance.annotateRange(start, end, cssClass, 'left');
+ instance.annotateRange(start, end, cssStyleObject, 'left');
assert.equal(annotateElementSpy.callCount, 0);
// Assert annotateElement is called once when side is the same.
- instance.annotateRange(start, end, cssClass, 'right');
+ instance.annotateRange(start, end, cssStyleObject, 'right');
assert.equal(annotateElementSpy.callCount, 1);
const args = annotateElementSpy.getCalls()[0].args;
assert.equal(args[0], el);
assert.equal(args[1], start);
assert.equal(args[2], end);
- assert.equal(args[3], cssClass);
+ assert.equal(args[3], cssStyleObject.getClassName(el));
});
test('test annotateLineNumber', () => {
- const cssClass = Gerrit.css('background-color: #000000');
+ const cssStyleObject = plugin.styles().css('background-color: #000000');
+
+ const className = cssStyleObject.getClassName(lineNumberEl);
// Assert that css class is *not* applied when side is different.
- instance.annotateLineNumber(cssClass, 'left');
- assert.isFalse(lineNumberEl.classList.contains(cssClass));
+ instance.annotateLineNumber(cssStyleObject, 'left');
+ assert.isFalse(lineNumberEl.classList.contains(className));
// Assert that css class is applied when side is the same.
- instance.annotateLineNumber(cssClass, 'right');
- assert.isTrue(lineNumberEl.classList.contains(cssClass));
+ instance.annotateLineNumber(cssStyleObject, 'right');
+ assert.isTrue(lineNumberEl.classList.contains(className));
});
});
</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.html
index e04867a..e460660 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.html
@@ -26,6 +26,7 @@
<link rel="import" href="../../plugins/gr-popup-interface/gr-popup-interface.html">
<link rel="import" href="../../plugins/gr-repo-api/gr-repo-api.html">
<link rel="import" href="../../plugins/gr-settings-api/gr-settings-api.html">
+<link rel="import" href="../../plugins/gr-styles-api/gr-styles-api.html">
<link rel="import" href="../../plugins/gr-theme-api/gr-theme-api.html">
<link rel="import" href="../gr-rest-api-interface/gr-rest-api-interface.html">
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
index fff2e33..2dd0e095 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
@@ -319,6 +319,10 @@
return new GrSettingsApi(this);
};
+ Plugin.prototype.styles = function() {
+ return new GrStylesApi();
+ };
+
/**
* To make REST requests for plugin-provided endpoints, use
* @example
@@ -511,7 +515,13 @@
'Please use plugin.getPluginName() instead.');
};
+ /**
+ * @deprecated Use plugin.styles().css(rulesStr) instead. Please, consult
+ * the documentation how to replace it accordingly.
+ */
Gerrit.css = function(rulesStr) {
+ console.warn('Gerrit.css(rulesStr) is deprecated!',
+ 'Use plugin.styles().css(rulesStr)');
if (!Gerrit._customStyleSheet) {
const styleEl = document.createElement('style');
document.head.appendChild(styleEl);
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.html b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.html
index 5697e77..61facc0 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.html
+++ b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text.html
@@ -16,8 +16,9 @@
-->
<link rel="import" href="/bower_components/polymer/polymer.html">
-<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
+<link rel="import" href="../../../behaviors/base-url-behavior/base-url-behavior.html">
<link rel="import" href="../../../styles/shared-styles.html">
+<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
<script src="/bower_components/ba-linkify/ba-linkify.js"></script>
<script src="link-text-parser.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.html b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.html
index 73295b1..3483d0e 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-linked-text/gr-linked-text_test.html
@@ -126,6 +126,24 @@
assert.equal(linkEl.textContent, changeID);
});
+ test('Change-Id pattern was parsed and linked with base url', () => {
+ window.CANONICAL_PATH = '/r';
+
+ // "Change-Id:" pattern.
+ const changeID = 'I11d6a37f5e9b5df0486f6c922d8836dfa780e03e';
+ const prefix = 'Change-Id: ';
+ element.content = prefix + changeID;
+
+ const textNode = element.$.output.childNodes[0];
+ const linkEl = element.$.output.childNodes[1];
+ assert.equal(textNode.textContent, prefix);
+ const url = '/r/q/' + changeID;
+ assert.equal(linkEl.target, '_blank');
+ // Since url is a path, the host is added automatically.
+ assert.isTrue(linkEl.href.endsWith(url));
+ assert.equal(linkEl.textContent, changeID);
+ });
+
test('Multiple matches', () => {
element.content = 'Issue 3650\nIssue 3450';
const linkEl1 = element.$.output.childNodes[0];
@@ -185,6 +203,15 @@
assert.equal(linkEl.textContent, 'foo');
});
+ test('html with base url', () => {
+ window.CANONICAL_PATH = '/r';
+
+ element.content = 'hash:foo';
+ const linkEl = element.$.output.childNodes[0];
+ assert.isTrue(linkEl.href.endsWith('/r/awesomesauce'));
+ assert.equal(linkEl.textContent, 'foo');
+ });
+
test('disabled config', () => {
element.content = 'foo:baz';
assert.equal(element.$.output.innerHTML, 'foo:baz');
diff --git a/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js b/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js
index 8526c3e..c5ac00e 100644
--- a/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js
+++ b/polygerrit-ui/app/elements/shared/gr-linked-text/link-text-parser.js
@@ -50,6 +50,7 @@
this.linkConfig = linkConfig;
this.callback = callback;
this.removeZeroWidthSpace = opt_removeZeroWidthSpace;
+ this.baseUrl = Gerrit.BaseUrlBehavior.getBaseUrl();
Object.preventExtensions(this);
}
@@ -172,6 +173,10 @@
GrLinkTextParser.prototype.addLink =
function(text, href, position, length, outputArray) {
if (!text || this.hasOverlap(position, length, outputArray)) { return; }
+ if (!!this.baseUrl && href.startsWith('/') &&
+ !href.startsWith(this.baseUrl)) {
+ href = this.baseUrl + href;
+ }
this.addItem(text, href, null, position, length, outputArray);
};
@@ -189,6 +194,10 @@
GrLinkTextParser.prototype.addHTML =
function(html, position, length, outputArray) {
if (this.hasOverlap(position, length, outputArray)) { return; }
+ if (!!this.baseUrl && html.match(/<a href=\"\//g) &&
+ !html.match(`/<a href=\"${this.baseUrl}/g`)) {
+ html = html.replace(/<a href=\"\//g, `<a href=\"${this.baseUrl}\/`);
+ }
this.addItem(null, null, html, position, length, outputArray);
};
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 e235741..9b63f75 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
@@ -349,11 +349,7 @@
* @param {Defs.FetchJSONRequest} req
*/
_fetchJSON(req) {
- if (!req.fetchOptions) req.fetchOptions = {};
- if (!req.fetchOptions.headers) req.fetchOptions.headers = new Headers();
- if (!req.fetchOptions.headers.has('Accept')) {
- req.fetchOptions.headers.append('Accept', 'application/json');
- }
+ req = this._addAcceptJsonHeader(req);
return this._fetchRawJSON(req).then(response => {
if (!response) {
return;
@@ -425,6 +421,19 @@
return JSON.parse(source.substring(JSON_PREFIX.length));
},
+ /**
+ * @param {Defs.FetchJSONRequest} req
+ * @return {Defs.FetchJSONRequest}
+ */
+ _addAcceptJsonHeader(req) {
+ if (!req.fetchOptions) req.fetchOptions = {};
+ if (!req.fetchOptions.headers) req.fetchOptions.headers = new Headers();
+ if (!req.fetchOptions.headers.has('Accept')) {
+ req.fetchOptions.headers.append('Accept', 'application/json');
+ }
+ return req;
+ },
+
getConfig(noCache) {
if (!noCache) {
return this._fetchSharedCacheURL({
@@ -1126,7 +1135,8 @@
return;
}
this._credentialCheck.checking = true;
- const req = {url: '/accounts/self/detail', reportUrlAsIs: true};
+ let req = {url: '/accounts/self/detail', reportUrlAsIs: true};
+ req = this._addAcceptJsonHeader(req);
// Skip the REST response cache.
return this._fetchRawJSON(req).then(res => {
if (!res) { return; }
@@ -1399,7 +1409,7 @@
return this.getChangeActionURL(changeNum, null, '/detail').then(url => {
const urlWithParams = this._urlWithParams(url, optionsHex);
const params = {O: optionsHex};
- const req = {
+ let req = {
url,
errFn: opt_errFn,
cancelCondition: opt_cancelCondition,
@@ -1407,6 +1417,7 @@
fetchOptions: this._etags.getOptions(urlWithParams),
anonymizedUrl: '/changes/*~*/detail?O=' + optionsHex,
};
+ req = this._addAcceptJsonHeader(req);
return this._fetchRawJSON(req).then(response => {
if (response && response.status === 304) {
return Promise.resolve(this._parsePrefixedJSON(
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
index 2299d62..9d0d83a 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
@@ -575,6 +575,15 @@
});
});
+ test('checkCredentials accepts only json', () => {
+ const authFetchStub = sandbox.stub(element._auth, 'fetch')
+ .returns(Promise.resolve());
+ element.checkCredentials();
+ assert.isTrue(authFetchStub.called);
+ assert.equal(authFetchStub.lastCall.args[1].headers.get('Accept'),
+ 'application/json');
+ });
+
test('legacy n,z key in change url is replaced', () => {
const stub = sandbox.stub(element, '_fetchJSON')
.returns(Promise.resolve([]));
@@ -1227,6 +1236,16 @@
});
});
+ test('_getChangeDetail accepts only json', () => {
+ const authFetchStub = sandbox.stub(element._auth, 'fetch')
+ .returns(Promise.resolve());
+ const errFn = sinon.stub();
+ element._getChangeDetail(123, '516714', errFn);
+ assert.isTrue(authFetchStub.called);
+ assert.equal(authFetchStub.lastCall.args[1].headers.get('Accept'),
+ 'application/json');
+ });
+
test('_getChangeDetail populates _projectLookup', () => {
sandbox.stub(element, 'getChangeActionURL')
.returns(Promise.resolve(''));
diff --git a/polygerrit-ui/app/samples/coverage-plugin.html b/polygerrit-ui/app/samples/coverage-plugin.html
index f8b5560..f5f9c6e 100644
--- a/polygerrit-ui/app/samples/coverage-plugin.html
+++ b/polygerrit-ui/app/samples/coverage-plugin.html
@@ -32,6 +32,11 @@
const coverageData = {};
let displayCoverage = false;
const annotationApi = plugin.annotationApi();
+ const styleApi = plugin.styles();
+
+ const coverageStyle = styleApi.css('background-color: #EF9B9B !important');
+ const emptyStyle = styleApi.css('');
+
annotationApi.addLayer(context => {
if (Object.keys(coverageData).length === 0) {
// Coverage data is not ready yet.
@@ -41,16 +46,16 @@
const line = context.line;
// Highlight lines missing coverage with this background color if
// coverage should be displayed, else do nothing.
- const cssClass = displayCoverage
- ? Gerrit.css('background-color: #EF9B9B')
- : Gerrit.css('');
+ const annotationStyle = displayCoverage
+ ? coverageStyle
+ : emptyStyle;
if (coverageData[path] &&
coverageData[path].changeNum === context.changeNum &&
coverageData[path].patchNum === context.patchNum) {
const linesMissingCoverage = coverageData[path].linesMissingCoverage;
if (linesMissingCoverage.includes(line.afterNumber)) {
- context.annotateRange(0, line.text.length, cssClass, 'right');
- context.annotateLineNumber(cssClass, 'right');
+ context.annotateRange(0, line.text.length, annotationStyle, 'right');
+ context.annotateLineNumber(annotationStyle, 'right');
}
}
}).enableToggleCheckbox('Display Coverage', checkbox => {
diff --git a/polygerrit-ui/app/test/index.html b/polygerrit-ui/app/test/index.html
index 2b052a0..0fbc8f1 100644
--- a/polygerrit-ui/app/test/index.html
+++ b/polygerrit-ui/app/test/index.html
@@ -125,6 +125,7 @@
'edit/gr-edit-file-controls/gr-edit-file-controls_test.html',
'edit/gr-editor-view/gr-editor-view_test.html',
'plugins/gr-admin-api/gr-admin-api_test.html',
+ 'plugins/gr-styles-api/gr-styles-api_test.html',
'plugins/gr-attribute-helper/gr-attribute-helper_test.html',
'plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.html',
'plugins/gr-event-helper/gr-event-helper_test.html',
diff --git a/tools/BUILD b/tools/BUILD
index 89ce558..f4718e1 100644
--- a/tools/BUILD
+++ b/tools/BUILD
@@ -55,7 +55,7 @@
"-Xep:FunctionalInterfaceClash:ERROR",
"-Xep:FutureReturnValueIgnored:ERROR",
"-Xep:GetClassOnEnum:ERROR",
- "-Xep:ImmutableAnnotationChecker:ERROR",
+ "-Xep:ImmutableAnnotationChecker:WARN",
"-Xep:ImmutableEnumChecker:ERROR",
"-Xep:IncompatibleModifiers:ERROR",
"-Xep:InjectOnConstructorOfAbstractClass:ERROR",