Merge branch 'stable-3.1'
* stable-3.1:
Set version to 3.1.0-rc3
Remove robot comments without human reply from "Comment Threads" tab
Fix selected label style
Remove unused jsAPI from gr-diff-builder
Remove buttons (Reply, Ack, Quote, Done) for robot comments
Report tab change - mostly for checks plugin
Increase padding from 4px to 8px for commit message
Add keyup to some shortcuts to avoid multiple events for longpress
Move externs to types folder
Added a types.js file to support cross-module types
Upgrade JGit to latest master revision
Change-Id: Ic4af9229dd1e9e690cf326f84f918cc07e210acf
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 419f440..b764439 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -2666,6 +2666,28 @@
filterClass = org.anyorg.MySecureIPFilter
----
+[[filterClass.className.initParam]]filterClass.<className>.initParam::
++
+Gerrit supports customized pluggable HTTP filters as `filterClass`. This
+option allows to pass extra initialization parameters to the filter. It
+allows for multiple key/value pairs to be passed in this pattern:
++
+----
+initParam = <key>=<value>
+----
+For a comprehensive example:
++
+----
+[httpd]
+ filterClass = org.anyorg.AFilter
+ filterClass = org.anyorg.BFilter
+[filterClass "org.anyorg.AFilter"]
+ key1 = value1
+ key2 = value2
+[filterClass "org.anyorg.BFilter"]
+ key3 = value3
+----
+
[[httpd.idleTimeout]]httpd.idleTimeout::
+
Maximum idle time for a connection, which roughly translates to the
@@ -3594,6 +3616,39 @@
+
Default is false.
+[[operator-alias]]
+=== Section operator alias
+
+Operator aliasing allows global aliases to be defined for query operators.
+Currently only change queries are supported. The alias name is the git
+config key name, and the operator being aliased is the git config value.
+
+For example:
+
+----
+[operator-alias "change"]
+ oldage = age
+ number = change
+----
+
+This section is particularly useful to alias operator names which may be
+long and clunky because they include a plugin name in them to a shorter
+name without the plugin name.
+
+Aliases are resolved dynamically at invocation time to any currently
+loaded versions of plugins. If the alias points to an operator provided
+by a plugin which is not currently loaded, or the plugin does not define
+the operator, then "unsupported operator" is returned to the user.
+
+Aliases will override existing operators. In the case of multiple aliases
+with the same name, the last one defined will be used.
+
+When the target of an alias doesn't exist, the operator with the name
+of the alias will be used (if present). This enables an admin to config
+the system to override a core operator with an operator provided by a
+plugin when present and otherwise fall back to the operator provided by
+core.
+
[[pack]]
=== Section pack
@@ -3748,6 +3803,19 @@
+
Default is true.
+[[receive.enableInMemoryRefCache]]receive.enableInMemoryRefCache::
++
+If true, Gerrit will cache all refs advertised during push in memory and
+base later receive operations on that cache.
++
+Turning this cache off is considered experimental.
++
+This cache provides value when the ref database is slow and/or does not
+offer an inverse lookup of object ID to ref name. When RefTable is used,
+this cache can be turned off (experimental) to get speed improvements.
++
+Default is true.
+
[[receive.enableSignedPush]]receive.enableSignedPush::
+
If true, server-side signed push validation is enabled.
diff --git a/Documentation/config-plugins.txt b/Documentation/config-plugins.txt
index edeec54..af00d1c 100644
--- a/Documentation/config-plugins.txt
+++ b/Documentation/config-plugins.txt
@@ -37,11 +37,9 @@
[[core-plugins]]
== Core Plugins
-Core plugins are packaged within the Gerrit war file and can easily be
-installed during the link:pgm-init.html[Gerrit initialization].
-
-The core plugins are developed and maintained by the Gerrit maintainers
-and the Gerrit community.
+link:dev-core-plugins.html[Core plugins] are packaged within the Gerrit
+war file and can easily be installed during the link:pgm-init.html[
+Gerrit initialization].
Note that the documentation and configuration links in the list below are
to the plugins' master branch. Please refer to the appropriate branch or
diff --git a/Documentation/dev-core-plugins.txt b/Documentation/dev-core-plugins.txt
new file mode 100644
index 0000000..11027ef
--- /dev/null
+++ b/Documentation/dev-core-plugins.txt
@@ -0,0 +1,94 @@
+= Gerrit Code Review - Core Plugins
+
+[[definition]]
+== What are core plugins?
+
+Core plugins are plugins that are packaged within the Gerrit war file. This
+means during the link:pgm-init.html[Gerrit initialization] they can be easily
+installed without downloading any additional files.
+
+To make working with core plugins easy, they are linked as
+link:https://gerrit.googlesource.com/gerrit/+/refs/heads/master/.gitmodules[Git
+submodules] in the `gerrit` repository. E.g. this means they can be easily
+link:dev-readme.html#clone[cloned] together with Gerrit.
+
+All core plugins are developed and maintained by the
+link:dev-roles.html#maintainers[Gerrit maintainers] and everyone can
+link:dev-contributing.html[contribute] to them.
+
+Adding a new core plugin feature that is large or complex requires a
+link:dev-design-doc.html[design doc] (also see
+link:dev-contributing.html#design-driven-contribution-process[design-driven
+contribution process]). The link:dev-processes.html#steering-committee[
+engineering steering committee (ESC)] is the authority that approves the design
+docs. The ESC is also in charge of adding and removing core plugins.
+
+Non-Gerrit maintainers cannot have link:access-control.html#category_owner[
+Owner] permissions for core plugins.
+
+[[criteria]]
+=== Criteria for Core Plugins
+
+To be considered as a core plugin, a plugin must fulfill the following
+criteria:
+
+1. License:
++
+The plugin code is available under the
+link:http://www.apache.org/licenses/LICENSE-2.0[Apache License Version 2.0].
+
+2. Hosting:
++
+The plugin development is hosted on the
+link:https://gerrit-review.googlesource.com[gerrit-review] Gerrit Server.
+
+3. Scope:
++
+The plugin functionality is Gerrit-related, has a clear scope and does not
+conflict with other core plugins or existing and planned Gerrit core features.
+
+4. Relevance:
++
+The plugin functionality is relevant to a majority of the Gerrit community:
++
+--
+** An out of the box Gerrit installation would seem like it is missing
+ something if the plugin is not installed.
+** It's expected that most sites would use the plugin.
+** Multiple parties (different organizations/companies) already use the plugin
+ and agree that it should be offered as core plugin.
+** If the same or similar functionality is provided by multiple plugins,
+ the plugin is the clear recommended solution by the community.
+--
++
+Whether a plugin is relevant to a majority of the Gerrit community must be
+discussed on a case-by-case basis. In case of doubt, it's up to the
+link:dev-processes.html#steering-committee[engineering steering committee] to
+make a decision.
+
+5. Code Quality:
++
+The plugin code is mature and has a good test coverage. Maintaining the plugin
+code creates only little overhead for the Gerrit maintainers.
+
+6. Documentation:
++
+The plugin functionality is fully documented.
+
+7. Ownership:
++
+Existing plugin owners which are not Gerrit maintainers must agree to give up
+their ownership. If the current plugin owners disagree, forking the plugin is
+possible, but this should happen only in exceptional cases.
+
+[[list]]
+== Which core plugins exist?
+
+See link:config-plugins.html#core-plugins[here].
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/Documentation/dev-processes.txt b/Documentation/dev-processes.txt
index f4e77a8..6472f2a 100644
--- a/Documentation/dev-processes.txt
+++ b/Documentation/dev-processes.txt
@@ -7,7 +7,8 @@
The Gerrit project has an engineering steering committee (ESC) that is
in charge of:
-* Gerrit core (the `gerrit` project) and the core plugins
+* Gerrit core (the `gerrit` project) and the link:dev-core-plugins.html[core
+ plugins]
* defining the project vision and the project scope
* maintaining a roadmap, a release plan and a prioritized backlog
* ensuring timely design reviews
@@ -294,6 +295,11 @@
vulnerability and define action items to follow up in the
link:https://bugs.chromium.org/p/gerrit[issue tracker].
+[[core-plugins]]
+== Core Plugins
+
+See link:dev-core-plugins.html[here].
+
[[upgrading-libraries]]
== Upgrading Libraries
diff --git a/Documentation/dev-readme.txt b/Documentation/dev-readme.txt
index 02b1891..34b409c 100644
--- a/Documentation/dev-readme.txt
+++ b/Documentation/dev-readme.txt
@@ -5,6 +5,7 @@
== Git Setup
+[[clone]]
=== Getting the Source
Create a new client workspace:
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 09ec415..4165f93 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -1501,6 +1501,90 @@
change is new
----
+[[revert-submission]]
+=== Revert Submission
+--
+'POST /changes/link:#change-id[\{change-id\}]/revert_submission'
+--
+
+Creates open revert changes for all of the changes of a certain submission.
+
+Details for the revert can be specified in the request body inside a link:#revert-input[
+RevertInput] The topic of all created revert changes will be
+`revert-{submission_id}-{random_string_of_size_10}`.
+
+The changes will not be rebased on onto the destination branch so the users may still
+have to manually rebase them to resolve conflicts and make them submittable.
+
+.Request
+----
+ POST /changes/myProject~master~I1ffe09a505e25f15ce1521bcfb222e51e62c2a14/revert_submission HTTP/1.0
+----
+
+As response link:#revert-submission-info[RevertSubmissionInfo] entity
+is returned. That entity describes the revert changes.
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ "revert_changes":
+ [
+ {
+ "id": "myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940",
+ "project": "myProject",
+ "branch": "master",
+ "topic": "revert--1571043962462-3640749-ABCEEZGHIJ",
+ "change_id": "I8473b95934b5732ac55d26311a706c9c2bde9940",
+ "subject": "Revert \"Implementing Feature X\"",
+ "status": "NEW",
+ "created": "2013-02-01 09:59:32.126000000",
+ "updated": "2013-02-21 11:16:36.775000000",
+ "mergeable": true,
+ "insertions": 6,
+ "deletions": 4,
+ "_number": 3965,
+ "owner": {
+ "name": "John Doe"
+ }
+ },
+ {
+ "id": "anyProject~master~1eee2c9d8f352483781e772f35dc586a69ff5646",
+ "project": "anyProject",
+ "branch": "master",
+ "topic": "revert--1571043962462-3640749-ABCEEZGHIJ",
+ "change_id": "I1eee2c9d8f352483781e772f35dc586a69ff5646",
+ "subject": "Revert \"Implementing Feature Y\"",
+ "status": "NEW",
+ "created": "2013-02-04 09:59:33.126000000",
+ "updated": "2013-02-21 11:16:37.775000000",
+ "mergeable": true,
+ "insertions": 62,
+ "deletions": 11,
+ "_number": 3966,
+ "owner": {
+ "name": "Jane Doe"
+ }
+ }
+ ]
+----
+
+If any of the changes cannot be reverted because the change state doesn't
+allow reverting the change, the response is "`409 Conflict`" and
+the error message is contained in the response body.
+
+.Response
+----
+ HTTP/1.1 409 Conflict
+ Content-Disposition: attachment
+ Content-Type: text/plain; charset=UTF-8
+
+ change is new
+----
+
[[submit-change]]
=== Submit Change
--
@@ -6931,10 +7015,24 @@
Additional information about whom to notify about the revert as a map
of recipient type to link:#notify-info[NotifyInfo] entity.
|`topic` |optional|
-Name of the topic for the revert change. If not set, the default is the topic
-of the change being reverted.
+Name of the topic for the revert change. If not set, the default for Revert
+endpoint is the topic of the change being reverted, and the default for the
+RevertSubmission endpoint is `revert-{submission_id}-{timestamp.now}`.
|=============================
+[[revert-submission-info]]
+=== RevertSubmissionInfo
+The `RevertSubmissionInfo` describes the revert changes.
+
+[options="header",cols="1,6"]
+|==============================
+|Field Name | Description
+|`revert_changes` |
+A list of link:#change-info[ChangeInfo] that describes the revert changes. Each
+entity in that list is a revert change that was created in that revert
+submission.
+|==============================
+
[[review-info]]
=== ReviewInfo
The `ReviewInfo` entity contains information about a review.
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index c1349aa..77be4b3 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -2925,6 +2925,314 @@
HTTP/1.1 204 No Content
----
+[[label-endpoints]]
+== Label Endpoints
+
+[[list-labels]]
+=== List Labels
+--
+'GET /projects/link:#project-name[\{project-name\}]/labels/'
+--
+
+Lists the labels that are defined in this project.
+
+The calling user must have read access to the `refs/meta/config` branch of the
+project.
+
+.Request
+----
+ GET /projects/All-Projects/labels/ HTTP/1.0
+----
+
+As result a list of link:#label-definition-info[LabelDefinitionInfo] entities
+is returned that describe the labels that are defined in this project
+(inherited labels are not returned unless the `inherited` parameter is set, see
+link:#list-with-inherited-labels[below]). The returned labels are sorted by
+label name.
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ [
+ {
+ "name": "Code-Review",
+ "project": "All-Projects",
+ "function": "MaxWithBlock",
+ "values": {
+ " 0": "No score",
+ "-1": "I would prefer this is not merged as is",
+ "-2": "This shall not be merged",
+ "+1": "Looks good to me, but someone else must approve",
+ "+2": "Looks good to me, approved"
+ },
+ "default_value": 0,
+ "can_override": true,
+ "copy_min_score": true,
+ "copy_all_scores_if_no_change": true,
+ "copy_all_scores_on_trivial_rebase": true,
+ "allow_post_submit": true
+ }
+ ]
+----
+
+[[list-with-inherited-labels]]
+To include inherited labels from all parent projects the parameter `inherited`
+can be set.
+
+The calling user must have read access to the `refs/meta/config` branch of the
+project and all its parent projects.
+
+.Request
+----
+ GET /projects/My-Project/labels/?inherited HTTP/1.0
+----
+
+As result a list of link:#label-definition-info[LabelDefinitionInfo] entities
+is returned that describe the labels that are defined in this project and in
+all its parent projects. The returned labels are sorted by parent projects
+in-order from `All-Projects` through the project hierarchy to this project.
+Labels that belong to the same project are sorted by label name.
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ [
+ {
+ "name": "Code-Review",
+ "project": "All-Projects",
+ "function": "MaxWithBlock",
+ "values": {
+ " 0": "No score",
+ "-1": "I would prefer this is not merged as is",
+ "-2": "This shall not be merged",
+ "+1": "Looks good to me, but someone else must approve",
+ "+2": "Looks good to me, approved"
+ },
+ "default_value": 0,
+ "can_override": true,
+ "copy_min_score": true,
+ "copy_all_scores_if_no_change": true,
+ "copy_all_scores_on_trivial_rebase": true,
+ "allow_post_submit": true
+ },
+ {
+ "name": "Foo-Review",
+ "project": "My-Project",
+ "function": "MaxWithBlock",
+ "values": {
+ " 0": "No score",
+ "-1": "I would prefer this is not merged as is",
+ "-2": "This shall not be merged",
+ "+1": "Looks good to me, but someone else must approve",
+ "+2": "Looks good to me, approved"
+ },
+ "default_value": 0,
+ "can_override": true,
+ "copy_any_score": true,
+ "allow_post_submit": true
+ }
+ ]
+----
+
+[[get-label]]
+=== Get Label
+--
+'GET /projects/link:#project-name[\{project-name\}]/labels/link:#label-name[\{label-name\}]'
+--
+
+Retrieves the definition of a label that is defined in this project.
+
+The calling user must have read access to the `refs/meta/config` branch of the
+project.
+
+.Request
+----
+ GET /projects/All-Projects/labels/Code-Review HTTP/1.0
+----
+
+As response a link:#label-definition-info[LabelDefinitionInfo] entity is
+returned that describes the label.
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ {
+ "name": "Code-Review",
+ "project": "All-Projects",
+ "function": "MaxWithBlock",
+ "values": {
+ " 0": "No score",
+ "-1": "I would prefer this is not merged as is",
+ "-2": "This shall not be merged",
+ "+1": "Looks good to me, but someone else must approve",
+ "+2": "Looks good to me, approved"
+ },
+ "default_value": 0,
+ "can_override": true,
+ "copy_min_score": true,
+ "copy_all_scores_if_no_change": true,
+ "copy_all_scores_on_trivial_rebase": true,
+ "allow_post_submit": true
+ }
+----
+
+[[create-label]]
+=== Create Label
+--
+'PUT /projects/link:#project-name[\{project-name\}]/labels/link:#label-name[\{label-name\}]'
+--
+
+Creates a new label definition in this project.
+
+The calling user must have write access to the `refs/meta/config` branch of the
+project.
+
+If a label with this name is already defined in this project, this label
+definition is updated (see link:#set-label[Set Label]).
+
+.Request
+----
+ PUT /projects/My-Project/labels/Foo HTTP/1.0
+ Content-Type: application/json; charset=UTF-8
+
+ {
+ "commit_message": "Create Foo Label",
+ "values": {
+ " 0": "No score",
+ "-1": "I would prefer this is not merged as is",
+ "-2": "This shall not be merged",
+ "+1": "Looks good to me, but someone else must approve",
+ "+2": "Looks good to me, approved"
+ }
+ }
+----
+
+As response a link:#label-definition-info[LabelDefinitionInfo] entity is
+returned that describes the created label.
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ {
+ "name": "Foo",
+ "project_name": "My-Project",
+ "function": "MaxWithBlock",
+ "values": {
+ " 0": "No score",
+ "-1": "I would prefer this is not merged as is",
+ "-2": "This shall not be merged",
+ "+1": "Looks good to me, but someone else must approve",
+ "+2": "Looks good to me, approved"
+ },
+ "default_value": 0,
+ "can_override": true,
+ "copy_all_scores_if_no_change": true,
+ "allow_post_submit": true
+ }
+----
+
+[[set-label]]
+=== Set Label
+--
+'PUT /projects/link:#project-name[\{project-name\}]/labels/link:#label-name[\{label-name\}]'
+--
+
+Updates the definition of a label that is defined in this project.
+
+The calling user must have write access to the `refs/meta/config` branch of the
+project.
+
+Properties which are not set in the input entity are not modified.
+
+.Request
+----
+ PUT /projects/All-Projects/labels/Code-Review HTTP/1.0
+ Content-Type: application/json; charset=UTF-8
+
+ {
+ "commit_message": "Ignore self approvals for Code-Review label",
+ "ignore_self_approval": true
+ }
+----
+
+As response a link:#label-definition-info[LabelDefinitionInfo] entity is
+returned that describes the updated label.
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ {
+ "name": "Code-Review",
+ "project": "All-Projects",
+ "function": "MaxWithBlock",
+ "values": {
+ " 0": "No score",
+ "-1": "I would prefer this is not merged as is",
+ "-2": "This shall not be merged",
+ "+1": "Looks good to me, but someone else must approve",
+ "+2": "Looks good to me, approved"
+ },
+ "default_value": 0,
+ "can_override": true,
+ "copy_min_score": true,
+ "copy_all_scores_if_no_change": true,
+ "copy_all_scores_on_trivial_rebase": true,
+ "allow_post_submit": true,
+ "ignore_self_approval": true
+ }
+----
+
+[[delete-label]]
+=== Delete Label
+--
+'DELETE /projects/link:#project-name[\{project-name\}]/labels/link:#label-name[\{label-name\}]'
+--
+
+Deletes the definition of a label that is defined in this project.
+
+The calling user must have write access to the `refs/meta/config` branch of the
+project.
+
+The request body does not need to include a link:#delete-label-input[
+DeleteLabelInput] entity if no commit message is specified.
+
+.Request
+----
+ DELETE /projects/My-Project/labels/Foo-Review HTTP/1.0
+ Content-Type: application/json; charset=UTF-8
+
+ {
+ "commit_message": "Delete Foo-Review label",
+ }
+----
+
+If a label was deleted the response is "`204 No Content`".
+
+.Response
+----
+ HTTP/1.1 204 No Content
+----
+
[[ids]]
== IDs
@@ -2949,6 +3257,10 @@
A special dashboard ID is `default` which represents the default
dashboard of a project.
+[[label-name]]
+=== \{label-name\}
+The name of a review label.
+
[[project-name]]
=== \{project-name\}
The name of the project.
@@ -3122,6 +3434,25 @@
|`enabled` |optional|Whether the commentlink is enabled, as documented
in link:config-gerrit.html#commentlink.name.enabled[
commentlink.name.enabled]. If not set the commentlink is enabled.
+
+[[commentlink-input]]
+=== CommentLinkInput
+The `CommentLinkInput` entity describes the input for a
+link:config-gerrit.html#commentlink[commentlink].
+
+|==================================================
+[options="header",cols="1,^2,4"]
+|==================================================
+|Field Name | |Description
+|`match` | |A JavaScript regular expression to match
+positions to be replaced with a hyperlink, as documented in
+link:config-gerrit.html#commentlink.name.match[commentlink.name.match].
+|`link` | |The URL to direct the user to whenever the
+regular expression is matched, as documented in
+link:config-gerrit.html#commentlink.name.link[commentlink.name.link].
+|`enabled` |optional|Whether the commentlink is enabled, as documented
+in link:config-gerrit.html#commentlink.name.enabled[
+commentlink.name.enabled]. If not set the commentlink is enabled.
|==================================================
[[config-info]]
@@ -3272,6 +3603,11 @@
Whether empty commits should be rejected when a change is merged.
Can be `TRUE`, `FALSE` or `INHERIT`. +
If not set, this setting is not updated.
+|commentlinks |optional|
+Map of commentlink names to link:#commentlink-input[CommentLinkInput]
+entities to add or update on the project. If the given commentlink
+already exists, it will be updated with the given values, otherwise
+it will be created. If the value is null, that entry is deleted.
|======================================================
[[config-parameter-info]]
@@ -3377,6 +3713,19 @@
Tokens such as `${project}` are not resolved.
|===========================
+[[delete-label-input]]
+=== DeleteLabelInput
+The `DeleteLabelInput` entity contains information for deleting a label
+definition in a project.
+
+[options="header",cols="1,^2,4"]
+|=============================
+|Field Name ||Description
+|`commit_message`|optional|
+Message that should be used to commit the deletion of the label in the
+`project.config` file to the `refs/meta/config` branch.
+|=============================
+
[[delete-branches-input]]
=== DeleteBranchesInput
The `DeleteBranchesInput` entity contains information about branches that should
@@ -3459,6 +3808,123 @@
Not set if there is no parent.
|================================
+[[label-definition-info]]
+=== LabelDefinitionInfo
+The `LabelTypeInfo` entity describes a link:config-labels.html[
+review label].
+
+[options="header",cols="1,^2,4"]
+|=============================
+|Field Name ||Description
+|`name` ||
+The link:config-labels.html#label_name[name] of the label.
+|`project_name` ||
+The name of the project in which this label is defined.
+|`function` ||
+The link:config-labels.html#label_function[function] of the label (can be
+`MaxWithBlock`, `AnyWithBlock`, `MaxNoBlock`, `NoBlock`, `NoOp` and `PatchSetLock`.
+|`values` ||
+The link:config-labels.html#label_value[values] of the label as a map of label
+value to value description. The label values are formatted strings, e.g. "+1"
+instead of "1", " 0" instead of "0".
+|`default_value` ||
+The link:config-labels.html#label_defaultValue[default value] of the label (as
+integer).
+|`branches` |optional|
+A list of link:config-labels.html#label_branch[branches] for which the label
+applies. A branch can be a ref, a ref pattern or a regular expression. If not
+set, the label applies for all branches.
+|`can_override` |`false` if not set|
+Whether this label can be link:config-labels.html#label_canOverride[overridden]
+by child projects.
+|`copy_any_score`|`false` if not set|
+Whether link:config-labels.html#label_copyAnyScore[copyAnyScore] is set on the
+label.
+|`copy_min_score`|`false` if not set|
+Whether link:config-labels.html#label_copyMinScore[copyMinScore] is set on the
+label.
+|`copy_max_score`|`false` if not set|
+Whether link:config-labels.html#label_copyMaxScore[copyMaxScore] is set on the
+label.
+|`copy_all_scores_if_no_change`|`false` if not set|
+Whether link:config-labels.html#label_copyAllScoresIfNoChange[
+copyAllScoresIfNoChange] is set on the label.
+|`copy_all_scores_if_no_code_change`|`false` if not set|
+Whether link:config-labels.html#label_copyAllScoresIfNoCodeChange[
+copyAllScoresIfNoCodeChange] is set on the label.
+|`copy_all_scores_on_trivial_rebase`|`false` if not set|
+Whether link:config-labels.html#label_copyAllScoresOnTrivialRebase[
+copyAllScoresOnTrivialRebase] is set on the label.
+|`copy_all_scores_on_merge_first_parent_update`|`false` if not set|
+Whether link:config-labels.html#label_copyAllScoresOnMergeFirstParentUpdate[
+copyAllScoresOnMergeFirstParentUpdate] is set on the label.
+|`allow_post_submit`|`false` if not set|
+Whether link:config-labels.html#label_allowPostSubmit[allowPostSubmit] is set
+on the label.
+|`ignore_self_approval`|`false` if not set|
+Whether link:config-labels.html#label_ignoreSelfApproval[ignoreSelfApproval] is
+set on the label.
+|=============================
+
+[[label-definition-input]]
+=== LabelDefinitionInput
+The `LabelTypeInput` entity describes a link:config-labels.html[
+review label].
+
+[options="header",cols="1,^2,4"]
+|=============================
+|Field Name ||Description
+|`commit_message`|optional|
+Message that should be used to commit the change of the label in the
+`project.config` file to the `refs/meta/config` branch.
+|`name` |optional|
+The new link:config-labels.html#label_name[name] of the label.
+|`function` |optional|
+The new link:config-labels.html#label_function[function] of the label (can be
+`MaxWithBlock`, `AnyWithBlock`, `MaxNoBlock`, `NoBlock`, `NoOp` and `PatchSetLock`.
+|`values` |optional|
+The new link:config-labels.html#label_value[values] of the label as a map of
+label value to value description. The label values are formatted strings, e.g.
+"+1" instead of "1", " 0" instead of "0".
+|`default_value` |optional|
+The new link:config-labels.html#label_defaultValue[default value] of the label
+(as integer).
+|`branches` |optional|
+The new branches for which the label applies as a list of
+link:config-labels.html#label_branch[branches]. A branch can be a ref, a ref
+pattern or a regular expression. If not set, the label applies for all
+branches.
+|`can_override` |optional|
+Whether this label can be link:config-labels.html#label_canOverride[overridden]
+by child projects.
+|`copy_any_score`|optional|
+Whether link:config-labels.html#label_copyAnyScore[copyAnyScore] is set on the
+label.
+|`copy_min_score`|optional|
+Whether link:config-labels.html#label_copyMinScore[copyMinScore] is set on the
+label.
+|`copy_max_score`|optional|
+Whether link:config-labels.html#label_copyMaxScore[copyMaxScore] is set on the
+label.
+|`copy_all_scores_if_no_change`|optional|
+Whether link:config-labels.html#label_copyAllScoresIfNoChange[
+copyAllScoresIfNoChange] is set on the label.
+|`copy_all_scores_if_no_code_change`|optional|
+Whether link:config-labels.html#label_copyAllScoresIfNoCodeChange[
+copyAllScoresIfNoCodeChange] is set on the label.
+|`copy_all_scores_on_trivial_rebase`|optional|
+Whether link:config-labels.html#label_copyAllScoresOnTrivialRebase[
+copyAllScoresOnTrivialRebase] is set on the label.
+|`copy_all_scores_on_merge_first_parent_update`|optional|
+Whether link:config-labels.html#label_copyAllScoresOnMergeFirstParentUpdate[
+copyAllScoresOnMergeFirstParentUpdate] is set on the label.
+|`allow_post_submit`|optional|
+Whether link:config-labels.html#label_allowPostSubmit[allowPostSubmit] is set
+on the label.
+|`ignore_self_approval`|optional|
+Whether link:config-labels.html#label_ignoreSelfApproval[ignoreSelfApproval] is
+set on the label.
+|=============================
[[label-type-info]]
=== LabelTypeInfo
diff --git a/WORKSPACE b/WORKSPACE
index 475f822..ac3d5e0 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -776,8 +776,8 @@
maven_jar(
name = "dropwizard-core",
- artifact = "io.dropwizard.metrics:metrics-core:4.0.7",
- sha1 = "673899f605f52ca35836673ccfee97154a496a61",
+ artifact = "io.dropwizard.metrics:metrics-core:4.1.1",
+ sha1 = "ebfafc716d9c3b6151dc7c2c09ce925a163a4f21",
)
# When updating Bouncy Castle, also update it in bazlets.
diff --git a/java/com/google/gerrit/common/data/LabelType.java b/java/com/google/gerrit/common/data/LabelType.java
index f9cd562..14b8310 100644
--- a/java/com/google/gerrit/common/data/LabelType.java
+++ b/java/com/google/gerrit/common/data/LabelType.java
@@ -157,6 +157,10 @@
return name;
}
+ public void setName(String name) {
+ this.name = checkName(name);
+ }
+
public boolean matches(PatchSetApproval psa) {
return psa.labelId().get().equalsIgnoreCase(name);
}
@@ -173,6 +177,7 @@
return canOverride;
}
+ @Nullable
public List<String> getRefPatterns() {
return refPatterns;
}
@@ -198,7 +203,7 @@
}
public void setRefPatterns(List<String> refPatterns) {
- if (refPatterns != null) {
+ if (refPatterns != null && !refPatterns.isEmpty()) {
this.refPatterns =
refPatterns.stream().collect(collectingAndThen(toList(), Collections::unmodifiableList));
} else {
@@ -210,6 +215,10 @@
return values;
}
+ public void setValues(List<LabelValue> values) {
+ this.values = sortValues(values);
+ }
+
public LabelValue getMin() {
if (values.isEmpty()) {
return null;
diff --git a/java/com/google/gerrit/common/data/PatchScript.java b/java/com/google/gerrit/common/data/PatchScript.java
index c177e35..73e301b 100644
--- a/java/com/google/gerrit/common/data/PatchScript.java
+++ b/java/com/google/gerrit/common/data/PatchScript.java
@@ -37,30 +37,44 @@
GITLINK
}
+ public static class PatchScriptFileInfo {
+ public final String name;
+ public final FileMode mode;
+ public final SparseFileContent content;
+ public final DisplayMethod displayMethod;
+ public final String mimeType;
+ public final String commitId;
+
+ public PatchScriptFileInfo(
+ String name,
+ FileMode mode,
+ SparseFileContent content,
+ DisplayMethod displayMethod,
+ String mimeType,
+ String commitId) {
+ this.name = name;
+ this.mode = mode;
+ this.content = content;
+ this.displayMethod = displayMethod;
+ this.mimeType = mimeType;
+ this.commitId = commitId;
+ }
+ }
+
private Change.Key changeId;
private ChangeType changeType;
- private String oldName;
- private String newName;
- private FileMode oldMode;
- private FileMode newMode;
private List<String> header;
private DiffPreferencesInfo diffPrefs;
- private SparseFileContent a;
- private SparseFileContent b;
private List<Edit> edits;
private Set<Edit> editsDueToRebase;
- private DisplayMethod displayMethodA;
- private DisplayMethod displayMethodB;
- private transient String mimeTypeA;
- private transient String mimeTypeB;
private CommentDetail comments;
private List<Patch> history;
private boolean hugeFile;
private boolean intralineFailure;
private boolean intralineTimeout;
private boolean binary;
- private transient String commitIdA;
- private transient String commitIdB;
+ private PatchScriptFileInfo fileInfoA;
+ private PatchScriptFileInfo fileInfoB;
public PatchScript(
Change.Key ck,
@@ -89,50 +103,39 @@
String cmb) {
changeId = ck;
changeType = ct;
- oldName = on;
- newName = nn;
- oldMode = om;
- newMode = nm;
header = h;
diffPrefs = dp;
- a = ca;
- b = cb;
edits = e;
this.editsDueToRebase = editsDueToRebase;
- displayMethodA = ma;
- displayMethodB = mb;
- mimeTypeA = mta;
- mimeTypeB = mtb;
comments = cd;
history = hist;
hugeFile = hf;
intralineFailure = idf;
intralineTimeout = idt;
binary = bin;
- commitIdA = cma;
- commitIdB = cmb;
- }
- protected PatchScript() {}
+ fileInfoA = new PatchScriptFileInfo(on, om, ca, ma, mta, cma);
+ fileInfoB = new PatchScriptFileInfo(nn, nm, cb, mb, mtb, cmb);
+ }
public Change.Key getChangeId() {
return changeId;
}
public DisplayMethod getDisplayMethodA() {
- return displayMethodA;
+ return fileInfoA.displayMethod;
}
public DisplayMethod getDisplayMethodB() {
- return displayMethodB;
+ return fileInfoB.displayMethod;
}
public FileMode getFileModeA() {
- return oldMode;
+ return fileInfoA.mode;
}
public FileMode getFileModeB() {
- return newMode;
+ return fileInfoB.mode;
}
public List<String> getPatchHeader() {
@@ -144,11 +147,11 @@
}
public String getOldName() {
- return oldName;
+ return fileInfoA.name;
}
public String getNewName() {
- return newName;
+ return fileInfoB.name;
}
public CommentDetail getCommentDetail() {
@@ -188,19 +191,19 @@
}
public SparseFileContent getA() {
- return a;
+ return fileInfoA.content;
}
public SparseFileContent getB() {
- return b;
+ return fileInfoB.content;
}
public String getMimeTypeA() {
- return mimeTypeA;
+ return fileInfoA.mimeType;
}
public String getMimeTypeB() {
- return mimeTypeB;
+ return fileInfoB.mimeType;
}
public List<Edit> getEdits() {
@@ -216,10 +219,18 @@
}
public String getCommitIdA() {
- return commitIdA;
+ return fileInfoA.commitId;
}
public String getCommitIdB() {
- return commitIdB;
+ return fileInfoB.commitId;
+ }
+
+ public PatchScriptFileInfo getFileInfoA() {
+ return fileInfoA;
+ }
+
+ public PatchScriptFileInfo getFileInfoB() {
+ return fileInfoB;
}
}
diff --git a/java/com/google/gerrit/extensions/api/changes/ChangeApi.java b/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
index 26a1a27..119d941 100644
--- a/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
+++ b/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
@@ -27,6 +27,7 @@
import com.google.gerrit.extensions.common.EditInfo;
import com.google.gerrit.extensions.common.MergePatchSetInput;
import com.google.gerrit.extensions.common.PureRevertInfo;
+import com.google.gerrit.extensions.common.RevertSubmissionInfo;
import com.google.gerrit.extensions.common.RobotCommentInfo;
import com.google.gerrit.extensions.common.SuggestedReviewerInfo;
import com.google.gerrit.extensions.restapi.NotImplementedException;
@@ -161,6 +162,12 @@
*/
ChangeApi revert(RevertInput in) throws RestApiException;
+ default RevertSubmissionInfo revertSubmission() throws RestApiException {
+ return revertSubmission(new RevertInput());
+ }
+
+ RevertSubmissionInfo revertSubmission(RevertInput in) throws RestApiException;
+
/** Create a merge patch set for the change. */
ChangeInfo createMergePatchSet(MergePatchSetInput in) throws RestApiException;
@@ -502,6 +509,11 @@
}
@Override
+ public RevertSubmissionInfo revertSubmission(RevertInput in) throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
public void rebase(RebaseInput in) throws RestApiException {
throw new NotImplementedException();
}
diff --git a/java/com/google/gerrit/extensions/api/projects/CommentLinkInput.java b/java/com/google/gerrit/extensions/api/projects/CommentLinkInput.java
new file mode 100644
index 0000000..3aad7e1
--- /dev/null
+++ b/java/com/google/gerrit/extensions/api/projects/CommentLinkInput.java
@@ -0,0 +1,27 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.api.projects;
+
+/*
+ * Input for a commentlink configuration on a project.
+ */
+public class CommentLinkInput {
+ /** A JavaScript regular expression to match positions to be replaced with a hyperlink. */
+ public String match;
+ /** The URL to direct the user to whenever the regular expression is matched. */
+ public String link;
+ /** Whether the commentlink is enabled. */
+ public Boolean enabled;
+}
diff --git a/java/com/google/gerrit/extensions/api/projects/ConfigInput.java b/java/com/google/gerrit/extensions/api/projects/ConfigInput.java
index 1a6d77b..8005fc5 100644
--- a/java/com/google/gerrit/extensions/api/projects/ConfigInput.java
+++ b/java/com/google/gerrit/extensions/api/projects/ConfigInput.java
@@ -38,4 +38,5 @@
public SubmitType submitType;
public ProjectState state;
public Map<String, Map<String, ConfigValue>> pluginConfigValues;
+ public Map<String, CommentLinkInput> commentLinks;
}
diff --git a/java/com/google/gerrit/extensions/api/projects/LabelApi.java b/java/com/google/gerrit/extensions/api/projects/LabelApi.java
new file mode 100644
index 0000000..975a57e
--- /dev/null
+++ b/java/com/google/gerrit/extensions/api/projects/LabelApi.java
@@ -0,0 +1,61 @@
+// 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.extensions.api.projects;
+
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.extensions.common.LabelDefinitionInfo;
+import com.google.gerrit.extensions.common.LabelDefinitionInput;
+import com.google.gerrit.extensions.restapi.NotImplementedException;
+import com.google.gerrit.extensions.restapi.RestApiException;
+
+public interface LabelApi {
+ LabelApi create(LabelDefinitionInput input) throws RestApiException;
+
+ LabelDefinitionInfo get() throws RestApiException;
+
+ LabelDefinitionInfo update(LabelDefinitionInput input) throws RestApiException;
+
+ default void delete() throws RestApiException {
+ delete(null);
+ }
+
+ void delete(@Nullable String commitMessage) throws RestApiException;
+
+ /**
+ * A default implementation which allows source compatibility when adding new methods to the
+ * interface.
+ */
+ class NotImplemented implements LabelApi {
+ @Override
+ public LabelApi create(LabelDefinitionInput input) throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public LabelDefinitionInfo get() throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public LabelDefinitionInfo update(LabelDefinitionInput input) throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public void delete(@Nullable String commitMessage) throws RestApiException {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/java/com/google/gerrit/extensions/api/projects/ParentInput.java b/java/com/google/gerrit/extensions/api/projects/ParentInput.java
index 6e481ae..d68bb3b 100644
--- a/java/com/google/gerrit/extensions/api/projects/ParentInput.java
+++ b/java/com/google/gerrit/extensions/api/projects/ParentInput.java
@@ -14,9 +14,9 @@
package com.google.gerrit.extensions.api.projects;
+import com.google.gerrit.extensions.common.InputWithCommitMessage;
import com.google.gerrit.extensions.restapi.DefaultInput;
-public class ParentInput {
+public class ParentInput extends InputWithCommitMessage {
@DefaultInput public String parent;
- public String commitMessage;
}
diff --git a/java/com/google/gerrit/extensions/api/projects/ProjectApi.java b/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
index c6d9dee..6d02ec4 100644
--- a/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
+++ b/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
@@ -19,6 +19,7 @@
import com.google.gerrit.extensions.api.config.AccessCheckInfo;
import com.google.gerrit.extensions.api.config.AccessCheckInput;
import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.common.LabelDefinitionInfo;
import com.google.gerrit.extensions.common.ProjectInfo;
import com.google.gerrit.extensions.restapi.NotImplementedException;
import com.google.gerrit.extensions.restapi.RestApiException;
@@ -204,6 +205,21 @@
/** Reindexes all changes of the project. */
void indexChanges() throws RestApiException;
+ ListLabelsRequest labels() throws RestApiException;
+
+ abstract class ListLabelsRequest {
+ protected boolean inherited;
+
+ public abstract List<LabelDefinitionInfo> get() throws RestApiException;
+
+ public ListLabelsRequest withInherited(boolean inherited) {
+ this.inherited = inherited;
+ return this;
+ }
+ }
+
+ LabelApi label(String labelName) throws RestApiException;
+
/**
* A default implementation which allows source compatibility when adding new methods to the
* interface.
@@ -378,5 +394,15 @@
public void indexChanges() throws RestApiException {
throw new NotImplementedException();
}
+
+ @Override
+ public ListLabelsRequest labels() throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public LabelApi label(String labelName) throws RestApiException {
+ throw new NotImplementedException();
+ }
}
}
diff --git a/java/com/google/gerrit/extensions/api/projects/SetDashboardInput.java b/java/com/google/gerrit/extensions/api/projects/SetDashboardInput.java
index 0083c0e..3662b7f 100644
--- a/java/com/google/gerrit/extensions/api/projects/SetDashboardInput.java
+++ b/java/com/google/gerrit/extensions/api/projects/SetDashboardInput.java
@@ -14,9 +14,9 @@
package com.google.gerrit.extensions.api.projects;
+import com.google.gerrit.extensions.common.InputWithCommitMessage;
import com.google.gerrit.extensions.restapi.DefaultInput;
-public class SetDashboardInput {
+public class SetDashboardInput extends InputWithCommitMessage {
@DefaultInput public String id;
- public String commitMessage;
}
diff --git a/java/com/google/gerrit/extensions/common/InputWithCommitMessage.java b/java/com/google/gerrit/extensions/common/InputWithCommitMessage.java
new file mode 100644
index 0000000..34bc203
--- /dev/null
+++ b/java/com/google/gerrit/extensions/common/InputWithCommitMessage.java
@@ -0,0 +1,30 @@
+// 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.extensions.common;
+
+import com.google.gerrit.common.Nullable;
+
+/** A generic input with a commit message only. */
+public class InputWithCommitMessage {
+ @Nullable public String commitMessage;
+
+ public InputWithCommitMessage() {
+ this(null);
+ }
+
+ public InputWithCommitMessage(@Nullable String commitMessage) {
+ this.commitMessage = commitMessage;
+ }
+}
diff --git a/java/com/google/gerrit/extensions/common/InputWithMessage.java b/java/com/google/gerrit/extensions/common/InputWithMessage.java
new file mode 100644
index 0000000..45d23cf
--- /dev/null
+++ b/java/com/google/gerrit/extensions/common/InputWithMessage.java
@@ -0,0 +1,34 @@
+// 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.extensions.common;
+
+import com.google.gerrit.common.Nullable;
+
+/**
+ * A generic input with a message only.
+ *
+ * <p>See also {@link InputWithCommitMessage}.
+ */
+public class InputWithMessage {
+ @Nullable public String message;
+
+ public InputWithMessage() {
+ this(null);
+ }
+
+ public InputWithMessage(@Nullable String message) {
+ this.message = message;
+ }
+}
diff --git a/java/com/google/gerrit/extensions/common/LabelDefinitionInfo.java b/java/com/google/gerrit/extensions/common/LabelDefinitionInfo.java
new file mode 100644
index 0000000..64c3997
--- /dev/null
+++ b/java/com/google/gerrit/extensions/common/LabelDefinitionInfo.java
@@ -0,0 +1,37 @@
+// 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.extensions.common;
+
+import java.util.List;
+import java.util.Map;
+
+public class LabelDefinitionInfo {
+ public String name;
+ public String projectName;
+ public String function;
+ public Map<String, String> values;
+ public short defaultValue;
+ public List<String> branches;
+ public Boolean canOverride;
+ public Boolean copyAnyScore;
+ public Boolean copyMinScore;
+ public Boolean copyMaxScore;
+ public Boolean copyAllScoresIfNoChange;
+ public Boolean copyAllScoresIfNoCodeChange;
+ public Boolean copyAllScoresOnTrivialRebase;
+ public Boolean copyAllScoresOnMergeFirstParentUpdate;
+ public Boolean allowPostSubmit;
+ public Boolean ignoreSelfApproval;
+}
diff --git a/java/com/google/gerrit/extensions/common/LabelDefinitionInput.java b/java/com/google/gerrit/extensions/common/LabelDefinitionInput.java
new file mode 100644
index 0000000..0523f61
--- /dev/null
+++ b/java/com/google/gerrit/extensions/common/LabelDefinitionInput.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.extensions.common;
+
+import java.util.List;
+import java.util.Map;
+
+public class LabelDefinitionInput extends InputWithCommitMessage {
+ public String name;
+ public String function;
+ public Map<String, String> values;
+ public Short defaultValue;
+ public List<String> branches;
+ public Boolean canOverride;
+ public Boolean copyAnyScore;
+ public Boolean copyMinScore;
+ public Boolean copyMaxScore;
+ public Boolean copyAllScoresIfNoChange;
+ public Boolean copyAllScoresIfNoCodeChange;
+ public Boolean copyAllScoresOnTrivialRebase;
+ public Boolean copyAllScoresOnMergeFirstParentUpdate;
+ public Boolean allowPostSubmit;
+ public Boolean ignoreSelfApproval;
+}
diff --git a/java/com/google/gerrit/extensions/common/RevertSubmissionInfo.java b/java/com/google/gerrit/extensions/common/RevertSubmissionInfo.java
new file mode 100644
index 0000000..dabd035
--- /dev/null
+++ b/java/com/google/gerrit/extensions/common/RevertSubmissionInfo.java
@@ -0,0 +1,21 @@
+// 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.extensions.common;
+
+import java.util.List;
+
+public class RevertSubmissionInfo {
+ public List<ChangeInfo> revertChanges;
+}
diff --git a/java/com/google/gerrit/extensions/restapi/Response.java b/java/com/google/gerrit/extensions/restapi/Response.java
index 5504cfd..bec7fc3 100644
--- a/java/com/google/gerrit/extensions/restapi/Response.java
+++ b/java/com/google/gerrit/extensions/restapi/Response.java
@@ -30,6 +30,12 @@
return new Impl<>(200, value);
}
+ /** HTTP 200 OK: with empty value. */
+ public static Response<String> ok() {
+ return ok("");
+ }
+
+ /** HTTP 200 OK: with forced revalidation of cache. */
public static <T> Response<T> withMustRevalidate(T value) {
return ok(value).caching(CacheControl.PRIVATE(0, TimeUnit.SECONDS).setMustRevalidate());
}
@@ -39,6 +45,11 @@
return new Impl<>(201, value);
}
+ /** HTTP 201 Created: with empty value. */
+ public static Response<String> created() {
+ return created("");
+ }
+
/** HTTP 202 Accepted: accepted as background task. */
public static Accepted accepted(String location) {
return new Accepted(location);
diff --git a/java/com/google/gerrit/index/query/QueryBuilder.java b/java/com/google/gerrit/index/query/QueryBuilder.java
index d24cfeb..85dcf3e 100644
--- a/java/com/google/gerrit/index/query/QueryBuilder.java
+++ b/java/com/google/gerrit/index/query/QueryBuilder.java
@@ -29,6 +29,7 @@
import com.google.common.base.Ascii;
import com.google.common.base.CharMatcher;
+import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.common.Nullable;
@@ -42,7 +43,9 @@
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
+import java.util.Map;
import org.antlr.runtime.tree.Tree;
/**
@@ -184,6 +187,7 @@
protected final Definition<T, Q> builderDef;
private final ImmutableMap<String, OperatorFactory<T, Q>> opFactories;
+ protected Map<String, String> opAliases = Collections.emptyMap();
protected QueryBuilder(
Definition<T, Q> def,
@@ -220,6 +224,10 @@
return toPredicate(QueryParser.parse(query));
}
+ public void setOperatorAliases(Map<String, String> opAliases) {
+ this.opAliases = opAliases;
+ }
+
/**
* Parse multiple user-supplied query strings into a list of predicates.
*
@@ -290,8 +298,12 @@
@SuppressWarnings("unchecked")
private Predicate<T> operator(String name, String value) throws QueryParseException {
+ String opName = MoreObjects.firstNonNull(opAliases.get(name), name);
@SuppressWarnings("rawtypes")
- OperatorFactory f = opFactories.get(name);
+ OperatorFactory f = opFactories.get(opName);
+ if (f == null && !opName.equals(name)) {
+ f = opFactories.get(name);
+ }
if (f == null) {
throw error("Unsupported operator " + name + ":" + value);
}
diff --git a/java/com/google/gerrit/pgm/http/jetty/JettyServer.java b/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
index 22bc21d..096e4a1 100644
--- a/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
+++ b/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
@@ -36,8 +36,10 @@
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.EnumSet;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.servlet.DispatcherType;
@@ -411,10 +413,20 @@
Class<? extends Filter> filterClass =
(Class<? extends Filter>) Class.forName(filterClassName);
Filter filter = env.webInjector.getInstance(filterClass);
- app.addFilter(
- new FilterHolder(filter),
- "/*",
- EnumSet.of(DispatcherType.REQUEST, DispatcherType.ASYNC));
+
+ Map<String, String> initParams = new HashMap<>();
+ Set<String> initParamKeys = cfg.getNames("filterClass", filterClassName, true);
+ initParamKeys.forEach(
+ paramKey -> {
+ String paramValue = cfg.getString("filterClass", filterClassName, paramKey);
+ initParams.put(paramKey, paramValue);
+ });
+
+ FilterHolder filterHolder = new FilterHolder(filter);
+ if (initParams.size() > 0) {
+ filterHolder.setInitParameters(initParams);
+ }
+ app.addFilter(filterHolder, "/*", EnumSet.of(DispatcherType.REQUEST, DispatcherType.ASYNC));
} catch (Throwable e) {
throw new IllegalArgumentException(
"Unable to instantiate front-end HTTP Filter " + filterClassName, e);
diff --git a/java/com/google/gerrit/server/BUILD b/java/com/google/gerrit/server/BUILD
index 6675595..dbaf9c3 100644
--- a/java/com/google/gerrit/server/BUILD
+++ b/java/com/google/gerrit/server/BUILD
@@ -54,6 +54,7 @@
"//java/com/google/gerrit/prettify:server",
"//java/com/google/gerrit/proto",
"//java/com/google/gerrit/server/cache/serialize",
+ "//java/com/google/gerrit/server/git/receive:ref_cache",
"//java/com/google/gerrit/server/ioutil",
"//java/com/google/gerrit/server/logging",
"//java/com/google/gerrit/server/util/git",
diff --git a/java/com/google/gerrit/server/ChangeUtil.java b/java/com/google/gerrit/server/ChangeUtil.java
index ee82a26..a166d97 100644
--- a/java/com/google/gerrit/server/ChangeUtil.java
+++ b/java/com/google/gerrit/server/ChangeUtil.java
@@ -25,7 +25,6 @@
import java.io.IOException;
import java.security.SecureRandom;
import java.util.Collection;
-import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.stream.Stream;
@@ -50,22 +49,6 @@
}
/**
- * Get the next patch set ID from a previously-read map of all refs.
- *
- * @param allRefs map of full ref name to ref.
- * @param id previous patch set ID.
- * @return next unused patch set ID for the same change, skipping any IDs whose corresponding ref
- * names appear in the {@code allRefs} map.
- */
- public static PatchSet.Id nextPatchSetIdFromAllRefsMap(Map<String, Ref> allRefs, PatchSet.Id id) {
- PatchSet.Id next = nextPatchSetId(id);
- while (allRefs.containsKey(next.toRefName())) {
- next = nextPatchSetId(next);
- }
- return next;
- }
-
- /**
* Get the next patch set ID from a previously-read map of refs below the change prefix.
*
* @param changeRefNames existing full change ref names with the same change ID as {@code id}.
@@ -95,9 +78,7 @@
/**
* Get the next patch set ID just looking at a single previous patch set ID.
*
- * <p>This patch set ID may or may not be available in the database; callers that want a
- * previously-unused ID should use {@link #nextPatchSetIdFromAllRefsMap} or {@link
- * #nextPatchSetIdFromChangeRefs}.
+ * <p>This patch set ID may or may not be available in the database.
*
* @param id previous patch set ID.
* @return next patch set ID for the same change, incrementing by 1.
diff --git a/java/com/google/gerrit/server/PublishCommentUtil.java b/java/com/google/gerrit/server/PublishCommentUtil.java
index 26539c5..c446c92 100644
--- a/java/com/google/gerrit/server/PublishCommentUtil.java
+++ b/java/com/google/gerrit/server/PublishCommentUtil.java
@@ -15,12 +15,13 @@
package com.google.gerrit.server;
import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.gerrit.entities.Comment.Status;
import static java.util.stream.Collectors.toSet;
import com.google.common.collect.ImmutableList;
+import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.Comment;
+import com.google.gerrit.entities.Comment.Status;
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.extensions.validators.CommentForValidation;
@@ -34,10 +35,14 @@
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.util.Collection;
+import java.util.HashSet;
import java.util.Map;
+import java.util.Set;
@Singleton
public class PublishCommentUtil {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
private final PatchListCache patchListCache;
private final PatchSetUtil psUtil;
private final CommentsUtil commentsUtil;
@@ -63,11 +68,32 @@
Map<PatchSet.Id, PatchSet> patchSets =
psUtil.getAsMap(notes, draftComments.stream().map(d -> psId(notes, d)).collect(toSet()));
+ Set<Comment> commentsToPublish = new HashSet<>();
for (Comment draftComment : draftComments) {
PatchSet.Id psIdOfDraftComment = psId(notes, draftComment);
PatchSet ps = patchSets.get(psIdOfDraftComment);
if (ps == null) {
- throw new StorageException("patch set " + psIdOfDraftComment + " not found");
+ // This can happen if changes with the same numeric ID exist:
+ // - change 12345 has 3 patch sets in repo X
+ // - another change 12345 has 7 patch sets in repo Y
+ // - the user saves a draft comment on patch set 6 of the change in repo Y
+ // - this draft comment gets stored in:
+ // AllUsers -> refs/draft-comments/45/12345/<account-id>
+ // - when posting a review with draft handling PUBLISH_ALL_REVISIONS on the change in
+ // repo X, the draft comments are loaded from
+ // AllUsers -> refs/draft-comments/45/12345/<account-id>, including the draft
+ // comment that was saved for patch set 6 of the change in repo Y
+ // - patch set 6 does not exist for the change in repo x, hence we get null for the patch
+ // set here
+ // Instead of failing hard (and returning an Internal Server Error) to the caller,
+ // just ignore that comment.
+ // Gerrit ensures that numeric change IDs are unique, but you can get duplicates if
+ // change refs of one repo are copied/pushed to another repo on the same host (this
+ // should never be done, but we know it happens).
+ logger.atWarning().log(
+ "Ignoring draft comment %s on non existing patch set %s (repo = %s)",
+ draftComment, psIdOfDraftComment, notes.getProjectName());
+ continue;
}
draftComment.writtenOn = ctx.getWhen();
draftComment.tag = tag;
@@ -79,8 +105,9 @@
} catch (PatchListNotAvailableException e) {
throw new StorageException(e);
}
+ commentsToPublish.add(draftComment);
}
- commentsUtil.putComments(ctx.getUpdate(psId), Status.PUBLISHED, draftComments);
+ commentsUtil.putComments(ctx.getUpdate(psId), Status.PUBLISHED, commentsToPublish);
}
private static PatchSet.Id psId(ChangeNotes notes, Comment c) {
diff --git a/java/com/google/gerrit/server/account/SetInactiveFlag.java b/java/com/google/gerrit/server/account/SetInactiveFlag.java
index fb3d4ea..a6c5d5c 100644
--- a/java/com/google/gerrit/server/account/SetInactiveFlag.java
+++ b/java/com/google/gerrit/server/account/SetInactiveFlag.java
@@ -106,6 +106,6 @@
if (exception.get().isPresent()) {
throw exception.get().get();
}
- return alreadyActive.get() ? Response.ok("") : Response.created("");
+ return alreadyActive.get() ? Response.ok() : Response.created();
}
}
diff --git a/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java b/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
index a04be30..0d640d9 100644
--- a/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
+++ b/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
@@ -48,8 +48,10 @@
import com.google.gerrit.extensions.common.CommentInfo;
import com.google.gerrit.extensions.common.CommitMessageInput;
import com.google.gerrit.extensions.common.Input;
+import com.google.gerrit.extensions.common.InputWithMessage;
import com.google.gerrit.extensions.common.MergePatchSetInput;
import com.google.gerrit.extensions.common.PureRevertInfo;
+import com.google.gerrit.extensions.common.RevertSubmissionInfo;
import com.google.gerrit.extensions.common.RobotCommentInfo;
import com.google.gerrit.extensions.common.SuggestedReviewerInfo;
import com.google.gerrit.extensions.registration.DynamicMap;
@@ -62,7 +64,6 @@
import com.google.gerrit.server.StarredChangesUtil.IllegalLabelException;
import com.google.gerrit.server.change.ChangeMessageResource;
import com.google.gerrit.server.change.ChangeResource;
-import com.google.gerrit.server.change.SetPrivateOp;
import com.google.gerrit.server.change.WorkInProgressOp;
import com.google.gerrit.server.restapi.change.Abandon;
import com.google.gerrit.server.restapi.change.ChangeIncludedIn;
@@ -96,6 +97,7 @@
import com.google.gerrit.server.restapi.change.Rebase;
import com.google.gerrit.server.restapi.change.Restore;
import com.google.gerrit.server.restapi.change.Revert;
+import com.google.gerrit.server.restapi.change.RevertSubmission;
import com.google.gerrit.server.restapi.change.Reviewers;
import com.google.gerrit.server.restapi.change.Revisions;
import com.google.gerrit.server.restapi.change.SetReadyForReview;
@@ -132,6 +134,7 @@
private final ChangeResource change;
private final Abandon abandon;
private final Revert revert;
+ private final RevertSubmission revertSubmission;
private final Restore restore;
private final CreateMergePatchSet updateByMerge;
private final Provider<SubmittedTogether> submittedTogether;
@@ -181,6 +184,7 @@
ListReviewers listReviewers,
Abandon abandon,
Revert revert,
+ RevertSubmission revertSubmission,
Restore restore,
CreateMergePatchSet updateByMerge,
Provider<SubmittedTogether> submittedTogether,
@@ -219,6 +223,7 @@
@Assisted ChangeResource change) {
this.changeApi = changeApi;
this.revert = revert;
+ this.revertSubmission = revertSubmission;
this.reviewers = reviewers;
this.revisions = revisions;
this.reviewerApi = reviewerApi;
@@ -319,7 +324,7 @@
@Override
public void setPrivate(boolean value, @Nullable String message) throws RestApiException {
try {
- SetPrivateOp.Input input = new SetPrivateOp.Input(message);
+ InputWithMessage input = new InputWithMessage(message);
if (value) {
postPrivate.apply(change, input);
} else {
@@ -358,6 +363,15 @@
}
@Override
+ public RevertSubmissionInfo revertSubmission(RevertInput in) throws RestApiException {
+ try {
+ return revertSubmission.apply(change, in).value();
+ } catch (Exception e) {
+ throw asRestApiException("Cannot revert a change submission", e);
+ }
+ }
+
+ @Override
public ChangeInfo createMergePatchSet(MergePatchSetInput in) throws RestApiException {
try {
return updateByMerge.apply(change, in).value();
diff --git a/java/com/google/gerrit/server/api/projects/LabelApiImpl.java b/java/com/google/gerrit/server/api/projects/LabelApiImpl.java
new file mode 100644
index 0000000..ad7ec31
--- /dev/null
+++ b/java/com/google/gerrit/server/api/projects/LabelApiImpl.java
@@ -0,0 +1,118 @@
+// 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.api.projects;
+
+import static com.google.gerrit.server.api.ApiUtil.asRestApiException;
+
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.extensions.api.projects.LabelApi;
+import com.google.gerrit.extensions.common.InputWithCommitMessage;
+import com.google.gerrit.extensions.common.LabelDefinitionInfo;
+import com.google.gerrit.extensions.common.LabelDefinitionInput;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.project.LabelResource;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectResource;
+import com.google.gerrit.server.restapi.project.CreateLabel;
+import com.google.gerrit.server.restapi.project.DeleteLabel;
+import com.google.gerrit.server.restapi.project.GetLabel;
+import com.google.gerrit.server.restapi.project.LabelsCollection;
+import com.google.gerrit.server.restapi.project.SetLabel;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+public class LabelApiImpl implements LabelApi {
+ interface Factory {
+ LabelApiImpl create(ProjectResource project, String label);
+ }
+
+ private final LabelsCollection labels;
+ private final CreateLabel createLabel;
+ private final GetLabel getLabel;
+ private final SetLabel setLabel;
+ private final DeleteLabel deleteLabel;
+ private final ProjectCache projectCache;
+ private final String label;
+
+ private ProjectResource project;
+
+ @Inject
+ LabelApiImpl(
+ LabelsCollection labels,
+ CreateLabel createLabel,
+ GetLabel getLabel,
+ SetLabel setLabel,
+ DeleteLabel deleteLabel,
+ ProjectCache projectCache,
+ @Assisted ProjectResource project,
+ @Assisted String label) {
+ this.labels = labels;
+ this.createLabel = createLabel;
+ this.getLabel = getLabel;
+ this.setLabel = setLabel;
+ this.deleteLabel = deleteLabel;
+ this.projectCache = projectCache;
+ this.project = project;
+ this.label = label;
+ }
+
+ @Override
+ public LabelApi create(LabelDefinitionInput input) throws RestApiException {
+ try {
+ createLabel.apply(project, IdString.fromDecoded(label), input);
+
+ // recreate project resource because project state was updated by creating the new label and
+ // needs to be reloaded
+ project =
+ new ProjectResource(projectCache.checkedGet(project.getNameKey()), project.getUser());
+ return this;
+ } catch (Exception e) {
+ throw asRestApiException("Cannot create branch", e);
+ }
+ }
+
+ @Override
+ public LabelDefinitionInfo get() throws RestApiException {
+ try {
+ return getLabel.apply(resource()).value();
+ } catch (Exception e) {
+ throw asRestApiException("Cannot get label", e);
+ }
+ }
+
+ @Override
+ public LabelDefinitionInfo update(LabelDefinitionInput input) throws RestApiException {
+ try {
+ return setLabel.apply(resource(), input).value();
+ } catch (Exception e) {
+ throw asRestApiException("Cannot update label", e);
+ }
+ }
+
+ @Override
+ public void delete(@Nullable String commitMessage) throws RestApiException {
+ try {
+ deleteLabel.apply(resource(), new InputWithCommitMessage(commitMessage));
+ } catch (Exception e) {
+ throw asRestApiException("Cannot delete label", e);
+ }
+ }
+
+ private LabelResource resource() throws RestApiException, PermissionBackendException {
+ return labels.parse(project, IdString.fromDecoded(label));
+ }
+}
diff --git a/java/com/google/gerrit/server/api/projects/Module.java b/java/com/google/gerrit/server/api/projects/Module.java
index f1e21d28..8df5495 100644
--- a/java/com/google/gerrit/server/api/projects/Module.java
+++ b/java/com/google/gerrit/server/api/projects/Module.java
@@ -28,5 +28,6 @@
factory(ChildProjectApiImpl.Factory.class);
factory(CommitApiImpl.Factory.class);
factory(DashboardApiImpl.Factory.class);
+ factory(LabelApiImpl.Factory.class);
}
}
diff --git a/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java b/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
index 1ac905d..d7ab91b 100644
--- a/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
+++ b/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
@@ -37,6 +37,7 @@
import com.google.gerrit.extensions.api.projects.DescriptionInput;
import com.google.gerrit.extensions.api.projects.HeadInput;
import com.google.gerrit.extensions.api.projects.IndexProjectInput;
+import com.google.gerrit.extensions.api.projects.LabelApi;
import com.google.gerrit.extensions.api.projects.ParentInput;
import com.google.gerrit.extensions.api.projects.ProjectApi;
import com.google.gerrit.extensions.api.projects.ProjectInput;
@@ -44,6 +45,7 @@
import com.google.gerrit.extensions.api.projects.TagInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.Input;
+import com.google.gerrit.extensions.common.LabelDefinitionInfo;
import com.google.gerrit.extensions.common.ProjectInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.IdString;
@@ -73,6 +75,7 @@
import com.google.gerrit.server.restapi.project.IndexChanges;
import com.google.gerrit.server.restapi.project.ListBranches;
import com.google.gerrit.server.restapi.project.ListDashboards;
+import com.google.gerrit.server.restapi.project.ListLabels;
import com.google.gerrit.server.restapi.project.ListTags;
import com.google.gerrit.server.restapi.project.ProjectsCollection;
import com.google.gerrit.server.restapi.project.PutConfig;
@@ -127,6 +130,8 @@
private final SetParent setParent;
private final Index index;
private final IndexChanges indexChanges;
+ private final Provider<ListLabels> listLabels;
+ private final LabelApiImpl.Factory labelApi;
@AssistedInject
ProjectApiImpl(
@@ -162,6 +167,8 @@
SetParent setParent,
Index index,
IndexChanges indexChanges,
+ Provider<ListLabels> listLabels,
+ LabelApiImpl.Factory labelApi,
@Assisted ProjectResource project) {
this(
permissionBackend,
@@ -197,6 +204,8 @@
setParent,
index,
indexChanges,
+ listLabels,
+ labelApi,
null);
}
@@ -234,6 +243,8 @@
SetParent setParent,
Index index,
IndexChanges indexChanges,
+ Provider<ListLabels> listLabels,
+ LabelApiImpl.Factory labelApi,
@Assisted String name) {
this(
permissionBackend,
@@ -269,6 +280,8 @@
setParent,
index,
indexChanges,
+ listLabels,
+ labelApi,
name);
}
@@ -306,6 +319,8 @@
SetParent setParent,
Index index,
IndexChanges indexChanges,
+ Provider<ListLabels> listLabels,
+ LabelApiImpl.Factory labelApi,
String name) {
this.permissionBackend = permissionBackend;
this.createProject = createProject;
@@ -341,6 +356,8 @@
this.name = name;
this.index = index;
this.indexChanges = indexChanges;
+ this.listLabels = listLabels;
+ this.labelApi = labelApi;
}
@Override
@@ -672,4 +689,27 @@
}
return project;
}
+
+ @Override
+ public ListLabelsRequest labels() {
+ return new ListLabelsRequest() {
+ @Override
+ public List<LabelDefinitionInfo> get() throws RestApiException {
+ try {
+ return listLabels.get().withInherited(inherited).apply(checkExists()).value();
+ } catch (Exception e) {
+ throw asRestApiException("Cannot list labels", e);
+ }
+ }
+ };
+ }
+
+ @Override
+ public LabelApi label(String labelName) throws RestApiException {
+ try {
+ return labelApi.create(checkExists(), labelName);
+ } catch (Exception e) {
+ throw asRestApiException("Cannot parse label", e);
+ }
+ }
}
diff --git a/java/com/google/gerrit/server/change/FileContentUtil.java b/java/com/google/gerrit/server/change/FileContentUtil.java
index 5c7946c..49c1fe2 100644
--- a/java/com/google/gerrit/server/change/FileContentUtil.java
+++ b/java/com/google/gerrit/server/change/FileContentUtil.java
@@ -100,7 +100,7 @@
public BinaryResult getContent(
Repository repo, ProjectState project, ObjectId revstr, String path)
- throws IOException, ResourceNotFoundException {
+ throws IOException, ResourceNotFoundException, BadRequestException {
try (RevWalk rw = new RevWalk(repo)) {
RevCommit commit = rw.parseCommit(revstr);
try (TreeWalk tw = TreeWalk.forPath(rw.getObjectReader(), path, commit.getTree())) {
@@ -114,6 +114,10 @@
return BinaryResult.create(id.name()).setContentType(X_GIT_GITLINK).base64();
}
+ if (mode == org.eclipse.jgit.lib.FileMode.TREE) {
+ throw new BadRequestException("cannot retrieve content of directories");
+ }
+
ObjectLoader obj = repo.open(id, OBJ_BLOB);
byte[] raw;
try {
diff --git a/java/com/google/gerrit/server/change/SetPrivateOp.java b/java/com/google/gerrit/server/change/SetPrivateOp.java
index 28d178d..382a4f6 100644
--- a/java/com/google/gerrit/server/change/SetPrivateOp.java
+++ b/java/com/google/gerrit/server/change/SetPrivateOp.java
@@ -19,6 +19,7 @@
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.ChangeMessage;
import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.extensions.common.InputWithMessage;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.server.ChangeMessagesUtil;
@@ -34,25 +35,15 @@
import com.google.inject.assistedinject.Assisted;
public class SetPrivateOp implements BatchUpdateOp {
- public static class Input {
- String message;
-
- public Input() {}
-
- public Input(String message) {
- this.message = message;
- }
- }
-
public interface Factory {
- SetPrivateOp create(boolean isPrivate, @Nullable Input input);
+ SetPrivateOp create(boolean isPrivate, @Nullable InputWithMessage input);
}
private final PrivateStateChanged privateStateChanged;
private final PatchSetUtil psUtil;
private final ChangeMessagesUtil cmUtil;
private final boolean isPrivate;
- @Nullable private final Input input;
+ @Nullable private final InputWithMessage input;
private Change change;
private PatchSet ps;
@@ -64,7 +55,7 @@
PatchSetUtil psUtil,
ChangeMessagesUtil cmUtil,
@Assisted boolean isPrivate,
- @Assisted @Nullable Input input) {
+ @Assisted @Nullable InputWithMessage input) {
this.privateStateChanged = privateStateChanged;
this.psUtil = psUtil;
this.cmUtil = cmUtil;
diff --git a/java/com/google/gerrit/server/change/WorkInProgressOp.java b/java/com/google/gerrit/server/change/WorkInProgressOp.java
index 78edadab..283cff8 100644
--- a/java/com/google/gerrit/server/change/WorkInProgressOp.java
+++ b/java/com/google/gerrit/server/change/WorkInProgressOp.java
@@ -21,6 +21,7 @@
import com.google.gerrit.entities.ChangeMessage;
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
+import com.google.gerrit.extensions.common.InputWithMessage;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.extensions.events.WorkInProgressStateChanged;
@@ -34,15 +35,15 @@
/* Set work in progress or ready for review state on a change */
public class WorkInProgressOp implements BatchUpdateOp {
- public static class Input {
- @Nullable public String message;
-
+ public static class Input extends InputWithMessage {
@Nullable public NotifyHandling notify;
- public Input() {}
+ public Input() {
+ this(null);
+ }
- public Input(String message) {
- this.message = message;
+ public Input(@Nullable String message) {
+ super(message);
}
}
diff --git a/java/com/google/gerrit/server/config/OperatorAliasConfig.java b/java/com/google/gerrit/server/config/OperatorAliasConfig.java
new file mode 100644
index 0000000..0c5fc6e
--- /dev/null
+++ b/java/com/google/gerrit/server/config/OperatorAliasConfig.java
@@ -0,0 +1,46 @@
+// 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.config;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import java.util.HashMap;
+import java.util.Map;
+import org.eclipse.jgit.lib.Config;
+
+@Singleton
+public class OperatorAliasConfig {
+ private static final String SECTION = "operator-alias";
+ private static final String SUBSECTION_CHANGE = "change";
+ private final Config cfg;
+ private final Map<String, String> changeQueryOperatorAliases;
+
+ @Inject
+ OperatorAliasConfig(@GerritServerConfig Config cfg) {
+ this.cfg = cfg;
+ changeQueryOperatorAliases = new HashMap<>();
+ loadChangeOperatorAliases();
+ }
+
+ public Map<String, String> getChangeQueryOperatorAliases() {
+ return changeQueryOperatorAliases;
+ }
+
+ private void loadChangeOperatorAliases() {
+ for (String name : cfg.getNames(SECTION, SUBSECTION_CHANGE)) {
+ changeQueryOperatorAliases.put(name, cfg.getString(SECTION, SUBSECTION_CHANGE, name));
+ }
+ }
+}
diff --git a/java/com/google/gerrit/server/edit/ChangeEditModifier.java b/java/com/google/gerrit/server/edit/ChangeEditModifier.java
index afaf695..c05a47d 100644
--- a/java/com/google/gerrit/server/edit/ChangeEditModifier.java
+++ b/java/com/google/gerrit/server/edit/ChangeEditModifier.java
@@ -17,12 +17,14 @@
import com.google.common.collect.ImmutableList;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.MergeConflictException;
import com.google.gerrit.extensions.restapi.RawInput;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.git.LockFailureException;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.GerritPersonIdent;
@@ -228,7 +230,12 @@
createCommit(repository, basePatchSetCommit, baseTree, newCommitMessage, nowTimestamp);
if (optionalChangeEdit.isPresent()) {
- updateEdit(repository, optionalChangeEdit.get(), newEditCommit, nowTimestamp);
+ updateEdit(
+ notes.getProjectName(),
+ repository,
+ optionalChangeEdit.get(),
+ newEditCommit,
+ nowTimestamp);
} else {
createEdit(repository, notes, basePatchSet, newEditCommit, nowTimestamp);
}
@@ -331,7 +338,12 @@
createCommit(repository, basePatchSetCommit, newTreeId, commitMessage, nowTimestamp);
if (optionalChangeEdit.isPresent()) {
- updateEdit(repository, optionalChangeEdit.get(), newEditCommit, nowTimestamp);
+ updateEdit(
+ notes.getProjectName(),
+ repository,
+ optionalChangeEdit.get(),
+ newEditCommit,
+ nowTimestamp);
} else {
createEdit(repository, notes, basePatchSet, newEditCommit, nowTimestamp);
}
@@ -384,7 +396,12 @@
createCommit(repository, patchSetCommit, newTreeId, commitMessage, nowTimestamp);
if (optionalChangeEdit.isPresent()) {
- return updateEdit(repository, optionalChangeEdit.get(), newEditCommit, nowTimestamp);
+ return updateEdit(
+ notes.getProjectName(),
+ repository,
+ optionalChangeEdit.get(),
+ newEditCommit,
+ nowTimestamp);
}
return createEdit(repository, notes, patchSet, newEditCommit, nowTimestamp);
}
@@ -531,7 +548,13 @@
throws IOException {
Change change = notes.getChange();
String editRefName = getEditRefName(change, basePatchSet);
- updateReference(repository, editRefName, ObjectId.zeroId(), newEditCommitId, timestamp);
+ updateReference(
+ notes.getProjectName(),
+ repository,
+ editRefName,
+ ObjectId.zeroId(),
+ newEditCommitId,
+ timestamp);
reindex(change);
RevCommit newEditCommit = lookupCommit(repository, newEditCommitId);
@@ -544,11 +567,16 @@
}
private ChangeEdit updateEdit(
- Repository repository, ChangeEdit changeEdit, ObjectId newEditCommitId, Timestamp timestamp)
+ Project.NameKey projectName,
+ Repository repository,
+ ChangeEdit changeEdit,
+ ObjectId newEditCommitId,
+ Timestamp timestamp)
throws IOException {
String editRefName = changeEdit.getRefName();
RevCommit currentEditCommit = changeEdit.getEditCommit();
- updateReference(repository, editRefName, currentEditCommit, newEditCommitId, timestamp);
+ updateReference(
+ projectName, repository, editRefName, currentEditCommit, newEditCommitId, timestamp);
reindex(changeEdit.getChange());
RevCommit newEditCommit = lookupCommit(repository, newEditCommitId);
@@ -557,6 +585,7 @@
}
private void updateReference(
+ Project.NameKey projectName,
Repository repository,
String refName,
ObjectId currentObjectId,
@@ -571,14 +600,12 @@
ru.setForceUpdate(true);
try (RevWalk revWalk = new RevWalk(repository)) {
RefUpdate.Result res = ru.update(revWalk);
+ String message = "cannot update " + ru.getName() + " in " + projectName + ": " + res;
+ if (res == RefUpdate.Result.LOCK_FAILURE) {
+ throw new LockFailureException(message, ru);
+ }
if (res != RefUpdate.Result.NEW && res != RefUpdate.Result.FORCED) {
- throw new IOException(
- "cannot update "
- + ru.getName()
- + " in "
- + repository.getDirectory()
- + ": "
- + ru.getResult());
+ throw new IOException(message);
}
}
}
diff --git a/java/com/google/gerrit/server/fixes/FixReplacementInterpreter.java b/java/com/google/gerrit/server/fixes/FixReplacementInterpreter.java
index a1682fe..9d6df7d 100644
--- a/java/com/google/gerrit/server/fixes/FixReplacementInterpreter.java
+++ b/java/com/google/gerrit/server/fixes/FixReplacementInterpreter.java
@@ -20,6 +20,7 @@
import com.google.gerrit.common.RawInputUtil;
import com.google.gerrit.entities.Comment;
import com.google.gerrit.entities.FixReplacement;
+import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
@@ -69,7 +70,8 @@
ProjectState projectState,
ObjectId patchSetCommitId,
List<FixReplacement> fixReplacements)
- throws ResourceNotFoundException, IOException, ResourceConflictException {
+ throws BadRequestException, ResourceNotFoundException, IOException,
+ ResourceConflictException {
requireNonNull(fixReplacements, "Fix replacements must not be null");
Map<String, List<FixReplacement>> fixReplacementsPerFilePath =
@@ -91,7 +93,8 @@
ObjectId patchSetCommitId,
String filePath,
List<FixReplacement> fixReplacements)
- throws ResourceNotFoundException, IOException, ResourceConflictException {
+ throws BadRequestException, ResourceNotFoundException, IOException,
+ ResourceConflictException {
String fileContent = getFileContent(repository, projectState, patchSetCommitId, filePath);
String newFileContent = getNewFileContent(fileContent, fixReplacements);
return new ChangeFileContentModification(filePath, RawInputUtil.create(newFileContent));
@@ -99,7 +102,7 @@
private String getFileContent(
Repository repository, ProjectState projectState, ObjectId patchSetCommitId, String filePath)
- throws ResourceNotFoundException, IOException {
+ throws ResourceNotFoundException, BadRequestException, IOException {
try (BinaryResult fileContent =
fileContentUtil.getContent(repository, projectState, patchSetCommitId, filePath)) {
return fileContent.asString();
diff --git a/java/com/google/gerrit/server/git/DelegateRefDatabase.java b/java/com/google/gerrit/server/git/DelegateRefDatabase.java
index 34dd6a9..decae05 100644
--- a/java/com/google/gerrit/server/git/DelegateRefDatabase.java
+++ b/java/com/google/gerrit/server/git/DelegateRefDatabase.java
@@ -17,6 +17,9 @@
import java.io.IOException;
import java.util.List;
import java.util.Map;
+import java.util.Set;
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefDatabase;
import org.eclipse.jgit.lib.RefRename;
@@ -24,7 +27,8 @@
import org.eclipse.jgit.lib.Repository;
/**
- * Wrapper around {@link RefDatabase} that delegates all calls to the wrapped {@link RefDatabase}.
+ * Wrapper around {@link RefDatabase} that delegates all calls to the wrapped {@link Repository}'s
+ * {@link RefDatabase}.
*/
public class DelegateRefDatabase extends RefDatabase {
@@ -41,7 +45,7 @@
@Override
public void close() {
- delegate.close();
+ delegate.getRefDatabase().close();
}
@Override
@@ -71,6 +75,12 @@
}
@Override
+ @NonNull
+ public Set<Ref> getTipsWithSha1(ObjectId id) throws IOException {
+ return delegate.getRefDatabase().getTipsWithSha1(id);
+ }
+
+ @Override
public List<Ref> getAdditionalRefs() throws IOException {
return delegate.getRefDatabase().getAdditionalRefs();
}
diff --git a/java/com/google/gerrit/server/git/DelegateRepository.java b/java/com/google/gerrit/server/git/DelegateRepository.java
index 800490d..b61488b 100644
--- a/java/com/google/gerrit/server/git/DelegateRepository.java
+++ b/java/com/google/gerrit/server/git/DelegateRepository.java
@@ -81,7 +81,8 @@
@SuppressWarnings("rawtypes")
private static BaseRepositoryBuilder toBuilder(Repository repo) {
if (!repo.isBare()) {
- throw new IllegalArgumentException("non-bare repository is not supported");
+ throw new IllegalArgumentException(
+ "non-bare repository is not supported: " + repo.getIdentifier());
}
return new BaseRepositoryBuilder<>().setFS(repo.getFS()).setGitDir(repo.getDirectory());
diff --git a/java/com/google/gerrit/server/git/GroupCollector.java b/java/com/google/gerrit/server/git/GroupCollector.java
index c284f7f4..1f0dcd4 100644
--- a/java/com/google/gerrit/server/git/GroupCollector.java
+++ b/java/com/google/gerrit/server/git/GroupCollector.java
@@ -23,16 +23,18 @@
import com.google.common.collect.Iterables;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.MultimapBuilder;
-import com.google.common.collect.Multimaps;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;
import com.google.common.collect.SortedSetMultimap;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.change.RevisionResource;
+import com.google.gerrit.server.git.receive.ReceivePackRefCache;
import com.google.gerrit.server.notedb.ChangeNotes;
+import java.io.IOException;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Deque;
@@ -87,24 +89,29 @@
return rsrc.getPatchSet().groups();
}
- private interface Lookup {
+ interface Lookup {
List<String> lookup(PatchSet.Id psId);
}
- private final ListMultimap<ObjectId, PatchSet.Id> patchSetsBySha;
+ private final ReceivePackRefCache receivePackRefCache;
private final ListMultimap<ObjectId, String> groups;
private final SetMultimap<String, String> groupAliases;
private final Lookup groupLookup;
private boolean done;
+ /**
+ * Returns a new {@link GroupCollector} instance.
+ *
+ * @see GroupCollector for what this class does.
+ */
public static GroupCollector create(
- ListMultimap<ObjectId, Ref> changeRefsById,
+ ReceivePackRefCache receivePackRefCache,
PatchSetUtil psUtil,
ChangeNotes.Factory notesFactory,
Project.NameKey project) {
return new GroupCollector(
- transformRefs(changeRefsById),
+ receivePackRefCache,
psId -> {
// TODO(dborowitz): Reuse open repository from caller.
ChangeNotes notes = notesFactory.createChecked(project, psId.changeId());
@@ -113,31 +120,32 @@
});
}
- private GroupCollector(ListMultimap<ObjectId, PatchSet.Id> patchSetsBySha, Lookup groupLookup) {
- this.patchSetsBySha = patchSetsBySha;
+ /**
+ * Returns a new {@link GroupCollector} instance.
+ *
+ * <p>Used in production code by using {@link com.google.gerrit.server.notedb.ChangeNotes.Factory}
+ * to get a group SHA1 (40 bytes string representation) from a {@link
+ * com.google.gerrit.entities.PatchSet.Id}. Unit tests use this method directly by passing their
+ * own lookup function.
+ *
+ * @see GroupCollector for what this class does.
+ */
+ @VisibleForTesting
+ GroupCollector(ReceivePackRefCache receivePackRefCache, Lookup groupLookup) {
+ this.receivePackRefCache = receivePackRefCache;
this.groupLookup = groupLookup;
groups = MultimapBuilder.hashKeys().arrayListValues().build();
groupAliases = MultimapBuilder.hashKeys().hashSetValues().build();
}
- private static ListMultimap<ObjectId, PatchSet.Id> transformRefs(
- ListMultimap<ObjectId, Ref> refs) {
- return Multimaps.transformValues(refs, r -> PatchSet.Id.fromRef(r.getName()));
- }
-
- @VisibleForTesting
- GroupCollector(
- ListMultimap<ObjectId, PatchSet.Id> patchSetsBySha,
- ListMultimap<PatchSet.Id, String> groupLookup) {
- this(
- patchSetsBySha,
- psId -> {
- List<String> groups = groupLookup.get(psId);
- return !groups.isEmpty() ? groups : null;
- });
- }
-
- public void visit(RevCommit c) {
+ /**
+ * Process the given {@link RevCommit}. Callers must call {@link #visit(RevCommit)} on all commits
+ * between the current branch tip and the tip of a push, in reverse topo order (parents before
+ * children). Once all commits have been visited, call {@link #getGroups()} for the result.
+ *
+ * @see GroupCollector for what this class does.
+ */
+ public void visit(RevCommit c) throws IOException {
checkState(!done, "visit() called after getGroups()");
Set<RevCommit> interestingParents = getInterestingParents(c);
@@ -197,7 +205,10 @@
}
}
- public SortedSetMultimap<ObjectId, String> getGroups() {
+ /**
+ * Returns the groups that got collected from visiting commits using {@link #visit(RevCommit)}.
+ */
+ public SortedSetMultimap<ObjectId, String> getGroups() throws IOException {
done = true;
SortedSetMultimap<ObjectId, String> result =
MultimapBuilder.hashKeys(groups.keySet().size()).treeSetValues().build();
@@ -218,12 +229,13 @@
return result;
}
- private boolean isGroupFromExistingPatchSet(RevCommit commit, String group) {
+ private boolean isGroupFromExistingPatchSet(RevCommit commit, String group) throws IOException {
ObjectId id = parseGroup(commit, group);
- return id != null && patchSetsBySha.containsKey(id);
+ return id != null && !receivePackRefCache.tipsFromObjectId(id, RefNames.REFS_CHANGES).isEmpty();
}
- private Set<String> resolveGroups(ObjectId forCommit, Collection<String> candidates) {
+ private Set<String> resolveGroups(ObjectId forCommit, Collection<String> candidates)
+ throws IOException {
Set<String> actual = Sets.newTreeSet();
Set<String> done = Sets.newHashSetWithExpectedSize(candidates.size());
Set<String> seen = Sets.newHashSetWithExpectedSize(candidates.size());
@@ -258,16 +270,20 @@
}
}
- private Iterable<String> resolveGroup(ObjectId forCommit, String group) {
+ private Iterable<String> resolveGroup(ObjectId forCommit, String group) throws IOException {
ObjectId id = parseGroup(forCommit, group);
if (id != null) {
- PatchSet.Id psId = Iterables.getFirst(patchSetsBySha.get(id), null);
- if (psId != null) {
- List<String> groups = groupLookup.lookup(psId);
- // Group for existing patch set may be missing, e.g. if group has not
- // been migrated yet.
- if (groups != null && !groups.isEmpty()) {
- return groups;
+ Ref ref =
+ Iterables.getFirst(receivePackRefCache.tipsFromObjectId(id, RefNames.REFS_CHANGES), null);
+ if (ref != null) {
+ PatchSet.Id psId = PatchSet.Id.fromRef(ref.getName());
+ if (psId != null) {
+ List<String> groups = groupLookup.lookup(psId);
+ // Group for existing patch set may be missing, e.g. if group has not
+ // been migrated yet.
+ if (groups != null && !groups.isEmpty()) {
+ return groups;
+ }
}
}
}
diff --git a/java/com/google/gerrit/server/git/PermissionAwareReadOnlyRefDatabase.java b/java/com/google/gerrit/server/git/PermissionAwareReadOnlyRefDatabase.java
index 8f7e684..8421e54 100644
--- a/java/com/google/gerrit/server/git/PermissionAwareReadOnlyRefDatabase.java
+++ b/java/com/google/gerrit/server/git/PermissionAwareReadOnlyRefDatabase.java
@@ -26,10 +26,13 @@
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefRename;
import org.eclipse.jgit.lib.RefUpdate;
@@ -156,4 +159,17 @@
}
return null;
}
+
+ @Override
+ @NonNull
+ public Set<Ref> getTipsWithSha1(ObjectId id) throws IOException {
+ Set<Ref> unfiltered = super.getTipsWithSha1(id);
+ Set<Ref> result = new HashSet<>(unfiltered.size());
+ for (Ref ref : unfiltered) {
+ if (exactRef(ref.getName()) != null) {
+ result.add(ref);
+ }
+ }
+ return result;
+ }
}
diff --git a/java/com/google/gerrit/server/git/receive/BUILD b/java/com/google/gerrit/server/git/receive/BUILD
index d89bb63..2b04d4d 100644
--- a/java/com/google/gerrit/server/git/receive/BUILD
+++ b/java/com/google/gerrit/server/git/receive/BUILD
@@ -2,9 +2,13 @@
java_library(
name = "receive",
- srcs = glob(["**/*.java"]),
+ srcs = glob(
+ ["**/*.java"],
+ exclude = ["ReceivePackRefCache.java"],
+ ),
visibility = ["//visibility:public"],
deps = [
+ ":ref_cache",
"//java/com/google/gerrit/common:annotations",
"//java/com/google/gerrit/common:server",
"//java/com/google/gerrit/entities",
@@ -26,3 +30,14 @@
"//lib/guice:guice-assistedinject",
],
)
+
+java_library(
+ name = "ref_cache",
+ srcs = glob(["ReceivePackRefCache.java"]),
+ visibility = ["//visibility:public"],
+ deps = [
+ "//java/com/google/gerrit/entities",
+ "//lib:guava",
+ "//lib:jgit",
+ ],
+)
diff --git a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
index c6c9b39..7dd21e1 100644
--- a/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
+++ b/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
@@ -343,7 +343,6 @@
private final SetPrivateOp.Factory setPrivateOpFactory;
// Assisted injected fields.
- private final AllRefsWatcher allRefsWatcher;
private final ProjectState projectState;
private final IdentifiedUser user;
private final ReceivePack receivePack;
@@ -363,12 +362,9 @@
private final ListMultimap<String, String> errors;
private final ListMultimap<String, String> pushOptions;
+ private final ReceivePackRefCache receivePackRefCache;
private final Map<Change.Id, ReplaceRequest> replaceByChange;
- // Collections lazily populated during processing.
- private ListMultimap<Change.Id, Ref> refsByChange;
- private ListMultimap<ObjectId, Ref> refsById;
-
// Other settings populated during processing.
private MagicBranchInput magicBranch;
private boolean newChangeForAllNotInTarget;
@@ -469,7 +465,6 @@
this.setPrivateOpFactory = setPrivateOpFactory;
// Assisted injected fields.
- this.allRefsWatcher = allRefsWatcher;
this.projectState = projectState;
this.user = user;
this.receivePack = rp;
@@ -501,6 +496,13 @@
this.messageSender = messageSender != null ? messageSender : new ReceivePackMessageSender();
this.resultChangeIds = resultChangeIds;
this.loggingTags = ImmutableMap.of();
+
+ // TODO(hiesel): Make this decision implicit once vetted
+ boolean useRefCache = config.getBoolean("receive", "enableInMemoryRefCache", true);
+ receivePackRefCache =
+ useRefCache
+ ? ReceivePackRefCache.withAdvertisedRefs(() -> allRefsWatcher.getAllRefs())
+ : ReceivePackRefCache.noCache(receivePack.getRepository().getRefDatabase());
}
void init() {
@@ -653,17 +655,27 @@
Task replaceProgress = progress.beginSubTask("updated", UNKNOWN);
List<CreateRequest> newChanges = Collections.emptyList();
- if (magicBranch != null && magicBranch.cmd.getResult() == NOT_ATTEMPTED) {
- newChanges = selectNewAndReplacedChangesFromMagicBranch(newProgress);
+ try {
+ if (magicBranch != null && magicBranch.cmd.getResult() == NOT_ATTEMPTED) {
+ try {
+ newChanges = selectNewAndReplacedChangesFromMagicBranch(newProgress);
+ } catch (IOException e) {
+ logger.atSevere().withCause(e).log(
+ "Failed to select new changes in %s", project.getName());
+ return;
+ }
+ }
+
+ // Commit validation has already happened, so any changes without Change-Id are for the
+ // deprecated feature.
+ warnAboutMissingChangeId(newChanges);
+ preparePatchSetsForReplace(newChanges);
+ insertChangesAndPatchSets(newChanges, replaceProgress);
+ } finally {
+ newProgress.end();
+ replaceProgress.end();
}
- // Commit validation has already happened, so any changes without Change-Id are for the
- // deprecated feature.
- warnAboutMissingChangeId(newChanges);
- preparePatchSetsForReplace(newChanges);
- insertChangesAndPatchSets(newChanges, replaceProgress);
- newProgress.end();
- replaceProgress.end();
queueSuccessMessages(newChanges);
logger.atFine().log(
@@ -1644,8 +1656,9 @@
/**
* returns the destination ref of the magic branch, and populates options in the cmdLineParser.
*/
- String parse(Repository repo, Set<String> refs, ListMultimap<String, String> pushOptions)
- throws CmdLineException {
+ String parse(
+ Repository repo, ReceivePackRefCache refCache, ListMultimap<String, String> pushOptions)
+ throws CmdLineException, IOException {
String ref = RefNames.fullName(MagicBranch.getDestBranchName(cmd.getRefName()));
ListMultimap<String, String> options = LinkedListMultimap.create(pushOptions);
@@ -1675,7 +1688,7 @@
int split = ref.length();
for (; ; ) {
String name = ref.substring(0, split);
- if (refs.contains(name) || name.equals(head)) {
+ if (refCache.exactRef(name) != null || name.equals(head)) {
break;
}
@@ -1734,7 +1747,7 @@
*
* <p>Assumes we are handling a magic branch here.
*/
- private void parseMagicBranch(ReceiveCommand cmd) throws PermissionBackendException {
+ private void parseMagicBranch(ReceiveCommand cmd) throws PermissionBackendException, IOException {
try (TraceTimer traceTimer = newTimer("parseMagicBranch")) {
logger.atFine().log("Found magic branch %s", cmd.getRefName());
MagicBranchInput magicBranch = new MagicBranchInput(user, projectState, cmd, labelTypes);
@@ -1743,7 +1756,7 @@
magicBranch.cmdLineParser = optionParserFactory.create(magicBranch);
try {
- ref = magicBranch.parse(repo, receivePack.getAdvertisedRefs().keySet(), pushOptions);
+ ref = magicBranch.parse(repo, receivePackRefCache, pushOptions);
} catch (CmdLineException e) {
if (!magicBranch.cmdLineParser.wasHelpRequestedByOption()) {
logger.atFine().log("Invalid branch syntax");
@@ -1775,7 +1788,7 @@
// review to these branches is allowed even if the branch does not exist yet. This allows to
// push initial code for review to an empty repository and to review an initial project
// configuration.
- if (!receivePack.getAdvertisedRefs().containsKey(ref)
+ if (receivePackRefCache.exactRef(ref) == null
&& !ref.equals(readHEAD(repo))
&& !ref.equals(RefNames.REFS_CONFIG)) {
logger.atFine().log("Ref %s not found", ref);
@@ -1850,11 +1863,12 @@
reject(cmd, "cannot use merged with base");
return;
}
- RevCommit branchTip = readBranchTip(magicBranch.dest);
- if (branchTip == null) {
+ Ref refTip = receivePackRefCache.exactRef(magicBranch.dest.branch());
+ if (refTip == null) {
reject(cmd, magicBranch.dest.branch() + " not found");
return;
}
+ RevCommit branchTip = receivePack.getRevWalk().parseCommit(refTip.getObjectId());
if (!walk.isMergedInto(tip, branchTip)) {
reject(cmd, "not merged into branch");
return;
@@ -1891,8 +1905,9 @@
}
}
} else if (newChangeForAllNotInTarget) {
- RevCommit branchTip = readBranchTip(magicBranch.dest);
- if (branchTip != null) {
+ Ref refTip = receivePackRefCache.exactRef(magicBranch.dest.branch());
+ if (refTip != null) {
+ RevCommit branchTip = receivePack.getRevWalk().parseCommit(refTip.getObjectId());
magicBranch.baseCommit = Collections.singletonList(branchTip);
logger.atFine().log("Set baseCommit = %s", magicBranch.baseCommit.get(0).name());
} else {
@@ -1939,7 +1954,7 @@
newTimer("validateConnected", Metadata.builder().branchName(dest.branch()))) {
RevWalk walk = receivePack.getRevWalk();
try {
- Ref targetRef = receivePack.getAdvertisedRefs().get(dest.branch());
+ Ref targetRef = receivePackRefCache.exactRef(dest.branch());
if (targetRef == null || targetRef.getObjectId() == null) {
// The destination branch does not yet exist. Assume the
// history being sent for review will start it and thus
@@ -1986,14 +2001,6 @@
}
}
- private RevCommit readBranchTip(BranchNameKey branch) throws IOException {
- Ref r = allRefs().get(branch.branch());
- if (r == null) {
- return null;
- }
- return receivePack.getRevWalk().parseCommit(r.getObjectId());
- }
-
/**
* Update an existing change. If draft comments are to be published, these are validated and may
* be withheld.
@@ -2001,7 +2008,8 @@
* @return True if the command succeeded, false if it was rejected.
*/
private boolean requestReplaceAndValidateComments(
- ReceiveCommand cmd, boolean checkMergedInto, Change change, RevCommit newCommit) {
+ ReceiveCommand cmd, boolean checkMergedInto, Change change, RevCommit newCommit)
+ throws IOException {
try (TraceTimer traceTimer = newTimer("requestReplaceAndValidateComments")) {
if (change.isClosed()) {
reject(
@@ -2063,14 +2071,14 @@
}
}
- private List<CreateRequest> selectNewAndReplacedChangesFromMagicBranch(Task newProgress) {
+ private List<CreateRequest> selectNewAndReplacedChangesFromMagicBranch(Task newProgress)
+ throws IOException {
try (TraceTimer traceTimer = newTimer("selectNewAndReplacedChangesFromMagicBranch")) {
logger.atFine().log("Finding new and replaced changes");
List<CreateRequest> newChanges = new ArrayList<>();
- ListMultimap<ObjectId, Ref> existing = changeRefsById();
GroupCollector groupCollector =
- GroupCollector.create(changeRefsById(), psUtil, notesFactory, project.getNameKey());
+ GroupCollector.create(receivePackRefCache, psUtil, notesFactory, project.getNameKey());
BranchCommitValidator validator =
commitValidatorFactory.create(projectState, magicBranch.dest, user);
@@ -2111,7 +2119,8 @@
receivePack.getRevWalk().parseBody(c);
String name = c.name();
groupCollector.visit(c);
- Collection<Ref> existingRefs = existing.get(c);
+ Collection<Ref> existingRefs =
+ receivePackRefCache.tipsFromObjectId(c, RefNames.REFS_CHANGES);
if (rejectImplicitMerges) {
Collections.addAll(mergedParents, c.getParents());
@@ -2275,7 +2284,8 @@
// In case the change look up from the index failed,
// double check against the existing refs
- if (foundInExistingRef(existing.get(p.commit))) {
+ if (foundInExistingRef(
+ receivePackRefCache.tipsFromObjectId(p.commit, RefNames.REFS_CHANGES))) {
if (pending.size() == 1) {
reject(magicBranch.cmd, "commit(s) already exists (as current patchset)");
return Collections.emptyList();
@@ -2382,7 +2392,7 @@
for (RevCommit c : magicBranch.baseCommit) {
receivePack.getRevWalk().markUninteresting(c);
}
- Ref targetRef = allRefs().get(magicBranch.dest.branch());
+ Ref targetRef = receivePackRefCache.exactRef(magicBranch.dest.branch());
if (targetRef != null) {
logger.atFine().log(
"Marking target ref %s (%s) uninteresting",
@@ -2397,7 +2407,7 @@
private void rejectImplicitMerges(Set<RevCommit> mergedParents) throws IOException {
try (TraceTimer traceTimer = newTimer("rejectImplicitMerges")) {
if (!mergedParents.isEmpty()) {
- Ref targetRef = allRefs().get(magicBranch.dest.branch());
+ Ref targetRef = receivePackRefCache.exactRef(magicBranch.dest.branch());
if (targetRef != null) {
RevWalk rw = receivePack.getRevWalk();
RevCommit tip = rw.parseCommit(targetRef.getObjectId());
@@ -2432,13 +2442,15 @@
// Mark all branch tips as uninteresting in the given revwalk,
// so we get only the new commits when walking rw.
- private void markHeadsAsUninteresting(RevWalk rw, @Nullable String forRef) {
+ private void markHeadsAsUninteresting(RevWalk rw, @Nullable String forRef) throws IOException {
try (TraceTimer traceTimer =
newTimer("markHeadsAsUninteresting", Metadata.builder().branchName(forRef))) {
int i = 0;
- for (Ref ref : allRefs().values()) {
- if ((ref.getName().startsWith(R_HEADS) || ref.getName().equals(forRef))
- && ref.getObjectId() != null) {
+ for (Ref ref :
+ Iterables.concat(
+ receivePackRefCache.byPrefix(R_HEADS),
+ Collections.singletonList(receivePackRefCache.exactRef(forRef)))) {
+ if (ref != null && ref.getObjectId() != null) {
try {
rw.markUninteresting(rw.parseCommit(ref.getObjectId()));
i++;
@@ -2703,7 +2715,8 @@
ReplaceOp replaceOp;
ReplaceRequest(
- Change.Id toChange, RevCommit newCommit, ReceiveCommand cmd, boolean checkMergedInto) {
+ Change.Id toChange, RevCommit newCommit, ReceiveCommand cmd, boolean checkMergedInto)
+ throws IOException {
this.ontoChange = toChange;
this.newCommitId = newCommit.copy();
this.inputCommand = requireNonNull(cmd);
@@ -2715,11 +2728,12 @@
revCommit = null;
}
revisions = HashBiMap.create();
- for (Ref ref : refs(toChange)) {
+ for (Ref ref : receivePackRefCache.byPrefix(RefNames.changeRefPrefix(toChange))) {
try {
- revisions.forcePut(
- receivePack.getRevWalk().parseCommit(ref.getObjectId()),
- PatchSet.Id.fromRef(ref.getName()));
+ PatchSet.Id psId = PatchSet.Id.fromRef(ref.getName());
+ if (psId != null) {
+ revisions.forcePut(receivePack.getRevWalk().parseCommit(ref.getObjectId()), psId);
+ }
} catch (IOException err) {
logger.atWarning().withCause(err).log(
"Project %s contains invalid change ref %s", project.getName(), ref.getName());
@@ -2785,6 +2799,16 @@
Change change = notes.getChange();
priorPatchSet = change.currentPatchSetId();
if (!revisions.containsValue(priorPatchSet)) {
+ logger.atWarning().log(
+ "Change %d is missing revision for patch set %s"
+ + " (it has revisions for these patch sets: %s)",
+ change.getChangeId(),
+ priorPatchSet.getId(),
+ Iterables.toString(
+ revisions.values().stream()
+ .limit(100) // Enough for "normal" changes.
+ .map(PatchSet.Id::getId)
+ .collect(Collectors.toList())));
reject(inputCommand, "change " + ontoChange + " missing revisions");
return false;
}
@@ -2812,11 +2836,16 @@
return false;
}
- for (Ref r : receivePack.getRepository().getRefDatabase().getRefsByPrefix("refs/changes")) {
- if (r.getObjectId().equals(newCommit)) {
- reject(inputCommand, "commit already exists (in the project)");
- return false;
- }
+ List<Ref> existingChangesWithSameCommit =
+ receivePackRefCache.tipsFromObjectId(newCommit, RefNames.REFS_CHANGES);
+ if (!existingChangesWithSameCommit.isEmpty()) {
+ // TODO(hiesel, hanwen): Remove this check entirely when Gerrit requires change IDs
+ // without the option to turn that off.
+ reject(
+ inputCommand,
+ "commit already exists (in the project): "
+ + existingChangesWithSameCommit.get(0).getName());
+ return false;
}
try (TraceTimer traceTimer2 = newTimer("validateNewPatchSetNoteDb#isMergedInto")) {
@@ -2950,14 +2979,20 @@
private void newPatchSet() throws IOException {
try (TraceTimer traceTimer = newTimer("newPatchSet")) {
RevCommit newCommit = receivePack.getRevWalk().parseCommit(newCommitId);
- psId =
- ChangeUtil.nextPatchSetIdFromAllRefsMap(
- allRefs(), notes.getChange().currentPatchSetId());
+ psId = nextPatchSetId(notes.getChange().currentPatchSetId());
info = patchSetInfoFactory.get(receivePack.getRevWalk(), newCommit, psId);
cmd = new ReceiveCommand(ObjectId.zeroId(), newCommitId, psId.toRefName());
}
}
+ private PatchSet.Id nextPatchSetId(PatchSet.Id psId) throws IOException {
+ PatchSet.Id next = ChangeUtil.nextPatchSetId(psId);
+ while (receivePackRefCache.exactRef(next.toRefName()) != null) {
+ next = ChangeUtil.nextPatchSetId(next);
+ }
+ return next;
+ }
+
void addOps(BatchUpdate bu, @Nullable Task progress) throws IOException {
try (TraceTimer traceTimer = newTimer("addOps")) {
if (magicBranch != null && magicBranch.edit) {
@@ -3091,45 +3126,6 @@
}
}
- private List<Ref> refs(Change.Id changeId) {
- return refsByChange().get(changeId);
- }
-
- private void initChangeRefMaps() {
- if (refsByChange != null) {
- return;
- }
-
- try (TraceTimer traceTimer = newTimer("initChangeRefMaps")) {
- int estRefsPerChange = 4;
- refsById = MultimapBuilder.hashKeys().arrayListValues().build();
- refsByChange =
- MultimapBuilder.hashKeys(allRefs().size() / estRefsPerChange)
- .arrayListValues(estRefsPerChange)
- .build();
- for (Ref ref : allRefs().values()) {
- ObjectId obj = ref.getObjectId();
- if (obj != null) {
- PatchSet.Id psId = PatchSet.Id.fromRef(ref.getName());
- if (psId != null) {
- refsById.put(obj, ref);
- refsByChange.put(psId.changeId(), ref);
- }
- }
- }
- }
- }
-
- private ListMultimap<Change.Id, Ref> refsByChange() {
- initChangeRefMaps();
- return refsByChange;
- }
-
- private ListMultimap<ObjectId, Ref> changeRefsById() {
- initChangeRefMaps();
- return refsById;
- }
-
private static boolean parentsEqual(RevCommit a, RevCommit b) {
if (a.getParentCount() != b.getParentCount()) {
return false;
@@ -3214,7 +3210,6 @@
if (!(parsedObject instanceof RevCommit)) {
return;
}
- ListMultimap<ObjectId, Ref> existing = changeRefsById();
walk.markStart((RevCommit) parsedObject);
markHeadsAsUninteresting(walk, cmd.getRefName());
int limit = receiveConfig.maxBatchCommits;
@@ -3231,7 +3226,7 @@
"more than %d commits, and %s not set", limit, PUSH_OPTION_SKIP_VALIDATION));
return;
}
- if (existing.keySet().contains(c)) {
+ if (!receivePackRefCache.tipsFromObjectId(c, RefNames.REFS_CHANGES).isEmpty()) {
continue;
}
@@ -3281,7 +3276,6 @@
rw.markUninteresting(rw.parseCommit(cmd.getOldId()));
}
- ListMultimap<ObjectId, Ref> byCommit = changeRefsById();
Map<Change.Key, ChangeNotes> byKey = null;
List<ReplaceRequest> replaceAndClose = new ArrayList<>();
@@ -3291,7 +3285,8 @@
for (RevCommit c; (c = rw.next()) != null; ) {
rw.parseBody(c);
- for (Ref ref : byCommit.get(c.copy())) {
+ for (Ref ref :
+ receivePackRefCache.tipsFromObjectId(c.copy(), RefNames.REFS_CHANGES)) {
PatchSet.Id psId = PatchSet.Id.fromRef(ref.getName());
Optional<ChangeNotes> notes = getChangeNotes(psId.changeId());
if (notes.isPresent() && notes.get().getChange().getDest().equals(branch)) {
@@ -3403,13 +3398,6 @@
}
}
- // allRefsWatcher hooks into the protocol negotation to get a list of all known refs.
- // This is used as a cache of ref -> sha1 values, and to build an inverse index
- // of (change => list of refs) and a (SHA1 => refs).
- private Map<String, Ref> allRefs() {
- return allRefsWatcher.getAllRefs();
- }
-
private TraceTimer newTimer(String name) {
return newTimer(getClass(), name);
}
diff --git a/java/com/google/gerrit/server/git/receive/ReceivePackRefCache.java b/java/com/google/gerrit/server/git/receive/ReceivePackRefCache.java
new file mode 100644
index 0000000..376ab2d
--- /dev/null
+++ b/java/com/google/gerrit/server/git/receive/ReceivePackRefCache.java
@@ -0,0 +1,174 @@
+// 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.git.receive;
+
+import static com.google.common.collect.ImmutableList.toImmutableList;
+
+import com.google.common.base.Supplier;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.MultimapBuilder;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.RefNames;
+import java.io.IOException;
+import java.util.Map;
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
+
+/**
+ * Simple cache for accessing refs by name, prefix or {@link ObjectId}. Intended to be used when
+ * processing a {@code git push}.
+ *
+ * <p>This class is not thread safe.
+ */
+public interface ReceivePackRefCache {
+
+ /**
+ * Returns an instance that delegates all calls to the provided {@link RefDatabase}. To be used in
+ * tests or when the ref database is fast with forward (name to {@link ObjectId}) and inverse
+ * ({@code ObjectId} to name) lookups.
+ */
+ static ReceivePackRefCache noCache(RefDatabase delegate) {
+ return new NoCache(delegate);
+ }
+
+ /**
+ * Returns an instance that answers calls based on refs previously advertised and captured in
+ * {@link AllRefsWatcher}. Speeds up inverse lookups by building a {@code Map<ObjectId,
+ * List<Ref>>} and a {@code Map<Change.Id, List<Ref>>}.
+ *
+ * <p>This implementation speeds up lookups when the ref database does not support inverse ({@code
+ * ObjectId} to name) lookups.
+ */
+ static ReceivePackRefCache withAdvertisedRefs(Supplier<Map<String, Ref>> allRefsSupplier) {
+ return new WithAdvertisedRefs(allRefsSupplier);
+ }
+
+ /** Returns a list of refs whose name starts with {@code prefix} that point to {@code id}. */
+ ImmutableList<Ref> tipsFromObjectId(ObjectId id, @Nullable String prefix) throws IOException;
+
+ /** Returns all refs whose name starts with {@code prefix}. */
+ ImmutableList<Ref> byPrefix(String prefix) throws IOException;
+
+ /** Returns a ref whose name matches {@code ref} or {@code null} if such a ref does not exist. */
+ @Nullable
+ Ref exactRef(String ref) throws IOException;
+
+ class NoCache implements ReceivePackRefCache {
+ private final RefDatabase delegate;
+
+ private NoCache(RefDatabase delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public ImmutableList<Ref> tipsFromObjectId(ObjectId id, @Nullable String prefix)
+ throws IOException {
+ return delegate.getTipsWithSha1(id).stream()
+ .filter(r -> prefix == null || r.getName().startsWith(prefix))
+ .collect(toImmutableList());
+ }
+
+ @Override
+ public ImmutableList<Ref> byPrefix(String prefix) throws IOException {
+ return delegate.getRefsByPrefix(prefix).stream().collect(toImmutableList());
+ }
+
+ @Override
+ @Nullable
+ public Ref exactRef(String name) throws IOException {
+ return delegate.exactRef(name);
+ }
+ }
+
+ class WithAdvertisedRefs implements ReceivePackRefCache {
+ /** We estimate that a change has an average of 4 patch sets plus the meta ref. */
+ private static final int ESTIMATED_NUMBER_OF_REFS_PER_CHANGE = 5;
+
+ private final Supplier<Map<String, Ref>> allRefsSupplier;
+
+ // Collections lazily populated during processing.
+ private Map<String, Ref> allRefs;
+ /** Contains only patch set refs. */
+ private ListMultimap<Change.Id, Ref> refsByChange;
+ /** Contains all refs. */
+ private ListMultimap<ObjectId, Ref> refsByObjectId;
+
+ private WithAdvertisedRefs(Supplier<Map<String, Ref>> allRefsSupplier) {
+ this.allRefsSupplier = allRefsSupplier;
+ }
+
+ @Override
+ public ImmutableList<Ref> tipsFromObjectId(ObjectId id, String prefix) {
+ lazilyInitRefMaps();
+ return refsByObjectId.get(id).stream()
+ .filter(r -> prefix == null || r.getName().startsWith(prefix))
+ .collect(toImmutableList());
+ }
+
+ @Override
+ public ImmutableList<Ref> byPrefix(String prefix) {
+ lazilyInitRefMaps();
+ if (RefNames.isRefsChanges(prefix)) {
+ Change.Id cId = Change.Id.fromRefPart(prefix);
+ if (cId != null) {
+ return refsByChange.get(cId).stream()
+ .filter(r -> r.getName().startsWith(prefix))
+ .collect(toImmutableList());
+ }
+ }
+ return allRefs().values().stream()
+ .filter(r -> r.getName().startsWith(prefix))
+ .collect(toImmutableList());
+ }
+
+ @Override
+ @Nullable
+ public Ref exactRef(String name) {
+ return allRefs().get(name);
+ }
+
+ private Map<String, Ref> allRefs() {
+ if (allRefs == null) {
+ allRefs = allRefsSupplier.get();
+ }
+ return allRefs;
+ }
+
+ private void lazilyInitRefMaps() {
+ if (refsByChange != null) {
+ return;
+ }
+
+ refsByObjectId = MultimapBuilder.hashKeys().arrayListValues().build();
+ refsByChange =
+ MultimapBuilder.hashKeys(allRefs().size() / ESTIMATED_NUMBER_OF_REFS_PER_CHANGE)
+ .arrayListValues(ESTIMATED_NUMBER_OF_REFS_PER_CHANGE)
+ .build();
+ for (Ref ref : allRefs().values()) {
+ ObjectId objectId = ref.getObjectId();
+ if (objectId != null) {
+ refsByObjectId.put(objectId, ref);
+ Change.Id changeId = Change.Id.fromRef(ref.getName());
+ if (changeId != null) {
+ refsByChange.put(changeId, ref);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/java/com/google/gerrit/server/index/StalenessCheckResult.java b/java/com/google/gerrit/server/index/StalenessCheckResult.java
new file mode 100644
index 0000000..cd3f592
--- /dev/null
+++ b/java/com/google/gerrit/server/index/StalenessCheckResult.java
@@ -0,0 +1,39 @@
+// 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.index;
+
+import com.google.auto.value.AutoValue;
+import java.util.Optional;
+
+/** Structured result of a staleness check. */
+@AutoValue
+public abstract class StalenessCheckResult {
+
+ public static StalenessCheckResult notStale() {
+ return new AutoValue_StalenessCheckResult(false, Optional.empty());
+ }
+
+ public static StalenessCheckResult stale(String reason) {
+ return new AutoValue_StalenessCheckResult(true, Optional.of(reason));
+ }
+
+ public static StalenessCheckResult stale(String reason, Object... args) {
+ return stale(String.format(reason, args));
+ }
+
+ public abstract boolean isStale();
+
+ public abstract Optional<String> reason();
+}
diff --git a/java/com/google/gerrit/server/index/account/AccountIndexerImpl.java b/java/com/google/gerrit/server/index/account/AccountIndexerImpl.java
index b908846..5aafec8 100644
--- a/java/com/google/gerrit/server/index/account/AccountIndexerImpl.java
+++ b/java/com/google/gerrit/server/index/account/AccountIndexerImpl.java
@@ -23,6 +23,7 @@
import com.google.gerrit.index.Index;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.account.AccountState;
+import com.google.gerrit.server.index.StalenessCheckResult;
import com.google.gerrit.server.logging.Metadata;
import com.google.gerrit.server.logging.TraceContext;
import com.google.gerrit.server.logging.TraceContext.TraceTimer;
@@ -116,7 +117,9 @@
@Override
public boolean reindexIfStale(Account.Id id) {
try {
- if (stalenessChecker.isStale(id)) {
+ StalenessCheckResult stalenessCheckResult = stalenessChecker.check(id);
+ if (stalenessCheckResult.isStale()) {
+ logger.atInfo().log("Reindexing stale document %s", stalenessCheckResult);
index(id);
return true;
}
diff --git a/java/com/google/gerrit/server/index/account/StalenessChecker.java b/java/com/google/gerrit/server/index/account/StalenessChecker.java
index aad9527..50fdcde 100644
--- a/java/com/google/gerrit/server/index/account/StalenessChecker.java
+++ b/java/com/google/gerrit/server/index/account/StalenessChecker.java
@@ -35,6 +35,7 @@
import com.google.gerrit.server.config.AllUsersNameProvider;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.index.IndexUtils;
+import com.google.gerrit.server.index.StalenessCheckResult;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.IOException;
@@ -87,16 +88,16 @@
this.indexConfig = indexConfig;
}
- public boolean isStale(Account.Id id) throws IOException {
+ public StalenessCheckResult check(Account.Id id) throws IOException {
AccountIndex i = indexes.getSearchIndex();
if (i == null) {
// No index; caller couldn't do anything if it is stale.
- return false;
+ return StalenessCheckResult.notStale();
}
if (!i.getSchema().hasField(AccountField.REF_STATE)
|| !i.getSchema().hasField(AccountField.EXTERNAL_ID_STATE)) {
// Index version not new enough for this check.
- return false;
+ return StalenessCheckResult.notStale();
}
boolean useLegacyNumericFields = i.getSchema().useLegacyNumericFields();
@@ -112,7 +113,11 @@
Ref ref = repo.exactRef(RefNames.refsUsers(id));
// Stale if the account actually exists.
- return ref != null;
+ if (ref == null) {
+ return StalenessCheckResult.notStale();
+ }
+ return StalenessCheckResult.stale(
+ "Document missing in index, but found %s in the repo", ref);
}
}
@@ -124,8 +129,9 @@
e.getKey().get().equals(AllUsersNameProvider.DEFAULT) ? allUsersName : e.getKey();
try (Repository repo = repoManager.openRepository(repoName)) {
if (!e.getValue().match(repo)) {
- // Ref was modified since the account was indexed.
- return true;
+ return StalenessCheckResult.stale(
+ "Ref was modified since the account was indexed (%s != %s)",
+ e.getValue(), repo.exactRef(e.getValue().ref()));
}
}
}
@@ -134,17 +140,22 @@
ListMultimap<ObjectId, ObjectId> extIdStates =
parseExternalIdStates(result.get().getValue(AccountField.EXTERNAL_ID_STATE));
if (extIdStates.size() != extIds.size()) {
- // External IDs of the account were modified since the account was indexed.
- return true;
+ return StalenessCheckResult.stale(
+ "External IDs of the account were modified since the account was indexed. (%s != %s)",
+ extIdStates.size(), extIds.size());
}
for (ExternalId extId : extIds) {
+ if (!extIdStates.containsKey(extId.key().sha1())) {
+ return StalenessCheckResult.stale("External ID missing: %s", extId.key().sha1());
+ }
if (!extIdStates.containsEntry(extId.key().sha1(), extId.blobId())) {
- // External IDs of the account were modified since the account was indexed.
- return true;
+ return StalenessCheckResult.stale(
+ "External ID has unexpected value. (%s != %s)",
+ extIdStates.get(extId.key().sha1()), extId.blobId());
}
}
- return false;
+ return StalenessCheckResult.notStale();
}
public static ListMultimap<ObjectId, ObjectId> parseExternalIdStates(
diff --git a/java/com/google/gerrit/server/index/change/ChangeIndexer.java b/java/com/google/gerrit/server/index/change/ChangeIndexer.java
index f6d86bf..5211a07 100644
--- a/java/com/google/gerrit/server/index/change/ChangeIndexer.java
+++ b/java/com/google/gerrit/server/index/change/ChangeIndexer.java
@@ -28,6 +28,7 @@
import com.google.gerrit.index.Index;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.index.IndexExecutor;
+import com.google.gerrit.server.index.StalenessCheckResult;
import com.google.gerrit.server.logging.Metadata;
import com.google.gerrit.server.logging.TraceContext;
import com.google.gerrit.server.logging.TraceContext.TraceTimer;
@@ -432,7 +433,9 @@
public Boolean callImpl() throws Exception {
remove();
try {
- if (stalenessChecker.isStale(id)) {
+ StalenessCheckResult stalenessCheckResult = stalenessChecker.check(id);
+ if (stalenessCheckResult.isStale()) {
+ logger.atInfo().log("Reindexing stale document %s", stalenessCheckResult);
indexImpl(changeDataFactory.create(project, id));
return true;
}
diff --git a/java/com/google/gerrit/server/index/change/StalenessChecker.java b/java/com/google/gerrit/server/index/change/StalenessChecker.java
index 47fd7ba..236163d 100644
--- a/java/com/google/gerrit/server/index/change/StalenessChecker.java
+++ b/java/com/google/gerrit/server/index/change/StalenessChecker.java
@@ -35,6 +35,7 @@
import com.google.gerrit.index.IndexConfig;
import com.google.gerrit.index.RefState;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.index.StalenessCheckResult;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -68,27 +69,28 @@
this.indexConfig = indexConfig;
}
- public boolean isStale(Change.Id id) {
+ public StalenessCheckResult check(Change.Id id) {
ChangeIndex i = indexes.getSearchIndex();
if (i == null) {
- return false; // No index; caller couldn't do anything if it is stale.
+ return StalenessCheckResult
+ .notStale(); // No index; caller couldn't do anything if it is stale.
}
if (!i.getSchema().hasField(ChangeField.REF_STATE)
|| !i.getSchema().hasField(ChangeField.REF_STATE_PATTERN)) {
- return false; // Index version not new enough for this check.
+ return StalenessCheckResult.notStale(); // Index version not new enough for this check.
}
Optional<ChangeData> result =
i.get(id, IndexedChangeQuery.createOptions(indexConfig, 0, 1, FIELDS));
if (!result.isPresent()) {
- return true; // Not in index, but caller wants it to be.
+ return StalenessCheckResult.stale("Document %s missing from index", id);
}
ChangeData cd = result.get();
- return isStale(repoManager, id, parseStates(cd), parsePatterns(cd));
+ return check(repoManager, id, parseStates(cd), parsePatterns(cd));
}
@UsedAt(UsedAt.Project.GOOGLE)
- public static boolean isStale(
+ public static StalenessCheckResult check(
GitRepositoryManager repoManager,
Change.Id id,
SetMultimap<Project.NameKey, RefState> states,
@@ -97,7 +99,7 @@
}
@VisibleForTesting
- static boolean refsAreStale(
+ static StalenessCheckResult refsAreStale(
GitRepositoryManager repoManager,
Change.Id id,
SetMultimap<Project.NameKey, RefState> states,
@@ -105,12 +107,13 @@
Set<Project.NameKey> projects = Sets.union(states.keySet(), patterns.keySet());
for (Project.NameKey p : projects) {
- if (refsAreStale(repoManager, id, p, states, patterns)) {
- return true;
+ StalenessCheckResult result = refsAreStale(repoManager, id, p, states, patterns);
+ if (result.isStale()) {
+ return result;
}
}
- return false;
+ return StalenessCheckResult.notStale();
}
private SetMultimap<Project.NameKey, RefState> parseStates(ChangeData cd) {
@@ -136,7 +139,7 @@
return result;
}
- private static boolean refsAreStale(
+ private static StalenessCheckResult refsAreStale(
GitRepositoryManager repoManager,
Change.Id id,
Project.NameKey project,
@@ -146,18 +149,22 @@
Set<RefState> states = allStates.get(project);
for (RefState state : states) {
if (!state.match(repo)) {
- return true;
+ return StalenessCheckResult.stale(
+ "Ref states don't match for document %s (%s != %s)",
+ id, state, repo.exactRef(state.ref()));
}
}
for (RefStatePattern pattern : allPatterns.get(project)) {
if (!pattern.match(repo, states)) {
- return true;
+ return StalenessCheckResult.stale(
+ "Ref patterns don't match for document %s. Pattern: %s States: %s",
+ id, pattern, states);
}
}
- return false;
+ return StalenessCheckResult.notStale();
} catch (IOException e) {
logger.atWarning().withCause(e).log("error checking staleness of %s in %s", id, project);
- return true;
+ return StalenessCheckResult.stale("Exceptions while processing document %s", e.getMessage());
}
}
diff --git a/java/com/google/gerrit/server/index/group/GroupIndexerImpl.java b/java/com/google/gerrit/server/index/group/GroupIndexerImpl.java
index 790066d..70dc8fa 100644
--- a/java/com/google/gerrit/server/index/group/GroupIndexerImpl.java
+++ b/java/com/google/gerrit/server/index/group/GroupIndexerImpl.java
@@ -23,6 +23,7 @@
import com.google.gerrit.index.Index;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.group.InternalGroup;
+import com.google.gerrit.server.index.StalenessCheckResult;
import com.google.gerrit.server.logging.Metadata;
import com.google.gerrit.server.logging.TraceContext;
import com.google.gerrit.server.logging.TraceContext.TraceTimer;
@@ -116,7 +117,9 @@
@Override
public boolean reindexIfStale(AccountGroup.UUID uuid) {
try {
- if (stalenessChecker.isStale(uuid)) {
+ StalenessCheckResult stalenessCheckResult = stalenessChecker.check(uuid);
+ if (stalenessCheckResult.isStale()) {
+ logger.atInfo().log("Reindexing stale document %s", stalenessCheckResult);
index(uuid);
return true;
}
diff --git a/java/com/google/gerrit/server/index/group/StalenessChecker.java b/java/com/google/gerrit/server/index/group/StalenessChecker.java
index 3a721c3..54a6f85 100644
--- a/java/com/google/gerrit/server/index/group/StalenessChecker.java
+++ b/java/com/google/gerrit/server/index/group/StalenessChecker.java
@@ -21,6 +21,7 @@
import com.google.gerrit.index.query.FieldBundle;
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.index.StalenessCheckResult;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.IOException;
@@ -59,10 +60,11 @@
this.allUsers = allUsers;
}
- public boolean isStale(AccountGroup.UUID uuid) throws IOException {
+ public StalenessCheckResult check(AccountGroup.UUID uuid) throws IOException {
GroupIndex i = indexes.getSearchIndex();
if (i == null) {
- return false; // No index; caller couldn't do anything if it is stale.
+ // No index; caller couldn't do anything if it is stale.
+ return StalenessCheckResult.notStale();
}
Optional<FieldBundle> result =
@@ -73,14 +75,23 @@
Ref ref = repo.exactRef(RefNames.refsGroups(uuid));
// Stale if the group actually exists.
- return ref != null;
+ if (ref == null) {
+ return StalenessCheckResult.notStale();
+ }
+ return StalenessCheckResult.stale(
+ "Document missing in index, but found %s in the repo", ref);
}
}
try (Repository repo = repoManager.openRepository(allUsers)) {
Ref ref = repo.exactRef(RefNames.refsGroups(uuid));
ObjectId head = ref == null ? ObjectId.zeroId() : ref.getObjectId();
- return !head.equals(ObjectId.fromString(result.get().getValue(GroupField.REF_STATE), 0));
+ ObjectId idFromIndex = ObjectId.fromString(result.get().getValue(GroupField.REF_STATE), 0);
+ if (head.equals(idFromIndex)) {
+ return StalenessCheckResult.notStale();
+ }
+ return StalenessCheckResult.stale(
+ "Document has unexpected ref state (%s != %s)", head, idFromIndex);
}
}
}
diff --git a/java/com/google/gerrit/server/index/project/StalenessChecker.java b/java/com/google/gerrit/server/index/project/StalenessChecker.java
index e4c1a7d..e325a33 100644
--- a/java/com/google/gerrit/server/index/project/StalenessChecker.java
+++ b/java/com/google/gerrit/server/index/project/StalenessChecker.java
@@ -27,6 +27,7 @@
import com.google.gerrit.index.project.ProjectIndex;
import com.google.gerrit.index.project.ProjectIndexCollection;
import com.google.gerrit.index.query.FieldBundle;
+import com.google.gerrit.server.index.StalenessCheckResult;
import com.google.gerrit.server.project.ProjectCache;
import com.google.inject.Inject;
import java.util.Optional;
@@ -47,17 +48,18 @@
this.indexConfig = indexConfig;
}
- public boolean isStale(Project.NameKey project) {
+ public StalenessCheckResult check(Project.NameKey project) {
ProjectData projectData = projectCache.get(project).toProjectData();
ProjectIndex i = indexes.getSearchIndex();
if (i == null) {
- return false; // No index; caller couldn't do anything if it is stale.
+ return StalenessCheckResult
+ .notStale(); // No index; caller couldn't do anything if it is stale.
}
Optional<FieldBundle> result =
i.getRaw(project, QueryOptions.create(indexConfig, 0, 1, FIELDS));
if (!result.isPresent()) {
- return true;
+ return StalenessCheckResult.stale("Document %s missing from index", project);
}
SetMultimap<Project.NameKey, RefState> indexedRefStates =
@@ -73,6 +75,10 @@
p.getProject().getNameKey(),
RefState.create(RefNames.REFS_CONFIG, p.getProject().getConfigRefState())));
- return !currentRefStates.equals(indexedRefStates);
+ if (currentRefStates.equals(indexedRefStates)) {
+ return StalenessCheckResult.notStale();
+ }
+ return StalenessCheckResult.stale(
+ "Document has unexpected ref states (%s != %s)", currentRefStates, indexedRefStates);
}
}
diff --git a/java/com/google/gerrit/server/logging/Metadata.java b/java/com/google/gerrit/server/logging/Metadata.java
index 7af204e..60e41ed 100644
--- a/java/com/google/gerrit/server/logging/Metadata.java
+++ b/java/com/google/gerrit/server/logging/Metadata.java
@@ -92,6 +92,9 @@
// The version of a secondary index.
public abstract Optional<Integer> indexVersion();
+ // The number of inputs to an operation, eg. Reachable.fromRefs.
+ public abstract Optional<Integer> inputSize();
+
// The name of the implementation method.
public abstract Optional<String> methodName();
@@ -295,6 +298,8 @@
public abstract Builder indexVersion(int indexVersion);
+ public abstract Builder inputSize(int size);
+
public abstract Builder methodName(@Nullable String methodName);
public abstract Builder multiple(boolean multiple);
diff --git a/java/com/google/gerrit/server/notedb/ChangeNotesParser.java b/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
index 9c45aaf..3322b68 100644
--- a/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
+++ b/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
@@ -363,6 +363,15 @@
submissionId = parseSubmissionId(commit);
}
+ if (lastUpdatedOn == null || ts.after(lastUpdatedOn)) {
+ lastUpdatedOn = ts;
+ }
+
+ if (deletedPatchSets.contains(psId)) {
+ // Do not update PS details as PS was deleted and this meta data is of no relevance.
+ return;
+ }
+
// Parse mutable patch set fields first so they can be recorded in the PendingPatchSetFields.
parseDescription(psId, commit);
parseGroups(psId, commit);
@@ -410,10 +419,6 @@
previousWorkInProgressFooter = null;
parseWorkInProgress(commit);
-
- if (lastUpdatedOn == null || ts.after(lastUpdatedOn)) {
- lastUpdatedOn = ts;
- }
}
private String parseSubmissionId(ChangeNotesCommit commit) throws ConfigInvalidException {
@@ -487,10 +492,6 @@
throw parseException("patch set %s requires an identified user as uploader", psId.get());
}
if (patchSetCommitParsed(psId)) {
- if (deletedPatchSets.contains(psId)) {
- // Do not update PS details as PS was deleted and this meta data is of no relevance.
- return;
- }
ObjectId commitId = patchSets.get(psId).commitId().orElseThrow(IllegalStateException::new);
throw new ConfigInvalidException(
String.format(
diff --git a/java/com/google/gerrit/server/patch/PatchScriptBuilder.java b/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
index f3c8eab..0f228fe 100644
--- a/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
+++ b/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
@@ -53,6 +53,7 @@
import org.eclipse.jgit.treewalk.TreeWalk;
class PatchScriptBuilder {
+
static final int MAX_CONTEXT = 5000000;
static final int BIG_FILE = 9000;
@@ -66,10 +67,6 @@
private ComparisonType comparisonType;
private ObjectId aId;
private ObjectId bId;
-
- private final Side a;
- private final Side b;
-
private List<Edit> edits;
private final FileTypeRegistry registry;
private final PatchListCache patchListCache;
@@ -77,8 +74,6 @@
@Inject
PatchScriptBuilder(FileTypeRegistry ftr, PatchListCache plc) {
- a = new Side();
- b = new Side();
registry = ftr;
patchListCache = plc;
}
@@ -124,11 +119,9 @@
boolean intralineFailure = false;
boolean intralineTimeout = false;
- a.path = oldName(content);
- b.path = newName(content);
-
- a.resolve(null, aId);
- b.resolve(a, bId);
+ SideResolver resolver = new SideResolver();
+ Side a = resolver.resolve(oldName(content), null, aId);
+ Side b = resolver.resolve(newName(content), a, bId);
edits = new ArrayList<>(content.getEdits());
ImmutableSet<Edit> editsDueToRebase = content.getEditsDueToRebase();
@@ -161,7 +154,7 @@
}
}
- correctForDifferencesInNewlineAtEnd();
+ correctForDifferencesInNewlineAtEnd(a, b);
if (comments != null) {
ensureCommentsVisible(comments);
@@ -193,7 +186,7 @@
//
context = MAX_CONTEXT;
- packContent(diffPrefs.ignoreWhitespace != Whitespace.IGNORE_NONE);
+ packContent(a, b, diffPrefs.ignoreWhitespace != Whitespace.IGNORE_NONE);
}
return new PatchScript(
@@ -267,7 +260,7 @@
}
}
- private void correctForDifferencesInNewlineAtEnd() {
+ private void correctForDifferencesInNewlineAtEnd(Side a, Side b) {
// a.src.size() is the size ignoring a newline at the end whereas a.size() considers it.
int aSize = a.src.size();
int bSize = b.src.size();
@@ -280,7 +273,7 @@
}
Optional<Edit> lastEdit = getLast(edits);
- if (isNewlineAtEndDeleted()) {
+ if (isNewlineAtEndDeleted(a, b)) {
Optional<Edit> lastLineEdit = lastEdit.filter(edit -> edit.getEndA() == aSize);
if (lastLineEdit.isPresent()) {
lastLineEdit.get().extendA();
@@ -288,7 +281,7 @@
Edit newlineEdit = new Edit(aSize, aSize + 1, bSize, bSize);
edits.add(newlineEdit);
}
- } else if (isNewlineAtEndAdded()) {
+ } else if (isNewlineAtEndAdded(a, b)) {
Optional<Edit> lastLineEdit = lastEdit.filter(edit -> edit.getEndB() == bSize);
if (lastLineEdit.isPresent()) {
lastLineEdit.get().extendB();
@@ -303,11 +296,11 @@
return list.isEmpty() ? Optional.empty() : Optional.ofNullable(list.get(list.size() - 1));
}
- private boolean isNewlineAtEndDeleted() {
+ private boolean isNewlineAtEndDeleted(Side a, Side b) {
return !a.src.isMissingNewlineAtEnd() && b.src.isMissingNewlineAtEnd();
}
- private boolean isNewlineAtEndAdded() {
+ private boolean isNewlineAtEndAdded(Side a, Side b) {
return a.src.isMissingNewlineAtEnd() && !b.src.isMissingNewlineAtEnd();
}
@@ -425,7 +418,7 @@
return last.getEndA() + (b - last.getEndB());
}
- private void packContent(boolean ignoredWhitespace) {
+ private void packContent(Side a, Side b, boolean ignoredWhitespace) {
EditList list = new EditList(edits, context, a.size(), b.size());
for (EditList.Hunk hunk : list.getHunks()) {
while (hunk.next()) {
@@ -459,16 +452,38 @@
}
}
- private class Side {
- String path;
- ObjectId id;
- FileMode mode;
- byte[] srcContent;
- Text src;
- MimeType mimeType = MimeUtil2.UNKNOWN_MIME_TYPE;
- DisplayMethod displayMethod = DisplayMethod.DIFF;
- PatchScript.FileMode fileMode = PatchScript.FileMode.FILE;
- final SparseFileContent dst = new SparseFileContent();
+ private static class Side {
+
+ final String path;
+ final ObjectId id;
+ final FileMode mode;
+ final byte[] srcContent;
+ final Text src;
+ final MimeType mimeType;
+ final DisplayMethod displayMethod;
+ final PatchScript.FileMode fileMode;
+ final SparseFileContent dst;
+
+ public Side(
+ String path,
+ ObjectId id,
+ FileMode mode,
+ byte[] srcContent,
+ Text src,
+ MimeType mimeType,
+ DisplayMethod displayMethod,
+ PatchScript.FileMode fileMode) {
+ this.path = path;
+ this.id = id;
+ this.mode = mode;
+ this.srcContent = srcContent;
+ this.src = src;
+ this.mimeType = mimeType;
+ this.displayMethod = displayMethod;
+ this.fileMode = fileMode;
+ dst = new SparseFileContent();
+ dst.setSize(size());
+ }
int size() {
if (src == null) {
@@ -488,110 +503,120 @@
String getSourceLine(int lineNumber) {
return lineNumber >= src.size() ? "" : src.getString(lineNumber);
}
+ }
- void resolve(Side other, ObjectId within) throws IOException {
+ private class SideResolver {
+
+ Side resolve(final String path, final Side other, final ObjectId within) throws IOException {
try {
- final boolean reuse;
- if (Patch.COMMIT_MSG.equals(path)) {
+ boolean isCommitMsg = Patch.COMMIT_MSG.equals(path);
+ boolean isMergeList = Patch.MERGE_LIST.equals(path);
+ if (isCommitMsg || isMergeList) {
if (comparisonType.isAgainstParentOrAutoMerge() && Objects.equals(aId, within)) {
- id = ObjectId.zeroId();
- src = Text.EMPTY;
- srcContent = Text.NO_BYTES;
+ return createSide(
+ path,
+ ObjectId.zeroId(),
+ FileMode.MISSING,
+ Text.NO_BYTES,
+ Text.EMPTY,
+ MimeUtil2.UNKNOWN_MIME_TYPE,
+ DisplayMethod.NONE,
+ false);
+ }
+ Text src =
+ isCommitMsg
+ ? Text.forCommit(reader, within)
+ : Text.forMergeList(comparisonType, reader, within);
+ byte[] srcContent = src.getContent();
+ DisplayMethod displayMethod;
+ FileMode mode;
+ if (src == Text.EMPTY) {
mode = FileMode.MISSING;
displayMethod = DisplayMethod.NONE;
} else {
- id = within;
- src = Text.forCommit(reader, within);
- srcContent = src.getContent();
- if (src == Text.EMPTY) {
- mode = FileMode.MISSING;
- displayMethod = DisplayMethod.NONE;
- } else {
- mode = FileMode.REGULAR_FILE;
- }
+ mode = FileMode.REGULAR_FILE;
+ displayMethod = DisplayMethod.DIFF;
}
- reuse = false;
- } else if (Patch.MERGE_LIST.equals(path)) {
- if (comparisonType.isAgainstParentOrAutoMerge() && Objects.equals(aId, within)) {
- id = ObjectId.zeroId();
- src = Text.EMPTY;
- srcContent = Text.NO_BYTES;
- mode = FileMode.MISSING;
- displayMethod = DisplayMethod.NONE;
- } else {
- id = within;
- src = Text.forMergeList(comparisonType, reader, within);
- srcContent = src.getContent();
- if (src == Text.EMPTY) {
- mode = FileMode.MISSING;
- displayMethod = DisplayMethod.NONE;
- } else {
- mode = FileMode.REGULAR_FILE;
- }
- }
- reuse = false;
+ return createSide(
+ path,
+ within,
+ mode,
+ srcContent,
+ src,
+ MimeUtil2.UNKNOWN_MIME_TYPE,
+ displayMethod,
+ false);
+ }
+ final TreeWalk tw = find(path, within);
+ ObjectId id = tw != null ? tw.getObjectId(0) : ObjectId.zeroId();
+ FileMode mode = tw != null ? tw.getFileMode(0) : FileMode.MISSING;
+ boolean reuse =
+ other != null
+ && other.id.equals(id)
+ && (other.mode == mode || isBothFile(other.mode, mode));
+ Text src = null;
+ byte[] srcContent;
+ if (reuse) {
+ srcContent = other.srcContent;
+
+ } else if (mode.getObjectType() == Constants.OBJ_BLOB) {
+ srcContent = Text.asByteArray(db.open(id, Constants.OBJ_BLOB));
+
+ } else if (mode.getObjectType() == Constants.OBJ_COMMIT) {
+ String strContent = "Subproject commit " + ObjectId.toString(id);
+ srcContent = strContent.getBytes(UTF_8);
+
} else {
- final TreeWalk tw = find(within);
+ srcContent = Text.NO_BYTES;
+ }
+ MimeType mimeType = MimeUtil2.UNKNOWN_MIME_TYPE;
+ DisplayMethod displayMethod = DisplayMethod.DIFF;
+ if (reuse) {
+ mimeType = other.mimeType;
+ displayMethod = other.displayMethod;
+ src = other.src;
- id = tw != null ? tw.getObjectId(0) : ObjectId.zeroId();
- mode = tw != null ? tw.getFileMode(0) : FileMode.MISSING;
- reuse =
- other != null
- && other.id.equals(id)
- && (other.mode == mode || isBothFile(other.mode, mode));
-
- if (reuse) {
- srcContent = other.srcContent;
-
- } else if (mode.getObjectType() == Constants.OBJ_BLOB) {
- srcContent = Text.asByteArray(db.open(id, Constants.OBJ_BLOB));
-
- } else if (mode.getObjectType() == Constants.OBJ_COMMIT) {
- String strContent = "Subproject commit " + ObjectId.toString(id);
- srcContent = strContent.getBytes(UTF_8);
-
- } else {
- srcContent = Text.NO_BYTES;
- }
-
- if (reuse) {
- mimeType = other.mimeType;
- displayMethod = other.displayMethod;
- src = other.src;
-
- } else if (srcContent.length > 0 && FileMode.SYMLINK != mode) {
- mimeType = registry.getMimeType(path, srcContent);
- if ("image".equals(mimeType.getMediaType()) && registry.isSafeInline(mimeType)) {
- displayMethod = DisplayMethod.IMG;
- }
+ } else if (srcContent.length > 0 && FileMode.SYMLINK != mode) {
+ mimeType = registry.getMimeType(path, srcContent);
+ if ("image".equals(mimeType.getMediaType()) && registry.isSafeInline(mimeType)) {
+ displayMethod = DisplayMethod.IMG;
}
}
-
- if (mode == FileMode.MISSING) {
- displayMethod = DisplayMethod.NONE;
- }
-
- if (!reuse) {
- if (srcContent == Text.NO_BYTES) {
- src = Text.EMPTY;
- } else {
- src = new Text(srcContent);
- }
- }
-
- dst.setSize(size());
-
- if (mode == FileMode.SYMLINK) {
- fileMode = PatchScript.FileMode.SYMLINK;
- } else if (mode == FileMode.GITLINK) {
- fileMode = PatchScript.FileMode.GITLINK;
- }
+ return createSide(path, id, mode, srcContent, src, mimeType, displayMethod, reuse);
} catch (IOException err) {
throw new IOException("Cannot read " + within.name() + ":" + path, err);
}
}
- private TreeWalk find(ObjectId within)
+ private Side createSide(
+ String path,
+ ObjectId id,
+ FileMode mode,
+ byte[] srcContent,
+ Text src,
+ MimeType mimeType,
+ DisplayMethod displayMethod,
+ boolean reuse) {
+ if (!reuse) {
+ if (srcContent == Text.NO_BYTES) {
+ src = Text.EMPTY;
+ } else {
+ src = new Text(srcContent);
+ }
+ }
+ if (mode == FileMode.MISSING) {
+ displayMethod = DisplayMethod.NONE;
+ }
+ PatchScript.FileMode fileMode = PatchScript.FileMode.FILE;
+ if (mode == FileMode.SYMLINK) {
+ fileMode = PatchScript.FileMode.SYMLINK;
+ } else if (mode == FileMode.GITLINK) {
+ fileMode = PatchScript.FileMode.GITLINK;
+ }
+ return new Side(path, id, mode, srcContent, src, mimeType, displayMethod, fileMode);
+ }
+
+ private TreeWalk find(String path, ObjectId within)
throws MissingObjectException, IncorrectObjectTypeException, CorruptObjectException,
IOException {
if (path == null || within == null) {
diff --git a/java/com/google/gerrit/server/project/LabelDefinitionJson.java b/java/com/google/gerrit/server/project/LabelDefinitionJson.java
new file mode 100644
index 0000000..2ecd8c2
--- /dev/null
+++ b/java/com/google/gerrit/server/project/LabelDefinitionJson.java
@@ -0,0 +1,53 @@
+// 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.project;
+
+import static java.util.stream.Collectors.toMap;
+
+import com.google.gerrit.common.data.LabelType;
+import com.google.gerrit.common.data.LabelValue;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.extensions.common.LabelDefinitionInfo;
+
+public class LabelDefinitionJson {
+ public static LabelDefinitionInfo format(Project.NameKey projectName, LabelType labelType) {
+ LabelDefinitionInfo label = new LabelDefinitionInfo();
+ label.name = labelType.getName();
+ label.projectName = projectName.get();
+ label.function = labelType.getFunction().getFunctionName();
+ label.values =
+ labelType.getValues().stream().collect(toMap(LabelValue::formatValue, LabelValue::getText));
+ label.defaultValue = labelType.getDefaultValue();
+ label.branches = labelType.getRefPatterns() != null ? labelType.getRefPatterns() : null;
+ label.canOverride = toBoolean(labelType.canOverride());
+ label.copyAnyScore = toBoolean(labelType.isCopyAnyScore());
+ label.copyMinScore = toBoolean(labelType.isCopyMinScore());
+ label.copyMaxScore = toBoolean(labelType.isCopyMaxScore());
+ label.copyAllScoresIfNoChange = toBoolean(labelType.isCopyAllScoresIfNoChange());
+ label.copyAllScoresIfNoCodeChange = toBoolean(labelType.isCopyAllScoresIfNoCodeChange());
+ label.copyAllScoresOnTrivialRebase = toBoolean(labelType.isCopyAllScoresOnTrivialRebase());
+ label.copyAllScoresOnMergeFirstParentUpdate =
+ toBoolean(labelType.isCopyAllScoresOnMergeFirstParentUpdate());
+ label.allowPostSubmit = toBoolean(labelType.allowPostSubmit());
+ label.ignoreSelfApproval = toBoolean(labelType.ignoreSelfApproval());
+ return label;
+ }
+
+ private static Boolean toBoolean(boolean v) {
+ return v ? v : null;
+ }
+
+ private LabelDefinitionJson() {}
+}
diff --git a/java/com/google/gerrit/server/project/LabelResource.java b/java/com/google/gerrit/server/project/LabelResource.java
new file mode 100644
index 0000000..a7a2f07
--- /dev/null
+++ b/java/com/google/gerrit/server/project/LabelResource.java
@@ -0,0 +1,41 @@
+// 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.project;
+
+import com.google.gerrit.common.data.LabelType;
+import com.google.gerrit.extensions.restapi.RestResource;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.inject.TypeLiteral;
+
+public class LabelResource implements RestResource {
+ public static final TypeLiteral<RestView<LabelResource>> LABEL_KIND =
+ new TypeLiteral<RestView<LabelResource>>() {};
+
+ private final ProjectResource project;
+ private final LabelType labelType;
+
+ public LabelResource(ProjectResource project, LabelType labelType) {
+ this.project = project;
+ this.labelType = labelType;
+ }
+
+ public ProjectResource getProject() {
+ return project;
+ }
+
+ public LabelType getLabelType() {
+ return labelType;
+ }
+}
diff --git a/java/com/google/gerrit/server/project/ProjectConfig.java b/java/com/google/gerrit/server/project/ProjectConfig.java
index 44d9d98..4d551a2 100644
--- a/java/com/google/gerrit/server/project/ProjectConfig.java
+++ b/java/com/google/gerrit/server/project/ProjectConfig.java
@@ -19,6 +19,7 @@
import static com.google.gerrit.common.data.Permission.isPermission;
import static com.google.gerrit.entities.Project.DEFAULT_SUBMIT_TYPE;
import static com.google.gerrit.server.permissions.PluginPermissionsUtil.isValidPluginPermission;
+import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toList;
import com.google.common.base.CharMatcher;
@@ -112,10 +113,10 @@
public static final String KEY_CAN_OVERRIDE = "canOverride";
public static final String KEY_BRANCH = "branch";
- private static final String KEY_MATCH = "match";
+ public static final String KEY_MATCH = "match";
private static final String KEY_HTML = "html";
- private static final String KEY_LINK = "link";
- private static final String KEY_ENABLED = "enabled";
+ public static final String KEY_LINK = "link";
+ public static final String KEY_ENABLED = "enabled";
public static final String PROJECT_CONFIG = "project.config";
@@ -291,6 +292,11 @@
commentLinkSections.put(commentLink.name, commentLink);
}
+ public void removeCommentLinkSection(String name) {
+ requireNonNull(name);
+ requireNonNull(commentLinkSections.remove(name));
+ }
+
private ProjectConfig(Project.NameKey projectName, @Nullable StoredConfig baseConfig) {
this.projectName = projectName;
this.baseConfig = baseConfig;
@@ -1488,6 +1494,8 @@
List<String> refPatterns = label.getRefPatterns();
if (refPatterns != null && !refPatterns.isEmpty()) {
rc.setStringList(LABEL, name, KEY_BRANCH, refPatterns);
+ } else {
+ rc.unset(LABEL, name, KEY_BRANCH);
}
}
diff --git a/java/com/google/gerrit/server/project/Reachable.java b/java/com/google/gerrit/server/project/Reachable.java
index 4ea5d11..c30378b 100644
--- a/java/com/google/gerrit/server/project/Reachable.java
+++ b/java/com/google/gerrit/server/project/Reachable.java
@@ -17,6 +17,9 @@
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.entities.Project;
import com.google.gerrit.server.change.IncludedInResolver;
+import com.google.gerrit.server.logging.Metadata;
+import com.google.gerrit.server.logging.TraceContext;
+import com.google.gerrit.server.logging.TraceContext.TraceTimer;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackend.RefFilterOptions;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -58,7 +61,15 @@
.currentUser()
.project(project)
.filter(refs, repo, RefFilterOptions.defaults());
- return IncludedInResolver.includedInAny(repo, rw, commit, filtered.values());
+
+ // The filtering above already produces a voluminous trace. To separate the permission check
+ // from the reachability check, do the trace here:
+ try (TraceTimer timer =
+ TraceContext.newTimer(
+ "IncludedInResolver.includedInAny",
+ Metadata.builder().projectName(project.get()).inputSize(refs.size()).build())) {
+ return IncludedInResolver.includedInAny(repo, rw, commit, filtered.values());
+ }
} catch (IOException | PermissionBackendException e) {
logger.atSevere().withCause(e).log(
"Cannot verify permissions to commit object %s in repository %s", commit.name(), project);
diff --git a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index d2fc77d..5d4edc9 100644
--- a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -63,6 +63,7 @@
import com.google.gerrit.server.change.ChangeTriplet;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.AllUsersName;
+import com.google.gerrit.server.config.OperatorAliasConfig;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.index.change.ChangeField;
import com.google.gerrit.server.index.change.ChangeIndex;
@@ -219,6 +220,7 @@
final SubmitDryRun submitDryRun;
final GroupMembers groupMembers;
final Provider<AnonymousUser> anonymousUserProvider;
+ final OperatorAliasConfig operatorAliasConfig;
private final Provider<CurrentUser> self;
@@ -250,7 +252,8 @@
StarredChangesUtil starredChangesUtil,
AccountCache accountCache,
GroupMembers groupMembers,
- Provider<AnonymousUser> anonymousUserProvider) {
+ Provider<AnonymousUser> anonymousUserProvider,
+ OperatorAliasConfig operatorAliasConfig) {
this(
queryProvider,
rewriter,
@@ -277,7 +280,8 @@
starredChangesUtil,
accountCache,
groupMembers,
- anonymousUserProvider);
+ anonymousUserProvider,
+ operatorAliasConfig);
}
private Arguments(
@@ -306,7 +310,8 @@
StarredChangesUtil starredChangesUtil,
AccountCache accountCache,
GroupMembers groupMembers,
- Provider<AnonymousUser> anonymousUserProvider) {
+ Provider<AnonymousUser> anonymousUserProvider,
+ OperatorAliasConfig operatorAliasConfig) {
this.queryProvider = queryProvider;
this.rewriter = rewriter;
this.opFactories = opFactories;
@@ -333,6 +338,7 @@
this.hasOperands = hasOperands;
this.groupMembers = groupMembers;
this.anonymousUserProvider = anonymousUserProvider;
+ this.operatorAliasConfig = operatorAliasConfig;
}
Arguments asUser(CurrentUser otherUser) {
@@ -362,7 +368,8 @@
starredChangesUtil,
accountCache,
groupMembers,
- anonymousUserProvider);
+ anonymousUserProvider,
+ operatorAliasConfig);
}
Arguments asUser(Account.Id otherId) {
@@ -407,6 +414,7 @@
@Inject
ChangeQueryBuilder(Arguments args) {
this(mydef, args);
+ setupAliases();
}
@VisibleForTesting
@@ -415,6 +423,10 @@
this.args = args;
}
+ private void setupAliases() {
+ setOperatorAliases(args.operatorAliasConfig.getChangeQueryOperatorAliases());
+ }
+
public Arguments getArgs() {
return args;
}
diff --git a/java/com/google/gerrit/server/restapi/account/PutPreferred.java b/java/com/google/gerrit/server/restapi/account/PutPreferred.java
index 2ddea2f..c781246 100644
--- a/java/com/google/gerrit/server/restapi/account/PutPreferred.java
+++ b/java/com/google/gerrit/server/restapi/account/PutPreferred.java
@@ -145,6 +145,6 @@
if (exception.get().isPresent()) {
throw exception.get().get();
}
- return alreadyPreferred.get() ? Response.ok("") : Response.created("");
+ return alreadyPreferred.get() ? Response.ok() : Response.created();
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/ApplyFix.java b/java/com/google/gerrit/server/restapi/change/ApplyFix.java
index d31fd92..74c5bc2 100644
--- a/java/com/google/gerrit/server/restapi/change/ApplyFix.java
+++ b/java/com/google/gerrit/server/restapi/change/ApplyFix.java
@@ -18,6 +18,7 @@
import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.common.EditInfo;
import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
@@ -65,8 +66,8 @@
@Override
public Response<EditInfo> apply(FixResource fixResource, Void nothing)
- throws AuthException, ResourceConflictException, IOException, ResourceNotFoundException,
- PermissionBackendException {
+ throws AuthException, BadRequestException, ResourceConflictException, IOException,
+ ResourceNotFoundException, PermissionBackendException {
RevisionResource revisionResource = fixResource.getRevisionResource();
Project.NameKey project = revisionResource.getProject();
ProjectState projectState = projectCache.checkedGet(project);
diff --git a/java/com/google/gerrit/server/restapi/change/ChangeEdits.java b/java/com/google/gerrit/server/restapi/change/ChangeEdits.java
index 6955d8b..1808fa6 100644
--- a/java/com/google/gerrit/server/restapi/change/ChangeEdits.java
+++ b/java/com/google/gerrit/server/restapi/change/ChangeEdits.java
@@ -107,6 +107,7 @@
* PUT request with a path was called but change edit wasn't created yet. Change edit is created
* and PUT handler is called.
*/
+ @Singleton
public static class Create
implements RestCollectionCreateView<ChangeResource, ChangeEditResource, Put.Input> {
private final Put putEdit;
@@ -124,6 +125,7 @@
}
}
+ @Singleton
public static class DeleteFile
implements RestCollectionDeleteMissingView<ChangeResource, ChangeEditResource, Input> {
private final DeleteContent deleteContent;
diff --git a/java/com/google/gerrit/server/restapi/change/DeleteChangeMessage.java b/java/com/google/gerrit/server/restapi/change/DeleteChangeMessage.java
index 30cfad6..63f5bbe 100644
--- a/java/com/google/gerrit/server/restapi/change/DeleteChangeMessage.java
+++ b/java/com/google/gerrit/server/restapi/change/DeleteChangeMessage.java
@@ -28,6 +28,7 @@
import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.AccountLoader;
@@ -146,21 +147,18 @@
@Singleton
public static class DefaultDeleteChangeMessage
- extends RetryingRestModifyView<ChangeMessageResource, Input, ChangeMessageInfo> {
+ implements RestModifyView<ChangeMessageResource, Input> {
private final DeleteChangeMessage deleteChangeMessage;
@Inject
- public DefaultDeleteChangeMessage(
- DeleteChangeMessage deleteChangeMessage, RetryHelper retryHelper) {
- super(retryHelper);
+ public DefaultDeleteChangeMessage(DeleteChangeMessage deleteChangeMessage) {
this.deleteChangeMessage = deleteChangeMessage;
}
@Override
- protected Response<ChangeMessageInfo> applyImpl(
- BatchUpdate.Factory updateFactory, ChangeMessageResource resource, Input input)
- throws Exception {
- return deleteChangeMessage.applyImpl(updateFactory, resource, new DeleteChangeMessageInput());
+ public Response<ChangeMessageInfo> apply(ChangeMessageResource resource, Input input)
+ throws RestApiException {
+ return deleteChangeMessage.apply(resource, new DeleteChangeMessageInput());
}
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/DeletePrivate.java b/java/com/google/gerrit/server/restapi/change/DeletePrivate.java
index de7a683..8478bb5 100644
--- a/java/com/google/gerrit/server/restapi/change/DeletePrivate.java
+++ b/java/com/google/gerrit/server/restapi/change/DeletePrivate.java
@@ -17,6 +17,7 @@
import static com.google.gerrit.extensions.conditions.BooleanCondition.or;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.extensions.common.InputWithMessage;
import com.google.gerrit.extensions.conditions.BooleanCondition;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -36,7 +37,7 @@
@Singleton
public class DeletePrivate
- extends RetryingRestModifyView<ChangeResource, SetPrivateOp.Input, String> {
+ extends RetryingRestModifyView<ChangeResource, InputWithMessage, String> {
private final PermissionBackend permissionBackend;
private final SetPrivateOp.Factory setPrivateOpFactory;
@@ -52,7 +53,7 @@
@Override
protected Response<String> applyImpl(
- BatchUpdate.Factory updateFactory, ChangeResource rsrc, @Nullable SetPrivateOp.Input input)
+ BatchUpdate.Factory updateFactory, ChangeResource rsrc, @Nullable InputWithMessage input)
throws RestApiException, UpdateException {
if (!canDeletePrivate(rsrc).value()) {
throw new AuthException("not allowed to unmark private");
diff --git a/java/com/google/gerrit/server/restapi/change/GetDiff.java b/java/com/google/gerrit/server/restapi/change/GetDiff.java
index 857205a..74562c2 100644
--- a/java/com/google/gerrit/server/restapi/change/GetDiff.java
+++ b/java/com/google/gerrit/server/restapi/change/GetDiff.java
@@ -155,23 +155,23 @@
psf.setLoadHistory(false);
psf.setLoadComments(context != DiffPreferencesInfo.WHOLE_FILE_CONTEXT);
PatchScript ps = psf.call();
- Content content = new Content(ps);
+ ContentCollector contentCollector = new ContentCollector(ps);
Set<Edit> editsDueToRebase = ps.getEditsDueToRebase();
for (Edit edit : ps.getEdits()) {
if (edit.getType() == Edit.Type.EMPTY) {
continue;
}
- content.addCommon(edit.getBeginA());
+ contentCollector.addCommon(edit.getBeginA());
checkState(
- content.nextA == edit.getBeginA(),
+ contentCollector.nextA == edit.getBeginA(),
"nextA = %s; want %s",
- content.nextA,
+ contentCollector.nextA,
edit.getBeginA());
checkState(
- content.nextB == edit.getBeginB(),
+ contentCollector.nextB == edit.getBeginB(),
"nextB = %s; want %s",
- content.nextB,
+ contentCollector.nextB,
edit.getBeginB());
switch (edit.getType()) {
case DELETE:
@@ -180,19 +180,19 @@
List<Edit> internalEdit =
edit instanceof ReplaceEdit ? ((ReplaceEdit) edit).getInternalEdits() : null;
boolean dueToRebase = editsDueToRebase.contains(edit);
- content.addDiff(edit.getEndA(), edit.getEndB(), internalEdit, dueToRebase);
+ contentCollector.addDiff(edit.getEndA(), edit.getEndB(), internalEdit, dueToRebase);
break;
case EMPTY:
default:
throw new IllegalStateException();
}
}
- content.addCommon(ps.getA().size());
+ contentCollector.addCommon(ps.getA().size());
ProjectState state = projectCache.get(resource.getRevision().getChange().getProject());
DiffInfo result = new DiffInfo();
- String revA = basePatchSet != null ? basePatchSet.refName() : content.commitIdA;
+ String revA = basePatchSet != null ? basePatchSet.refName() : ps.getFileInfoA().commitId;
String revB =
resource.getRevision().getEdit().isPresent()
? resource.getRevision().getEdit().get().getRefName()
@@ -221,7 +221,7 @@
state, result.metaA.name, ps.getFileModeA(), ps.getMimeTypeA());
result.metaA.lines = ps.getA().size();
result.metaA.webLinks = getFileWebLinks(state.getProject(), revA, result.metaA.name);
- result.metaA.commitId = content.commitIdA;
+ result.metaA.commitId = ps.getFileInfoA().commitId;
}
if (ps.getDisplayMethodB() != DisplayMethod.NONE) {
@@ -232,7 +232,7 @@
state, result.metaB.name, ps.getFileModeB(), ps.getMimeTypeB());
result.metaB.lines = ps.getB().size();
result.metaB.webLinks = getFileWebLinks(state.getProject(), revB, result.metaB.name);
- result.metaB.commitId = content.commitIdB;
+ result.metaB.commitId = ps.getFileInfoB().commitId;
}
if (intraline) {
@@ -253,7 +253,7 @@
if (ps.getPatchHeader().size() > 0) {
result.diffHeader = ps.getPatchHeader();
}
- result.content = content.lines;
+ result.content = contentCollector.lines;
Response<DiffInfo> r = Response.ok(result);
if (resource.isCacheable()) {
@@ -297,24 +297,20 @@
return this;
}
- private static class Content {
+ private static class ContentCollector {
final List<ContentEntry> lines;
final SparseFileContent fileA;
final SparseFileContent fileB;
final boolean ignoreWS;
- final String commitIdA;
- final String commitIdB;
int nextA;
int nextB;
- Content(PatchScript ps) {
+ ContentCollector(PatchScript ps) {
lines = Lists.newArrayListWithExpectedSize(ps.getEdits().size() + 2);
fileA = ps.getA();
fileB = ps.getB();
ignoreWS = ps.isIgnoreWhitespace();
- commitIdA = ps.getCommitIdA();
- commitIdB = ps.getCommitIdB();
}
void addCommon(int end) {
diff --git a/java/com/google/gerrit/server/restapi/change/Ignore.java b/java/com/google/gerrit/server/restapi/change/Ignore.java
index 25cf311..a049e54 100644
--- a/java/com/google/gerrit/server/restapi/change/Ignore.java
+++ b/java/com/google/gerrit/server/restapi/change/Ignore.java
@@ -60,7 +60,7 @@
if (!isIgnored(rsrc)) {
stars.ignore(rsrc);
}
- return Response.ok("");
+ return Response.ok();
} catch (MutuallyExclusiveLabelsException e) {
throw new ResourceConflictException(e.getMessage());
}
diff --git a/java/com/google/gerrit/server/restapi/change/ListChangeMessages.java b/java/com/google/gerrit/server/restapi/change/ListChangeMessages.java
index bfc9f12..099d0a6 100644
--- a/java/com/google/gerrit/server/restapi/change/ListChangeMessages.java
+++ b/java/com/google/gerrit/server/restapi/change/ListChangeMessages.java
@@ -32,18 +32,19 @@
@Singleton
public class ListChangeMessages implements RestReadView<ChangeResource> {
private final ChangeMessagesUtil changeMessagesUtil;
- private final AccountLoader accountLoader;
+ private final AccountLoader.Factory accountLoaderFactory;
@Inject
public ListChangeMessages(
ChangeMessagesUtil changeMessagesUtil, AccountLoader.Factory accountLoaderFactory) {
this.changeMessagesUtil = changeMessagesUtil;
- this.accountLoader = accountLoaderFactory.create(true);
+ this.accountLoaderFactory = accountLoaderFactory;
}
@Override
public Response<List<ChangeMessageInfo>> apply(ChangeResource resource)
throws PermissionBackendException {
+ AccountLoader accountLoader = accountLoaderFactory.create(true);
List<ChangeMessage> messages = changeMessagesUtil.byChange(resource.getNotes());
List<ChangeMessageInfo> messageInfos =
messages.stream()
diff --git a/java/com/google/gerrit/server/restapi/change/MarkAsReviewed.java b/java/com/google/gerrit/server/restapi/change/MarkAsReviewed.java
index 4c942d2..fa4555b 100644
--- a/java/com/google/gerrit/server/restapi/change/MarkAsReviewed.java
+++ b/java/com/google/gerrit/server/restapi/change/MarkAsReviewed.java
@@ -54,7 +54,7 @@
public Response<String> apply(ChangeResource rsrc, Input input)
throws RestApiException, IllegalLabelException {
stars.markAsReviewed(rsrc);
- return Response.ok("");
+ return Response.ok();
}
private boolean isReviewed(ChangeResource rsrc) {
diff --git a/java/com/google/gerrit/server/restapi/change/MarkAsUnreviewed.java b/java/com/google/gerrit/server/restapi/change/MarkAsUnreviewed.java
index 5945b14..601fc4a 100644
--- a/java/com/google/gerrit/server/restapi/change/MarkAsUnreviewed.java
+++ b/java/com/google/gerrit/server/restapi/change/MarkAsUnreviewed.java
@@ -52,7 +52,7 @@
@Override
public Response<String> apply(ChangeResource rsrc, Input input) throws IllegalLabelException {
stars.markAsUnreviewed(rsrc);
- return Response.ok("");
+ return Response.ok();
}
private boolean isReviewed(ChangeResource rsrc) {
diff --git a/java/com/google/gerrit/server/restapi/change/Module.java b/java/com/google/gerrit/server/restapi/change/Module.java
index a57bd64..539463f 100644
--- a/java/com/google/gerrit/server/restapi/change/Module.java
+++ b/java/com/google/gerrit/server/restapi/change/Module.java
@@ -96,6 +96,7 @@
post(CHANGE_KIND, "hashtags").to(PostHashtags.class);
post(CHANGE_KIND, "restore").to(Restore.class);
post(CHANGE_KIND, "revert").to(Revert.class);
+ post(CHANGE_KIND, "revert_submission").to(RevertSubmission.class);
post(CHANGE_KIND, "submit").to(Submit.CurrentRevision.class);
get(CHANGE_KIND, "submitted_together").to(SubmittedTogether.class);
post(CHANGE_KIND, "rebase").to(Rebase.CurrentRevision.class);
diff --git a/java/com/google/gerrit/server/restapi/change/PostPrivate.java b/java/com/google/gerrit/server/restapi/change/PostPrivate.java
index f008df3..c9ad049 100644
--- a/java/com/google/gerrit/server/restapi/change/PostPrivate.java
+++ b/java/com/google/gerrit/server/restapi/change/PostPrivate.java
@@ -18,6 +18,7 @@
import static com.google.gerrit.extensions.conditions.BooleanCondition.or;
import com.google.gerrit.entities.Change;
+import com.google.gerrit.extensions.common.InputWithMessage;
import com.google.gerrit.extensions.conditions.BooleanCondition;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
@@ -39,7 +40,7 @@
import org.eclipse.jgit.lib.Config;
@Singleton
-public class PostPrivate extends RetryingRestModifyView<ChangeResource, SetPrivateOp.Input, String>
+public class PostPrivate extends RetryingRestModifyView<ChangeResource, InputWithMessage, String>
implements UiAction<ChangeResource> {
private final PermissionBackend permissionBackend;
private final SetPrivateOp.Factory setPrivateOpFactory;
@@ -59,7 +60,7 @@
@Override
public Response<String> applyImpl(
- BatchUpdate.Factory updateFactory, ChangeResource rsrc, SetPrivateOp.Input input)
+ BatchUpdate.Factory updateFactory, ChangeResource rsrc, InputWithMessage input)
throws RestApiException, UpdateException {
if (disablePrivateChanges) {
throw new MethodNotAllowedException("private changes are disabled");
@@ -70,7 +71,7 @@
}
if (rsrc.getChange().isPrivate()) {
- return Response.ok("");
+ return Response.ok();
}
SetPrivateOp op = setPrivateOpFactory.create(true, input);
@@ -79,7 +80,7 @@
u.addOp(rsrc.getId(), op).execute();
}
- return Response.created("");
+ return Response.created();
}
@Override
diff --git a/java/com/google/gerrit/server/restapi/change/PostReview.java b/java/com/google/gerrit/server/restapi/change/PostReview.java
index 974a72c..602ab19 100644
--- a/java/com/google/gerrit/server/restapi/change/PostReview.java
+++ b/java/com/google/gerrit/server/restapi/change/PostReview.java
@@ -249,6 +249,8 @@
ProjectState projectState = projectCache.checkedGet(revision.getProject());
LabelTypes labelTypes = projectState.getLabelTypes(revision.getNotes());
+ logger.atFine().log("strict label checking is %s", (strictLabels ? "enabled" : "disabled"));
+
input.drafts = firstNonNull(input.drafts, DraftHandling.KEEP);
logger.atFine().log("draft handling = %s", input.drafts);
@@ -270,6 +272,7 @@
if (input.notify == null) {
input.notify = defaultNotify(revision.getChange(), input);
}
+ logger.atFine().log("notify handling = %s", input.notify);
Map<String, AddReviewerResult> reviewerJsonResults = null;
List<ReviewerAddition> reviewerResults = Lists.newArrayList();
@@ -282,13 +285,18 @@
reviewerAdder.prepare(revision.getNotes(), revision.getUser(), reviewerInput, true);
reviewerJsonResults.put(reviewerInput.reviewer, result.result);
if (result.result.error != null) {
+ logger.atFine().log(
+ "Adding %s as reviewer failed: %s", reviewerInput.reviewer, result.result.error);
hasError = true;
continue;
}
if (result.result.confirm != null) {
+ logger.atFine().log(
+ "Adding %s as reviewer requires confirmation", reviewerInput.reviewer);
confirm = true;
continue;
}
+ logger.atFine().log("Adding %s as reviewer was prepared", reviewerInput.reviewer);
reviewerResults.add(result);
}
}
@@ -307,6 +315,9 @@
boolean ccOrReviewer = false;
if (input.labels != null && !input.labels.isEmpty()) {
ccOrReviewer = input.labels.values().stream().anyMatch(v -> v != 0);
+ if (ccOrReviewer) {
+ logger.atFine().log("calling user is cc/reviewer on the change due to voting on a label");
+ }
}
if (!ccOrReviewer) {
@@ -314,17 +325,22 @@
ReviewerSet currentReviewers =
approvalsUtil.getReviewers(revision.getChangeResource().getNotes());
ccOrReviewer = currentReviewers.all().contains(id);
+ if (ccOrReviewer) {
+ logger.atFine().log("calling user is already cc/reviewer on the change");
+ }
}
// Apply reviewer changes first. Revision emails should be sent to the
// updated set of reviewers. Also keep track of whether the user added
// themselves as a reviewer or to the CC list.
+ logger.atFine().log("adding reviewer additions");
for (ReviewerAddition reviewerResult : reviewerResults) {
reviewerResult.op.suppressEmail(); // Send a single batch email below.
bu.addOp(revision.getChange().getId(), reviewerResult.op);
if (!ccOrReviewer && reviewerResult.result.reviewers != null) {
for (ReviewerInfo reviewerInfo : reviewerResult.result.reviewers) {
if (Objects.equals(id.get(), reviewerInfo._accountId)) {
+ logger.atFine().log("calling user is explicitly added as reviewer");
ccOrReviewer = true;
break;
}
@@ -333,6 +349,7 @@
if (!ccOrReviewer && reviewerResult.result.ccs != null) {
for (AccountInfo accountInfo : reviewerResult.result.ccs) {
if (Objects.equals(id.get(), accountInfo._accountId)) {
+ logger.atFine().log("calling user is explicitly added as cc");
ccOrReviewer = true;
break;
}
@@ -344,6 +361,7 @@
// User posting this review isn't currently in the reviewer or CC list,
// isn't being explicitly added, and isn't voting on any label.
// Automatically CC them on this change so they receive replies.
+ logger.atFine().log("CCing calling user");
ReviewerAddition selfAddition = reviewerAdder.ccCurrentUser(revision.getUser(), revision);
selfAddition.op.suppressEmail();
bu.addOp(revision.getChange().getId(), selfAddition.op);
@@ -365,6 +383,7 @@
output.ready = true;
}
+ logger.atFine().log("setting work-in-progress to %s", input.workInProgress);
WorkInProgressOp wipOp =
workInProgressOpFactory.create(input.workInProgress, new WorkInProgressOp.Input());
wipOp.suppressEmail();
@@ -372,6 +391,7 @@
}
// Add the review op.
+ logger.atFine().log("posting review");
bu.addOp(
revision.getChange().getId(), new Op(projectState, revision.getPatchSet().id(), input));
@@ -455,6 +475,8 @@
private RevisionResource onBehalfOf(RevisionResource rev, LabelTypes labelTypes, ReviewInput in)
throws BadRequestException, AuthException, UnprocessableEntityException,
PermissionBackendException, IOException, ConfigInvalidException {
+ logger.atFine().log("request is executed on behalf of %s", in.onBehalfOf);
+
if (in.labels == null || in.labels.isEmpty()) {
throw new AuthException(
String.format("label required to post review on behalf of \"%s\"", in.onBehalfOf));
@@ -463,6 +485,8 @@
throw new AuthException("not allowed to modify other user's drafts");
}
+ logger.atFine().log("label input: %s", in.labels);
+
CurrentUser caller = rev.getUser();
PermissionBackend.ForChange perm = rev.permissions();
Iterator<Map.Entry<String, Short>> itr = in.labels.entrySet().iterator();
@@ -470,15 +494,22 @@
Map.Entry<String, Short> ent = itr.next();
LabelType type = labelTypes.byLabel(ent.getKey());
if (type == null) {
+ logger.atFine().log("label %s not found", ent.getKey());
if (strictLabels) {
throw new BadRequestException(
String.format("label \"%s\" is not a configured label", ent.getKey()));
}
+ logger.atFine().log("ignoring input for unknown label %s", ent.getKey());
itr.remove();
continue;
}
- if (!caller.isInternalUser()) {
+ if (caller.isInternalUser()) {
+ logger.atFine().log(
+ "skipping on behalf of permission check for label %s"
+ + " because caller is an internal user",
+ type.getName());
+ } else {
try {
perm.check(new LabelPermission.WithValue(ON_BEHALF_OF, type, ent.getValue()));
} catch (AuthException e) {
@@ -490,11 +521,13 @@
}
}
if (in.labels.isEmpty()) {
+ logger.atFine().log("labels are empty after unknown labels have been removed");
throw new AuthException(
String.format("label required to post review on behalf of \"%s\"", in.onBehalfOf));
}
IdentifiedUser reviewer = accountResolver.resolve(in.onBehalfOf).asUniqueUserOnBehalfOf(caller);
+ logger.atFine().log("on behalf of user was resolved to %s", reviewer.getLoggableName());
try {
permissionBackend.user(reviewer).change(rev.getNotes()).check(ChangePermission.READ);
} catch (AuthException e) {
@@ -508,16 +541,20 @@
private void checkLabels(RevisionResource rsrc, LabelTypes labelTypes, Map<String, Short> labels)
throws BadRequestException, AuthException, PermissionBackendException {
+ logger.atFine().log("checking label input: %s", labels);
+
PermissionBackend.ForChange perm = rsrc.permissions();
Iterator<Map.Entry<String, Short>> itr = labels.entrySet().iterator();
while (itr.hasNext()) {
Map.Entry<String, Short> ent = itr.next();
LabelType lt = labelTypes.byLabel(ent.getKey());
if (lt == null) {
+ logger.atFine().log("label %s not found", ent.getKey());
if (strictLabels) {
throw new BadRequestException(
String.format("label \"%s\" is not a configured label", ent.getKey()));
}
+ logger.atFine().log("ignoring input for unknown label %s", ent.getKey());
itr.remove();
continue;
}
@@ -529,10 +566,13 @@
}
if (lt.getValue(ent.getValue()) == null) {
+ logger.atFine().log("label value %s not found", ent.getValue());
if (strictLabels) {
throw new BadRequestException(
String.format("label \"%s\": %d is not a valid value", ent.getKey(), ent.getValue()));
}
+ logger.atFine().log(
+ "ignoring input for label %s because label value is unknown", ent.getKey());
itr.remove();
continue;
}
@@ -576,6 +616,7 @@
private <T extends CommentInput> void checkComments(
RevisionResource revision, Map<String, List<T>> commentsPerPath)
throws BadRequestException, PatchListNotAvailableException {
+ logger.atFine().log("checking comments");
Set<String> revisionFilePaths = getAffectedFilePaths(revision);
for (Map.Entry<String, List<T>> entry : commentsPerPath.entrySet()) {
String path = entry.getKey();
@@ -628,6 +669,7 @@
private void checkRobotComments(
RevisionResource revision, Map<String, List<RobotCommentInput>> in)
throws BadRequestException, PatchListNotAvailableException {
+ logger.atFine().log("checking robot comments");
for (Map.Entry<String, List<RobotCommentInput>> e : in.entrySet()) {
String commentPath = e.getKey();
for (RobotCommentInput c : e.getValue()) {
diff --git a/java/com/google/gerrit/server/restapi/change/Rebase.java b/java/com/google/gerrit/server/restapi/change/Rebase.java
index af8f971..50d1ad0 100644
--- a/java/com/google/gerrit/server/restapi/change/Rebase.java
+++ b/java/com/google/gerrit/server/restapi/change/Rebase.java
@@ -260,28 +260,23 @@
return description;
}
- public static class CurrentRevision
- extends RetryingRestModifyView<ChangeResource, RebaseInput, ChangeInfo> {
+ public static class CurrentRevision implements RestModifyView<ChangeResource, RebaseInput> {
private final PatchSetUtil psUtil;
private final Rebase rebase;
@Inject
- CurrentRevision(RetryHelper retryHelper, PatchSetUtil psUtil, Rebase rebase) {
- super(retryHelper);
+ CurrentRevision(PatchSetUtil psUtil, Rebase rebase) {
this.psUtil = psUtil;
this.rebase = rebase;
}
@Override
- protected Response<ChangeInfo> applyImpl(
- BatchUpdate.Factory updateFactory, ChangeResource rsrc, RebaseInput input)
- throws Exception {
+ public Response<ChangeInfo> apply(ChangeResource rsrc, RebaseInput input) throws Exception {
PatchSet ps = psUtil.current(rsrc.getNotes());
if (ps == null) {
throw new ResourceConflictException("current revision is missing");
}
- return Response.ok(
- rebase.applyImpl(updateFactory, new RevisionResource(rsrc, ps), input).value());
+ return Response.ok(rebase.apply(new RevisionResource(rsrc, ps), input).value());
}
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/RevertSubmission.java b/java/com/google/gerrit/server/restapi/change/RevertSubmission.java
new file mode 100644
index 0000000..105ffa2
--- /dev/null
+++ b/java/com/google/gerrit/server/restapi/change/RevertSubmission.java
@@ -0,0 +1,166 @@
+// 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.restapi.change;
+
+import static com.google.gerrit.extensions.conditions.BooleanCondition.and;
+import static com.google.gerrit.server.permissions.RefPermission.CREATE_CHANGE;
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.flogger.FluentLogger;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.extensions.api.changes.RevertInput;
+import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.common.RevertSubmissionInfo;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.webui.UiAction;
+import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.PatchSetUtil;
+import com.google.gerrit.server.change.ChangeResource;
+import com.google.gerrit.server.permissions.ChangePermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.project.ContributorAgreementsChecker;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.InternalChangeQuery;
+import com.google.gerrit.server.update.BatchUpdate;
+import com.google.gerrit.server.update.RetryHelper;
+import com.google.gerrit.server.update.RetryingRestModifyView;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.commons.lang.RandomStringUtils;
+
+@Singleton
+public class RevertSubmission
+ extends RetryingRestModifyView<ChangeResource, RevertInput, RevertSubmissionInfo>
+ implements UiAction<ChangeResource> {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+ private final Revert revert;
+ private final Provider<InternalChangeQuery> queryProvider;
+ private final ChangeResource.Factory changeResourceFactory;
+ private final Provider<CurrentUser> user;
+ private final PermissionBackend permissionBackend;
+ private final ProjectCache projectCache;
+ private final PatchSetUtil psUtil;
+ private final ContributorAgreementsChecker contributorAgreements;
+
+ @Inject
+ RevertSubmission(
+ RetryHelper retryHelper,
+ Revert revert,
+ Provider<InternalChangeQuery> queryProvider,
+ ChangeResource.Factory changeResourceFactory,
+ Provider<CurrentUser> user,
+ PermissionBackend permissionBackend,
+ ProjectCache projectCache,
+ PatchSetUtil psUtil,
+ ContributorAgreementsChecker contributorAgreements) {
+ super(retryHelper);
+ this.revert = revert;
+ this.queryProvider = queryProvider;
+ this.changeResourceFactory = changeResourceFactory;
+ this.user = user;
+ this.permissionBackend = permissionBackend;
+ this.projectCache = projectCache;
+ this.psUtil = psUtil;
+ this.contributorAgreements = contributorAgreements;
+ }
+
+ @Override
+ public Response<RevertSubmissionInfo> applyImpl(
+ BatchUpdate.Factory updateFactory, ChangeResource changeResource, RevertInput input)
+ throws Exception {
+
+ if (!changeResource.getChange().isMerged()) {
+ throw new ResourceConflictException(
+ String.format("change is %s.", ChangeUtil.status(changeResource.getChange())));
+ }
+
+ String submissionId =
+ requireNonNull(
+ changeResource.getChange().getSubmissionId(),
+ String.format("merged change %s has no submission ID", changeResource.getId()));
+
+ List<ChangeData> changeDatas = queryProvider.get().bySubmissionId(submissionId);
+
+ for (ChangeData changeData : changeDatas) {
+ Change change = changeData.change();
+
+ // Might do the permission tests multiple times, but these are necessary to ensure that the
+ // user has permissions to revert all changes. If they lack any permission, no revert will be
+ // done.
+
+ contributorAgreements.check(change.getProject(), changeResource.getUser());
+ permissionBackend.currentUser().ref(change.getDest()).check(CREATE_CHANGE);
+ permissionBackend.currentUser().change(changeData).check(ChangePermission.READ);
+ projectCache.checkedGet(change.getProject()).checkStatePermitsWrite();
+
+ requireNonNull(
+ psUtil.get(changeData.notes(), change.currentPatchSetId()),
+ String.format(
+ "current patch set %s of change %s not found",
+ change.currentPatchSetId(), change.currentPatchSetId()));
+ }
+ return Response.ok(revertSubmission(changeDatas, input, submissionId));
+ }
+
+ private RevertSubmissionInfo revertSubmission(
+ List<ChangeData> changeDatas, RevertInput input, String submissionId) throws Exception {
+ List<ChangeInfo> results;
+ results = new ArrayList<>();
+ if (input.topic == null) {
+ input.topic =
+ String.format(
+ "revert-%s-%s", submissionId, RandomStringUtils.randomAlphabetic(10).toUpperCase());
+ }
+ for (ChangeData changeData : changeDatas) {
+ ChangeResource change = changeResourceFactory.create(changeData.notes(), user.get());
+ // Reverts are done with retrying by using RetryingRestModifyView.
+ results.add(revert.apply(change, input).value());
+ }
+ RevertSubmissionInfo revertSubmissionInfo = new RevertSubmissionInfo();
+ revertSubmissionInfo.revertChanges = results;
+ return revertSubmissionInfo;
+ }
+
+ @Override
+ public Description getDescription(ChangeResource rsrc) {
+ Change change = rsrc.getChange();
+ boolean projectStatePermitsWrite = false;
+ try {
+ projectStatePermitsWrite = projectCache.checkedGet(rsrc.getProject()).statePermitsWrite();
+ } catch (IOException e) {
+ logger.atSevere().withCause(e).log(
+ "Failed to check if project state permits write: %s", rsrc.getProject());
+ }
+ return new UiAction.Description()
+ .setLabel("Revert submission")
+ .setTitle(
+ "Revert this change and all changes that have been submitted together with this change")
+ .setVisible(
+ and(
+ change.isMerged() && projectStatePermitsWrite,
+ permissionBackend
+ .user(rsrc.getUser())
+ .ref(change.getDest())
+ .testCond(CREATE_CHANGE)));
+ }
+}
diff --git a/java/com/google/gerrit/server/restapi/change/Reviewed.java b/java/com/google/gerrit/server/restapi/change/Reviewed.java
index 7152799..2793059 100644
--- a/java/com/google/gerrit/server/restapi/change/Reviewed.java
+++ b/java/com/google/gerrit/server/restapi/change/Reviewed.java
@@ -43,7 +43,7 @@
resource.getPatchKey().patchSetId(),
resource.getAccountId(),
resource.getPatchKey().fileName()));
- return reviewFlagUpdated ? Response.created("") : Response.ok("");
+ return reviewFlagUpdated ? Response.created() : Response.ok();
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/SetReadyForReview.java b/java/com/google/gerrit/server/restapi/change/SetReadyForReview.java
index 288806c..8470742 100644
--- a/java/com/google/gerrit/server/restapi/change/SetReadyForReview.java
+++ b/java/com/google/gerrit/server/restapi/change/SetReadyForReview.java
@@ -69,7 +69,7 @@
bu.setNotify(NotifyResolver.Result.create(firstNonNull(input.notify, NotifyHandling.ALL)));
bu.addOp(rsrc.getChange().getId(), opFactory.create(false, input));
bu.execute();
- return Response.ok("");
+ return Response.ok();
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/SetWorkInProgress.java b/java/com/google/gerrit/server/restapi/change/SetWorkInProgress.java
index 3fb0295..60884c9 100644
--- a/java/com/google/gerrit/server/restapi/change/SetWorkInProgress.java
+++ b/java/com/google/gerrit/server/restapi/change/SetWorkInProgress.java
@@ -69,7 +69,7 @@
bu.setNotify(NotifyResolver.Result.create(firstNonNull(input.notify, NotifyHandling.NONE)));
bu.addOp(rsrc.getChange().getId(), opFactory.create(true, input));
bu.execute();
- return Response.ok("");
+ return Response.ok();
}
}
diff --git a/java/com/google/gerrit/server/restapi/change/Unignore.java b/java/com/google/gerrit/server/restapi/change/Unignore.java
index 26d3233..999e736 100644
--- a/java/com/google/gerrit/server/restapi/change/Unignore.java
+++ b/java/com/google/gerrit/server/restapi/change/Unignore.java
@@ -50,7 +50,7 @@
if (isIgnored(rsrc)) {
stars.unignore(rsrc);
}
- return Response.ok("");
+ return Response.ok();
}
private boolean isIgnored(ChangeResource rsrc) {
diff --git a/java/com/google/gerrit/server/restapi/config/FlushCache.java b/java/com/google/gerrit/server/restapi/config/FlushCache.java
index 9ea9e33..f10ed8d 100644
--- a/java/com/google/gerrit/server/restapi/config/FlushCache.java
+++ b/java/com/google/gerrit/server/restapi/config/FlushCache.java
@@ -50,6 +50,6 @@
}
rsrc.getCache().invalidateAll();
- return Response.ok("");
+ return Response.ok();
}
}
diff --git a/java/com/google/gerrit/server/restapi/config/PostCaches.java b/java/com/google/gerrit/server/restapi/config/PostCaches.java
index c633af0..c9480c5 100644
--- a/java/com/google/gerrit/server/restapi/config/PostCaches.java
+++ b/java/com/google/gerrit/server/restapi/config/PostCaches.java
@@ -84,13 +84,13 @@
"specifying caches is not allowed for operation 'FLUSH_ALL'");
}
flushAll();
- return Response.ok("");
+ return Response.ok();
case FLUSH:
if (input.caches == null || input.caches.isEmpty()) {
throw new BadRequestException("caches must be specified for operation 'FLUSH'");
}
flush(input.caches);
- return Response.ok("");
+ return Response.ok();
default:
throw new BadRequestException("unsupported operation: " + input.operation);
}
diff --git a/java/com/google/gerrit/server/restapi/project/CommitsCollection.java b/java/com/google/gerrit/server/restapi/project/CommitsCollection.java
index 334447b..7c88ab3 100644
--- a/java/com/google/gerrit/server/restapi/project/CommitsCollection.java
+++ b/java/com/google/gerrit/server/restapi/project/CommitsCollection.java
@@ -26,6 +26,7 @@
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.index.change.ChangeIndexCollection;
import com.google.gerrit.server.project.CommitResource;
@@ -33,7 +34,9 @@
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.project.Reachable;
import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.CommitPredicate;
import com.google.gerrit.server.query.change.InternalChangeQuery;
+import com.google.gerrit.server.query.change.ProjectPredicate;
import com.google.gerrit.server.update.RetryHelper;
import com.google.gerrit.server.update.RetryHelper.Action;
import com.google.gerrit.server.update.RetryHelper.ActionType;
@@ -41,7 +44,11 @@
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.ObjectId;
@@ -116,12 +123,13 @@
public boolean canRead(ProjectState state, Repository repo, RevCommit commit) throws IOException {
Project.NameKey project = state.getNameKey();
if (indexes.getSearchIndex() == null) {
- // No index in slaves, fall back to scanning refs.
+ // No index in slaves, fall back to scanning refs. We must inspect change refs too
+ // as the commit might be a patchset of a not yet submitted change.
return reachable.fromRefs(project, repo, commit, repo.getRefDatabase().getRefs());
}
- // Check first if any change references the commit in question. This is much cheaper than ref
- // visibility filtering and reachability computation.
+ // Check first if any patchset of any change references the commit in question. This is much
+ // cheaper than ref visibility filtering and reachability computation.
List<ChangeData> changes =
executeIndexQuery(
() ->
@@ -134,6 +142,30 @@
return true;
}
+ // Maybe the commit was a merge commit of a change. Try to find promising candidates for
+ // branches to check, by seeing if its parents were associated to changes.
+ Predicate<ChangeData> pred =
+ Predicate.and(
+ new ProjectPredicate(project.get()),
+ Predicate.or(
+ Arrays.stream(commit.getParents())
+ .map(parent -> new CommitPredicate(parent.getId().getName()))
+ .collect(toImmutableList())));
+ changes = executeIndexQuery(() -> queryProvider.get().enforceVisibility(true).query(pred));
+
+ Set<Ref> branchesForCommitParents = new HashSet<>(changes.size());
+ for (ChangeData cd : changes) {
+ Ref ref = repo.exactRef(cd.change().getDest().branch());
+ if (ref != null) {
+ branchesForCommitParents.add(ref);
+ }
+ }
+
+ if (reachable.fromRefs(
+ project, repo, commit, branchesForCommitParents.stream().collect(Collectors.toList()))) {
+ return true;
+ }
+
// If we have already checked change refs using the change index, spare any further checks for
// changes.
List<Ref> refs =
diff --git a/java/com/google/gerrit/server/restapi/project/CreateLabel.java b/java/com/google/gerrit/server/restapi/project/CreateLabel.java
new file mode 100644
index 0000000..03b9452
--- /dev/null
+++ b/java/com/google/gerrit/server/restapi/project/CreateLabel.java
@@ -0,0 +1,188 @@
+// 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.restapi.project;
+
+import com.google.common.base.Strings;
+import com.google.gerrit.common.data.LabelFunction;
+import com.google.gerrit.common.data.LabelType;
+import com.google.gerrit.common.data.LabelValue;
+import com.google.gerrit.extensions.common.LabelDefinitionInfo;
+import com.google.gerrit.extensions.common.LabelDefinitionInput;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestCollectionCreateView;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.git.meta.MetaDataUpdate;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.permissions.ProjectPermission;
+import com.google.gerrit.server.project.LabelDefinitionJson;
+import com.google.gerrit.server.project.LabelResource;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectConfig;
+import com.google.gerrit.server.project.ProjectResource;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import java.io.IOException;
+import java.util.List;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+
+@Singleton
+public class CreateLabel
+ implements RestCollectionCreateView<ProjectResource, LabelResource, LabelDefinitionInput> {
+ private final Provider<CurrentUser> user;
+ private final PermissionBackend permissionBackend;
+ private final MetaDataUpdate.User updateFactory;
+ private final ProjectConfig.Factory projectConfigFactory;
+ private final ProjectCache projectCache;
+
+ @Inject
+ public CreateLabel(
+ Provider<CurrentUser> user,
+ PermissionBackend permissionBackend,
+ MetaDataUpdate.User updateFactory,
+ ProjectConfig.Factory projectConfigFactory,
+ ProjectCache projectCache) {
+ this.user = user;
+ this.permissionBackend = permissionBackend;
+ this.updateFactory = updateFactory;
+ this.projectConfigFactory = projectConfigFactory;
+ this.projectCache = projectCache;
+ }
+
+ @Override
+ public Response<LabelDefinitionInfo> apply(
+ ProjectResource rsrc, IdString id, LabelDefinitionInput input)
+ throws AuthException, BadRequestException, ResourceConflictException,
+ PermissionBackendException, IOException, ConfigInvalidException {
+ if (!user.get().isIdentifiedUser()) {
+ throw new AuthException("Authentication required");
+ }
+
+ permissionBackend
+ .currentUser()
+ .project(rsrc.getNameKey())
+ .check(ProjectPermission.WRITE_CONFIG);
+
+ if (input == null) {
+ input = new LabelDefinitionInput();
+ }
+
+ if (input.name != null && !input.name.equals(id.get())) {
+ throw new BadRequestException("name in input must match name in URL");
+ }
+
+ try (MetaDataUpdate md = updateFactory.create(rsrc.getNameKey())) {
+ ProjectConfig config = projectConfigFactory.read(md);
+
+ if (config.getLabelSections().containsKey(id.get())) {
+ throw new ResourceConflictException(String.format("label %s already exists", id.get()));
+ }
+
+ for (String labelName : config.getLabelSections().keySet()) {
+ if (labelName.equalsIgnoreCase(id.get())) {
+ throw new ResourceConflictException(
+ String.format("label %s conflicts with existing label %s", id.get(), labelName));
+ }
+ }
+
+ if (input.values == null || input.values.isEmpty()) {
+ throw new BadRequestException("values are required");
+ }
+
+ List<LabelValue> values = LabelDefinitionInputParser.parseValues(input.values);
+
+ LabelType labelType;
+ try {
+ labelType = new LabelType(id.get(), values);
+ } catch (IllegalArgumentException e) {
+ throw new BadRequestException("invalid name: " + id.get(), e);
+ }
+
+ if (input.function != null && !input.function.trim().isEmpty()) {
+ labelType.setFunction(LabelDefinitionInputParser.parseFunction(input.function));
+ } else {
+ labelType.setFunction(LabelFunction.MAX_WITH_BLOCK);
+ }
+
+ if (input.defaultValue != null) {
+ labelType.setDefaultValue(
+ LabelDefinitionInputParser.parseDefaultValue(labelType, input.defaultValue));
+ }
+
+ if (input.branches != null) {
+ labelType.setRefPatterns(LabelDefinitionInputParser.parseBranches(input.branches));
+ }
+
+ if (input.canOverride != null) {
+ labelType.setCanOverride(input.canOverride);
+ }
+
+ if (input.copyAnyScore != null) {
+ labelType.setCopyAnyScore(input.copyAnyScore);
+ }
+
+ if (input.copyMinScore != null) {
+ labelType.setCopyMinScore(input.copyMinScore);
+ }
+
+ if (input.copyMaxScore != null) {
+ labelType.setCopyMaxScore(input.copyMaxScore);
+ }
+
+ if (input.copyAllScoresIfNoChange != null) {
+ labelType.setCopyAllScoresIfNoChange(input.copyAllScoresIfNoChange);
+ }
+
+ if (input.copyAllScoresIfNoCodeChange != null) {
+ labelType.setCopyAllScoresIfNoCodeChange(input.copyAllScoresIfNoCodeChange);
+ }
+
+ if (input.copyAllScoresOnTrivialRebase != null) {
+ labelType.setCopyAllScoresOnTrivialRebase(input.copyAllScoresOnTrivialRebase);
+ }
+
+ if (input.copyAllScoresOnMergeFirstParentUpdate != null) {
+ labelType.setCopyAllScoresOnMergeFirstParentUpdate(
+ input.copyAllScoresOnMergeFirstParentUpdate);
+ }
+
+ if (input.allowPostSubmit != null) {
+ labelType.setAllowPostSubmit(input.allowPostSubmit);
+ }
+
+ if (input.ignoreSelfApproval != null) {
+ labelType.setIgnoreSelfApproval(input.ignoreSelfApproval);
+ }
+
+ if (input.commitMessage != null) {
+ md.setMessage(Strings.emptyToNull(input.commitMessage.trim()));
+ } else {
+ md.setMessage("Update label");
+ }
+
+ config.getLabelSections().put(labelType.getName(), labelType);
+ config.commit(md);
+
+ projectCache.evict(rsrc.getProjectState().getProject());
+
+ return Response.created(LabelDefinitionJson.format(rsrc.getNameKey(), labelType));
+ }
+ }
+}
diff --git a/java/com/google/gerrit/server/restapi/project/DeleteLabel.java b/java/com/google/gerrit/server/restapi/project/DeleteLabel.java
new file mode 100644
index 0000000..5464abf
--- /dev/null
+++ b/java/com/google/gerrit/server/restapi/project/DeleteLabel.java
@@ -0,0 +1,99 @@
+// 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.restapi.project;
+
+import com.google.common.base.Strings;
+import com.google.gerrit.extensions.common.InputWithCommitMessage;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.git.meta.MetaDataUpdate;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.permissions.ProjectPermission;
+import com.google.gerrit.server.project.LabelResource;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectConfig;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import java.io.IOException;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+
+@Singleton
+public class DeleteLabel implements RestModifyView<LabelResource, InputWithCommitMessage> {
+ private final Provider<CurrentUser> user;
+ private final PermissionBackend permissionBackend;
+ private final MetaDataUpdate.User updateFactory;
+ private final ProjectConfig.Factory projectConfigFactory;
+ private final ProjectCache projectCache;
+
+ @Inject
+ public DeleteLabel(
+ Provider<CurrentUser> user,
+ PermissionBackend permissionBackend,
+ MetaDataUpdate.User updateFactory,
+ ProjectConfig.Factory projectConfigFactory,
+ ProjectCache projectCache) {
+ this.user = user;
+ this.permissionBackend = permissionBackend;
+ this.updateFactory = updateFactory;
+ this.projectConfigFactory = projectConfigFactory;
+ this.projectCache = projectCache;
+ }
+
+ @Override
+ public Response<?> apply(LabelResource rsrc, InputWithCommitMessage input)
+ throws AuthException, ResourceNotFoundException, PermissionBackendException, IOException,
+ ConfigInvalidException {
+ if (!user.get().isIdentifiedUser()) {
+ throw new AuthException("Authentication required");
+ }
+
+ permissionBackend
+ .currentUser()
+ .project(rsrc.getProject().getNameKey())
+ .check(ProjectPermission.WRITE_CONFIG);
+
+ if (input == null) {
+ input = new InputWithCommitMessage();
+ }
+
+ try (MetaDataUpdate md = updateFactory.create(rsrc.getProject().getNameKey())) {
+ ProjectConfig config = projectConfigFactory.read(md);
+
+ if (!config.getLabelSections().containsKey(rsrc.getLabelType().getName())) {
+ throw new ResourceNotFoundException(IdString.fromDecoded(rsrc.getLabelType().getName()));
+ }
+
+ config.getLabelSections().remove(rsrc.getLabelType().getName());
+
+ if (input.commitMessage != null) {
+ md.setMessage(Strings.emptyToNull(input.commitMessage.trim()));
+ } else {
+ md.setMessage("Delete label");
+ }
+
+ config.commit(md);
+ }
+
+ projectCache.evict(rsrc.getProject().getProjectState().getProject());
+
+ return Response.none();
+ }
+}
diff --git a/java/com/google/gerrit/server/restapi/project/GetLabel.java b/java/com/google/gerrit/server/restapi/project/GetLabel.java
new file mode 100644
index 0000000..626cb42
--- /dev/null
+++ b/java/com/google/gerrit/server/restapi/project/GetLabel.java
@@ -0,0 +1,34 @@
+// 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.restapi.project;
+
+import com.google.gerrit.extensions.common.LabelDefinitionInfo;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.project.LabelDefinitionJson;
+import com.google.gerrit.server.project.LabelResource;
+import com.google.inject.Singleton;
+
+@Singleton
+public class GetLabel implements RestReadView<LabelResource> {
+ @Override
+ public Response<LabelDefinitionInfo> apply(LabelResource rsrc)
+ throws AuthException, BadRequestException {
+ return Response.ok(
+ LabelDefinitionJson.format(rsrc.getProject().getNameKey(), rsrc.getLabelType()));
+ }
+}
diff --git a/java/com/google/gerrit/server/restapi/project/LabelDefinitionInputParser.java b/java/com/google/gerrit/server/restapi/project/LabelDefinitionInputParser.java
new file mode 100644
index 0000000..a45c67f
--- /dev/null
+++ b/java/com/google/gerrit/server/restapi/project/LabelDefinitionInputParser.java
@@ -0,0 +1,87 @@
+// 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.restapi.project;
+
+import com.google.common.primitives.Shorts;
+import com.google.gerrit.common.data.LabelFunction;
+import com.google.gerrit.common.data.LabelType;
+import com.google.gerrit.common.data.LabelValue;
+import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.exceptions.InvalidNameException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.server.project.RefPattern;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+
+public class LabelDefinitionInputParser {
+ public static LabelFunction parseFunction(String functionString) throws BadRequestException {
+ Optional<LabelFunction> function = LabelFunction.parse(functionString.trim());
+ return function.orElseThrow(
+ () -> new BadRequestException("unknown function: " + functionString));
+ }
+
+ public static List<LabelValue> parseValues(Map<String, String> values)
+ throws BadRequestException {
+ List<LabelValue> valueList = new ArrayList<>();
+ for (Entry<String, String> e : values.entrySet()) {
+ short value;
+ try {
+ value = Shorts.checkedCast(PermissionRule.parseInt(e.getKey().trim()));
+ } catch (NumberFormatException ex) {
+ throw new BadRequestException("invalid value: " + e.getKey(), ex);
+ }
+ String valueDescription = e.getValue().trim();
+ if (valueDescription.isEmpty()) {
+ throw new BadRequestException("description for value '" + e.getKey() + "' cannot be empty");
+ }
+ valueList.add(new LabelValue(value, valueDescription));
+ }
+ return valueList;
+ }
+
+ public static short parseDefaultValue(LabelType labelType, short defaultValue)
+ throws BadRequestException {
+ if (labelType.getValue(defaultValue) == null) {
+ throw new BadRequestException("invalid default value: " + defaultValue);
+ }
+ return defaultValue;
+ }
+
+ public static List<String> parseBranches(List<String> branches) throws BadRequestException {
+ List<String> validBranches = new ArrayList<>();
+ for (String branch : branches) {
+ String newBranch = branch.trim();
+ if (newBranch.isEmpty()) {
+ continue;
+ }
+ if (!RefPattern.isRE(newBranch) && !newBranch.startsWith(RefNames.REFS)) {
+ newBranch = RefNames.REFS_HEADS + newBranch;
+ }
+ try {
+ RefPattern.validate(newBranch);
+ } catch (InvalidNameException e) {
+ throw new BadRequestException("invalid branch: " + branch, e);
+ }
+ validBranches.add(newBranch);
+ }
+ return validBranches;
+ }
+
+ private LabelDefinitionInputParser() {}
+}
diff --git a/java/com/google/gerrit/server/restapi/project/LabelsCollection.java b/java/com/google/gerrit/server/restapi/project/LabelsCollection.java
new file mode 100644
index 0000000..0409729
--- /dev/null
+++ b/java/com/google/gerrit/server/restapi/project/LabelsCollection.java
@@ -0,0 +1,81 @@
+// 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.restapi.project;
+
+import com.google.gerrit.common.data.LabelType;
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ChildCollection;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.permissions.ProjectPermission;
+import com.google.gerrit.server.project.LabelResource;
+import com.google.gerrit.server.project.ProjectResource;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+@Singleton
+public class LabelsCollection implements ChildCollection<ProjectResource, LabelResource> {
+ private final Provider<ListLabels> list;
+ private final DynamicMap<RestView<LabelResource>> views;
+ private final Provider<CurrentUser> user;
+ private final PermissionBackend permissionBackend;
+
+ @Inject
+ LabelsCollection(
+ Provider<ListLabels> list,
+ DynamicMap<RestView<LabelResource>> views,
+ Provider<CurrentUser> user,
+ PermissionBackend permissionBackend) {
+ this.list = list;
+ this.views = views;
+ this.user = user;
+ this.permissionBackend = permissionBackend;
+ }
+
+ @Override
+ public RestView<ProjectResource> list() throws RestApiException {
+ return list.get();
+ }
+
+ @Override
+ public LabelResource parse(ProjectResource parent, IdString id)
+ throws AuthException, ResourceNotFoundException, PermissionBackendException {
+ if (!user.get().isIdentifiedUser()) {
+ throw new AuthException("Authentication required");
+ }
+
+ permissionBackend
+ .currentUser()
+ .project(parent.getNameKey())
+ .check(ProjectPermission.READ_CONFIG);
+ LabelType labelType = parent.getProjectState().getConfig().getLabelSections().get(id.get());
+ if (labelType == null) {
+ throw new ResourceNotFoundException(id);
+ }
+ return new LabelResource(parent, labelType);
+ }
+
+ @Override
+ public DynamicMap<RestView<LabelResource>> views() {
+ return views;
+ }
+}
diff --git a/java/com/google/gerrit/server/restapi/project/ListLabels.java b/java/com/google/gerrit/server/restapi/project/ListLabels.java
new file mode 100644
index 0000000..19a8915
--- /dev/null
+++ b/java/com/google/gerrit/server/restapi/project/ListLabels.java
@@ -0,0 +1,91 @@
+// 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.restapi.project;
+
+import com.google.gerrit.common.data.LabelType;
+import com.google.gerrit.extensions.common.LabelDefinitionInfo;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.permissions.ProjectPermission;
+import com.google.gerrit.server.project.LabelDefinitionJson;
+import com.google.gerrit.server.project.ProjectResource;
+import com.google.gerrit.server.project.ProjectState;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.List;
+import org.kohsuke.args4j.Option;
+
+public class ListLabels implements RestReadView<ProjectResource> {
+ private final Provider<CurrentUser> user;
+ private final PermissionBackend permissionBackend;
+
+ @Inject
+ public ListLabels(Provider<CurrentUser> user, PermissionBackend permissionBackend) {
+ this.user = user;
+ this.permissionBackend = permissionBackend;
+ }
+
+ @Option(name = "--inherited", usage = "to include inherited label definitions")
+ private boolean inherited;
+
+ public ListLabels withInherited(boolean inherited) {
+ this.inherited = inherited;
+ return this;
+ }
+
+ @Override
+ public Response<List<LabelDefinitionInfo>> apply(ProjectResource rsrc)
+ throws AuthException, PermissionBackendException {
+ if (!user.get().isIdentifiedUser()) {
+ throw new AuthException("Authentication required");
+ }
+
+ if (inherited) {
+ List<LabelDefinitionInfo> allLabels = new ArrayList<>();
+ for (ProjectState projectState : rsrc.getProjectState().treeInOrder()) {
+ try {
+ permissionBackend
+ .currentUser()
+ .project(projectState.getNameKey())
+ .check(ProjectPermission.READ_CONFIG);
+ } catch (AuthException e) {
+ throw new AuthException(projectState.getNameKey() + ": " + e.getMessage(), e);
+ }
+ allLabels.addAll(listLabels(projectState));
+ }
+ return Response.ok(allLabels);
+ }
+
+ permissionBackend.currentUser().project(rsrc.getNameKey()).check(ProjectPermission.READ_CONFIG);
+ return Response.ok(listLabels(rsrc.getProjectState()));
+ }
+
+ private List<LabelDefinitionInfo> listLabels(ProjectState projectState) {
+ Collection<LabelType> labelTypes = projectState.getConfig().getLabelSections().values();
+ List<LabelDefinitionInfo> labels = new ArrayList<>(labelTypes.size());
+ for (LabelType labelType : labelTypes) {
+ labels.add(LabelDefinitionJson.format(projectState.getNameKey(), labelType));
+ }
+ labels.sort(Comparator.comparing(l -> l.name));
+ return labels;
+ }
+}
diff --git a/java/com/google/gerrit/server/restapi/project/Module.java b/java/com/google/gerrit/server/restapi/project/Module.java
index de5661d..2c76cbd 100644
--- a/java/com/google/gerrit/server/restapi/project/Module.java
+++ b/java/com/google/gerrit/server/restapi/project/Module.java
@@ -19,6 +19,7 @@
import static com.google.gerrit.server.project.CommitResource.COMMIT_KIND;
import static com.google.gerrit.server.project.DashboardResource.DASHBOARD_KIND;
import static com.google.gerrit.server.project.FileResource.FILE_KIND;
+import static com.google.gerrit.server.project.LabelResource.LABEL_KIND;
import static com.google.gerrit.server.project.ProjectResource.PROJECT_KIND;
import static com.google.gerrit.server.project.TagResource.TAG_KIND;
@@ -42,6 +43,7 @@
DynamicMap.mapOf(binder(), FILE_KIND);
DynamicMap.mapOf(binder(), COMMIT_KIND);
DynamicMap.mapOf(binder(), TAG_KIND);
+ DynamicMap.mapOf(binder(), LABEL_KIND);
DynamicSet.bind(binder(), GerritConfigListener.class).to(SetParent.class);
@@ -65,6 +67,12 @@
child(PROJECT_KIND, "children").to(ChildProjectsCollection.class);
get(CHILD_PROJECT_KIND).to(GetChildProject.class);
+ child(PROJECT_KIND, "labels").to(LabelsCollection.class);
+ create(LABEL_KIND).to(CreateLabel.class);
+ get(LABEL_KIND).to(GetLabel.class);
+ put(LABEL_KIND).to(SetLabel.class);
+ delete(LABEL_KIND).to(DeleteLabel.class);
+
get(PROJECT_KIND, "HEAD").to(GetHead.class);
put(PROJECT_KIND, "HEAD").to(SetHead.class);
diff --git a/java/com/google/gerrit/server/restapi/project/PutConfig.java b/java/com/google/gerrit/server/restapi/project/PutConfig.java
index 696ac37..a0badd7 100644
--- a/java/com/google/gerrit/server/restapi/project/PutConfig.java
+++ b/java/com/google/gerrit/server/restapi/project/PutConfig.java
@@ -14,11 +14,17 @@
package com.google.gerrit.server.restapi.project;
+import static com.google.gerrit.server.project.ProjectConfig.COMMENTLINK;
+import static com.google.gerrit.server.project.ProjectConfig.KEY_ENABLED;
+import static com.google.gerrit.server.project.ProjectConfig.KEY_LINK;
+import static com.google.gerrit.server.project.ProjectConfig.KEY_MATCH;
+
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.entities.BooleanProjectConfig;
import com.google.gerrit.entities.Project;
+import com.google.gerrit.extensions.api.projects.CommentLinkInput;
import com.google.gerrit.extensions.api.projects.ConfigInfo;
import com.google.gerrit.extensions.api.projects.ConfigInput;
import com.google.gerrit.extensions.api.projects.ConfigValue;
@@ -59,6 +65,7 @@
import java.util.regex.Pattern;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.Config;
@Singleton
public class PutConfig implements RestModifyView<ProjectResource, ConfigInput> {
@@ -154,6 +161,10 @@
setPluginConfigValues(projectState, projectConfig, input.pluginConfigValues);
}
+ if (input.commentLinks != null) {
+ updateCommentLinks(projectConfig, input.commentLinks);
+ }
+
md.setMessage("Modified project settings\n");
try {
projectConfig.commit(md);
@@ -278,6 +289,25 @@
}
}
+ private void updateCommentLinks(
+ ProjectConfig projectConfig, Map<String, CommentLinkInput> input) {
+ for (Map.Entry<String, CommentLinkInput> e : input.entrySet()) {
+ String name = e.getKey();
+ CommentLinkInput value = e.getValue();
+ if (value != null) {
+ // Add or update the commentlink section
+ Config cfg = new Config();
+ cfg.setString(COMMENTLINK, name, KEY_MATCH, value.match);
+ cfg.setString(COMMENTLINK, name, KEY_LINK, value.link);
+ cfg.setBoolean(COMMENTLINK, name, KEY_ENABLED, value.enabled == null || value.enabled);
+ projectConfig.addCommentLinkSection(ProjectConfig.buildCommentLink(cfg, name, false));
+ } else {
+ // Delete the commentlink section
+ projectConfig.removeCommentLinkSection(name);
+ }
+ }
+ }
+
private static void validateProjectConfigEntryIsEditable(
ProjectConfigEntry projectConfigEntry,
ProjectState projectState,
diff --git a/java/com/google/gerrit/server/restapi/project/SetLabel.java b/java/com/google/gerrit/server/restapi/project/SetLabel.java
new file mode 100644
index 0000000..b7cffce
--- /dev/null
+++ b/java/com/google/gerrit/server/restapi/project/SetLabel.java
@@ -0,0 +1,206 @@
+// 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.restapi.project;
+
+import com.google.common.base.Strings;
+import com.google.gerrit.common.data.LabelType;
+import com.google.gerrit.extensions.common.LabelDefinitionInfo;
+import com.google.gerrit.extensions.common.LabelDefinitionInput;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.git.meta.MetaDataUpdate;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.permissions.ProjectPermission;
+import com.google.gerrit.server.project.LabelDefinitionJson;
+import com.google.gerrit.server.project.LabelResource;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectConfig;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import java.io.IOException;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+
+@Singleton
+public class SetLabel implements RestModifyView<LabelResource, LabelDefinitionInput> {
+ private final Provider<CurrentUser> user;
+ private final PermissionBackend permissionBackend;
+ private final MetaDataUpdate.User updateFactory;
+ private final ProjectConfig.Factory projectConfigFactory;
+ private final ProjectCache projectCache;
+
+ @Inject
+ public SetLabel(
+ Provider<CurrentUser> user,
+ PermissionBackend permissionBackend,
+ MetaDataUpdate.User updateFactory,
+ ProjectConfig.Factory projectConfigFactory,
+ ProjectCache projectCache) {
+ this.user = user;
+ this.permissionBackend = permissionBackend;
+ this.updateFactory = updateFactory;
+ this.projectConfigFactory = projectConfigFactory;
+ this.projectCache = projectCache;
+ }
+
+ @Override
+ public Response<LabelDefinitionInfo> apply(LabelResource rsrc, LabelDefinitionInput input)
+ throws AuthException, BadRequestException, ResourceConflictException,
+ PermissionBackendException, IOException, ConfigInvalidException {
+ if (!user.get().isIdentifiedUser()) {
+ throw new AuthException("Authentication required");
+ }
+
+ permissionBackend
+ .currentUser()
+ .project(rsrc.getProject().getNameKey())
+ .check(ProjectPermission.WRITE_CONFIG);
+
+ if (input == null) {
+ input = new LabelDefinitionInput();
+ }
+
+ LabelType labelType = rsrc.getLabelType();
+
+ try (MetaDataUpdate md = updateFactory.create(rsrc.getProject().getNameKey())) {
+ boolean dirty = false;
+
+ ProjectConfig config = projectConfigFactory.read(md);
+ config.getLabelSections().remove(labelType.getName());
+
+ if (input.name != null) {
+ String newName = input.name.trim();
+ if (newName.isEmpty()) {
+ throw new BadRequestException("name cannot be empty");
+ }
+ if (!newName.equals(labelType.getName())) {
+ if (config.getLabelSections().containsKey(newName)) {
+ throw new ResourceConflictException(String.format("name %s already in use", newName));
+ }
+
+ for (String labelName : config.getLabelSections().keySet()) {
+ if (labelName.equalsIgnoreCase(newName)) {
+ throw new ResourceConflictException(
+ String.format("name %s conflicts with existing label %s", newName, labelName));
+ }
+ }
+
+ try {
+ labelType.setName(newName);
+ } catch (IllegalArgumentException e) {
+ throw new BadRequestException("invalid name: " + input.name, e);
+ }
+ dirty = true;
+ }
+ }
+
+ if (input.function != null) {
+ if (input.function.trim().isEmpty()) {
+ throw new BadRequestException("function cannot be empty");
+ }
+ labelType.setFunction(LabelDefinitionInputParser.parseFunction(input.function));
+ dirty = true;
+ }
+
+ if (input.values != null) {
+ if (input.values.isEmpty()) {
+ throw new BadRequestException("values cannot be empty");
+ }
+ labelType.setValues(LabelDefinitionInputParser.parseValues(input.values));
+ dirty = true;
+ }
+
+ if (input.defaultValue != null) {
+ labelType.setDefaultValue(
+ LabelDefinitionInputParser.parseDefaultValue(labelType, input.defaultValue));
+ dirty = true;
+ }
+
+ if (input.branches != null) {
+ labelType.setRefPatterns(LabelDefinitionInputParser.parseBranches(input.branches));
+ dirty = true;
+ }
+
+ if (input.canOverride != null) {
+ labelType.setCanOverride(input.canOverride);
+ dirty = true;
+ }
+
+ if (input.copyAnyScore != null) {
+ labelType.setCopyAnyScore(input.copyAnyScore);
+ dirty = true;
+ }
+
+ if (input.copyMinScore != null) {
+ labelType.setCopyMinScore(input.copyMinScore);
+ dirty = true;
+ }
+
+ if (input.copyMaxScore != null) {
+ labelType.setCopyMaxScore(input.copyMaxScore);
+ dirty = true;
+ }
+
+ if (input.copyAllScoresIfNoChange != null) {
+ labelType.setCopyAllScoresIfNoChange(input.copyAllScoresIfNoChange);
+ }
+
+ if (input.copyAllScoresIfNoCodeChange != null) {
+ labelType.setCopyAllScoresIfNoCodeChange(input.copyAllScoresIfNoCodeChange);
+ dirty = true;
+ }
+
+ if (input.copyAllScoresOnTrivialRebase != null) {
+ labelType.setCopyAllScoresOnTrivialRebase(input.copyAllScoresOnTrivialRebase);
+ dirty = true;
+ }
+
+ if (input.copyAllScoresOnMergeFirstParentUpdate != null) {
+ labelType.setCopyAllScoresOnMergeFirstParentUpdate(
+ input.copyAllScoresOnMergeFirstParentUpdate);
+ dirty = true;
+ }
+
+ if (input.allowPostSubmit != null) {
+ labelType.setAllowPostSubmit(input.allowPostSubmit);
+ dirty = true;
+ }
+
+ if (input.ignoreSelfApproval != null) {
+ labelType.setIgnoreSelfApproval(input.ignoreSelfApproval);
+ dirty = true;
+ }
+
+ if (dirty) {
+ config.getLabelSections().put(labelType.getName(), labelType);
+
+ if (input.commitMessage != null) {
+ md.setMessage(Strings.emptyToNull(input.commitMessage.trim()));
+ } else {
+ md.setMessage("Update label");
+ }
+
+ config.commit(md);
+ projectCache.evict(rsrc.getProject().getProjectState().getProject());
+ }
+ }
+ return Response.ok(LabelDefinitionJson.format(rsrc.getProject().getNameKey(), labelType));
+ }
+}
diff --git a/java/com/google/gerrit/server/submit/MergeOp.java b/java/com/google/gerrit/server/submit/MergeOp.java
index a06027a..406c0a1 100644
--- a/java/com/google/gerrit/server/submit/MergeOp.java
+++ b/java/com/google/gerrit/server/submit/MergeOp.java
@@ -504,7 +504,7 @@
try {
integrateIntoHistory(cs);
} catch (IntegrationException e) {
- logger.atSevere().withCause(e).log("Error from integrateIntoHistory");
+ logger.atWarning().withCause(e).log("Error from integrateIntoHistory");
throw new ResourceConflictException(e.getMessage(), e);
}
return null;
diff --git a/java/com/google/gerrit/server/submit/MergeOpRepoManager.java b/java/com/google/gerrit/server/submit/MergeOpRepoManager.java
index c2577e7..9a7ced5 100644
--- a/java/com/google/gerrit/server/submit/MergeOpRepoManager.java
+++ b/java/com/google/gerrit/server/submit/MergeOpRepoManager.java
@@ -42,10 +42,9 @@
import java.util.Map;
import java.util.Objects;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
-import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.ObjectReader;
-import org.eclipse.jgit.lib.RefUpdate;
+import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevFlag;
import org.eclipse.jgit.revwalk.RevSort;
@@ -130,19 +129,17 @@
}
public static class OpenBranch {
- final RefUpdate update;
final CodeReviewCommit oldTip;
MergeTip mergeTip;
OpenBranch(OpenRepo or, BranchNameKey name) throws IntegrationException {
try {
- update = or.repo.updateRef(name.branch());
- if (update.getOldObjectId() != null) {
- oldTip = or.rw.parseCommit(update.getOldObjectId());
+ Ref ref = or.getRepo().exactRef(name.branch());
+ if (ref != null) {
+ oldTip = or.rw.parseCommit(ref.getObjectId());
} else if (Objects.equals(or.repo.getFullBranch(), name.branch())
|| Objects.equals(RefNames.REFS_CONFIG, name.branch())) {
oldTip = null;
- update.setExpectedOldObjectId(ObjectId.zeroId());
} else {
throw new IntegrationException(
"The destination branch " + name + " does not exist anymore.");
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
index ba12a12..4ba6475 100644
--- a/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/AccountIT.java
@@ -3010,7 +3010,7 @@
// Newly created account is not stale.
AccountInfo accountInfo = gApi.accounts().create(name("foo")).get();
Account.Id accountId = Account.id(accountInfo._accountId);
- assertThat(stalenessChecker.isStale(accountId)).isFalse();
+ assertThat(stalenessChecker.check(accountId).isStale()).isFalse();
// Manually updating the user ref makes the index document stale.
String userRef = RefNames.refsUsers(accountId);
@@ -3078,11 +3078,11 @@
// has to happen directly on the accounts cache because AccountCacheImpl triggers a reindex for
// the account.
accountsCache.invalidate(accountId);
- assertThat(stalenessChecker.isStale(accountId)).isTrue();
+ assertThat(stalenessChecker.check(accountId).isStale()).isTrue();
// Reindex fixes staleness
accountIndexer.index(accountId);
- assertThat(stalenessChecker.isStale(accountId)).isFalse();
+ assertThat(stalenessChecker.check(accountId).isStale()).isFalse();
}
@Test
diff --git a/javatests/com/google/gerrit/acceptance/api/accounts/AgreementsIT.java b/javatests/com/google/gerrit/acceptance/api/accounts/AgreementsIT.java
index 5550d98..9059cb1 100644
--- a/javatests/com/google/gerrit/acceptance/api/accounts/AgreementsIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/accounts/AgreementsIT.java
@@ -239,6 +239,28 @@
}
@Test
+ public void revertSubmissionWithoutCLA() throws Exception {
+ assume().that(isContributorAgreementsEnabled()).isTrue();
+
+ // Create a change succeeds when agreement is not required
+ setUseContributorAgreements(InheritableBoolean.FALSE);
+ ChangeInfo change = gApi.changes().create(newChangeInput()).get();
+
+ // Approve and submit it
+ requestScopeOperations.setApiUser(admin.id());
+ gApi.changes().id(change.changeId).current().review(ReviewInput.approve());
+ gApi.changes().id(change.changeId).current().submit(new SubmitInput());
+
+ // Revert Submission is not allowed when CLA is required but not signed
+ requestScopeOperations.setApiUser(user.id());
+ setUseContributorAgreements(InheritableBoolean.TRUE);
+ AuthException thrown =
+ assertThrows(
+ AuthException.class, () -> gApi.changes().id(change.changeId).revertSubmission());
+ assertThat(thrown).hasMessageThat().contains("Contributor Agreement");
+ }
+
+ @Test
public void revertExcludedProjectChangeWithoutCLA() throws Exception {
// Contributor agreements configured with excludeProjects = ExcludedProject
// in AbstractDaemonTest.configureContributorAgreement(...)
diff --git a/javatests/com/google/gerrit/acceptance/api/change/QueryChangeIT.java b/javatests/com/google/gerrit/acceptance/api/change/QueryChangeIT.java
index 92f914b..d73bab0 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/QueryChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/QueryChangeIT.java
@@ -18,6 +18,7 @@
import com.google.common.collect.ImmutableList;
import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.GerritConfig;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.restapi.TopLevelResource;
@@ -97,6 +98,31 @@
assertThat(result2.get(1).get(0)._moreChanges).isTrue();
}
+ @Test
+ @SuppressWarnings("unchecked")
+ @GerritConfig(name = "operator-alias.change.numberaliastest", value = "change")
+ public void aliasQuery() throws Exception {
+ String cId1 = createChange().getChangeId();
+ String cId2 = createChange().getChangeId();
+ int numericId1 = gApi.changes().id(cId1).get()._number;
+ int numericId2 = gApi.changes().id(cId2).get()._number;
+
+ QueryChanges queryChanges = queryChangesProvider.get();
+ queryChanges.addQuery("numberaliastest:12345");
+ queryChanges.addQuery("numberaliastest:" + numericId1);
+ queryChanges.addQuery("numberaliastest:" + numericId2);
+
+ List<List<ChangeInfo>> result =
+ (List<List<ChangeInfo>>) queryChanges.apply(TopLevelResource.INSTANCE).value();
+ assertThat(result).hasSize(3);
+ assertThat(result.get(0)).hasSize(0);
+ assertThat(result.get(1)).hasSize(1);
+ assertThat(result.get(2)).hasSize(1);
+
+ assertThat(result.get(1).get(0)._number).isEqualTo(numericId1);
+ assertThat(result.get(2).get(0)._number).isEqualTo(numericId2);
+ }
+
private static void assertNoChangeHasMoreChangesSet(List<ChangeInfo> results) {
for (ChangeInfo info : results) {
assertThat(info._moreChanges).isNull();
diff --git a/javatests/com/google/gerrit/acceptance/api/change/RevertIT.java b/javatests/com/google/gerrit/acceptance/api/change/RevertIT.java
index 0607a3c..0e89de2 100644
--- a/javatests/com/google/gerrit/acceptance/api/change/RevertIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/change/RevertIT.java
@@ -21,12 +21,16 @@
import static java.util.stream.Collectors.toList;
import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.GerritConfig;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.TestProjectInput;
+import com.google.gerrit.acceptance.UseClockStep;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.Project;
import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.extensions.api.changes.ChangeApi;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.api.changes.RevertInput;
import com.google.gerrit.extensions.api.changes.ReviewInput;
@@ -36,6 +40,8 @@
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.ChangeMessageInfo;
import com.google.gerrit.extensions.common.PureRevertInfo;
+import com.google.gerrit.extensions.common.RevertSubmissionInfo;
+import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
@@ -45,8 +51,10 @@
import com.google.inject.Inject;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Repository;
import org.junit.Test;
@@ -361,17 +369,20 @@
@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();
+ PushOneCommit.Result result = createChange();
+ gApi.changes()
+ .id(result.getChangeId())
+ .revision(result.getCommit().name())
+ .review(ReviewInput.approve());
+ gApi.changes().id(result.getChangeId()).revision(result.getCommit().name()).submit();
projectCache.checkedGet(project).getProject().setState(ProjectState.READ_ONLY);
+ String expected = "project state " + ProjectState.READ_ONLY + " does not permit write";
ResourceConflictException thrown =
assertThrows(
- ResourceConflictException.class, () -> gApi.changes().id(r.getChangeId()).revert());
- assertThat(thrown)
- .hasMessageThat()
- .contains("project state " + ProjectState.READ_ONLY + " does not permit write");
+ ResourceConflictException.class,
+ () -> gApi.changes().id(result.getChangeId()).revert());
+ assertThat(thrown).hasMessageThat().contains(expected);
}
@Test
@@ -412,6 +423,339 @@
assertThat(thrown).hasMessageThat().contains("Not found: " + r.getChangeId());
}
+ @Test
+ @GerritConfig(name = "change.submitWholeTopic", value = "true")
+ public void cantCreateRevertSubmissionWithoutProjectWritePermission() throws Exception {
+ String secondProject = "secondProject";
+ projectOperations.newProject().name(secondProject).create();
+ TestRepository<InMemoryRepository> secondRepo =
+ cloneProject(Project.nameKey("secondProject"), admin);
+ String topic = "topic";
+ PushOneCommit.Result result1 =
+ createChange(testRepo, "master", "first change", "a.txt", "message", topic);
+ PushOneCommit.Result result2 =
+ createChange(secondRepo, "master", "second change", "b.txt", "message", topic);
+ gApi.changes().id(result1.getChangeId()).current().review(ReviewInput.approve());
+ gApi.changes().id(result2.getChangeId()).current().review(ReviewInput.approve());
+ gApi.changes().id(result1.getChangeId()).revision(result1.getCommit().name()).submit();
+
+ // revoke write permissions for the first repository.
+ projectCache.checkedGet(project).getProject().setState(ProjectState.READ_ONLY);
+
+ String expected = "project state " + ProjectState.READ_ONLY + " does not permit write";
+
+ // assert that if first repository has no write permissions, it will fail.
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.changes().id(result1.getChangeId()).revertSubmission());
+ assertThat(thrown).hasMessageThat().contains(expected);
+
+ // assert that if the first repository has no write permissions and a change from another
+ // repository is trying to revert the submission, it will fail.
+ thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.changes().id(result2.getChangeId()).revertSubmission());
+ assertThat(thrown).hasMessageThat().contains(expected);
+ }
+
+ @Test
+ @GerritConfig(name = "change.submitWholeTopic", value = "true")
+ public void cantCreateRevertSubmissionWithoutCreateChangePermission() throws Exception {
+ String secondProject = "secondProject";
+ projectOperations.newProject().name(secondProject).create();
+ TestRepository<InMemoryRepository> secondRepo =
+ cloneProject(Project.nameKey("secondProject"), admin);
+ String topic = "topic";
+ PushOneCommit.Result result1 =
+ createChange(testRepo, "master", "first change", "a.txt", "message", topic);
+ PushOneCommit.Result result2 =
+ createChange(secondRepo, "master", "second change", "b.txt", "message", topic);
+ gApi.changes().id(result1.getChangeId()).current().review(ReviewInput.approve());
+ gApi.changes().id(result2.getChangeId()).current().review(ReviewInput.approve());
+ gApi.changes().id(result1.getChangeId()).revision(result1.getCommit().name()).submit();
+
+ // revoke create change permissions for the first repository.
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(Permission.PUSH).ref("refs/for/*").group(REGISTERED_USERS))
+ .update();
+
+ // assert that if first repository has no write create change, it will fail.
+ PermissionDeniedException thrown =
+ assertThrows(
+ PermissionDeniedException.class,
+ () -> gApi.changes().id(result1.getChangeId()).revertSubmission());
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("not permitted: create change on refs/heads/master");
+
+ // assert that if the first repository has no create change permissions and a change from
+ // another repository is trying to revert the submission, it will fail.
+ thrown =
+ assertThrows(
+ PermissionDeniedException.class,
+ () -> gApi.changes().id(result2.getChangeId()).revertSubmission());
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("not permitted: create change on refs/heads/master");
+ }
+
+ @Test
+ @GerritConfig(name = "change.submitWholeTopic", value = "true")
+ public void cantCreateRevertSubmissionWithoutReadPermission() throws Exception {
+ String secondProject = "secondProject";
+ projectOperations.newProject().name(secondProject).create();
+ TestRepository<InMemoryRepository> secondRepo =
+ cloneProject(Project.nameKey("secondProject"), admin);
+ String topic = "topic";
+ PushOneCommit.Result result1 =
+ createChange(testRepo, "master", "first change", "a.txt", "message", topic);
+ PushOneCommit.Result result2 =
+ createChange(secondRepo, "master", "second change", "b.txt", "message", topic);
+ gApi.changes().id(result1.getChangeId()).current().review(ReviewInput.approve());
+ gApi.changes().id(result2.getChangeId()).current().review(ReviewInput.approve());
+ gApi.changes().id(result1.getChangeId()).revision(result1.getCommit().name()).submit();
+
+ // revoke read permissions for the first repository.
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(block(Permission.READ).ref("refs/heads/master").group(REGISTERED_USERS))
+ .update();
+
+ // assert that if first repository has no read permissions, it will fail.
+ ResourceNotFoundException resourceNotFoundException =
+ assertThrows(
+ ResourceNotFoundException.class,
+ () -> gApi.changes().id(result1.getChangeId()).revertSubmission());
+ assertThat(resourceNotFoundException)
+ .hasMessageThat()
+ .isEqualTo("Not found: " + result1.getChangeId());
+
+ // assert that if the first repository has no READ permissions and a change from another
+ // repository is trying to revert the submission, it will fail.
+ AuthException authException =
+ assertThrows(
+ AuthException.class, () -> gApi.changes().id(result2.getChangeId()).revertSubmission());
+ assertThat(authException).hasMessageThat().isEqualTo("read not permitted");
+ }
+
+ @Test
+ public void revertSubmissionPreservesReviewersAndCcs() throws Exception {
+ PushOneCommit.Result r = createChange("first change", "a.txt", "message");
+
+ ReviewInput in = ReviewInput.approve();
+ in.reviewer(user.email());
+ in.reviewer(accountCreator.user2().email(), ReviewerState.CC, true);
+ // Add user as reviewer that will create the revert
+ in.reviewer(accountCreator.admin2().email());
+
+ gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).review(in);
+ gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).submit();
+
+ // expect both the original reviewers and CCs to be preserved
+ // original owner should be added as reviewer, user requesting the revert (new owner) removed
+ requestScopeOperations.setApiUser(accountCreator.admin2().id());
+ Map<ReviewerState, Collection<AccountInfo>> result =
+ getChangeApis(gApi.changes().id(r.getChangeId()).revertSubmission()).get(0).get().reviewers;
+ assertThat(result).containsKey(ReviewerState.REVIEWER);
+
+ List<Integer> reviewers =
+ result.get(ReviewerState.REVIEWER).stream().map(a -> a._accountId).collect(toList());
+ assertThat(result).containsKey(ReviewerState.CC);
+ List<Integer> ccs =
+ result.get(ReviewerState.CC).stream().map(a -> a._accountId).collect(toList());
+ assertThat(ccs).containsExactly(accountCreator.user2().id().get());
+ assertThat(reviewers).containsExactly(user.id().get(), admin.id().get());
+ }
+
+ @Test
+ public void revertSubmissionNotifications() throws Exception {
+ PushOneCommit.Result firstResult = createChange("first change", "a.txt", "message");
+ approve(firstResult.getChangeId());
+ gApi.changes().id(firstResult.getChangeId()).addReviewer(user.email());
+ PushOneCommit.Result secondResult = createChange("second change", "b.txt", "other");
+ approve(secondResult.getChangeId());
+ gApi.changes().id(secondResult.getChangeId()).addReviewer(user.email());
+
+ gApi.changes()
+ .id(secondResult.getChangeId())
+ .revision(secondResult.getCommit().name())
+ .submit();
+
+ sender.clear();
+ RevertInput revertInput = new RevertInput();
+ revertInput.notify = NotifyHandling.ALL;
+
+ RevertSubmissionInfo revertChanges =
+ gApi.changes().id(secondResult.getChangeId()).revertSubmission(revertInput);
+
+ List<Message> messages = sender.getMessages();
+
+ assertThat(messages).hasSize(4);
+ assertThat(sender.getMessages(revertChanges.revertChanges.get(0).changeId, "newchange"))
+ .hasSize(1);
+ assertThat(sender.getMessages(firstResult.getChangeId(), "revert")).hasSize(1);
+ assertThat(sender.getMessages(revertChanges.revertChanges.get(1).changeId, "newchange"))
+ .hasSize(1);
+ assertThat(sender.getMessages(secondResult.getChangeId(), "revert")).hasSize(1);
+ }
+
+ @Test
+ public void suppressRevertSubmissionNotifications() throws Exception {
+ PushOneCommit.Result firstResult = createChange("first change", "a.txt", "message");
+ approve(firstResult.getChangeId());
+ gApi.changes().id(firstResult.getChangeId()).addReviewer(user.email());
+ PushOneCommit.Result secondResult = createChange("second change", "b.txt", "other");
+ approve(secondResult.getChangeId());
+ gApi.changes().id(secondResult.getChangeId()).addReviewer(user.email());
+
+ gApi.changes()
+ .id(secondResult.getChangeId())
+ .revision(secondResult.getCommit().name())
+ .submit();
+
+ RevertInput revertInput = new RevertInput();
+ revertInput.notify = NotifyHandling.NONE;
+
+ sender.clear();
+ gApi.changes().id(secondResult.getChangeId()).revertSubmission(revertInput);
+ assertThat(sender.getMessages()).isEmpty();
+ }
+
+ @Test
+ public void revertSubmissionOfSingleChange() throws Exception {
+ PushOneCommit.Result result = createChange("Change", "a.txt", "message");
+ approve(result.getChangeId());
+ gApi.changes().id(result.getChangeId()).current().submit();
+ List<ChangeApi> revertChanges =
+ getChangeApis(gApi.changes().id(result.getChangeId()).revertSubmission());
+
+ String sha1Commit = result.getCommit().getName();
+
+ assertThat(revertChanges.get(0).current().commit(false).parents.get(0).commit)
+ .isEqualTo(sha1Commit);
+
+ assertThat(revertChanges.get(0).current().files().get("a.txt").linesDeleted).isEqualTo(1);
+
+ assertThat(revertChanges.get(0).get().revertOf)
+ .isEqualTo(result.getChange().change().getChangeId());
+ assertThat(revertChanges.get(0).get().topic)
+ .startsWith("revert-" + result.getChange().change().getSubmissionId() + "-");
+ }
+
+ @Test
+ public void revertSubmissionWithSetTopic() throws Exception {
+ PushOneCommit.Result result = createChange();
+ gApi.changes().id(result.getChangeId()).current().review(ReviewInput.approve());
+ gApi.changes().id(result.getChangeId()).topic("topic");
+ gApi.changes().id(result.getChangeId()).revision(result.getCommit().name()).submit();
+ RevertInput revertInput = new RevertInput();
+ revertInput.topic = "reverted-not-default";
+ assertThat(
+ gApi.changes()
+ .id(result.getChangeId())
+ .revertSubmission(revertInput)
+ .revertChanges
+ .get(0)
+ .topic)
+ .isEqualTo(revertInput.topic);
+ }
+
+ @Test
+ public void revertSubmissionWithSetMessage() throws Exception {
+ PushOneCommit.Result result = createChange();
+ gApi.changes().id(result.getChangeId()).current().review(ReviewInput.approve());
+ gApi.changes().id(result.getChangeId()).revision(result.getCommit().name()).submit();
+ RevertInput revertInput = new RevertInput();
+ revertInput.message = "Message from input";
+ assertThat(
+ gApi.changes()
+ .id(result.getChangeId())
+ .revertSubmission(revertInput)
+ .revertChanges
+ .get(0)
+ .subject)
+ .isEqualTo(revertInput.message);
+ }
+
+ @Test
+ @GerritConfig(name = "change.submitWholeTopic", value = "true")
+ @UseClockStep
+ public void revertSubmissionDifferentRepositoriesWithDependantChange() throws Exception {
+ projectOperations.newProject().name("secondProject").create();
+ TestRepository<InMemoryRepository> secondRepo =
+ cloneProject(Project.nameKey("secondProject"), admin);
+ List<PushOneCommit.Result> resultCommits = new ArrayList<>();
+ String topic = "topic";
+ resultCommits.add(createChange(testRepo, "master", "first change", "a.txt", "message", topic));
+ resultCommits.add(
+ createChange(secondRepo, "master", "first change", "a.txt", "message", topic));
+ resultCommits.add(
+ createChange(secondRepo, "master", "second change", "b.txt", "Other message", topic));
+ for (PushOneCommit.Result result : resultCommits) {
+ approve(result.getChangeId());
+ }
+ // submit all changes
+ gApi.changes().id(resultCommits.get(1).getChangeId()).current().submit();
+ List<ChangeApi> revertChanges =
+ getChangeApis(gApi.changes().id(resultCommits.get(1).getChangeId()).revertSubmission());
+
+ // The reverts are by update time, so the reversal ensures that
+ // revertChanges[i] is the revert of resultCommits[i]
+ Collections.reverse(revertChanges);
+
+ assertThat(revertChanges.get(0).current().files().get("a.txt").linesDeleted).isEqualTo(1);
+ assertThat(revertChanges.get(1).current().files().get("a.txt").linesDeleted).isEqualTo(1);
+ assertThat(revertChanges.get(2).current().files().get("b.txt").linesDeleted).isEqualTo(1);
+ // has size 3 because of the same topic, and submitWholeTopic is true.
+ assertThat(gApi.changes().id(revertChanges.get(0).get()._number).submittedTogether())
+ .hasSize(3);
+
+ // expected messages on source change:
+ // 1. Uploaded patch set 1.
+ // 2. Patch Set 1: Code-Review+2
+ // 3. Change has been successfully merged by Administrator
+ // 4. Created a revert of this change as %s
+
+ for (int i = 0; i < resultCommits.size(); i++) {
+ assertThat(revertChanges.get(i).current().commit(false).parents.get(0).commit)
+ .isEqualTo(resultCommits.get(i).getCommit().getName());
+ assertThat(revertChanges.get(i).get().revertOf)
+ .isEqualTo(resultCommits.get(i).getChange().change().getChangeId());
+ List<ChangeMessageInfo> sourceMessages =
+ new ArrayList<>(gApi.changes().id(resultCommits.get(i).getChangeId()).get().messages);
+ assertThat(sourceMessages).hasSize(4);
+ String expectedMessage =
+ String.format(
+ "Created a revert of this change as %s", revertChanges.get(i).get().changeId);
+ assertThat(sourceMessages.get(3).message).isEqualTo(expectedMessage);
+ // Expected message on the created change: "Uploaded patch set 1."
+ List<ChangeMessageInfo> messages =
+ revertChanges.get(i).get().messages.stream().collect(toList());
+ assertThat(messages).hasSize(1);
+ assertThat(messages.get(0).message).isEqualTo("Uploaded patch set 1.");
+ assertThat(revertChanges.get(i).get().revertOf)
+ .isEqualTo(gApi.changes().id(resultCommits.get(i).getChangeId()).get()._number);
+ assertThat(revertChanges.get(i).get().topic)
+ .startsWith("revert-" + resultCommits.get(0).getChange().change().getSubmissionId());
+ }
+ }
+
+ @Test
+ public void cantRevertSubmissionWithAnOpenChange() throws Exception {
+ PushOneCommit.Result result = createChange("first change", "a.txt", "message");
+ approve(result.getChangeId());
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.changes().id(result.getChangeId()).revertSubmission());
+ assertThat(thrown).hasMessageThat().isEqualTo("change is new.");
+ }
+
@Override
protected PushOneCommit.Result createChange() throws Exception {
return createChange("refs/for/master");
@@ -451,4 +795,13 @@
.create();
}
}
+
+ private List<ChangeApi> getChangeApis(RevertSubmissionInfo revertSubmissionInfo)
+ throws Exception {
+ List<ChangeApi> results = new ArrayList<>();
+ for (ChangeInfo changeInfo : revertSubmissionInfo.revertChanges) {
+ results.add(gApi.changes().id(changeInfo._number));
+ }
+ return results;
+ }
}
diff --git a/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java b/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java
index 07fb577..70e4f89 100644
--- a/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/group/GroupsIT.java
@@ -1265,7 +1265,7 @@
// Newly created group is not stale
GroupInfo groupInfo = gApi.groups().create(name("foo")).get();
AccountGroup.UUID groupUuid = AccountGroup.uuid(groupInfo.id);
- assertThat(stalenessChecker.isStale(groupUuid)).isFalse();
+ assertThat(stalenessChecker.check(groupUuid).isStale()).isFalse();
// Manual update makes index document stale
String groupRef = RefNames.refsGroups(groupUuid);
@@ -1406,11 +1406,11 @@
private void assertStaleGroupAndReindex(AccountGroup.UUID groupUuid) throws IOException {
// Evict group from cache to be sure that we use the index state for staleness checks.
groupCache.evict(groupUuid);
- assertThat(stalenessChecker.isStale(groupUuid)).isTrue();
+ assertThat(stalenessChecker.check(groupUuid).isStale()).isTrue();
// Reindex fixes staleness
groupIndexer.index(groupUuid);
- assertThat(stalenessChecker.isStale(groupUuid)).isFalse();
+ assertThat(stalenessChecker.check(groupUuid).isStale()).isFalse();
}
private void pushToGroupBranchForReviewAndSubmit(
diff --git a/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java b/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java
index eebcc5b..1a8790a 100644
--- a/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/project/ProjectIT.java
@@ -49,6 +49,7 @@
import com.google.gerrit.extensions.annotations.Exports;
import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.api.projects.CommentLinkInfo;
+import com.google.gerrit.extensions.api.projects.CommentLinkInput;
import com.google.gerrit.extensions.api.projects.ConfigInfo;
import com.google.gerrit.extensions.api.projects.ConfigInput;
import com.google.gerrit.extensions.api.projects.ConfigValue;
@@ -717,6 +718,139 @@
assertCommentLinks(getConfig(), expected);
}
+ @Test
+ public void projectConfigUsesLocallySetCommentlinks() throws Exception {
+ ConfigInput input = new ConfigInput();
+ addCommentLink(input, BUGZILLA, BUGZILLA_MATCH, BUGZILLA_LINK);
+ addCommentLink(input, JIRA, JIRA_MATCH, JIRA_LINK);
+ ConfigInfo info = setConfig(project, input);
+
+ Map<String, CommentLinkInfo> expected = new HashMap<>();
+ expected.put(BUGZILLA, commentLinkInfo(BUGZILLA, BUGZILLA_MATCH, BUGZILLA_LINK));
+ expected.put(JIRA, commentLinkInfo(JIRA, JIRA_MATCH, JIRA_LINK));
+ assertCommentLinks(info, expected);
+ assertCommentLinks(getConfig(), expected);
+ }
+
+ @Test
+ @GerritConfig(name = "commentlink.bugzilla.match", value = BUGZILLA_MATCH)
+ @GerritConfig(name = "commentlink.bugzilla.link", value = BUGZILLA_LINK)
+ public void projectConfigUsesCommentLinksFromGlobalAndLocal() throws Exception {
+ Map<String, CommentLinkInfo> expected = new HashMap<>();
+ expected.put(BUGZILLA, commentLinkInfo(BUGZILLA, BUGZILLA_MATCH, BUGZILLA_LINK));
+ assertCommentLinks(getConfig(), expected);
+
+ ConfigInput input = new ConfigInput();
+ addCommentLink(input, JIRA, JIRA_MATCH, JIRA_LINK);
+ ConfigInfo info = setConfig(project, input);
+ expected.put(JIRA, commentLinkInfo(JIRA, JIRA_MATCH, JIRA_LINK));
+
+ assertCommentLinks(info, expected);
+ assertCommentLinks(getConfig(), expected);
+ }
+
+ @Test
+ @GerritConfig(name = "commentlink.bugzilla.match", value = BUGZILLA_MATCH)
+ @GerritConfig(name = "commentlink.bugzilla.link", value = BUGZILLA_LINK)
+ public void localCommentLinkOverridesGlobalConfig() throws Exception {
+ String otherLink = "https://other.example.com";
+ ConfigInput input = new ConfigInput();
+ addCommentLink(input, BUGZILLA, BUGZILLA_MATCH, otherLink);
+
+ Map<String, CommentLinkInfo> expected = new HashMap<>();
+ expected.put(BUGZILLA, commentLinkInfo(BUGZILLA, BUGZILLA_MATCH, otherLink));
+
+ ConfigInfo info = setConfig(project, input);
+ assertCommentLinks(info, expected);
+ assertCommentLinks(getConfig(), expected);
+ }
+
+ @Test
+ public void localCommentLinksAreInheritedFromParent() throws Exception {
+ ConfigInput input = new ConfigInput();
+ addCommentLink(input, BUGZILLA, BUGZILLA_MATCH, BUGZILLA_LINK);
+ addCommentLink(input, JIRA, JIRA_MATCH, JIRA_LINK);
+ ConfigInfo info = setConfig(project, input);
+
+ Map<String, CommentLinkInfo> expected = new HashMap<>();
+ expected.put(BUGZILLA, commentLinkInfo(BUGZILLA, BUGZILLA_MATCH, BUGZILLA_LINK));
+ expected.put(JIRA, commentLinkInfo(JIRA, JIRA_MATCH, JIRA_LINK));
+ assertCommentLinks(info, expected);
+
+ Project.NameKey child = projectOperations.newProject().parent(project).create();
+ assertCommentLinks(getConfig(child), expected);
+ }
+
+ @Test
+ public void localCommentLinkOverridesParentCommentLink() throws Exception {
+ ConfigInput input = new ConfigInput();
+ addCommentLink(input, BUGZILLA, BUGZILLA_MATCH, BUGZILLA_LINK);
+ addCommentLink(input, JIRA, JIRA_MATCH, JIRA_LINK);
+ ConfigInfo info = setConfig(project, input);
+
+ Map<String, CommentLinkInfo> expected = new HashMap<>();
+ expected.put(BUGZILLA, commentLinkInfo(BUGZILLA, BUGZILLA_MATCH, BUGZILLA_LINK));
+ expected.put(JIRA, commentLinkInfo(JIRA, JIRA_MATCH, JIRA_LINK));
+ assertCommentLinks(info, expected);
+
+ Project.NameKey child = projectOperations.newProject().parent(project).create();
+
+ String otherLink = "https://other.example.com";
+ input = new ConfigInput();
+ addCommentLink(input, BUGZILLA, BUGZILLA_MATCH, otherLink);
+ info = setConfig(child, input);
+
+ expected = new HashMap<>();
+ expected.put(BUGZILLA, commentLinkInfo(BUGZILLA, BUGZILLA_MATCH, otherLink));
+ expected.put(JIRA, commentLinkInfo(JIRA, JIRA_MATCH, JIRA_LINK));
+
+ assertCommentLinks(getConfig(child), expected);
+ }
+
+ @Test
+ public void updateExistingCommentLink() throws Exception {
+ ConfigInput input = new ConfigInput();
+ addCommentLink(input, BUGZILLA, BUGZILLA_MATCH, BUGZILLA_LINK);
+ addCommentLink(input, JIRA, JIRA_MATCH, JIRA_LINK);
+ ConfigInfo info = setConfig(project, input);
+
+ Map<String, CommentLinkInfo> expected = new HashMap<>();
+ expected.put(BUGZILLA, commentLinkInfo(BUGZILLA, BUGZILLA_MATCH, BUGZILLA_LINK));
+ expected.put(JIRA, commentLinkInfo(JIRA, JIRA_MATCH, JIRA_LINK));
+ assertCommentLinks(info, expected);
+
+ String otherLink = "https://other.example.com";
+ input = new ConfigInput();
+ addCommentLink(input, BUGZILLA, BUGZILLA_MATCH, otherLink);
+ info = setConfig(project, input);
+
+ expected = new HashMap<>();
+ expected.put(BUGZILLA, commentLinkInfo(BUGZILLA, BUGZILLA_MATCH, otherLink));
+ expected.put(JIRA, commentLinkInfo(JIRA, JIRA_MATCH, JIRA_LINK));
+ assertCommentLinks(getConfig(project), expected);
+ }
+
+ @Test
+ public void removeCommentLink() throws Exception {
+ ConfigInput input = new ConfigInput();
+ addCommentLink(input, BUGZILLA, BUGZILLA_MATCH, BUGZILLA_LINK);
+ addCommentLink(input, JIRA, JIRA_MATCH, JIRA_LINK);
+ ConfigInfo info = setConfig(project, input);
+
+ Map<String, CommentLinkInfo> expected = new HashMap<>();
+ expected.put(BUGZILLA, commentLinkInfo(BUGZILLA, BUGZILLA_MATCH, BUGZILLA_LINK));
+ expected.put(JIRA, commentLinkInfo(JIRA, JIRA_MATCH, JIRA_LINK));
+ assertCommentLinks(info, expected);
+
+ input = new ConfigInput();
+ addCommentLink(input, BUGZILLA, null);
+ info = setConfig(project, input);
+
+ expected = new HashMap<>();
+ expected.put(JIRA, commentLinkInfo(JIRA, JIRA_MATCH, JIRA_LINK));
+ assertCommentLinks(getConfig(project), expected);
+ }
+
private CommentLinkInfo commentLinkInfo(String name, String match, String link) {
return new CommentLinkInfoImpl(name, match, link, null /*html*/, null /*enabled*/);
}
@@ -725,6 +859,21 @@
assertThat(actual.commentlinks).containsExactlyEntriesIn(expected);
}
+ private void addCommentLink(ConfigInput configInput, String name, String match, String link) {
+ CommentLinkInput commentLinkInput = new CommentLinkInput();
+ commentLinkInput.match = match;
+ commentLinkInput.link = link;
+ addCommentLink(configInput, name, commentLinkInput);
+ }
+
+ private void addCommentLink(
+ ConfigInput configInput, String name, CommentLinkInput commentLinkInput) {
+ if (configInput.commentLinks == null) {
+ configInput.commentLinks = new HashMap<>();
+ }
+ configInput.commentLinks.put(name, commentLinkInput);
+ }
+
private ConfigInfo setConfig(Project.NameKey name, ConfigInput input) throws Exception {
return gApi.projects().name(name.get()).config(input);
}
diff --git a/javatests/com/google/gerrit/acceptance/api/project/ProjectIndexerIT.java b/javatests/com/google/gerrit/acceptance/api/project/ProjectIndexerIT.java
index 019df0e..023f43e 100644
--- a/javatests/com/google/gerrit/acceptance/api/project/ProjectIndexerIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/project/ProjectIndexerIT.java
@@ -83,19 +83,19 @@
@Test
public void stalenessChecker_currentProject_notStale() throws Exception {
- assertThat(stalenessChecker.isStale(project)).isFalse();
+ assertThat(stalenessChecker.check(project).isStale()).isFalse();
}
@Test
public void stalenessChecker_currentProjectUpdates_isStale() throws Exception {
updateProjectConfigWithoutIndexUpdate(project);
- assertThat(stalenessChecker.isStale(project)).isTrue();
+ assertThat(stalenessChecker.check(project).isStale()).isTrue();
}
@Test
public void stalenessChecker_parentProjectUpdates_isStale() throws Exception {
updateProjectConfigWithoutIndexUpdate(allProjects);
- assertThat(stalenessChecker.isStale(project)).isTrue();
+ assertThat(stalenessChecker.check(project).isStale()).isTrue();
}
@Test
@@ -106,10 +106,10 @@
u.getConfig().getProject().setParentName(p1);
u.save();
}
- assertThat(stalenessChecker.isStale(project)).isFalse();
+ assertThat(stalenessChecker.check(project).isStale()).isFalse();
updateProjectConfigWithoutIndexUpdate(p1, c -> c.getProject().setParentName(p2));
- assertThat(stalenessChecker.isStale(project)).isTrue();
+ assertThat(stalenessChecker.check(project).isStale()).isTrue();
}
private void updateProjectConfigWithoutIndexUpdate(Project.NameKey project) throws Exception {
diff --git a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
index 32941ff..ad73e0f 100644
--- a/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/revision/RevisionIT.java
@@ -1328,6 +1328,23 @@
}
@Test
+ public void cannotGetContentOfDirectory() throws Exception {
+ Map<String, String> files = ImmutableMap.of("dir/file1.txt", "content 1");
+ PushOneCommit.Result result =
+ pushFactory.create(admin.newIdent(), testRepo, SUBJECT, files).to("refs/for/master");
+ result.assertOkStatus();
+
+ assertThrows(
+ BadRequestException.class,
+ () ->
+ gApi.changes()
+ .id(result.getChangeId())
+ .revision(result.getCommit().name())
+ .file("dir")
+ .content());
+ }
+
+ @Test
public void contentType() throws Exception {
PushOneCommit.Result r = createChange();
diff --git a/javatests/com/google/gerrit/acceptance/filter/BUILD b/javatests/com/google/gerrit/acceptance/filter/BUILD
new file mode 100644
index 0000000..22aead3
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/filter/BUILD
@@ -0,0 +1,22 @@
+load("//javatests/com/google/gerrit/acceptance:tests.bzl", "acceptance_tests")
+
+acceptance_tests(
+ srcs = glob([
+ "*IT.java",
+ ]),
+ group = "filter",
+ labels = ["filter"],
+ deps = [
+ ":util",
+ ],
+)
+
+java_library(
+ name = "util",
+ testonly = True,
+ srcs = [
+ "FakeMustInitParamsFilter.java",
+ "FakeNoInitParamsFilter.java",
+ ],
+ deps = ["//java/com/google/gerrit/acceptance:lib"],
+)
diff --git a/javatests/com/google/gerrit/acceptance/filter/FakeMustInitParamsFilter.java b/javatests/com/google/gerrit/acceptance/filter/FakeMustInitParamsFilter.java
new file mode 100644
index 0000000..89d268e
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/filter/FakeMustInitParamsFilter.java
@@ -0,0 +1,56 @@
+// 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.acceptance.filter;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+public class FakeMustInitParamsFilter implements Filter {
+
+ // `PARAM_X` and `PARAM_Y` are init param keys
+ private static final String INIT_PARAM_1 = "PARAM-1";
+ private static final String INIT_PARAM_2 = "PARAM-2";
+ // the map is used for testing
+ private static final Map<String, String> initParams = new HashMap<>();
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+ initParams.put(INIT_PARAM_1, filterConfig.getInitParameter(INIT_PARAM_1));
+ initParams.put(INIT_PARAM_2, filterConfig.getInitParameter(INIT_PARAM_2));
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+ throws IOException, ServletException {
+ chain.doFilter(request, response);
+ }
+
+ @Override
+ public void destroy() {
+ // do nothing.
+ }
+
+ // the function is used for testing
+ Map<String, String> getInitParams() {
+ return initParams;
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/filter/FakeNoInitParamsFilter.java b/javatests/com/google/gerrit/acceptance/filter/FakeNoInitParamsFilter.java
new file mode 100644
index 0000000..6fd6366
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/filter/FakeNoInitParamsFilter.java
@@ -0,0 +1,42 @@
+// 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.acceptance.filter;
+
+import java.io.IOException;
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+public class FakeNoInitParamsFilter implements Filter {
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+ // no init params in this filter.
+ // do nothing.
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
+ throws IOException, ServletException {
+ chain.doFilter(request, response);
+ }
+
+ @Override
+ public void destroy() {
+ // do nothing.
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/filter/FilterClassIT.java b/javatests/com/google/gerrit/acceptance/filter/FilterClassIT.java
new file mode 100644
index 0000000..a23c5ce
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/filter/FilterClassIT.java
@@ -0,0 +1,57 @@
+// 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.acceptance.filter;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.testing.ConfigSuite;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.Config;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class FilterClassIT extends AbstractDaemonTest {
+ @ConfigSuite.Default
+ public static Config enableFilter() throws ConfigInvalidException {
+ Config cfg = new Config();
+ cfg.fromText(
+ ""
+ + "[httpd]\n"
+ + " filterClass = com.google.gerrit.acceptance.filter.FakeNoInitParamsFilter\n"
+ + " filterClass = com.google.gerrit.acceptance.filter.FakeMustInitParamsFilter\n"
+ + "[filterClass \"com.google.gerrit.acceptance.filter.FakeMustInitParamsFilter\"]\n"
+ + " PARAM-1 = hello\n"
+ + " PARAM-2 = world\n");
+ return cfg;
+ }
+
+ @Test
+ public void filterLoad() {
+ FakeNoInitParamsFilter fakeNoInitParamsFilter =
+ server.getTestInjector().getBinding(FakeNoInitParamsFilter.class).getProvider().get();
+ Assert.assertNotNull(fakeNoInitParamsFilter);
+ FakeMustInitParamsFilter fakeMustInitParamsFilter =
+ server.getTestInjector().getBinding(FakeMustInitParamsFilter.class).getProvider().get();
+ Assert.assertNotNull(fakeMustInitParamsFilter);
+ }
+
+ @Test
+ public void filterInitParams() {
+ FakeMustInitParamsFilter fakeMustInitParamsFilter =
+ server.getTestInjector().getBinding(FakeMustInitParamsFilter.class).getProvider().get();
+ Assert.assertEquals(2, fakeMustInitParamsFilter.getInitParams().size());
+ Assert.assertEquals("hello", fakeMustInitParamsFilter.getInitParams().get("PARAM-1"));
+ Assert.assertEquals("world", fakeMustInitParamsFilter.getInitParams().get("PARAM-2"));
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/rest/binding/ChangesRestApiBindingsIT.java b/javatests/com/google/gerrit/acceptance/rest/binding/ChangesRestApiBindingsIT.java
index 8a284d9..83bc3eb 100644
--- a/javatests/com/google/gerrit/acceptance/rest/binding/ChangesRestApiBindingsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/binding/ChangesRestApiBindingsIT.java
@@ -84,6 +84,7 @@
RestCall.post("/changes/%s/rebase"),
RestCall.post("/changes/%s/restore"),
RestCall.post("/changes/%s/revert"),
+ RestCall.post("/changes/%s/revert_submission"),
RestCall.get("/changes/%s/pure_revert"),
RestCall.post("/changes/%s/submit"),
RestCall.get("/changes/%s/submitted_together"),
diff --git a/javatests/com/google/gerrit/acceptance/rest/binding/ProjectsRestApiBindingsIT.java b/javatests/com/google/gerrit/acceptance/rest/binding/ProjectsRestApiBindingsIT.java
index f48a603..b8ab752 100644
--- a/javatests/com/google/gerrit/acceptance/rest/binding/ProjectsRestApiBindingsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/binding/ProjectsRestApiBindingsIT.java
@@ -29,6 +29,7 @@
import com.google.gerrit.acceptance.rest.util.RestApiCallHelper;
import com.google.gerrit.acceptance.rest.util.RestCall;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.common.data.LabelFunction;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.entities.Project;
import com.google.gerrit.extensions.api.changes.ReviewInput;
@@ -73,6 +74,7 @@
RestCall.get("/projects/%s/branches"),
RestCall.post("/projects/%s/branches:delete"),
RestCall.put("/projects/%s/branches/new-branch"),
+ RestCall.get("/projects/%s/labels"),
RestCall.get("/projects/%s/tags"),
RestCall.post("/projects/%s/tags:delete"),
RestCall.put("/projects/%s/tags/new-tag"),
@@ -80,7 +82,8 @@
// GET /projects/<project>/branches/<branch>/commits is not implemented
.expectedResponseCode(SC_NOT_FOUND)
.build(),
- RestCall.get("/projects/%s/dashboards"));
+ RestCall.get("/projects/%s/dashboards"),
+ RestCall.put("/projects/%s/labels/new-label"));
/**
* Child project REST endpoints to be tested, each URL contains placeholders for the parent
@@ -158,6 +161,18 @@
private static final ImmutableList<RestCall> COMMIT_FILE_ENDPOINTS =
ImmutableList.of(RestCall.get("/projects/%s/commits/%s/files/%s/content"));
+ /**
+ * Label REST endpoints to be tested, each URL contains placeholders for the project identifier
+ * and the label name.
+ */
+ private static final ImmutableList<RestCall> LABEL_ENDPOINTS =
+ ImmutableList.of(
+ RestCall.get("/projects/%s/labels/%s"),
+ RestCall.put("/projects/%s/labels/%s"),
+
+ // Label deletion must be tested last
+ RestCall.delete("/projects/%s/labels/%s"));
+
private static final String FILENAME = "test.txt";
@Inject private ProjectOperations projectOperations;
@@ -212,6 +227,13 @@
adminRestSession, COMMIT_FILE_ENDPOINTS, project.get(), commit, FILENAME);
}
+ @Test
+ public void labelEndpoints() throws Exception {
+ String label = "Foo-Review";
+ configLabel(label, LabelFunction.NO_OP);
+ RestApiCallHelper.execute(adminRestSession, LABEL_ENDPOINTS, project.get(), label);
+ }
+
private String createAndSubmitChange(String filename) throws Exception {
RevCommit c =
testRepo
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/ActionsIT.java b/javatests/com/google/gerrit/acceptance/rest/change/ActionsIT.java
index dda7bbd..8b51e7f 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/ActionsIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/ActionsIT.java
@@ -63,11 +63,25 @@
return gApi.changes().id(id).revision(1).actions();
}
+ protected Map<String, ActionInfo> getChangeActions(String id) throws Exception {
+ return gApi.changes().id(id).get().actions;
+ }
+
protected String getETag(String id) throws Exception {
return gApi.changes().id(id).current().etag();
}
@Test
+ public void changeActionOneMergedChangeHasReverts() throws Exception {
+ String changeId = createChangeWithTopic().getChangeId();
+ gApi.changes().id(changeId).current().review(ReviewInput.approve());
+ gApi.changes().id(changeId).current().submit();
+ Map<String, ActionInfo> actions = getChangeActions(changeId);
+ assertThat(actions).containsKey("revert");
+ assertThat(actions).containsKey("revert_submission");
+ }
+
+ @Test
public void revisionActionsOneChangePerTopicUnapproved() throws Exception {
String changeId = createChangeWithTopic().getChangeId();
Map<String, ActionInfo> actions = getActions(changeId);
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java b/javatests/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
index a1167ed..10bae39 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
@@ -39,6 +39,7 @@
import com.google.gerrit.extensions.api.changes.CherryPickInput;
import com.google.gerrit.extensions.api.changes.NotifyHandling;
import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.client.ChangeStatus;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
@@ -606,6 +607,67 @@
}
}
+ @Test
+ public void createChangeWithSubmittedMergeSource() throws Exception {
+ // Provide coverage for a performance optimization in CommitsCollection#canRead.
+ BranchInput branchInput = new BranchInput();
+ String mergeTarget = "refs/heads/new-branch";
+ RevCommit startCommit = projectOperations.project(project).getHead("master");
+
+ branchInput.revision = startCommit.name();
+ branchInput.ref = mergeTarget;
+
+ gApi.projects().name(project.get()).branch(mergeTarget).create(branchInput);
+
+ PushOneCommit.Result result1 =
+ pushFactory
+ .create(
+ admin.newIdent(), testRepo, "subject1", ImmutableMap.of("file1.txt", "content 1"))
+ .to("refs/for/master");
+ result1.assertOkStatus();
+
+ testRepo.branch("HEAD").update(startCommit);
+ PushOneCommit.Result result2 =
+ pushFactory
+ .create(
+ admin.newIdent(), testRepo, "subject2", ImmutableMap.of("file2.txt", "content 2"))
+ .to("refs/for/master");
+ result2.assertOkStatus();
+
+ ReviewInput reviewInput = ReviewInput.approve().label("Code-Review", 2);
+
+ gApi.changes().id(result1.getChangeId()).revision("current").review(reviewInput);
+ gApi.changes().id(result1.getChangeId()).revision("current").submit();
+
+ gApi.changes().id(result2.getChangeId()).revision("current").review(reviewInput);
+ gApi.changes().id(result2.getChangeId()).revision("current").submit();
+
+ String mergeRev = gApi.projects().name(project.get()).branch("master").get().revision;
+ RevCommit mergeCommit = projectOperations.project(project).getHead("master");
+ assertThat(mergeCommit.getParents().length).isEqualTo(2);
+
+ testRepo.git().fetch().call();
+ testRepo.branch("HEAD").update(mergeCommit);
+ PushOneCommit.Result result3 =
+ pushFactory
+ .create(
+ admin.newIdent(), testRepo, "subject3", ImmutableMap.of("file1.txt", "content 3"))
+ .to("refs/for/master");
+ result2.assertOkStatus();
+ gApi.changes().id(result3.getChangeId()).revision("current").review(reviewInput);
+ gApi.changes().id(result3.getChangeId()).revision("current").submit();
+
+ // Now master doesn't point directly to mergeRev
+ ChangeInput in = new ChangeInput();
+ in.branch = mergeTarget;
+ in.merge = new MergeInput();
+ in.project = project.get();
+ in.merge.source = mergeRev;
+ in.subject = "propagate merge";
+
+ gApi.changes().create(in);
+ }
+
private ChangeInput newChangeInput(ChangeStatus status) {
ChangeInput in = new ChangeInput();
in.project = project.get();
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/BUILD b/javatests/com/google/gerrit/acceptance/rest/project/BUILD
index e082559..b50a12b 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/BUILD
+++ b/javatests/com/google/gerrit/acceptance/rest/project/BUILD
@@ -6,6 +6,7 @@
group = f[:f.index(".")],
labels = ["rest"],
deps = [
+ ":labelassert",
":project",
":push_tag_util",
":refassert",
@@ -14,6 +15,19 @@
) for f in glob(["*IT.java"])]
java_library(
+ name = "labelassert",
+ srcs = [
+ "LabelAssert.java",
+ ],
+ deps = [
+ "//java/com/google/gerrit/common:server",
+ "//java/com/google/gerrit/extensions:api",
+ "//java/com/google/gerrit/server",
+ "//lib/truth",
+ ],
+)
+
+java_library(
name = "refassert",
srcs = [
"RefAssert.java",
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/CreateLabelIT.java b/javatests/com/google/gerrit/acceptance/rest/project/CreateLabelIT.java
new file mode 100644
index 0000000..28e8b14
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/rest/project/CreateLabelIT.java
@@ -0,0 +1,596 @@
+// 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.acceptance.rest.project;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
+import com.google.gerrit.common.data.LabelFunction;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.extensions.common.LabelDefinitionInfo;
+import com.google.gerrit.extensions.common.LabelDefinitionInput;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.inject.Inject;
+import org.junit.Test;
+
+@NoHttpd
+public class CreateLabelIT extends AbstractDaemonTest {
+ @Inject private RequestScopeOperations requestScopeOperations;
+ @Inject private ProjectOperations projectOperations;
+
+ @Test
+ public void anonymous() throws Exception {
+ requestScopeOperations.setApiUserAnonymous();
+ AuthException thrown =
+ assertThrows(
+ AuthException.class,
+ () ->
+ gApi.projects()
+ .name(project.get())
+ .label("Foo-Review")
+ .create(new LabelDefinitionInput()));
+ assertThat(thrown).hasMessageThat().contains("Authentication required");
+ }
+
+ @Test
+ public void notAllowed() throws Exception {
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.READ).ref(RefNames.REFS_CONFIG).group(REGISTERED_USERS))
+ .update();
+
+ requestScopeOperations.setApiUser(user.id());
+ AuthException thrown =
+ assertThrows(
+ AuthException.class,
+ () ->
+ gApi.projects()
+ .name(project.get())
+ .label("Foo-Review")
+ .create(new LabelDefinitionInput()));
+ assertThat(thrown).hasMessageThat().contains("write refs/meta/config not permitted");
+ }
+
+ @Test
+ public void cannotCreateLabelIfNameDoesntMatch() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.name = "Foo";
+
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.projects().name(project.get()).label("Bar").create(input));
+ assertThat(thrown).hasMessageThat().contains("name in input must match name in URL");
+ }
+
+ @Test
+ public void cannotCreateLabelWithNameThatIsAlreadyInUse() throws Exception {
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () ->
+ gApi.projects()
+ .name(allProjects.get())
+ .label("Code-Review")
+ .create(new LabelDefinitionInput()));
+ assertThat(thrown).hasMessageThat().contains("label Code-Review already exists");
+ }
+
+ @Test
+ public void cannotCreateLabelWithNameThatConflicts() throws Exception {
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () ->
+ gApi.projects()
+ .name(allProjects.get())
+ .label("code-review")
+ .create(new LabelDefinitionInput()));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("label code-review conflicts with existing label Code-Review");
+ }
+
+ @Test
+ public void cannotCreateLabelWithInvalidName() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.values = ImmutableMap.of("+1", "Looks Good", "0", "Don't Know", "-1", "Looks Bad");
+
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.projects().name(project.get()).label("INVALID_NAME").create(input));
+ assertThat(thrown).hasMessageThat().contains("invalid name: INVALID_NAME");
+ }
+
+ @Test
+ public void cannotCreateLabelWithoutValues() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.projects().name(project.get()).label("Foo").create(input));
+ assertThat(thrown).hasMessageThat().contains("values are required");
+
+ input.values = ImmutableMap.of();
+ thrown =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.projects().name(project.get()).label("Foo").create(input));
+ assertThat(thrown).hasMessageThat().contains("values are required");
+ }
+
+ @Test
+ public void cannotCreateLabelWithInvalidValues() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.values = ImmutableMap.of("invalidValue", "description");
+
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.projects().name(project.get()).label("Foo").create(input));
+ assertThat(thrown).hasMessageThat().contains("invalid value: invalidValue");
+ }
+
+ @Test
+ public void cannotCreateLabelWithValuesThatHaveEmptyDescription() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.values = ImmutableMap.of("+1", "");
+
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.projects().name(project.get()).label("Foo").create(input));
+ assertThat(thrown).hasMessageThat().contains("description for value '+1' cannot be empty");
+ }
+
+ @Test
+ public void cannotCreateLabelWithInvalidDefaultValue() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.values = ImmutableMap.of("+1", "Looks Good", "0", "Don't Know", "-1", "Looks Bad");
+ input.defaultValue = 5;
+
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.projects().name(project.get()).label("Foo").create(input));
+ assertThat(thrown).hasMessageThat().contains("invalid default value: " + input.defaultValue);
+ }
+
+ @Test
+ public void cannotCreateLabelWithUnknownFunction() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.values = ImmutableMap.of("+1", "Looks Good", "0", "Don't Know", "-1", "Looks Bad");
+ input.function = "UnknownFuction";
+
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.projects().name(project.get()).label("Foo").create(input));
+ assertThat(thrown).hasMessageThat().contains("unknown function: " + input.function);
+ }
+
+ @Test
+ public void cannotCreateLabelWithInvalidBranch() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.values = ImmutableMap.of("+1", "Looks Good", "0", "Don't Know", "-1", "Looks Bad");
+ input.branches = ImmutableList.of("refs heads master");
+
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.projects().name(project.get()).label("Foo").create(input));
+ assertThat(thrown).hasMessageThat().contains("invalid branch: refs heads master");
+ }
+
+ @Test
+ public void createWithNameAndValuesOnly() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+
+ LabelDefinitionInfo createdLabel =
+ gApi.projects().name(project.get()).label("Foo").create(input).get();
+
+ assertThat(createdLabel.name).isEqualTo("Foo");
+ assertThat(createdLabel.projectName).isEqualTo(project.get());
+ assertThat(createdLabel.function).isEqualTo(LabelFunction.MAX_WITH_BLOCK.getFunctionName());
+ assertThat(createdLabel.values).containsExactlyEntriesIn(input.values);
+ assertThat(createdLabel.defaultValue).isEqualTo(0);
+ assertThat(createdLabel.branches).isNull();
+ assertThat(createdLabel.canOverride).isTrue();
+ assertThat(createdLabel.copyAnyScore).isNull();
+ assertThat(createdLabel.copyMinScore).isNull();
+ assertThat(createdLabel.copyMaxScore).isNull();
+ assertThat(createdLabel.copyAllScoresIfNoChange).isTrue();
+ assertThat(createdLabel.copyAllScoresIfNoCodeChange).isNull();
+ assertThat(createdLabel.copyAllScoresOnTrivialRebase).isNull();
+ assertThat(createdLabel.copyAllScoresOnMergeFirstParentUpdate).isNull();
+ assertThat(createdLabel.allowPostSubmit).isTrue();
+ assertThat(createdLabel.ignoreSelfApproval).isNull();
+ }
+
+ @Test
+ public void createWithFunction() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+ input.function = LabelFunction.NO_OP.getFunctionName();
+
+ LabelDefinitionInfo createdLabel =
+ gApi.projects().name(project.get()).label("Foo").create(input).get();
+
+ assertThat(createdLabel.function).isEqualTo(LabelFunction.NO_OP.getFunctionName());
+ }
+
+ @Test
+ public void functionEmptyAfterTrim() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+ input.function = " ";
+
+ LabelDefinitionInfo createdLabel =
+ gApi.projects().name(project.get()).label("Foo").create(input).get();
+
+ assertThat(createdLabel.function).isEqualTo(LabelFunction.MAX_WITH_BLOCK.getFunctionName());
+ }
+
+ @Test
+ public void valuesAndDescriptionsAreTrimmed() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ // Positive values can be specified as '<value>' or '+<value>'.
+ input.values =
+ ImmutableMap.of(
+ " 2 ",
+ " Looks Very Good ",
+ " +1 ",
+ " Looks Good ",
+ " 0 ",
+ " Don't Know ",
+ " -1 ",
+ " Looks Bad ",
+ " -2 ",
+ " Looks Very Bad ");
+
+ LabelDefinitionInfo createdLabel =
+ gApi.projects().name(project.get()).label("Foo").create(input).get();
+ assertThat(createdLabel.values)
+ .containsExactly(
+ "+2", "Looks Very Good",
+ "+1", "Looks Good",
+ " 0", "Don't Know",
+ "-1", "Looks Bad",
+ "-2", "Looks Very Bad");
+ }
+
+ @Test
+ public void createWithDefaultValue() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+ input.defaultValue = 1;
+
+ LabelDefinitionInfo createdLabel =
+ gApi.projects().name(project.get()).label("Foo").create(input).get();
+
+ assertThat(createdLabel.defaultValue).isEqualTo(input.defaultValue);
+ }
+
+ @Test
+ public void createWithBranches() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+ // Branches can be full ref, ref pattern or regular expression.
+ input.branches =
+ ImmutableList.of("refs/heads/master", "refs/heads/foo/*", "^refs/heads/stable-.*");
+
+ LabelDefinitionInfo createdLabel =
+ gApi.projects().name(project.get()).label("Foo").create(input).get();
+ assertThat(createdLabel.branches).containsExactlyElementsIn(input.branches);
+ }
+
+ @Test
+ public void branchesAreTrimmed() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+ input.branches =
+ ImmutableList.of(" refs/heads/master ", " refs/heads/foo/* ", " ^refs/heads/stable-.* ");
+
+ LabelDefinitionInfo createdLabel =
+ gApi.projects().name(project.get()).label("Foo").create(input).get();
+ assertThat(createdLabel.branches)
+ .containsExactly("refs/heads/master", "refs/heads/foo/*", "^refs/heads/stable-.*");
+ }
+
+ @Test
+ public void emptyBranchesAreIgnored() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+ input.branches = ImmutableList.of("refs/heads/master", "", " ");
+
+ LabelDefinitionInfo createdLabel =
+ gApi.projects().name(project.get()).label("Foo").create(input).get();
+ assertThat(createdLabel.branches).containsExactly("refs/heads/master");
+ }
+
+ @Test
+ public void branchesAreAutomaticallyPrefixedWithRefsHeads() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+ input.branches = ImmutableList.of("master", "refs/meta/config");
+
+ LabelDefinitionInfo createdLabel =
+ gApi.projects().name(project.get()).label("Foo").create(input).get();
+ assertThat(createdLabel.branches).containsExactly("refs/heads/master", "refs/meta/config");
+ }
+
+ @Test
+ public void createWithCanOverride() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+ input.canOverride = true;
+
+ LabelDefinitionInfo createdLabel =
+ gApi.projects().name(project.get()).label("foo").create(input).get();
+ assertThat(createdLabel.canOverride).isTrue();
+ }
+
+ @Test
+ public void createWithoutCanOverride() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+ input.canOverride = false;
+
+ LabelDefinitionInfo createdLabel =
+ gApi.projects().name(project.get()).label("foo").create(input).get();
+ assertThat(createdLabel.canOverride).isNull();
+ }
+
+ @Test
+ public void createWithCopyAnyScore() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+ input.copyAnyScore = true;
+
+ LabelDefinitionInfo createdLabel =
+ gApi.projects().name(project.get()).label("foo").create(input).get();
+ assertThat(createdLabel.copyAnyScore).isTrue();
+ }
+
+ @Test
+ public void createWithoutCopyAnyScore() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+ input.copyAnyScore = false;
+
+ LabelDefinitionInfo createdLabel =
+ gApi.projects().name(project.get()).label("foo").create(input).get();
+ assertThat(createdLabel.copyAnyScore).isNull();
+ }
+
+ @Test
+ public void createWithCopyMinScore() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+ input.copyMinScore = true;
+
+ LabelDefinitionInfo createdLabel =
+ gApi.projects().name(project.get()).label("foo").create(input).get();
+ assertThat(createdLabel.copyMinScore).isTrue();
+ }
+
+ @Test
+ public void createWithoutCopyMinScore() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+ input.copyMinScore = false;
+
+ LabelDefinitionInfo createdLabel =
+ gApi.projects().name(project.get()).label("foo").create(input).get();
+ assertThat(createdLabel.copyMinScore).isNull();
+ }
+
+ @Test
+ public void createWithCopyMaxScore() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+ input.copyMaxScore = true;
+
+ LabelDefinitionInfo createdLabel =
+ gApi.projects().name(project.get()).label("foo").create(input).get();
+ assertThat(createdLabel.copyMaxScore).isTrue();
+ }
+
+ @Test
+ public void createWithoutCopyMaxScore() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+ input.copyMaxScore = false;
+
+ LabelDefinitionInfo createdLabel =
+ gApi.projects().name(project.get()).label("foo").create(input).get();
+ assertThat(createdLabel.copyMaxScore).isNull();
+ }
+
+ @Test
+ public void createWithCopyAllScoresIfNoChange() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+ input.copyAllScoresIfNoChange = true;
+
+ LabelDefinitionInfo createdLabel =
+ gApi.projects().name(project.get()).label("foo").create(input).get();
+ assertThat(createdLabel.copyAllScoresIfNoChange).isTrue();
+ }
+
+ @Test
+ public void createWithoutCopyAllScoresIfNoChange() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+ input.copyAllScoresIfNoChange = false;
+
+ LabelDefinitionInfo createdLabel =
+ gApi.projects().name(project.get()).label("foo").create(input).get();
+ assertThat(createdLabel.copyAllScoresIfNoChange).isNull();
+ }
+
+ @Test
+ public void createWithCopyAllScoresIfNoCodeChange() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+ input.copyAllScoresIfNoCodeChange = true;
+
+ LabelDefinitionInfo createdLabel =
+ gApi.projects().name(project.get()).label("foo").create(input).get();
+ assertThat(createdLabel.copyAllScoresIfNoCodeChange).isTrue();
+ }
+
+ @Test
+ public void createWithoutCopyAllScoresIfNoCodeChange() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+ input.copyAllScoresIfNoCodeChange = false;
+
+ LabelDefinitionInfo createdLabel =
+ gApi.projects().name(project.get()).label("foo").create(input).get();
+ assertThat(createdLabel.copyAllScoresIfNoCodeChange).isNull();
+ }
+
+ @Test
+ public void createWithCopyAllScoresOnTrivialRebase() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+ input.copyAllScoresOnTrivialRebase = true;
+
+ LabelDefinitionInfo createdLabel =
+ gApi.projects().name(project.get()).label("foo").create(input).get();
+ assertThat(createdLabel.copyAllScoresOnTrivialRebase).isTrue();
+ }
+
+ @Test
+ public void createWithoutCopyAllScoresOnTrivialRebase() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+ input.copyAllScoresOnTrivialRebase = false;
+
+ LabelDefinitionInfo createdLabel =
+ gApi.projects().name(project.get()).label("foo").create(input).get();
+ assertThat(createdLabel.copyAllScoresOnTrivialRebase).isNull();
+ }
+
+ @Test
+ public void createWithCopyAllScoresOnMergeFirstParentUpdate() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+ input.copyAllScoresOnMergeFirstParentUpdate = true;
+
+ LabelDefinitionInfo createdLabel =
+ gApi.projects().name(project.get()).label("foo").create(input).get();
+ assertThat(createdLabel.copyAllScoresOnMergeFirstParentUpdate).isTrue();
+ }
+
+ @Test
+ public void createWithoutCopyAllScoresOnMergeFirstParentUpdate() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+ input.copyAllScoresOnMergeFirstParentUpdate = false;
+
+ LabelDefinitionInfo createdLabel =
+ gApi.projects().name(project.get()).label("foo").create(input).get();
+ assertThat(createdLabel.copyAllScoresOnMergeFirstParentUpdate).isNull();
+ }
+
+ @Test
+ public void createWithAllowPostSubmit() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+ input.allowPostSubmit = true;
+
+ LabelDefinitionInfo createdLabel =
+ gApi.projects().name(project.get()).label("foo").create(input).get();
+ assertThat(createdLabel.allowPostSubmit).isTrue();
+ }
+
+ @Test
+ public void createWithoutAllowPostSubmit() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+ input.allowPostSubmit = false;
+
+ LabelDefinitionInfo createdLabel =
+ gApi.projects().name(project.get()).label("foo").create(input).get();
+ assertThat(createdLabel.allowPostSubmit).isNull();
+ }
+
+ @Test
+ public void createWithIgnoreSelfApproval() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+ input.ignoreSelfApproval = true;
+
+ LabelDefinitionInfo createdLabel =
+ gApi.projects().name(project.get()).label("foo").create(input).get();
+ assertThat(createdLabel.ignoreSelfApproval).isTrue();
+ }
+
+ @Test
+ public void createWithoutIgnoreSelfApproval() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+ input.ignoreSelfApproval = false;
+
+ LabelDefinitionInfo createdLabel =
+ gApi.projects().name(project.get()).label("foo").create(input).get();
+ assertThat(createdLabel.ignoreSelfApproval).isNull();
+ }
+
+ @Test
+ public void defaultCommitMessage() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+ gApi.projects().name(project.get()).label("Foo").create(input);
+ assertThat(projectOperations.project(project).getHead(RefNames.REFS_CONFIG).getShortMessage())
+ .isEqualTo("Update label");
+ }
+
+ @Test
+ public void withCommitMessage() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+ input.commitMessage = "Add Foo Label";
+ gApi.projects().name(project.get()).label("Foo").create(input);
+ assertThat(projectOperations.project(project).getHead(RefNames.REFS_CONFIG).getShortMessage())
+ .isEqualTo(input.commitMessage);
+ }
+
+ @Test
+ public void commitMessageIsTrimmed() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
+ input.commitMessage = " Add Foo Label ";
+ gApi.projects().name(project.get()).label("Foo").create(input);
+ assertThat(projectOperations.project(project).getHead(RefNames.REFS_CONFIG).getShortMessage())
+ .isEqualTo("Add Foo Label");
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/DeleteLabelIT.java b/javatests/com/google/gerrit/acceptance/rest/project/DeleteLabelIT.java
new file mode 100644
index 0000000..c916285
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/rest/project/DeleteLabelIT.java
@@ -0,0 +1,108 @@
+// 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.acceptance.rest.project;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.inject.Inject;
+import org.junit.Test;
+
+public class DeleteLabelIT extends AbstractDaemonTest {
+ @Inject private RequestScopeOperations requestScopeOperations;
+ @Inject private ProjectOperations projectOperations;
+
+ @Test
+ public void anonymous() throws Exception {
+ requestScopeOperations.setApiUserAnonymous();
+ AuthException thrown =
+ assertThrows(
+ AuthException.class,
+ () -> gApi.projects().name(allProjects.get()).label("Code-Review").delete());
+ assertThat(thrown).hasMessageThat().contains("Authentication required");
+ }
+
+ @Test
+ public void notAllowed() throws Exception {
+ projectOperations
+ .project(allProjects)
+ .forUpdate()
+ .add(allow(Permission.READ).ref(RefNames.REFS_CONFIG).group(REGISTERED_USERS))
+ .update();
+
+ requestScopeOperations.setApiUser(user.id());
+ AuthException thrown =
+ assertThrows(
+ AuthException.class,
+ () -> gApi.projects().name(allProjects.get()).label("Code-Review").delete());
+ assertThat(thrown).hasMessageThat().contains("write refs/meta/config not permitted");
+ }
+
+ @Test
+ public void nonExisting() throws Exception {
+ ResourceNotFoundException thrown =
+ assertThrows(
+ ResourceNotFoundException.class,
+ () -> gApi.projects().name(allProjects.get()).label("Non-Existing-Review").delete());
+ assertThat(thrown).hasMessageThat().contains("Not found: Non-Existing-Review");
+ }
+
+ @Test
+ public void delete() throws Exception {
+ gApi.projects().name(allProjects.get()).label("Code-Review").delete();
+
+ ResourceNotFoundException thrown =
+ assertThrows(
+ ResourceNotFoundException.class,
+ () -> gApi.projects().name(project.get()).label("Code-Review").get());
+ assertThat(thrown).hasMessageThat().contains("Not found: Code-Review");
+ }
+
+ @Test
+ public void defaultCommitMessage() throws Exception {
+ gApi.projects().name(allProjects.get()).label("Code-Review").delete();
+ assertThat(
+ projectOperations.project(allProjects).getHead(RefNames.REFS_CONFIG).getShortMessage())
+ .isEqualTo("Delete label");
+ }
+
+ @Test
+ public void withCommitMessage() throws Exception {
+ gApi.projects().name(allProjects.get()).label("Code-Review").delete("Delete Code-Review label");
+ assertThat(
+ projectOperations.project(allProjects).getHead(RefNames.REFS_CONFIG).getShortMessage())
+ .isEqualTo("Delete Code-Review label");
+ }
+
+ @Test
+ public void commitMessageIsTrimmed() throws Exception {
+ gApi.projects()
+ .name(allProjects.get())
+ .label("Code-Review")
+ .delete(" Delete Code-Review label ");
+ assertThat(
+ projectOperations.project(allProjects).getHead(RefNames.REFS_CONFIG).getShortMessage())
+ .isEqualTo("Delete Code-Review label");
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/GetLabelIT.java b/javatests/com/google/gerrit/acceptance/rest/project/GetLabelIT.java
new file mode 100644
index 0000000..9f98490
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/rest/project/GetLabelIT.java
@@ -0,0 +1,154 @@
+// 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.acceptance.rest.project;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
+import com.google.gerrit.common.data.LabelFunction;
+import com.google.gerrit.common.data.LabelType;
+import com.google.gerrit.extensions.common.LabelDefinitionInfo;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.inject.Inject;
+import org.junit.Test;
+
+@NoHttpd
+public class GetLabelIT extends AbstractDaemonTest {
+ @Inject private RequestScopeOperations requestScopeOperations;
+
+ @Test
+ public void anonymous() throws Exception {
+ requestScopeOperations.setApiUserAnonymous();
+ AuthException thrown =
+ assertThrows(
+ AuthException.class,
+ () -> gApi.projects().name(allProjects.get()).label("Code-Review").get());
+ assertThat(thrown).hasMessageThat().contains("Authentication required");
+ }
+
+ @Test
+ public void notAllowed() throws Exception {
+ requestScopeOperations.setApiUser(user.id());
+ AuthException thrown =
+ assertThrows(
+ AuthException.class,
+ () -> gApi.projects().name(allProjects.get()).label("Code-Review").get());
+ assertThat(thrown).hasMessageThat().contains("read refs/meta/config not permitted");
+ }
+
+ @Test
+ public void notFound() throws Exception {
+ ResourceNotFoundException thrown =
+ assertThrows(
+ ResourceNotFoundException.class,
+ () -> gApi.projects().name(project.get()).label("Foo-Review").get());
+ assertThat(thrown).hasMessageThat().contains("Not found: Foo-Review");
+ }
+
+ @Test
+ public void allProjectsCodeReviewLabel() throws Exception {
+ LabelDefinitionInfo codeReviewLabel =
+ gApi.projects().name(allProjects.get()).label("Code-Review").get();
+ LabelAssert.assertCodeReviewLabel(codeReviewLabel);
+ }
+
+ @Test
+ public void labelWithDefaultValue() throws Exception {
+ configLabel("foo", LabelFunction.NO_OP);
+
+ // set default value
+ try (ProjectConfigUpdate u = updateProject(project)) {
+ LabelType labelType = u.getConfig().getLabelSections().get("foo");
+ labelType.setDefaultValue((short) 1);
+ u.getConfig().getLabelSections().put(labelType.getName(), labelType);
+ u.save();
+ }
+
+ LabelDefinitionInfo fooLabel = gApi.projects().name(project.get()).label("foo").get();
+ assertThat(fooLabel.defaultValue).isEqualTo(1);
+ }
+
+ @Test
+ public void labelLimitedToBranches() throws Exception {
+ configLabel(
+ "foo", LabelFunction.NO_OP, ImmutableList.of("refs/heads/master", "^refs/heads/stable-.*"));
+
+ LabelDefinitionInfo fooLabel = gApi.projects().name(project.get()).label("foo").get();
+ assertThat(fooLabel.branches).containsExactly("refs/heads/master", "^refs/heads/stable-.*");
+ }
+
+ @Test
+ public void labelWithoutRules() throws Exception {
+ configLabel("foo", LabelFunction.NO_OP);
+
+ // unset rules which are enabled by default
+ try (ProjectConfigUpdate u = updateProject(project)) {
+ LabelType labelType = u.getConfig().getLabelSections().get("foo");
+ labelType.setCanOverride(false);
+ labelType.setCopyAllScoresIfNoChange(false);
+ labelType.setAllowPostSubmit(false);
+ u.getConfig().getLabelSections().put(labelType.getName(), labelType);
+ u.save();
+ }
+
+ LabelDefinitionInfo fooLabel = gApi.projects().name(project.get()).label("foo").get();
+ assertThat(fooLabel.canOverride).isNull();
+ assertThat(fooLabel.copyAnyScore).isNull();
+ assertThat(fooLabel.copyMinScore).isNull();
+ assertThat(fooLabel.copyMaxScore).isNull();
+ assertThat(fooLabel.copyAllScoresIfNoChange).isNull();
+ assertThat(fooLabel.copyAllScoresIfNoCodeChange).isNull();
+ assertThat(fooLabel.copyAllScoresOnTrivialRebase).isNull();
+ assertThat(fooLabel.copyAllScoresOnMergeFirstParentUpdate).isNull();
+ assertThat(fooLabel.allowPostSubmit).isNull();
+ assertThat(fooLabel.ignoreSelfApproval).isNull();
+ }
+
+ @Test
+ public void labelWithAllRules() throws Exception {
+ configLabel("foo", LabelFunction.NO_OP);
+
+ // set rules which are not enabled by default
+ try (ProjectConfigUpdate u = updateProject(project)) {
+ LabelType labelType = u.getConfig().getLabelSections().get("foo");
+ labelType.setCopyAnyScore(true);
+ labelType.setCopyMinScore(true);
+ labelType.setCopyMaxScore(true);
+ labelType.setCopyAllScoresIfNoCodeChange(true);
+ labelType.setCopyAllScoresOnTrivialRebase(true);
+ labelType.setCopyAllScoresOnMergeFirstParentUpdate(true);
+ labelType.setIgnoreSelfApproval(true);
+ u.getConfig().getLabelSections().put(labelType.getName(), labelType);
+ u.save();
+ }
+
+ LabelDefinitionInfo fooLabel = gApi.projects().name(project.get()).label("foo").get();
+ assertThat(fooLabel.canOverride).isTrue();
+ assertThat(fooLabel.copyAnyScore).isTrue();
+ assertThat(fooLabel.copyMinScore).isTrue();
+ assertThat(fooLabel.copyMaxScore).isTrue();
+ assertThat(fooLabel.copyAllScoresIfNoChange).isTrue();
+ assertThat(fooLabel.copyAllScoresIfNoCodeChange).isTrue();
+ assertThat(fooLabel.copyAllScoresOnTrivialRebase).isTrue();
+ assertThat(fooLabel.copyAllScoresOnMergeFirstParentUpdate).isTrue();
+ assertThat(fooLabel.allowPostSubmit).isTrue();
+ assertThat(fooLabel.ignoreSelfApproval).isTrue();
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/LabelAssert.java b/javatests/com/google/gerrit/acceptance/rest/project/LabelAssert.java
new file mode 100644
index 0000000..7998ecb
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/rest/project/LabelAssert.java
@@ -0,0 +1,55 @@
+// 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.acceptance.rest.project;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.common.data.LabelFunction;
+import com.google.gerrit.extensions.common.LabelDefinitionInfo;
+import com.google.gerrit.server.config.AllProjectsNameProvider;
+
+public class LabelAssert {
+ public static void assertCodeReviewLabel(LabelDefinitionInfo codeReviewLabel) {
+ assertThat(codeReviewLabel.name).isEqualTo("Code-Review");
+ assertThat(codeReviewLabel.projectName).isEqualTo(AllProjectsNameProvider.DEFAULT);
+ assertThat(codeReviewLabel.function).isEqualTo(LabelFunction.MAX_WITH_BLOCK.getFunctionName());
+ assertThat(codeReviewLabel.values)
+ .containsExactly(
+ "+2",
+ "Looks good to me, approved",
+ "+1",
+ "Looks good to me, but someone else must approve",
+ " 0",
+ "No score",
+ "-1",
+ "I would prefer this is not merged as is",
+ "-2",
+ "This shall not be merged");
+ assertThat(codeReviewLabel.defaultValue).isEqualTo(0);
+ assertThat(codeReviewLabel.branches).isNull();
+ assertThat(codeReviewLabel.canOverride).isTrue();
+ assertThat(codeReviewLabel.copyAnyScore).isNull();
+ assertThat(codeReviewLabel.copyMinScore).isTrue();
+ assertThat(codeReviewLabel.copyMaxScore).isNull();
+ assertThat(codeReviewLabel.copyAllScoresIfNoChange).isTrue();
+ assertThat(codeReviewLabel.copyAllScoresIfNoCodeChange).isNull();
+ assertThat(codeReviewLabel.copyAllScoresOnTrivialRebase).isTrue();
+ assertThat(codeReviewLabel.copyAllScoresOnMergeFirstParentUpdate).isNull();
+ assertThat(codeReviewLabel.allowPostSubmit).isTrue();
+ assertThat(codeReviewLabel.ignoreSelfApproval).isNull();
+ }
+
+ private LabelAssert() {}
+}
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/ListLabelsIT.java b/javatests/com/google/gerrit/acceptance/rest/project/ListLabelsIT.java
new file mode 100644
index 0000000..d2539e5
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/rest/project/ListLabelsIT.java
@@ -0,0 +1,268 @@
+// 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.acceptance.rest.project;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+import static java.util.stream.Collectors.toList;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
+import com.google.gerrit.common.data.LabelFunction;
+import com.google.gerrit.common.data.LabelType;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.extensions.common.LabelDefinitionInfo;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.inject.Inject;
+import java.util.List;
+import org.junit.Test;
+
+@NoHttpd
+public class ListLabelsIT extends AbstractDaemonTest {
+ @Inject private RequestScopeOperations requestScopeOperations;
+ @Inject private ProjectOperations projectOperations;
+
+ @Test
+ public void anonymous() throws Exception {
+ requestScopeOperations.setApiUserAnonymous();
+ AuthException thrown =
+ assertThrows(AuthException.class, () -> gApi.projects().name(project.get()).labels().get());
+ assertThat(thrown).hasMessageThat().contains("Authentication required");
+ }
+
+ @Test
+ public void notAllowed() throws Exception {
+ requestScopeOperations.setApiUser(user.id());
+ AuthException thrown =
+ assertThrows(AuthException.class, () -> gApi.projects().name(project.get()).labels().get());
+ assertThat(thrown).hasMessageThat().contains("read refs/meta/config not permitted");
+ }
+
+ @Test
+ public void noLabels() throws Exception {
+ assertThat(gApi.projects().name(project.get()).labels().get()).isEmpty();
+ }
+
+ @Test
+ public void allProjectsLabels() throws Exception {
+ List<LabelDefinitionInfo> labels = gApi.projects().name(allProjects.get()).labels().get();
+ assertThat(labelNames(labels)).containsExactly("Code-Review");
+
+ LabelDefinitionInfo codeReviewLabel = Iterables.getOnlyElement(labels);
+ LabelAssert.assertCodeReviewLabel(codeReviewLabel);
+ }
+
+ @Test
+ public void labelsAreSortedByName() throws Exception {
+ configLabel("foo", LabelFunction.NO_OP);
+ configLabel("bar", LabelFunction.NO_OP);
+ configLabel("baz", LabelFunction.NO_OP);
+
+ List<LabelDefinitionInfo> labels = gApi.projects().name(project.get()).labels().get();
+ assertThat(labelNames(labels)).containsExactly("bar", "baz", "foo").inOrder();
+ }
+
+ @Test
+ public void labelWithDefaultValue() throws Exception {
+ configLabel("foo", LabelFunction.NO_OP);
+
+ // set default value
+ try (ProjectConfigUpdate u = updateProject(project)) {
+ LabelType labelType = u.getConfig().getLabelSections().get("foo");
+ labelType.setDefaultValue((short) 1);
+ u.getConfig().getLabelSections().put(labelType.getName(), labelType);
+ u.save();
+ }
+
+ List<LabelDefinitionInfo> labels = gApi.projects().name(project.get()).labels().get();
+ assertThat(labelNames(labels)).containsExactly("foo");
+
+ LabelDefinitionInfo fooLabel = Iterables.getOnlyElement(labels);
+ assertThat(fooLabel.defaultValue).isEqualTo(1);
+ }
+
+ @Test
+ public void labelLimitedToBranches() throws Exception {
+ configLabel(
+ "foo", LabelFunction.NO_OP, ImmutableList.of("refs/heads/master", "^refs/heads/stable-.*"));
+
+ List<LabelDefinitionInfo> labels = gApi.projects().name(project.get()).labels().get();
+ assertThat(labelNames(labels)).containsExactly("foo");
+
+ LabelDefinitionInfo fooLabel = Iterables.getOnlyElement(labels);
+ assertThat(fooLabel.branches).containsExactly("refs/heads/master", "^refs/heads/stable-.*");
+ }
+
+ @Test
+ public void labelWithoutRules() throws Exception {
+ configLabel("foo", LabelFunction.NO_OP);
+
+ // unset rules which are enabled by default
+ try (ProjectConfigUpdate u = updateProject(project)) {
+ LabelType labelType = u.getConfig().getLabelSections().get("foo");
+ labelType.setCanOverride(false);
+ labelType.setCopyAllScoresIfNoChange(false);
+ labelType.setAllowPostSubmit(false);
+ u.getConfig().getLabelSections().put(labelType.getName(), labelType);
+ u.save();
+ }
+
+ List<LabelDefinitionInfo> labels = gApi.projects().name(project.get()).labels().get();
+ assertThat(labelNames(labels)).containsExactly("foo");
+
+ LabelDefinitionInfo fooLabel = Iterables.getOnlyElement(labels);
+ assertThat(fooLabel.canOverride).isNull();
+ assertThat(fooLabel.copyAnyScore).isNull();
+ assertThat(fooLabel.copyMinScore).isNull();
+ assertThat(fooLabel.copyMaxScore).isNull();
+ assertThat(fooLabel.copyAllScoresIfNoChange).isNull();
+ assertThat(fooLabel.copyAllScoresIfNoCodeChange).isNull();
+ assertThat(fooLabel.copyAllScoresOnTrivialRebase).isNull();
+ assertThat(fooLabel.copyAllScoresOnMergeFirstParentUpdate).isNull();
+ assertThat(fooLabel.allowPostSubmit).isNull();
+ assertThat(fooLabel.ignoreSelfApproval).isNull();
+ }
+
+ @Test
+ public void labelWithAllRules() throws Exception {
+ configLabel("foo", LabelFunction.NO_OP);
+
+ // set rules which are not enabled by default
+ try (ProjectConfigUpdate u = updateProject(project)) {
+ LabelType labelType = u.getConfig().getLabelSections().get("foo");
+ labelType.setCopyAnyScore(true);
+ labelType.setCopyMinScore(true);
+ labelType.setCopyMaxScore(true);
+ labelType.setCopyAllScoresIfNoCodeChange(true);
+ labelType.setCopyAllScoresOnTrivialRebase(true);
+ labelType.setCopyAllScoresOnMergeFirstParentUpdate(true);
+ labelType.setIgnoreSelfApproval(true);
+ u.getConfig().getLabelSections().put(labelType.getName(), labelType);
+ u.save();
+ }
+
+ List<LabelDefinitionInfo> labels = gApi.projects().name(project.get()).labels().get();
+ assertThat(labelNames(labels)).containsExactly("foo");
+
+ LabelDefinitionInfo fooLabel = Iterables.getOnlyElement(labels);
+ assertThat(fooLabel.canOverride).isTrue();
+ assertThat(fooLabel.copyAnyScore).isTrue();
+ assertThat(fooLabel.copyMinScore).isTrue();
+ assertThat(fooLabel.copyMaxScore).isTrue();
+ assertThat(fooLabel.copyAllScoresIfNoChange).isTrue();
+ assertThat(fooLabel.copyAllScoresIfNoCodeChange).isTrue();
+ assertThat(fooLabel.copyAllScoresOnTrivialRebase).isTrue();
+ assertThat(fooLabel.copyAllScoresOnMergeFirstParentUpdate).isTrue();
+ assertThat(fooLabel.allowPostSubmit).isTrue();
+ assertThat(fooLabel.ignoreSelfApproval).isTrue();
+ }
+
+ @Test
+ public void withInheritedLabelsNotAllowed() throws Exception {
+ projectOperations
+ .project(project)
+ .forUpdate()
+ .add(allow(Permission.READ).ref(RefNames.REFS_CONFIG).group(REGISTERED_USERS))
+ .update();
+
+ requestScopeOperations.setApiUser(user.id());
+
+ // can list labels without inheritance
+ gApi.projects().name(project.get()).labels().get();
+
+ // cannot list labels with inheritance
+ AuthException thrown =
+ assertThrows(
+ AuthException.class,
+ () -> gApi.projects().name(project.get()).labels().withInherited(true).get());
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("All-Projects: read refs/meta/config not permitted");
+ }
+
+ @Test
+ public void inheritedLabelsOnly() throws Exception {
+ List<LabelDefinitionInfo> labels =
+ gApi.projects().name(project.get()).labels().withInherited(true).get();
+ assertThat(labelNames(labels)).containsExactly("Code-Review");
+
+ LabelDefinitionInfo codeReviewLabel = Iterables.getOnlyElement(labels);
+ LabelAssert.assertCodeReviewLabel(codeReviewLabel);
+ }
+
+ @Test
+ public void withInheritedLabels() throws Exception {
+ configLabel("foo", LabelFunction.NO_OP);
+ configLabel("bar", LabelFunction.NO_OP);
+ configLabel("baz", LabelFunction.NO_OP);
+
+ List<LabelDefinitionInfo> labels =
+ gApi.projects().name(project.get()).labels().withInherited(true).get();
+ assertThat(labelNames(labels)).containsExactly("Code-Review", "bar", "baz", "foo").inOrder();
+
+ LabelAssert.assertCodeReviewLabel(labels.get(0));
+ assertThat(labels.get(1).name).isEqualTo("bar");
+ assertThat(labels.get(1).projectName).isEqualTo(project.get());
+ assertThat(labels.get(2).name).isEqualTo("baz");
+ assertThat(labels.get(2).projectName).isEqualTo(project.get());
+ assertThat(labels.get(3).name).isEqualTo("foo");
+ assertThat(labels.get(3).projectName).isEqualTo(project.get());
+ }
+
+ @Test
+ public void withInheritedLabelsAndOverriddenLabel() throws Exception {
+ configLabel("Code-Review", LabelFunction.NO_OP);
+
+ List<LabelDefinitionInfo> labels =
+ gApi.projects().name(project.get()).labels().withInherited(true).get();
+ assertThat(labelNames(labels)).containsExactly("Code-Review", "Code-Review");
+
+ LabelAssert.assertCodeReviewLabel(labels.get(0));
+ assertThat(labels.get(1).name).isEqualTo("Code-Review");
+ assertThat(labels.get(1).projectName).isEqualTo(project.get());
+ assertThat(labels.get(1).function).isEqualTo(LabelFunction.NO_OP.getFunctionName());
+ }
+
+ @Test
+ public void withInheritedLabelsFromMultipleParents() throws Exception {
+ configLabel(project, "foo", LabelFunction.NO_OP);
+
+ Project.NameKey childProject =
+ projectOperations.newProject().name("child").parent(project).create();
+ configLabel(childProject, "bar", LabelFunction.NO_OP);
+
+ List<LabelDefinitionInfo> labels =
+ gApi.projects().name(childProject.get()).labels().withInherited(true).get();
+ assertThat(labelNames(labels)).containsExactly("Code-Review", "foo", "bar").inOrder();
+
+ LabelAssert.assertCodeReviewLabel(labels.get(0));
+ assertThat(labels.get(1).name).isEqualTo("foo");
+ assertThat(labels.get(1).projectName).isEqualTo(project.get());
+ assertThat(labels.get(2).name).isEqualTo("bar");
+ assertThat(labels.get(2).projectName).isEqualTo(childProject.get());
+ }
+
+ private static List<String> labelNames(List<LabelDefinitionInfo> labels) {
+ return labels.stream().map(l -> l.name).collect(toList());
+ }
+}
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/SetLabelIT.java b/javatests/com/google/gerrit/acceptance/rest/project/SetLabelIT.java
new file mode 100644
index 0000000..9cba930
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/rest/project/SetLabelIT.java
@@ -0,0 +1,880 @@
+// 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.acceptance.rest.project;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
+import com.google.gerrit.common.data.LabelFunction;
+import com.google.gerrit.common.data.LabelType;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.entities.RefNames;
+import com.google.gerrit.extensions.common.LabelDefinitionInfo;
+import com.google.gerrit.extensions.common.LabelDefinitionInput;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.inject.Inject;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.Test;
+
+@NoHttpd
+public class SetLabelIT extends AbstractDaemonTest {
+ @Inject private RequestScopeOperations requestScopeOperations;
+ @Inject private ProjectOperations projectOperations;
+
+ @Test
+ public void anonymous() throws Exception {
+ requestScopeOperations.setApiUserAnonymous();
+ AuthException thrown =
+ assertThrows(
+ AuthException.class,
+ () ->
+ gApi.projects()
+ .name(allProjects.get())
+ .label("Code-Review")
+ .update(new LabelDefinitionInput()));
+ assertThat(thrown).hasMessageThat().contains("Authentication required");
+ }
+
+ @Test
+ public void notAllowed() throws Exception {
+ projectOperations
+ .project(allProjects)
+ .forUpdate()
+ .add(allow(Permission.READ).ref(RefNames.REFS_CONFIG).group(REGISTERED_USERS))
+ .update();
+
+ requestScopeOperations.setApiUser(user.id());
+ AuthException thrown =
+ assertThrows(
+ AuthException.class,
+ () ->
+ gApi.projects()
+ .name(allProjects.get())
+ .label("Code-Review")
+ .update(new LabelDefinitionInput()));
+ assertThat(thrown).hasMessageThat().contains("write refs/meta/config not permitted");
+ }
+
+ @Test
+ public void updateName() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.name = "Foo-Review";
+
+ LabelDefinitionInfo updatedLabel =
+ gApi.projects().name(allProjects.get()).label("Code-Review").update(input);
+ assertThat(updatedLabel.name).isEqualTo(input.name);
+
+ assertThat(gApi.projects().name(allProjects.get()).label("Foo-Review").get()).isNotNull();
+ assertThrows(
+ ResourceNotFoundException.class,
+ () -> gApi.projects().name(allProjects.get()).label("Code-Review").get());
+ }
+
+ @Test
+ public void nameIsTrimmed() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.name = " Foo-Review ";
+
+ LabelDefinitionInfo updatedLabel =
+ gApi.projects().name(allProjects.get()).label("Code-Review").update(input);
+ assertThat(updatedLabel.name).isEqualTo("Foo-Review");
+
+ assertThat(gApi.projects().name(allProjects.get()).label("Foo-Review").get()).isNotNull();
+ assertThrows(
+ ResourceNotFoundException.class,
+ () -> gApi.projects().name(allProjects.get()).label("Code-Review").get());
+ }
+
+ @Test
+ public void cannotSetEmptyName() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.name = "";
+
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.projects().name(allProjects.get()).label("Code-Review").update(input));
+ assertThat(thrown).hasMessageThat().contains("name cannot be empty");
+ }
+
+ @Test
+ public void cannotSetInvalidName() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.name = "INVALID_NAME";
+
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.projects().name(allProjects.get()).label("Code-Review").update(input));
+ assertThat(thrown).hasMessageThat().contains("invalid name: " + input.name);
+ }
+
+ @Test
+ public void cannotSetNameIfNameClashes() throws Exception {
+ configLabel("Foo-Review", LabelFunction.NO_OP);
+ configLabel("Bar-Review", LabelFunction.NO_OP);
+
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.name = "Bar-Review";
+
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.projects().name(project.get()).label("Foo-Review").update(input));
+ assertThat(thrown).hasMessageThat().contains("name " + input.name + " already in use");
+ }
+
+ @Test
+ public void cannotSetNameIfNameConflicts() throws Exception {
+ configLabel("Foo-Review", LabelFunction.NO_OP);
+ configLabel("Bar-Review", LabelFunction.NO_OP);
+
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.name = "bar-review";
+
+ ResourceConflictException thrown =
+ assertThrows(
+ ResourceConflictException.class,
+ () -> gApi.projects().name(project.get()).label("Foo-Review").update(input));
+ assertThat(thrown)
+ .hasMessageThat()
+ .contains("name bar-review conflicts with existing label Bar-Review");
+ }
+
+ @Test
+ public void updateFunction() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.function = LabelFunction.NO_OP.getFunctionName();
+
+ LabelDefinitionInfo updatedLabel =
+ gApi.projects().name(allProjects.get()).label("Code-Review").update(input);
+ assertThat(updatedLabel.function).isEqualTo(input.function);
+
+ assertThat(gApi.projects().name(allProjects.get()).label("Code-Review").get().function)
+ .isEqualTo(input.function);
+ }
+
+ @Test
+ public void functionIsTrimmed() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.function = " " + LabelFunction.NO_OP.getFunctionName() + " ";
+
+ LabelDefinitionInfo updatedLabel =
+ gApi.projects().name(allProjects.get()).label("Code-Review").update(input);
+ assertThat(updatedLabel.function).isEqualTo(LabelFunction.NO_OP.getFunctionName());
+
+ assertThat(gApi.projects().name(allProjects.get()).label("Code-Review").get().function)
+ .isEqualTo(LabelFunction.NO_OP.getFunctionName());
+ }
+
+ @Test
+ public void cannotSetEmptyFunction() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.function = "";
+
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.projects().name(allProjects.get()).label("Code-Review").update(input));
+ assertThat(thrown).hasMessageThat().contains("function cannot be empty");
+ }
+
+ @Test
+ public void cannotSetUnknownFunction() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.function = "UnknownFunction";
+
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.projects().name(allProjects.get()).label("Code-Review").update(input));
+ assertThat(thrown).hasMessageThat().contains("unknown function: " + input.function);
+ }
+
+ @Test
+ public void cannotSetEmptyValues() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.values = ImmutableMap.of();
+
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.projects().name(allProjects.get()).label("Code-Review").update(input));
+ assertThat(thrown).hasMessageThat().contains("values cannot be empty");
+ }
+
+ @Test
+ public void updateValues() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ // Positive values can be specified as '<value>' or '+<value>'.
+ input.values =
+ ImmutableMap.of(
+ "2",
+ "Looks Very Good",
+ "+1",
+ "Looks Good",
+ "0",
+ "Don't Know",
+ "-1",
+ "Looks Bad",
+ "-2",
+ "Looks Very Bad");
+
+ LabelDefinitionInfo updatedLabel =
+ gApi.projects().name(allProjects.get()).label("Code-Review").update(input);
+ assertThat(updatedLabel.values)
+ .containsExactly(
+ "+2", "Looks Very Good",
+ "+1", "Looks Good",
+ " 0", "Don't Know",
+ "-1", "Looks Bad",
+ "-2", "Looks Very Bad");
+
+ assertThat(gApi.projects().name(allProjects.get()).label("Code-Review").get().values)
+ .containsExactly(
+ "+2", "Looks Very Good",
+ "+1", "Looks Good",
+ " 0", "Don't Know",
+ "-1", "Looks Bad",
+ "-2", "Looks Very Bad");
+ }
+
+ @Test
+ public void valuesAndDescriptionsAreTrimmed() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ // Positive values can be specified as '<value>' or '+<value>'.
+ input.values =
+ ImmutableMap.of(
+ " 2 ",
+ " Looks Very Good ",
+ " +1 ",
+ " Looks Good ",
+ " 0 ",
+ " Don't Know ",
+ " -1 ",
+ " Looks Bad ",
+ " -2 ",
+ " Looks Very Bad ");
+
+ LabelDefinitionInfo updatedLabel =
+ gApi.projects().name(allProjects.get()).label("Code-Review").update(input);
+ assertThat(updatedLabel.values)
+ .containsExactly(
+ "+2", "Looks Very Good",
+ "+1", "Looks Good",
+ " 0", "Don't Know",
+ "-1", "Looks Bad",
+ "-2", "Looks Very Bad");
+
+ assertThat(gApi.projects().name(allProjects.get()).label("Code-Review").get().values)
+ .containsExactly(
+ "+2", "Looks Very Good",
+ "+1", "Looks Good",
+ " 0", "Don't Know",
+ "-1", "Looks Bad",
+ "-2", "Looks Very Bad");
+ }
+
+ @Test
+ public void cannotSetInvalidValues() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.values = ImmutableMap.of("invalidValue", "description");
+
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.projects().name(allProjects.get()).label("Code-Review").update(input));
+ assertThat(thrown).hasMessageThat().contains("invalid value: invalidValue");
+ }
+
+ @Test
+ public void cannotSetValueWithEmptyDescription() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.values = ImmutableMap.of("+1", "");
+
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.projects().name(allProjects.get()).label("Code-Review").update(input));
+ assertThat(thrown).hasMessageThat().contains("description for value '+1' cannot be empty");
+ }
+
+ @Test
+ public void updateDefaultValue() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.defaultValue = 1;
+
+ LabelDefinitionInfo updatedLabel =
+ gApi.projects().name(allProjects.get()).label("Code-Review").update(input);
+ assertThat(updatedLabel.defaultValue).isEqualTo(input.defaultValue);
+
+ assertThat(gApi.projects().name(allProjects.get()).label("Code-Review").get().defaultValue)
+ .isEqualTo(input.defaultValue);
+ }
+
+ @Test
+ public void cannotSetInvalidDefaultValue() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.defaultValue = 5;
+
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.projects().name(allProjects.get()).label("Code-Review").update(input));
+ assertThat(thrown).hasMessageThat().contains("invalid default value: " + input.defaultValue);
+ }
+
+ @Test
+ public void updateBranches() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ // Branches can be full ref, ref pattern or regular expression.
+ input.branches =
+ ImmutableList.of("refs/heads/master", "refs/heads/foo/*", "^refs/heads/stable-.*");
+
+ LabelDefinitionInfo updatedLabel =
+ gApi.projects().name(allProjects.get()).label("Code-Review").update(input);
+ assertThat(updatedLabel.branches).containsExactlyElementsIn(input.branches);
+
+ assertThat(gApi.projects().name(allProjects.get()).label("Code-Review").get().branches)
+ .containsExactlyElementsIn(input.branches);
+ }
+
+ @Test
+ public void branchesAreTrimmed() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.branches =
+ ImmutableList.of(" refs/heads/master ", " refs/heads/foo/* ", " ^refs/heads/stable-.* ");
+
+ LabelDefinitionInfo updatedLabel =
+ gApi.projects().name(allProjects.get()).label("Code-Review").update(input);
+ assertThat(updatedLabel.branches)
+ .containsExactly("refs/heads/master", "refs/heads/foo/*", "^refs/heads/stable-.*");
+
+ assertThat(gApi.projects().name(allProjects.get()).label("Code-Review").get().branches)
+ .containsExactly("refs/heads/master", "refs/heads/foo/*", "^refs/heads/stable-.*");
+ }
+
+ @Test
+ public void emptyBranchesAreIgnored() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.branches = ImmutableList.of("refs/heads/master", "", " ");
+
+ LabelDefinitionInfo updatedLabel =
+ gApi.projects().name(allProjects.get()).label("Code-Review").update(input);
+ assertThat(updatedLabel.branches).containsExactly("refs/heads/master");
+
+ assertThat(gApi.projects().name(allProjects.get()).label("Code-Review").get().branches)
+ .containsExactly("refs/heads/master");
+ }
+
+ @Test
+ public void branchesCanBeUnset() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.branches = ImmutableList.of("refs/heads/master");
+ gApi.projects().name(allProjects.get()).label("Code-Review").update(input);
+ assertThat(gApi.projects().name(allProjects.get()).label("Code-Review").get().branches)
+ .isNotNull();
+
+ input.branches = ImmutableList.of();
+ LabelDefinitionInfo updatedLabel =
+ gApi.projects().name(allProjects.get()).label("Code-Review").update(input);
+ assertThat(updatedLabel.branches).isNull();
+ assertThat(gApi.projects().name(allProjects.get()).label("Code-Review").get().branches)
+ .isNull();
+ }
+
+ @Test
+ public void cannotSetInvalidBranch() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.branches = ImmutableList.of("refs heads master");
+
+ BadRequestException thrown =
+ assertThrows(
+ BadRequestException.class,
+ () -> gApi.projects().name(allProjects.get()).label("Code-Review").update(input));
+ assertThat(thrown).hasMessageThat().contains("invalid branch: refs heads master");
+ }
+
+ @Test
+ public void branchesAreAutomaticallyPrefixedWithRefsHeads() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.branches = ImmutableList.of("master", "refs/meta/config");
+
+ LabelDefinitionInfo updatedLabel =
+ gApi.projects().name(allProjects.get()).label("Code-Review").update(input);
+ assertThat(updatedLabel.branches).containsExactly("refs/heads/master", "refs/meta/config");
+
+ assertThat(gApi.projects().name(allProjects.get()).label("Code-Review").get().branches)
+ .containsExactly("refs/heads/master", "refs/meta/config");
+ }
+
+ @Test
+ public void setCanOverride() throws Exception {
+ configLabel("foo", LabelFunction.NO_OP);
+ try (ProjectConfigUpdate u = updateProject(project)) {
+ LabelType labelType = u.getConfig().getLabelSections().get("foo");
+ labelType.setCanOverride(false);
+ u.getConfig().getLabelSections().put(labelType.getName(), labelType);
+ u.save();
+ }
+ assertThat(gApi.projects().name(project.get()).label("foo").get().canOverride).isNull();
+
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.canOverride = true;
+
+ LabelDefinitionInfo updatedLabel =
+ gApi.projects().name(project.get()).label("foo").update(input);
+ assertThat(updatedLabel.canOverride).isTrue();
+
+ assertThat(gApi.projects().name(project.get()).label("foo").get().canOverride).isTrue();
+ }
+
+ @Test
+ public void unsetCanOverride() throws Exception {
+ configLabel("foo", LabelFunction.NO_OP);
+ assertThat(gApi.projects().name(project.get()).label("foo").get().canOverride).isTrue();
+
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.canOverride = false;
+
+ LabelDefinitionInfo updatedLabel =
+ gApi.projects().name(project.get()).label("foo").update(input);
+ assertThat(updatedLabel.canOverride).isNull();
+
+ assertThat(gApi.projects().name(project.get()).label("foo").get().canOverride).isNull();
+ }
+
+ @Test
+ public void setCopyAnyScore() throws Exception {
+ configLabel("foo", LabelFunction.NO_OP);
+ assertThat(gApi.projects().name(project.get()).label("foo").get().copyAnyScore).isNull();
+
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.copyAnyScore = true;
+
+ LabelDefinitionInfo updatedLabel =
+ gApi.projects().name(project.get()).label("foo").update(input);
+ assertThat(updatedLabel.copyAnyScore).isTrue();
+
+ assertThat(gApi.projects().name(project.get()).label("foo").get().copyAnyScore).isTrue();
+ }
+
+ @Test
+ public void unsetCopyAnyScore() throws Exception {
+ configLabel("foo", LabelFunction.NO_OP);
+ try (ProjectConfigUpdate u = updateProject(project)) {
+ LabelType labelType = u.getConfig().getLabelSections().get("foo");
+ labelType.setCopyAnyScore(true);
+ u.getConfig().getLabelSections().put(labelType.getName(), labelType);
+ u.save();
+ }
+ assertThat(gApi.projects().name(project.get()).label("foo").get().copyAnyScore).isTrue();
+
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.copyAnyScore = false;
+
+ LabelDefinitionInfo updatedLabel =
+ gApi.projects().name(project.get()).label("foo").update(input);
+ assertThat(updatedLabel.copyAnyScore).isNull();
+
+ assertThat(gApi.projects().name(project.get()).label("foo").get().copyAnyScore).isNull();
+ }
+
+ @Test
+ public void setCopyMinScore() throws Exception {
+ configLabel("foo", LabelFunction.NO_OP);
+ assertThat(gApi.projects().name(project.get()).label("foo").get().copyMinScore).isNull();
+
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.copyMinScore = true;
+
+ LabelDefinitionInfo updatedLabel =
+ gApi.projects().name(project.get()).label("foo").update(input);
+ assertThat(updatedLabel.copyMinScore).isTrue();
+
+ assertThat(gApi.projects().name(project.get()).label("foo").get().copyMinScore).isTrue();
+ }
+
+ @Test
+ public void unsetCopyMinScore() throws Exception {
+ configLabel("foo", LabelFunction.NO_OP);
+ try (ProjectConfigUpdate u = updateProject(project)) {
+ LabelType labelType = u.getConfig().getLabelSections().get("foo");
+ labelType.setCopyMinScore(true);
+ u.getConfig().getLabelSections().put(labelType.getName(), labelType);
+ u.save();
+ }
+ assertThat(gApi.projects().name(project.get()).label("foo").get().copyMinScore).isTrue();
+
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.copyMinScore = false;
+
+ LabelDefinitionInfo updatedLabel =
+ gApi.projects().name(project.get()).label("foo").update(input);
+ assertThat(updatedLabel.copyMinScore).isNull();
+
+ assertThat(gApi.projects().name(project.get()).label("foo").get().copyMinScore).isNull();
+ }
+
+ @Test
+ public void setCopyMaxScore() throws Exception {
+ configLabel("foo", LabelFunction.NO_OP);
+ assertThat(gApi.projects().name(project.get()).label("foo").get().copyMaxScore).isNull();
+
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.copyMaxScore = true;
+
+ LabelDefinitionInfo updatedLabel =
+ gApi.projects().name(project.get()).label("foo").update(input);
+ assertThat(updatedLabel.copyMaxScore).isTrue();
+
+ assertThat(gApi.projects().name(project.get()).label("foo").get().copyMaxScore).isTrue();
+ }
+
+ @Test
+ public void unsetCopyMaxScore() throws Exception {
+ configLabel("foo", LabelFunction.NO_OP);
+ try (ProjectConfigUpdate u = updateProject(project)) {
+ LabelType labelType = u.getConfig().getLabelSections().get("foo");
+ labelType.setCopyMaxScore(true);
+ u.getConfig().getLabelSections().put(labelType.getName(), labelType);
+ u.save();
+ }
+ assertThat(gApi.projects().name(project.get()).label("foo").get().copyMaxScore).isTrue();
+
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.copyMaxScore = false;
+
+ LabelDefinitionInfo updatedLabel =
+ gApi.projects().name(project.get()).label("foo").update(input);
+ assertThat(updatedLabel.copyMaxScore).isNull();
+
+ assertThat(gApi.projects().name(project.get()).label("foo").get().copyMaxScore).isNull();
+ }
+
+ @Test
+ public void setCopyAllScoresIfNoChange() throws Exception {
+ configLabel("foo", LabelFunction.NO_OP);
+ try (ProjectConfigUpdate u = updateProject(project)) {
+ LabelType labelType = u.getConfig().getLabelSections().get("foo");
+ labelType.setCopyAllScoresIfNoChange(false);
+ u.getConfig().getLabelSections().put(labelType.getName(), labelType);
+ u.save();
+ }
+ assertThat(gApi.projects().name(project.get()).label("foo").get().copyAllScoresIfNoChange)
+ .isNull();
+
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.copyAllScoresIfNoChange = true;
+
+ LabelDefinitionInfo updatedLabel =
+ gApi.projects().name(project.get()).label("foo").update(input);
+ assertThat(updatedLabel.copyAllScoresIfNoChange).isTrue();
+
+ assertThat(gApi.projects().name(project.get()).label("foo").get().copyAllScoresIfNoChange)
+ .isTrue();
+ }
+
+ @Test
+ public void unsetCopyAllScoresIfNoChange() throws Exception {
+ configLabel("foo", LabelFunction.NO_OP);
+ assertThat(gApi.projects().name(project.get()).label("foo").get().copyAllScoresIfNoChange)
+ .isTrue();
+
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.copyAllScoresIfNoChange = false;
+
+ LabelDefinitionInfo updatedLabel =
+ gApi.projects().name(project.get()).label("foo").update(input);
+ assertThat(updatedLabel.copyAllScoresIfNoChange).isNull();
+
+ assertThat(gApi.projects().name(project.get()).label("foo").get().copyAllScoresIfNoChange)
+ .isNull();
+ }
+
+ @Test
+ public void setCopyAllScoresIfNoCodeChange() throws Exception {
+ configLabel("foo", LabelFunction.NO_OP);
+ assertThat(gApi.projects().name(project.get()).label("foo").get().copyAllScoresIfNoCodeChange)
+ .isNull();
+
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.copyAllScoresIfNoCodeChange = true;
+
+ LabelDefinitionInfo updatedLabel =
+ gApi.projects().name(project.get()).label("foo").update(input);
+ assertThat(updatedLabel.copyAllScoresIfNoCodeChange).isTrue();
+
+ assertThat(gApi.projects().name(project.get()).label("foo").get().copyAllScoresIfNoCodeChange)
+ .isTrue();
+ }
+
+ @Test
+ public void unsetCopyAllScoresIfNoCodeChange() throws Exception {
+ configLabel("foo", LabelFunction.NO_OP);
+ try (ProjectConfigUpdate u = updateProject(project)) {
+ LabelType labelType = u.getConfig().getLabelSections().get("foo");
+ labelType.setCopyAllScoresIfNoCodeChange(true);
+ u.getConfig().getLabelSections().put(labelType.getName(), labelType);
+ u.save();
+ }
+ assertThat(gApi.projects().name(project.get()).label("foo").get().copyAllScoresIfNoCodeChange)
+ .isTrue();
+
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.copyAllScoresIfNoCodeChange = false;
+
+ LabelDefinitionInfo updatedLabel =
+ gApi.projects().name(project.get()).label("foo").update(input);
+ assertThat(updatedLabel.copyAllScoresIfNoCodeChange).isNull();
+
+ assertThat(gApi.projects().name(project.get()).label("foo").get().copyAllScoresIfNoCodeChange)
+ .isNull();
+ }
+
+ @Test
+ public void setCopyAllScoresOnTrivialRebase() throws Exception {
+ configLabel("foo", LabelFunction.NO_OP);
+ assertThat(gApi.projects().name(project.get()).label("foo").get().copyAllScoresOnTrivialRebase)
+ .isNull();
+
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.copyAllScoresOnTrivialRebase = true;
+
+ LabelDefinitionInfo updatedLabel =
+ gApi.projects().name(project.get()).label("foo").update(input);
+ assertThat(updatedLabel.copyAllScoresOnTrivialRebase).isTrue();
+
+ assertThat(gApi.projects().name(project.get()).label("foo").get().copyAllScoresOnTrivialRebase)
+ .isTrue();
+ }
+
+ @Test
+ public void unsetCopyAllScoresOnTrivialRebase() throws Exception {
+ configLabel("foo", LabelFunction.NO_OP);
+ try (ProjectConfigUpdate u = updateProject(project)) {
+ LabelType labelType = u.getConfig().getLabelSections().get("foo");
+ labelType.setCopyAllScoresOnTrivialRebase(true);
+ u.getConfig().getLabelSections().put(labelType.getName(), labelType);
+ u.save();
+ }
+ assertThat(gApi.projects().name(project.get()).label("foo").get().copyAllScoresOnTrivialRebase)
+ .isTrue();
+
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.copyAllScoresOnTrivialRebase = false;
+
+ LabelDefinitionInfo updatedLabel =
+ gApi.projects().name(project.get()).label("foo").update(input);
+ assertThat(updatedLabel.copyAllScoresOnTrivialRebase).isNull();
+
+ assertThat(gApi.projects().name(project.get()).label("foo").get().copyAllScoresOnTrivialRebase)
+ .isNull();
+ }
+
+ @Test
+ public void setCopyAllScoresOnMergeFirstParentUpdate() throws Exception {
+ configLabel("foo", LabelFunction.NO_OP);
+ assertThat(
+ gApi.projects()
+ .name(project.get())
+ .label("foo")
+ .get()
+ .copyAllScoresOnMergeFirstParentUpdate)
+ .isNull();
+
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.copyAllScoresOnMergeFirstParentUpdate = true;
+
+ LabelDefinitionInfo updatedLabel =
+ gApi.projects().name(project.get()).label("foo").update(input);
+ assertThat(updatedLabel.copyAllScoresOnMergeFirstParentUpdate).isTrue();
+
+ assertThat(
+ gApi.projects()
+ .name(project.get())
+ .label("foo")
+ .get()
+ .copyAllScoresOnMergeFirstParentUpdate)
+ .isTrue();
+ }
+
+ @Test
+ public void unsetCopyAllScoresOnMergeFirstParentUpdate() throws Exception {
+ configLabel("foo", LabelFunction.NO_OP);
+ try (ProjectConfigUpdate u = updateProject(project)) {
+ LabelType labelType = u.getConfig().getLabelSections().get("foo");
+ labelType.setCopyAllScoresOnMergeFirstParentUpdate(true);
+ u.getConfig().getLabelSections().put(labelType.getName(), labelType);
+ u.save();
+ }
+ assertThat(
+ gApi.projects()
+ .name(project.get())
+ .label("foo")
+ .get()
+ .copyAllScoresOnMergeFirstParentUpdate)
+ .isTrue();
+
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.copyAllScoresOnMergeFirstParentUpdate = false;
+
+ LabelDefinitionInfo updatedLabel =
+ gApi.projects().name(project.get()).label("foo").update(input);
+ assertThat(updatedLabel.copyAllScoresOnMergeFirstParentUpdate).isNull();
+
+ assertThat(
+ gApi.projects()
+ .name(project.get())
+ .label("foo")
+ .get()
+ .copyAllScoresOnMergeFirstParentUpdate)
+ .isNull();
+ }
+
+ @Test
+ public void setAllowPostSubmit() throws Exception {
+ configLabel("foo", LabelFunction.NO_OP);
+ try (ProjectConfigUpdate u = updateProject(project)) {
+ LabelType labelType = u.getConfig().getLabelSections().get("foo");
+ labelType.setAllowPostSubmit(false);
+ u.getConfig().getLabelSections().put(labelType.getName(), labelType);
+ u.save();
+ }
+ assertThat(gApi.projects().name(project.get()).label("foo").get().allowPostSubmit).isNull();
+
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.allowPostSubmit = true;
+
+ LabelDefinitionInfo updatedLabel =
+ gApi.projects().name(project.get()).label("foo").update(input);
+ assertThat(updatedLabel.allowPostSubmit).isTrue();
+
+ assertThat(gApi.projects().name(project.get()).label("foo").get().allowPostSubmit).isTrue();
+ }
+
+ @Test
+ public void unsetAllowPostSubmit() throws Exception {
+ configLabel("foo", LabelFunction.NO_OP);
+ assertThat(gApi.projects().name(project.get()).label("foo").get().allowPostSubmit).isTrue();
+
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.allowPostSubmit = false;
+
+ LabelDefinitionInfo updatedLabel =
+ gApi.projects().name(project.get()).label("foo").update(input);
+ assertThat(updatedLabel.allowPostSubmit).isNull();
+
+ assertThat(gApi.projects().name(project.get()).label("foo").get().allowPostSubmit).isNull();
+ }
+
+ @Test
+ public void setIgnoreSelfApproval() throws Exception {
+ configLabel("foo", LabelFunction.NO_OP);
+ assertThat(gApi.projects().name(project.get()).label("foo").get().ignoreSelfApproval).isNull();
+
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.ignoreSelfApproval = true;
+
+ LabelDefinitionInfo updatedLabel =
+ gApi.projects().name(project.get()).label("foo").update(input);
+ assertThat(updatedLabel.ignoreSelfApproval).isTrue();
+
+ assertThat(gApi.projects().name(project.get()).label("foo").get().ignoreSelfApproval).isTrue();
+ }
+
+ @Test
+ public void unsetIgnoreSelfApproval() throws Exception {
+ configLabel("foo", LabelFunction.NO_OP);
+ try (ProjectConfigUpdate u = updateProject(project)) {
+ LabelType labelType = u.getConfig().getLabelSections().get("foo");
+ labelType.setIgnoreSelfApproval(true);
+ u.getConfig().getLabelSections().put(labelType.getName(), labelType);
+ u.save();
+ }
+ assertThat(gApi.projects().name(project.get()).label("foo").get().ignoreSelfApproval).isTrue();
+
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.ignoreSelfApproval = false;
+
+ LabelDefinitionInfo updatedLabel =
+ gApi.projects().name(project.get()).label("foo").update(input);
+ assertThat(updatedLabel.ignoreSelfApproval).isNull();
+
+ assertThat(gApi.projects().name(project.get()).label("foo").get().ignoreSelfApproval).isNull();
+ }
+
+ @Test
+ public void noOpUpdate() throws Exception {
+ RevCommit refsMetaConfigHead =
+ projectOperations.project(allProjects).getHead(RefNames.REFS_CONFIG);
+
+ LabelDefinitionInfo updatedLabel =
+ gApi.projects()
+ .name(allProjects.get())
+ .label("Code-Review")
+ .update(new LabelDefinitionInput());
+ LabelAssert.assertCodeReviewLabel(updatedLabel);
+
+ LabelAssert.assertCodeReviewLabel(
+ gApi.projects().name(allProjects.get()).label("Code-Review").get());
+
+ assertThat(projectOperations.project(allProjects).getHead(RefNames.REFS_CONFIG))
+ .isEqualTo(refsMetaConfigHead);
+ }
+
+ @Test
+ public void defaultCommitMessage() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.function = LabelFunction.NO_OP.getFunctionName();
+ gApi.projects().name(allProjects.get()).label("Code-Review").update(input);
+ assertThat(
+ projectOperations.project(allProjects).getHead(RefNames.REFS_CONFIG).getShortMessage())
+ .isEqualTo("Update label");
+ }
+
+ @Test
+ public void withCommitMessage() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.function = LabelFunction.NO_OP.getFunctionName();
+ input.commitMessage = "Set NoOp function";
+ gApi.projects().name(allProjects.get()).label("Code-Review").update(input);
+ assertThat(
+ projectOperations.project(allProjects).getHead(RefNames.REFS_CONFIG).getShortMessage())
+ .isEqualTo(input.commitMessage);
+ }
+
+ @Test
+ public void commitMessageIsTrimmed() throws Exception {
+ LabelDefinitionInput input = new LabelDefinitionInput();
+ input.function = LabelFunction.NO_OP.getFunctionName();
+ input.commitMessage = " Set NoOp function ";
+ gApi.projects().name(allProjects.get()).label("Code-Review").update(input);
+ assertThat(
+ projectOperations.project(allProjects).getHead(RefNames.REFS_CONFIG).getShortMessage())
+ .isEqualTo("Set NoOp function");
+ }
+}
diff --git a/javatests/com/google/gerrit/server/BUILD b/javatests/com/google/gerrit/server/BUILD
index b94a709..fd6c512 100644
--- a/javatests/com/google/gerrit/server/BUILD
+++ b/javatests/com/google/gerrit/server/BUILD
@@ -55,6 +55,7 @@
"//java/com/google/gerrit/server/account/externalids/testing",
"//java/com/google/gerrit/server/cache/serialize",
"//java/com/google/gerrit/server/cache/testing",
+ "//java/com/google/gerrit/server/git/receive:ref_cache",
"//java/com/google/gerrit/server/ioutil",
"//java/com/google/gerrit/server/logging",
"//java/com/google/gerrit/server/project/testing:project-test-util",
diff --git a/javatests/com/google/gerrit/server/account/externalids/ExternalIDCacheLoaderTest.java b/javatests/com/google/gerrit/server/account/externalids/ExternalIDCacheLoaderTest.java
index 054b1aa..2f64ed0 100644
--- a/javatests/com/google/gerrit/server/account/externalids/ExternalIDCacheLoaderTest.java
+++ b/javatests/com/google/gerrit/server/account/externalids/ExternalIDCacheLoaderTest.java
@@ -18,9 +18,9 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.RefNames;
@@ -44,7 +44,6 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
@@ -52,8 +51,7 @@
public class ExternalIDCacheLoaderTest {
private static AllUsersName ALL_USERS = new AllUsersName(AllUsersNameProvider.DEFAULT);
- @Mock Cache<ObjectId, AllExternalIds> externalIdCache;
-
+ private Cache<ObjectId, AllExternalIds> externalIdCache;
private ExternalIdCacheLoader loader;
private GitRepositoryManager repoManager = new InMemoryRepositoryManager();
private ExternalIdReader externalIdReader;
@@ -61,6 +59,7 @@
@Before
public void setUp() throws Exception {
+ externalIdCache = CacheBuilder.newBuilder().build();
repoManager.createRepository(ALL_USERS).close();
externalIdReader = new ExternalIdReader(repoManager, ALL_USERS, new DisabledMetricMaker());
externalIdReaderSpy = Mockito.spy(externalIdReader);
@@ -78,8 +77,7 @@
public void reloadsSingleUpdateUsingPartialReload() throws Exception {
ObjectId firstState = insertExternalId(1, 1);
ObjectId head = insertExternalId(2, 2);
-
- when(externalIdCache.getIfPresent(firstState)).thenReturn(allFromGit(firstState));
+ externalIdCache.put(firstState, allFromGit(firstState));
assertThat(loader.load(head)).isEqualTo(allFromGit(head));
verifyZeroInteractions(externalIdReaderSpy);
@@ -91,8 +89,7 @@
insertExternalId(2, 2);
insertExternalId(3, 3);
ObjectId head = insertExternalId(4, 4);
-
- when(externalIdCache.getIfPresent(firstState)).thenReturn(allFromGit(firstState));
+ externalIdCache.put(firstState, allFromGit(firstState));
assertThat(loader.load(head)).isEqualTo(allFromGit(head));
verifyZeroInteractions(externalIdReaderSpy);
@@ -100,11 +97,9 @@
@Test
public void reloadsAllExternalIdsWhenNoOldStateIsCached() throws Exception {
- ObjectId firstState = insertExternalId(1, 1);
+ insertExternalId(1, 1);
ObjectId head = insertExternalId(2, 2);
- when(externalIdCache.getIfPresent(firstState)).thenReturn(null);
-
assertThat(loader.load(head)).isEqualTo(allFromGit(head));
verify(externalIdReaderSpy, times(1)).all(head);
}
@@ -144,8 +139,7 @@
ObjectId firstState = insertExternalId(1, 1);
ObjectId head = deleteExternalId(1, 1);
assertThat(allFromGit(head).byAccount().size()).isEqualTo(0);
-
- when(externalIdCache.getIfPresent(firstState)).thenReturn(allFromGit(firstState));
+ externalIdCache.put(firstState, allFromGit(firstState));
assertThat(loader.load(head)).isEqualTo(allFromGit(head));
verifyZeroInteractions(externalIdReaderSpy);
@@ -159,8 +153,7 @@
externalId(1, 1),
ExternalId.create("fooschema", "bar1", Account.id(1), "foo@bar.com", "password"));
assertThat(allFromGit(head).byAccount().size()).isEqualTo(1);
-
- when(externalIdCache.getIfPresent(firstState)).thenReturn(allFromGit(firstState));
+ externalIdCache.put(firstState, allFromGit(firstState));
assertThat(loader.load(head)).isEqualTo(allFromGit(head));
verifyZeroInteractions(externalIdReaderSpy);
@@ -177,7 +170,7 @@
head = repo.exactRef(RefNames.REFS_EXTERNAL_IDS).getObjectId();
}
- when(externalIdCache.getIfPresent(firstState)).thenReturn(allFromGit(firstState));
+ externalIdCache.put(firstState, allFromGit(firstState));
assertThat(loader.load(head)).isEqualTo(allFromGit(head));
verifyZeroInteractions(externalIdReaderSpy);
@@ -190,8 +183,7 @@
ObjectId oldState = inserExternalIds(257);
assertAllFilesHaveSlashesInPath();
ObjectId head = insertExternalId(500, 500);
-
- when(externalIdCache.getIfPresent(oldState)).thenReturn(allFromGit(oldState));
+ externalIdCache.put(oldState, allFromGit(oldState));
assertThat(loader.load(head)).isEqualTo(allFromGit(head));
verifyZeroInteractions(externalIdReaderSpy);
@@ -205,8 +197,7 @@
// Create one more external ID and then have the Loader compute the new state
ObjectId head = insertExternalId(500, 500);
assertAllFilesHaveSlashesInPath(); // NoteMap resharded
-
- when(externalIdCache.getIfPresent(oldState)).thenReturn(allFromGit(oldState));
+ externalIdCache.put(oldState, allFromGit(oldState));
assertThat(loader.load(head)).isEqualTo(allFromGit(head));
verifyZeroInteractions(externalIdReaderSpy);
diff --git a/javatests/com/google/gerrit/server/git/GroupCollectorTest.java b/javatests/com/google/gerrit/server/git/GroupCollectorTest.java
index 6175385..63f83b0 100644
--- a/javatests/com/google/gerrit/server/git/GroupCollectorTest.java
+++ b/javatests/com/google/gerrit/server/git/GroupCollectorTest.java
@@ -21,10 +21,13 @@
import com.google.common.collect.SortedSetMultimap;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.server.git.receive.ReceivePackRefCache;
+import java.io.IOException;
import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevSort;
import org.eclipse.jgit.revwalk.RevWalk;
@@ -44,8 +47,7 @@
RevCommit branchTip = tr.commit().create();
RevCommit a = tr.commit().parent(branchTip).create();
- SortedSetMultimap<ObjectId, String> groups =
- collectGroups(newWalk(a, branchTip), patchSets(), groups());
+ SortedSetMultimap<ObjectId, String> groups = collectGroups(newWalk(a, branchTip), groups());
assertThat(groups).containsEntry(a, a.name());
}
@@ -56,8 +58,7 @@
RevCommit a = tr.commit().parent(branchTip).create();
RevCommit b = tr.commit().parent(a).create();
- SortedSetMultimap<ObjectId, String> groups =
- collectGroups(newWalk(b, branchTip), patchSets(), groups());
+ SortedSetMultimap<ObjectId, String> groups = collectGroups(newWalk(b, branchTip), groups());
assertThat(groups).containsEntry(a, a.name());
assertThat(groups).containsEntry(b, a.name());
@@ -67,12 +68,12 @@
public void commitWhoseParentIsExistingPatchSetGetsParentsGroup() throws Exception {
RevCommit branchTip = tr.commit().create();
RevCommit a = tr.commit().parent(branchTip).create();
+ createRef(psId(1, 1), a, tr);
RevCommit b = tr.commit().parent(a).create();
String group = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
SortedSetMultimap<ObjectId, String> groups =
- collectGroups(
- newWalk(b, branchTip), patchSets().put(a, psId(1, 1)), groups().put(psId(1, 1), group));
+ collectGroups(newWalk(b, branchTip), groups().put(psId(1, 1), group));
assertThat(groups).containsEntry(a, group);
assertThat(groups).containsEntry(b, group);
@@ -84,8 +85,7 @@
RevCommit a = tr.commit().parent(branchTip).create();
RevCommit b = tr.commit().parent(a).create();
- SortedSetMultimap<ObjectId, String> groups =
- collectGroups(newWalk(b, branchTip), patchSets().put(a, psId(1, 1)), groups());
+ SortedSetMultimap<ObjectId, String> groups = collectGroups(newWalk(b, branchTip), groups());
assertThat(groups).containsEntry(a, a.name());
assertThat(groups).containsEntry(b, a.name());
@@ -98,8 +98,7 @@
RevCommit b = tr.commit().parent(branchTip).create();
RevCommit m = tr.commit().parent(a).parent(b).create();
- SortedSetMultimap<ObjectId, String> groups =
- collectGroups(newWalk(m, branchTip), patchSets(), groups());
+ SortedSetMultimap<ObjectId, String> groups = collectGroups(newWalk(m, branchTip), groups());
assertThat(groups).containsEntry(a, a.name());
assertThat(groups).containsEntry(b, a.name());
@@ -110,13 +109,13 @@
public void mergeCommitWhereOneParentHasExistingGroup() throws Exception {
RevCommit branchTip = tr.commit().create();
RevCommit a = tr.commit().parent(branchTip).create();
+ createRef(psId(1, 1), a, tr);
RevCommit b = tr.commit().parent(branchTip).create();
RevCommit m = tr.commit().parent(a).parent(b).create();
String group = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
SortedSetMultimap<ObjectId, String> groups =
- collectGroups(
- newWalk(m, branchTip), patchSets().put(b, psId(1, 1)), groups().put(psId(1, 1), group));
+ collectGroups(newWalk(m, branchTip), groups().put(psId(1, 1), group));
// Merge commit and other parent get the existing group.
assertThat(groups).containsEntry(a, group);
@@ -128,16 +127,16 @@
public void mergeCommitWhereBothParentsHaveDifferentGroups() throws Exception {
RevCommit branchTip = tr.commit().create();
RevCommit a = tr.commit().parent(branchTip).create();
+ createRef(psId(1, 1), a, tr);
RevCommit b = tr.commit().parent(branchTip).create();
+ createRef(psId(2, 1), b, tr);
RevCommit m = tr.commit().parent(a).parent(b).create();
String group1 = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
String group2 = "1234567812345678123456781234567812345678";
SortedSetMultimap<ObjectId, String> groups =
collectGroups(
- newWalk(m, branchTip),
- patchSets().put(a, psId(1, 1)).put(b, psId(2, 1)),
- groups().put(psId(1, 1), group1).put(psId(2, 1), group2));
+ newWalk(m, branchTip), groups().put(psId(1, 1), group1).put(psId(2, 1), group2));
assertThat(groups).containsEntry(a, group1);
assertThat(groups).containsEntry(b, group2);
@@ -149,7 +148,9 @@
public void mergeCommitMergesGroupsFromParent() throws Exception {
RevCommit branchTip = tr.commit().create();
RevCommit a = tr.commit().parent(branchTip).create();
+ createRef(psId(1, 1), a, tr);
RevCommit b = tr.commit().parent(branchTip).create();
+ createRef(psId(2, 1), b, tr);
RevCommit m = tr.commit().parent(a).parent(b).create();
String group1 = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
@@ -158,7 +159,6 @@
SortedSetMultimap<ObjectId, String> groups =
collectGroups(
newWalk(m, branchTip),
- patchSets().put(a, psId(1, 1)).put(b, psId(2, 1)),
groups().put(psId(1, 1), group1).put(psId(2, 1), group2a).put(psId(2, 1), group2b));
assertThat(groups).containsEntry(a, group1);
@@ -171,12 +171,12 @@
public void mergeCommitWithOneUninterestingParentAndOtherParentIsExisting() throws Exception {
RevCommit branchTip = tr.commit().create();
RevCommit a = tr.commit().parent(branchTip).create();
+ createRef(psId(1, 1), a, tr);
RevCommit m = tr.commit().parent(branchTip).parent(a).create();
String group = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
SortedSetMultimap<ObjectId, String> groups =
- collectGroups(
- newWalk(m, branchTip), patchSets().put(a, psId(1, 1)), groups().put(psId(1, 1), group));
+ collectGroups(newWalk(m, branchTip), groups().put(psId(1, 1), group));
assertThat(groups).containsEntry(a, group);
assertThat(groups).containsEntry(m, group);
@@ -188,8 +188,7 @@
RevCommit a = tr.commit().parent(branchTip).create();
RevCommit m = tr.commit().parent(branchTip).parent(a).create();
- SortedSetMultimap<ObjectId, String> groups =
- collectGroups(newWalk(m, branchTip), patchSets(), groups());
+ SortedSetMultimap<ObjectId, String> groups = collectGroups(newWalk(m, branchTip), groups());
assertThat(groups).containsEntry(a, a.name());
assertThat(groups).containsEntry(m, a.name());
@@ -204,8 +203,7 @@
RevCommit m1 = tr.commit().parent(b).parent(c).create();
RevCommit m2 = tr.commit().parent(a).parent(m1).create();
- SortedSetMultimap<ObjectId, String> groups =
- collectGroups(newWalk(m2, branchTip), patchSets(), groups());
+ SortedSetMultimap<ObjectId, String> groups = collectGroups(newWalk(m2, branchTip), groups());
assertThat(groups).containsEntry(a, a.name());
assertThat(groups).containsEntry(b, a.name());
@@ -223,8 +221,7 @@
assertThat(m.getParentCount()).isEqualTo(2);
assertThat(m.getParent(0)).isEqualTo(m.getParent(1));
- SortedSetMultimap<ObjectId, String> groups =
- collectGroups(newWalk(m, branchTip), patchSets(), groups());
+ SortedSetMultimap<ObjectId, String> groups = collectGroups(newWalk(m, branchTip), groups());
assertThat(groups).containsEntry(a, a.name());
assertThat(groups).containsEntry(m, a.name());
@@ -234,7 +231,9 @@
public void mergeCommitWithOneNewParentAndTwoExistingPatchSets() throws Exception {
RevCommit branchTip = tr.commit().create();
RevCommit a = tr.commit().parent(branchTip).create();
+ createRef(psId(1, 1), a, tr);
RevCommit b = tr.commit().parent(branchTip).create();
+ createRef(psId(2, 1), b, tr);
RevCommit c = tr.commit().parent(b).create();
RevCommit m = tr.commit().parent(a).parent(c).create();
@@ -242,9 +241,7 @@
String group2 = "1234567812345678123456781234567812345678";
SortedSetMultimap<ObjectId, String> groups =
collectGroups(
- newWalk(m, branchTip),
- patchSets().put(a, psId(1, 1)).put(b, psId(2, 1)),
- groups().put(psId(1, 1), group1).put(psId(2, 1), group2));
+ newWalk(m, branchTip), groups().put(psId(1, 1), group1).put(psId(2, 1), group2));
assertThat(groups).containsEntry(a, group1);
assertThat(groups).containsEntry(b, group2);
@@ -264,16 +261,7 @@
rw.markStart(rw.parseCommit(d));
// Schema upgrade case: all commits are existing patch sets, but none have
// groups assigned yet.
- SortedSetMultimap<ObjectId, String> groups =
- collectGroups(
- rw,
- patchSets()
- .put(branchTip, psId(1, 1))
- .put(a, psId(2, 1))
- .put(b, psId(3, 1))
- .put(c, psId(4, 1))
- .put(d, psId(5, 1)),
- groups());
+ SortedSetMultimap<ObjectId, String> groups = collectGroups(rw, groups());
assertThat(groups).containsEntry(a, a.name());
assertThat(groups).containsEntry(b, a.name());
@@ -287,6 +275,13 @@
return PatchSet.id(Change.id(c), p);
}
+ private static void createRef(PatchSet.Id psId, ObjectId id, TestRepository<?> tr)
+ throws IOException {
+ RefUpdate ru = tr.getRepository().updateRef(psId.toRefName());
+ ru.setNewObjectId(id);
+ assertThat(ru.update()).isEqualTo(RefUpdate.Result.NEW);
+ }
+
private RevWalk newWalk(ObjectId start, ObjectId branchTip) throws Exception {
// Match RevWalk conditions from ReceiveCommits.
RevWalk rw = new RevWalk(tr.getRepository());
@@ -297,12 +292,12 @@
return rw;
}
- private static SortedSetMultimap<ObjectId, String> collectGroups(
- RevWalk rw,
- ImmutableListMultimap.Builder<ObjectId, PatchSet.Id> patchSetsBySha,
- ImmutableListMultimap.Builder<PatchSet.Id, String> groupLookup)
- throws Exception {
- GroupCollector gc = new GroupCollector(patchSetsBySha.build(), groupLookup.build());
+ private SortedSetMultimap<ObjectId, String> collectGroups(
+ RevWalk rw, ImmutableListMultimap.Builder<PatchSet.Id, String> groupLookup) throws Exception {
+ ImmutableListMultimap<PatchSet.Id, String> groups = groupLookup.build();
+ GroupCollector gc =
+ new GroupCollector(
+ ReceivePackRefCache.noCache(tr.getRepository().getRefDatabase()), (s) -> groups.get(s));
RevCommit c;
while ((c = rw.next()) != null) {
gc.visit(c);
@@ -310,12 +305,6 @@
return gc.getGroups();
}
- // Helper methods for constructing various map arguments, to avoid lots of
- // type specifications.
- private static ImmutableListMultimap.Builder<ObjectId, PatchSet.Id> patchSets() {
- return ImmutableListMultimap.builder();
- }
-
private static ImmutableListMultimap.Builder<PatchSet.Id, String> groups() {
return ImmutableListMultimap.builder();
}
diff --git a/javatests/com/google/gerrit/server/git/receive/ReceivePackRefCacheTest.java b/javatests/com/google/gerrit/server/git/receive/ReceivePackRefCacheTest.java
new file mode 100644
index 0000000..698acd8
--- /dev/null
+++ b/javatests/com/google/gerrit/server/git/receive/ReceivePackRefCacheTest.java
@@ -0,0 +1,140 @@
+// 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.git.receive;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.RefNames;
+import java.util.Map;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectIdRef;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
+import org.junit.Test;
+
+/** Tests for {@link ReceivePackRefCache}. */
+public class ReceivePackRefCacheTest {
+
+ @Test
+ public void noCache_prefixDelegatesToRefDb() throws Exception {
+ Ref ref = newRef("refs/changes/01/1/1", "badc0feebadc0feebadc0feebadc0feebadc0fee");
+ RefDatabase mockRefDb = mock(RefDatabase.class);
+ ReceivePackRefCache cache = ReceivePackRefCache.noCache(mockRefDb);
+ when(mockRefDb.getRefsByPrefix(RefNames.REFS_HEADS)).thenReturn(ImmutableList.of(ref));
+
+ assertThat(cache.byPrefix(RefNames.REFS_HEADS)).containsExactly(ref);
+ verify(mockRefDb).getRefsByPrefix(RefNames.REFS_HEADS);
+ verifyNoMoreInteractions(mockRefDb);
+ }
+
+ @Test
+ public void noCache_exactRefDelegatesToRefDb() throws Exception {
+ Ref ref = newRef("refs/changes/01/1/1", "badc0feebadc0feebadc0feebadc0feebadc0fee");
+ RefDatabase mockRefDb = mock(RefDatabase.class);
+ ReceivePackRefCache cache = ReceivePackRefCache.noCache(mockRefDb);
+ when(mockRefDb.exactRef("refs/heads/master")).thenReturn(ref);
+
+ assertThat(cache.exactRef("refs/heads/master")).isEqualTo(ref);
+ verify(mockRefDb).exactRef("refs/heads/master");
+ verifyNoMoreInteractions(mockRefDb);
+ }
+
+ @Test
+ public void noCache_tipsFromObjectIdDelegatesToRefDbAndFiltersByPrefix() throws Exception {
+ Ref refBla = newRef("refs/bla", "badc0feebadc0feebadc0feebadc0feebadc0fee");
+ Ref refheads = newRef(RefNames.REFS_HEADS, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
+
+ RefDatabase mockRefDb = mock(RefDatabase.class);
+ ReceivePackRefCache cache = ReceivePackRefCache.noCache(mockRefDb);
+ when(mockRefDb.getTipsWithSha1(ObjectId.zeroId()))
+ .thenReturn(ImmutableSet.of(refBla, refheads));
+
+ assertThat(cache.tipsFromObjectId(ObjectId.zeroId(), RefNames.REFS_HEADS))
+ .containsExactly(refheads);
+ verify(mockRefDb).getTipsWithSha1(ObjectId.zeroId());
+ verifyNoMoreInteractions(mockRefDb);
+ }
+
+ @Test
+ public void advertisedRefs_prefixScans() throws Exception {
+ Ref refBla =
+ new ObjectIdRef.Unpeeled(
+ Ref.Storage.NEW,
+ "refs/bla/1",
+ ObjectId.fromString("badc0feebadc0feebadc0feebadc0feebadc0fee"),
+ 1);
+ ReceivePackRefCache cache =
+ ReceivePackRefCache.withAdvertisedRefs(() -> ImmutableMap.of(refBla.getName(), refBla));
+
+ assertThat(cache.byPrefix("refs/bla")).containsExactly(refBla);
+ }
+
+ @Test
+ public void advertisedRefs_prefixScansChangeId() throws Exception {
+ Map<String, Ref> refs = setupTwoChanges();
+ ReceivePackRefCache cache = ReceivePackRefCache.withAdvertisedRefs(() -> refs);
+
+ assertThat(cache.byPrefix(RefNames.changeRefPrefix(Change.id(1))))
+ .containsExactly(refs.get("refs/changes/01/1/1"));
+ }
+
+ @Test
+ public void advertisedRefs_exactRef() throws Exception {
+ Map<String, Ref> refs = setupTwoChanges();
+ ReceivePackRefCache cache = ReceivePackRefCache.withAdvertisedRefs(() -> refs);
+
+ assertThat(cache.exactRef("refs/changes/01/1/1")).isEqualTo(refs.get("refs/changes/01/1/1"));
+ }
+
+ @Test
+ public void advertisedRefs_tipsFromObjectIdWithNoPrefix() throws Exception {
+ Map<String, Ref> refs = setupTwoChanges();
+ ReceivePackRefCache cache = ReceivePackRefCache.withAdvertisedRefs(() -> refs);
+
+ assertThat(
+ cache.tipsFromObjectId(
+ ObjectId.fromString("badc0feebadc0feebadc0feebadc0feebadc0fee"), null))
+ .containsExactly(refs.get("refs/changes/01/1/1"));
+ }
+
+ @Test
+ public void advertisedRefs_tipsFromObjectIdWithPrefix() throws Exception {
+ Map<String, Ref> refs = setupTwoChanges();
+ ReceivePackRefCache cache = ReceivePackRefCache.withAdvertisedRefs(() -> refs);
+
+ assertThat(
+ cache.tipsFromObjectId(
+ ObjectId.fromString("badc0feebadc0feebadc0feebadc0feebadc0fee"), "/refs/some"))
+ .isEmpty();
+ }
+
+ private static Ref newRef(String name, String sha1) {
+ return new ObjectIdRef.Unpeeled(Ref.Storage.NEW, name, ObjectId.fromString(sha1), 1);
+ }
+
+ private Map<String, Ref> setupTwoChanges() {
+ Ref ref1 = newRef("refs/changes/01/1/1", "badc0feebadc0feebadc0feebadc0feebadc0fee");
+ Ref ref2 = newRef("refs/changes/02/2/1", "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef");
+ return ImmutableMap.of(ref1.getName(), ref1, ref2.getName(), ref2);
+ }
+}
diff --git a/javatests/com/google/gerrit/server/index/change/FakeQueryBuilder.java b/javatests/com/google/gerrit/server/index/change/FakeQueryBuilder.java
index 0753127..9a48a68 100644
--- a/javatests/com/google/gerrit/server/index/change/FakeQueryBuilder.java
+++ b/javatests/com/google/gerrit/server/index/change/FakeQueryBuilder.java
@@ -27,7 +27,7 @@
new ChangeQueryBuilder.Definition<>(FakeQueryBuilder.class),
new ChangeQueryBuilder.Arguments(
null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, indexes, null, null, null, null, null, null, null));
+ null, null, null, null, indexes, null, null, null, null, null, null, null, null));
}
@Operator
diff --git a/javatests/com/google/gerrit/server/index/change/StalenessCheckerTest.java b/javatests/com/google/gerrit/server/index/change/StalenessCheckerTest.java
index c887875..6f40680 100644
--- a/javatests/com/google/gerrit/server/index/change/StalenessCheckerTest.java
+++ b/javatests/com/google/gerrit/server/index/change/StalenessCheckerTest.java
@@ -163,34 +163,37 @@
// Not stale.
assertThat(
refsAreStale(
- repoManager,
- C,
- ImmutableSetMultimap.of(
- P1, RefState.create(ref1, id1.name()),
- P2, RefState.create(ref2, id2.name())),
- ImmutableListMultimap.of()))
+ repoManager,
+ C,
+ ImmutableSetMultimap.of(
+ P1, RefState.create(ref1, id1.name()),
+ P2, RefState.create(ref2, id2.name())),
+ ImmutableListMultimap.of())
+ .isStale())
.isFalse();
// Wrong ref value.
assertThat(
refsAreStale(
- repoManager,
- C,
- ImmutableSetMultimap.of(
- P1, RefState.create(ref1, SHA1),
- P2, RefState.create(ref2, id2.name())),
- ImmutableListMultimap.of()))
+ repoManager,
+ C,
+ ImmutableSetMultimap.of(
+ P1, RefState.create(ref1, SHA1),
+ P2, RefState.create(ref2, id2.name())),
+ ImmutableListMultimap.of())
+ .isStale())
.isTrue();
// Swapped repos.
assertThat(
refsAreStale(
- repoManager,
- C,
- ImmutableSetMultimap.of(
- P1, RefState.create(ref1, id2.name()),
- P2, RefState.create(ref2, id1.name())),
- ImmutableListMultimap.of()))
+ repoManager,
+ C,
+ ImmutableSetMultimap.of(
+ P1, RefState.create(ref1, id2.name()),
+ P2, RefState.create(ref2, id1.name())),
+ ImmutableListMultimap.of())
+ .isStale())
.isTrue();
// Two refs in same repo, not stale.
@@ -199,32 +202,35 @@
tr1.update(ref3, id3);
assertThat(
refsAreStale(
- repoManager,
- C,
- ImmutableSetMultimap.of(
- P1, RefState.create(ref1, id1.name()),
- P1, RefState.create(ref3, id3.name())),
- ImmutableListMultimap.of()))
+ repoManager,
+ C,
+ ImmutableSetMultimap.of(
+ P1, RefState.create(ref1, id1.name()),
+ P1, RefState.create(ref3, id3.name())),
+ ImmutableListMultimap.of())
+ .isStale())
.isFalse();
// Ignore ref not mentioned.
assertThat(
refsAreStale(
- repoManager,
- C,
- ImmutableSetMultimap.of(P1, RefState.create(ref1, id1.name())),
- ImmutableListMultimap.of()))
+ repoManager,
+ C,
+ ImmutableSetMultimap.of(P1, RefState.create(ref1, id1.name())),
+ ImmutableListMultimap.of())
+ .isStale())
.isFalse();
// One ref wrong.
assertThat(
refsAreStale(
- repoManager,
- C,
- ImmutableSetMultimap.of(
- P1, RefState.create(ref1, id1.name()),
- P1, RefState.create(ref3, SHA1)),
- ImmutableListMultimap.of()))
+ repoManager,
+ C,
+ ImmutableSetMultimap.of(
+ P1, RefState.create(ref1, id1.name()),
+ P1, RefState.create(ref3, SHA1)),
+ ImmutableListMultimap.of())
+ .isStale())
.isTrue();
}
@@ -236,10 +242,11 @@
// ref1 is only ref matching pattern.
assertThat(
refsAreStale(
- repoManager,
- C,
- ImmutableSetMultimap.of(P1, RefState.create(ref1, id1.name())),
- ImmutableListMultimap.of(P1, RefStatePattern.create("refs/heads/*"))))
+ repoManager,
+ C,
+ ImmutableSetMultimap.of(P1, RefState.create(ref1, id1.name())),
+ ImmutableListMultimap.of(P1, RefStatePattern.create("refs/heads/*")))
+ .isStale())
.isFalse();
// Now ref2 matches pattern, so stale unless ref2 is present in state map.
@@ -247,19 +254,21 @@
ObjectId id2 = tr1.update(ref2, tr1.commit().message("commit 2"));
assertThat(
refsAreStale(
- repoManager,
- C,
- ImmutableSetMultimap.of(P1, RefState.create(ref1, id1.name())),
- ImmutableListMultimap.of(P1, RefStatePattern.create("refs/heads/*"))))
+ repoManager,
+ C,
+ ImmutableSetMultimap.of(P1, RefState.create(ref1, id1.name())),
+ ImmutableListMultimap.of(P1, RefStatePattern.create("refs/heads/*")))
+ .isStale())
.isTrue();
assertThat(
refsAreStale(
- repoManager,
- C,
- ImmutableSetMultimap.of(
- P1, RefState.create(ref1, id1.name()),
- P1, RefState.create(ref2, id2.name())),
- ImmutableListMultimap.of(P1, RefStatePattern.create("refs/heads/*"))))
+ repoManager,
+ C,
+ ImmutableSetMultimap.of(
+ P1, RefState.create(ref1, id1.name()),
+ P1, RefState.create(ref2, id2.name())),
+ ImmutableListMultimap.of(P1, RefStatePattern.create("refs/heads/*")))
+ .isStale())
.isFalse();
}
@@ -272,10 +281,11 @@
// ref1 is only ref matching pattern.
assertThat(
refsAreStale(
- repoManager,
- C,
- ImmutableSetMultimap.of(P1, RefState.create(ref1, id1.name())),
- ImmutableListMultimap.of(P1, RefStatePattern.create("refs/*/foo"))))
+ repoManager,
+ C,
+ ImmutableSetMultimap.of(P1, RefState.create(ref1, id1.name())),
+ ImmutableListMultimap.of(P1, RefStatePattern.create("refs/*/foo")))
+ .isStale())
.isFalse();
// Now ref2 matches pattern, so stale unless ref2 is present in state map.
@@ -283,19 +293,21 @@
ObjectId id3 = tr1.update(ref3, tr1.commit().message("commit 3"));
assertThat(
refsAreStale(
- repoManager,
- C,
- ImmutableSetMultimap.of(P1, RefState.create(ref1, id1.name())),
- ImmutableListMultimap.of(P1, RefStatePattern.create("refs/*/foo"))))
+ repoManager,
+ C,
+ ImmutableSetMultimap.of(P1, RefState.create(ref1, id1.name())),
+ ImmutableListMultimap.of(P1, RefStatePattern.create("refs/*/foo")))
+ .isStale())
.isTrue();
assertThat(
refsAreStale(
- repoManager,
- C,
- ImmutableSetMultimap.of(
- P1, RefState.create(ref1, id1.name()),
- P1, RefState.create(ref3, id3.name())),
- ImmutableListMultimap.of(P1, RefStatePattern.create("refs/*/foo"))))
+ repoManager,
+ C,
+ ImmutableSetMultimap.of(
+ P1, RefState.create(ref1, id1.name()),
+ P1, RefState.create(ref3, id3.name())),
+ ImmutableListMultimap.of(P1, RefStatePattern.create("refs/*/foo")))
+ .isStale())
.isFalse();
}
diff --git a/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java b/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java
index 5993206..145e914 100644
--- a/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java
+++ b/javatests/com/google/gerrit/server/notedb/ChangeNotesTest.java
@@ -3062,6 +3062,38 @@
assertThat(newNotes(c).getUpdateCount()).isEqualTo(3);
}
+ @Test
+ public void createPatchSetAfterPatchSetDeletion() throws Exception {
+ Change c = newChange();
+ assertThat(newNotes(c).getChange().currentPatchSetId().get()).isEqualTo(1);
+
+ // Create PS2.
+ incrementCurrentPatchSetFieldOnly(c);
+ RevCommit commit = tr.commit().message("PS" + c.currentPatchSetId().get()).create();
+ ChangeUpdate update = newUpdate(c, changeOwner);
+ update.setCommit(rw, commit);
+ update.setGroups(ImmutableList.of(commit.name()));
+ update.commit();
+ assertThat(newNotes(c).getChange().currentPatchSetId().get()).isEqualTo(2);
+
+ // Delete PS2.
+ update = newUpdate(c, changeOwner);
+ update.setPatchSetState(PatchSetState.DELETED);
+ update.commit();
+ c = newNotes(c).getChange();
+ assertThat(c.currentPatchSetId().get()).isEqualTo(1);
+
+ // Create another PS2
+ incrementCurrentPatchSetFieldOnly(c);
+ commit = tr.commit().message("PS" + c.currentPatchSetId().get()).create();
+ update = newUpdate(c, changeOwner);
+ update.setPatchSetState(PatchSetState.PUBLISHED);
+ update.setCommit(rw, commit);
+ update.setGroups(ImmutableList.of(commit.name()));
+ update.commit();
+ assertThat(newNotes(c).getChange().currentPatchSetId().get()).isEqualTo(2);
+ }
+
private String readNote(ChangeNotes notes, ObjectId noteId) throws Exception {
ObjectId dataId = notes.revisionNoteMap.noteMap.getNote(noteId).getData();
return new String(rw.getObjectReader().open(dataId, OBJ_BLOB).getCachedBytes(), UTF_8);
diff --git a/package.json b/package.json
index 6b9a38d..8fb3d3d 100644
--- a/package.json
+++ b/package.json
@@ -4,9 +4,9 @@
"description": "Gerrit Code Review",
"dependencies": {},
"devDependencies": {
- "eslint": "^5.16.0",
+ "eslint": "^6.6.0",
"eslint-config-google": "^0.13.0",
- "eslint-plugin-html": "^5.0.5",
+ "eslint-plugin-html": "^6.0.0",
"fried-twinkie": "^0.2.2",
"polylint": "^2.10.4",
"typescript": "^2.x.x",
@@ -16,6 +16,7 @@
"start": "polygerrit-ui/run-server.sh",
"test": "WCT_HEADLESS_MODE=1 WCT_ARGS='--verbose -l chrome' ./polygerrit-ui/app/run_test.sh",
"eslint": "./node_modules/eslint/bin/eslint.js --ignore-pattern 'bower_components/' --ignore-pattern 'gr-linked-text' --ignore-pattern 'scripts/vendor' --ext .html,.js polygerrit-ui/app || exit 0",
+ "eslintfix": "./node_modules/eslint/bin/eslint.js --fix --ignore-pattern 'bower_components/' --ignore-pattern 'gr-linked-text' --ignore-pattern 'scripts/vendor' --ext .html,.js polygerrit-ui/app || exit 0",
"test-template": "./polygerrit-ui/app/run_template_test.sh",
"polylint": "bazel test polygerrit-ui/app:polylint_test"
},
diff --git a/plugins/hooks b/plugins/hooks
index 472a14f..71cdc88 160000
--- a/plugins/hooks
+++ b/plugins/hooks
@@ -1 +1 @@
-Subproject commit 472a14fb2dacbccb5fa9644b935c65f6898e41f0
+Subproject commit 71cdc88f4804a4811e613d8679862cb33dd75cc1
diff --git a/plugins/replication b/plugins/replication
index 94a465e..4786c07 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit 94a465e0989ff8124aca3dca8e200aeb870cc9dd
+Subproject commit 4786c07027a0040810ae3e6a517f737cb57e1283
diff --git a/polygerrit-ui/app/.eslintrc.json b/polygerrit-ui/app/.eslintrc.json
index 97151f2..0b3febe 100644
--- a/polygerrit-ui/app/.eslintrc.json
+++ b/polygerrit-ui/app/.eslintrc.json
@@ -25,27 +25,35 @@
"block-spacing": ["error", "always"],
"brace-style": ["error", "1tbs", { "allowSingleLine": true }],
"camelcase": "off",
- "comma-dangle": ["error", "always-multiline"],
+ "comma-dangle": ["error", {
+ "arrays": "always-multiline",
+ "objects": "always-multiline",
+ "imports": "always-multiline",
+ "exports": "always-multiline",
+ "functions": "never"
+ }],
"eol-last": "off",
- "indent": "off",
- "indent-legacy": ["error", 2, {
+ "indent": ["error", 2, {
"MemberExpression": 2,
"FunctionDeclaration": {"body": 1, "parameters": 2},
"FunctionExpression": {"body": 1, "parameters": 2},
- "CallExpression": {"arguments": 2},
+ "CallExpression": {"arguments": 2 },
"ArrayExpression": 1,
"ObjectExpression": 1,
"SwitchCase": 1
}],
"keyword-spacing": ["error", { "after": true, "before": true }],
+ "lines-between-class-members": ["error", "always"],
"max-len": [
"error",
80,
2,
{"ignoreComments": true}
],
- "new-cap": ["error", { "capIsNewExceptions": ["Polymer"] }],
+ "new-cap": ["error", { "capIsNewExceptions": ["Polymer", "LegacyElementMixin", "GestureEventListeners", "LegacyDataMixin"] }],
"no-console": "off",
+ "no-prototype-builtins": "off",
+ "no-redeclare": "off",
"no-restricted-syntax": [
"error",
{
@@ -61,6 +69,19 @@
"no-useless-escape": "off",
"no-var": "error",
"object-shorthand": ["error", "always"],
+ "padding-line-between-statements": [
+ "error",
+ {
+ "blankLine": "always",
+ "prev": "class",
+ "next": "*"
+ },
+ {
+ "blankLine": "always",
+ "prev": "*",
+ "next": "class"
+ }
+ ],
"prefer-arrow-callback": "error",
"prefer-const": "error",
"prefer-spread": "error",
diff --git a/polygerrit-ui/app/behaviors/docs-url-behavior/docs-url-behavior.html b/polygerrit-ui/app/behaviors/docs-url-behavior/docs-url-behavior.html
index 64b725f..4e25530 100644
--- a/polygerrit-ui/app/behaviors/docs-url-behavior/docs-url-behavior.html
+++ b/polygerrit-ui/app/behaviors/docs-url-behavior/docs-url-behavior.html
@@ -56,7 +56,7 @@
cachedPromise = undefined;
},
},
- Gerrit.BaseUrlBehavior,
+ Gerrit.BaseUrlBehavior,
];
})(window);
</script>
diff --git a/polygerrit-ui/app/behaviors/gr-list-view-behavior/gr-list-view-behavior.html b/polygerrit-ui/app/behaviors/gr-list-view-behavior/gr-list-view-behavior.html
index f251db8..b6edb57 100644
--- a/polygerrit-ui/app/behaviors/gr-list-view-behavior/gr-list-view-behavior.html
+++ b/polygerrit-ui/app/behaviors/gr-list-view-behavior/gr-list-view-behavior.html
@@ -56,8 +56,8 @@
return 0;
},
},
- Gerrit.BaseUrlBehavior,
- Gerrit.URLEncodingBehavior,
+ Gerrit.BaseUrlBehavior,
+ Gerrit.URLEncodingBehavior,
];
})(window);
</script>
diff --git a/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html b/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html
index 5e4e8fd..5942833 100644
--- a/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html
+++ b/polygerrit-ui/app/behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html
@@ -124,8 +124,8 @@
// 2 -> 3, 3 -> 5, etc.
// Map an edit to the patchNum of parent*2... I.e. edit on 2 -> 4.
const num = r => r._number === Gerrit.PatchSetBehavior.EDIT_NAME ?
- 2 * editParent :
- 2 * (r._number - 1) + 1;
+ 2 * editParent :
+ 2 * (r._number - 1) + 1;
return revisions.sort((a, b) => num(b) - num(a));
},
diff --git a/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior_test.html b/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior_test.html
index 5a563ab..3183c7e 100644
--- a/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior_test.html
@@ -240,8 +240,8 @@
test('directory view', () => {
const {
- NEXT_FILE, NEXT_LINE, GO_TO_OPENED_CHANGES, SEARCH,
- SAVE_COMMENT,
+ NEXT_FILE, NEXT_LINE, GO_TO_OPENED_CHANGES, SEARCH,
+ SAVE_COMMENT,
} = kb.Shortcut;
const {DIFFS, EVERYWHERE, NAVIGATION} = kb.ShortcutSection;
const {GO_KEY, ShortcutManager} = kb;
diff --git a/polygerrit-ui/app/behaviors/rest-client-behavior/rest-client-behavior.html b/polygerrit-ui/app/behaviors/rest-client-behavior/rest-client-behavior.html
index 4252e6e..fbeaa64 100644
--- a/polygerrit-ui/app/behaviors/rest-client-behavior/rest-client-behavior.html
+++ b/polygerrit-ui/app/behaviors/rest-client-behavior/rest-client-behavior.html
@@ -175,7 +175,7 @@
return this.changeStatuses(change).join(', ');
},
},
- Gerrit.BaseUrlBehavior,
+ Gerrit.BaseUrlBehavior,
];
})(window);
</script>
diff --git a/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.js b/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.js
index b6f1b74..afe3955 100644
--- a/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.js
+++ b/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section.js
@@ -224,8 +224,8 @@
editRefInput() {
return Polymer.dom(this.root).querySelector(Polymer.Element ?
- 'iron-input.editRefInput' :
- 'input[is=iron-input].editRefInput');
+ 'iron-input.editRefInput' :
+ 'input[is=iron-input].editRefInput');
},
editReference() {
diff --git a/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section_test.html b/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section_test.html
index 5110dc1..c5fa827 100644
--- a/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-access-section/gr-access-section_test.html
@@ -206,8 +206,9 @@
id: 'administrateServer',
value: {},
};
- assert.equal(element._computePermissionName(name, permission,
- element.permissionValues, element.capabilities),
+ assert.equal(
+ element._computePermissionName(name, permission,
+ element.permissionValues, element.capabilities),
element.capabilities[permission.id].name);
name = 'refs/for/*';
@@ -216,8 +217,9 @@
value: {},
};
- assert.equal(element._computePermissionName(
- name, permission, element.permissionValues, element.capabilities),
+ assert.equal(
+ element._computePermissionName(
+ name, permission, element.permissionValues, element.capabilities),
element.permissionValues[permission.id].name);
name = 'refs/for/*';
@@ -228,8 +230,9 @@
},
};
- assert.equal(element._computePermissionName(name, permission,
- element.permissionValues, element.capabilities),
+ assert.equal(
+ element._computePermissionName(name, permission,
+ element.permissionValues, element.capabilities),
'Label Code-Review');
permission = {
@@ -239,8 +242,9 @@
},
};
- assert.equal(element._computePermissionName(name, permission,
- element.permissionValues, element.capabilities),
+ assert.equal(
+ element._computePermissionName(name, permission,
+ element.permissionValues, element.capabilities),
'Label Code-Review(On Behalf Of)');
});
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.js b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.js
index 4c099d6..74dcb87 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.js
+++ b/polygerrit-ui/app/elements/admin/gr-admin-group-list/gr-admin-group-list.js
@@ -120,11 +120,11 @@
return;
}
this._groups = Object.keys(groups)
- .map(key => {
- const group = groups[key];
- group.name = key;
- return group;
- });
+ .map(key => {
+ const group = groups[key];
+ group.name = key;
+ return group;
+ });
this._loading = false;
});
},
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.js b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.js
index d3f020d..72cca9e 100644
--- a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.js
+++ b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.js
@@ -105,23 +105,23 @@
.then(res => {
this._filteredLinks = res.links;
this._breadcrumbParentName = res.expandedSection ?
- res.expandedSection.name : '';
+ res.expandedSection.name : '';
if (!res.expandedSection) {
this._subsectionLinks = [];
return;
}
this._subsectionLinks = [res.expandedSection]
- .concat(res.expandedSection.children).map(section => {
- return {
- text: !section.detailType ? 'Home' : section.name,
- value: section.view + (section.detailType || ''),
- view: section.view,
- url: section.url,
- detailType: section.detailType,
- parent: this._groupId || this._repoName || '',
- };
- });
+ .concat(res.expandedSection.children).map(section => {
+ return {
+ text: !section.detailType ? 'Home' : section.name,
+ value: section.view + (section.detailType || ''),
+ view: section.view,
+ url: section.url,
+ detailType: section.detailType,
+ parent: this._groupId || this._repoName || '',
+ };
+ });
});
});
},
diff --git a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.js b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.js
index 5e4d102..e29e5f8 100644
--- a/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.js
+++ b/polygerrit-ui/app/elements/admin/gr-create-change-dialog/gr-create-change-dialog.js
@@ -107,21 +107,21 @@
}
return this.$.restAPI.getRepoBranches(
input, this.repoName, SUGGESTIONS_LIMIT).then(response => {
- const branches = [];
- let branch;
- for (const key in response) {
- if (!response.hasOwnProperty(key)) { continue; }
- if (response[key].ref.startsWith('refs/heads/')) {
- branch = response[key].ref.substring('refs/heads/'.length);
- } else {
- branch = response[key].ref;
- }
- branches.push({
- name: branch,
- });
- }
- return branches;
+ const branches = [];
+ let branch;
+ for (const key in response) {
+ if (!response.hasOwnProperty(key)) { continue; }
+ if (response[key].ref.startsWith('refs/heads/')) {
+ branch = response[key].ref.substring('refs/heads/'.length);
+ } else {
+ branch = response[key].ref;
+ }
+ branches.push({
+ name: branch,
});
+ }
+ return branches;
+ });
},
_formatBooleanString(config) {
diff --git a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.js b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.js
index 958c0ac..7f8e9ac 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.js
+++ b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members.js
@@ -141,15 +141,15 @@
_handleSavingGroupMember() {
return this.$.restAPI.saveGroupMembers(this._groupName,
this._groupMemberSearchId).then(config => {
- if (!config) {
- return;
- }
- this.$.restAPI.getGroupMembers(this._groupName).then(members => {
- this._groupMembers = members;
- });
- this._groupMemberSearchName = '';
- this._groupMemberSearchId = '';
- });
+ if (!config) {
+ return;
+ }
+ this.$.restAPI.getGroupMembers(this._groupName).then(members => {
+ this._groupMembers = members;
+ });
+ this._groupMemberSearchName = '';
+ this._groupMemberSearchId = '';
+ });
},
_handleDeleteConfirm() {
@@ -239,24 +239,24 @@
if (input.length === 0) { return Promise.resolve([]); }
return this.$.restAPI.getSuggestedAccounts(
input, SUGGESTIONS_LIMIT).then(accounts => {
- const accountSuggestions = [];
- let nameAndEmail;
- if (!accounts) { return []; }
- for (const key in accounts) {
- if (!accounts.hasOwnProperty(key)) { continue; }
- if (accounts[key].email !== undefined) {
- nameAndEmail = accounts[key].name +
+ const accountSuggestions = [];
+ let nameAndEmail;
+ if (!accounts) { return []; }
+ for (const key in accounts) {
+ if (!accounts.hasOwnProperty(key)) { continue; }
+ if (accounts[key].email !== undefined) {
+ nameAndEmail = accounts[key].name +
' <' + accounts[key].email + '>';
- } else {
- nameAndEmail = accounts[key].name;
- }
- accountSuggestions.push({
- name: nameAndEmail,
- value: accounts[key]._account_id,
- });
- }
- return accountSuggestions;
+ } else {
+ nameAndEmail = accounts[key].name;
+ }
+ accountSuggestions.push({
+ name: nameAndEmail,
+ value: accounts[key]._account_id,
});
+ }
+ return accountSuggestions;
+ });
},
_getGroupSuggestions(input) {
diff --git a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_test.html b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_test.html
index 15a59c8..bf9113b 100644
--- a/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-group-members/gr-group-members_test.html
@@ -169,10 +169,10 @@
.querySelectorAll('.nameColumn a')[0].href, includedGroups[0].url);
assert.equal(Polymer.dom(element.root)
.querySelectorAll('.nameColumn a')[1].href,
- 'https://test/site/group/url');
+ 'https://test/site/group/url');
assert.equal(Polymer.dom(element.root)
.querySelectorAll('.nameColumn a')[2].href,
- 'https://test/site/group/url');
+ 'https://test/site/group/url');
});
test('save members correctly', () => {
diff --git a/polygerrit-ui/app/elements/admin/gr-group/gr-group.js b/polygerrit-ui/app/elements/admin/gr-group/gr-group.js
index 68228b4..19cb45c 100644
--- a/polygerrit-ui/app/elements/admin/gr-group/gr-group.js
+++ b/polygerrit-ui/app/elements/admin/gr-group/gr-group.js
@@ -172,15 +172,15 @@
}
return this.$.restAPI.saveGroupOwner(this.groupId,
owner).then(config => {
- this._owner = false;
- });
+ this._owner = false;
+ });
},
_handleSaveDescription() {
return this.$.restAPI.saveGroupDescription(this.groupId,
this._groupConfig.description).then(config => {
- this._description = false;
- });
+ this._description = false;
+ });
},
_handleSaveOptions() {
@@ -190,8 +190,8 @@
return this.$.restAPI.saveGroupOptions(this.groupId,
options).then(config => {
- this._options = false;
- });
+ this._options = false;
+ });
},
_handleConfigName() {
diff --git a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.js b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.js
index b31aee6..c485b15 100644
--- a/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.js
+++ b/polygerrit-ui/app/elements/admin/gr-permission/gr-permission.js
@@ -228,7 +228,7 @@
_computeGroupName(groups, groupId) {
return groups && groups[groupId] && groups[groupId].name ?
- groups[groupId].name : groupId;
+ groups[groupId].name : groupId;
},
_getGroupSuggestions() {
diff --git a/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.js b/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.js
index b77f078..1dbfdc8 100644
--- a/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.js
+++ b/polygerrit-ui/app/elements/admin/gr-plugin-list/gr-plugin-list.js
@@ -92,11 +92,11 @@
return;
}
this._plugins = Object.keys(plugins)
- .map(key => {
- const plugin = plugins[key];
- plugin.name = key;
- return plugin;
- });
+ .map(key => {
+ const plugin = plugins[key];
+ plugin.name = key;
+ return plugin;
+ });
this._loading = false;
});
},
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 94bef54..18cd790 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
@@ -167,7 +167,7 @@
// current value appears. If there is no parent repo, it is
// initialized as an empty string.
this._inheritFromFilter = res.inherits_from ?
- this._inheritsFrom.name : '';
+ this._inheritsFrom.name : '';
this._local = res.local;
this._groups = res.groups;
this._weblinks = res.config_web_links || [];
@@ -370,11 +370,11 @@
};
const originalInheritsFromId = this._originalInheritsFrom ?
- this.singleDecodeURL(this._originalInheritsFrom.id) :
- null;
+ this.singleDecodeURL(this._originalInheritsFrom.id) :
+ null;
const inheritsFromId = this._inheritsFrom ?
- this.singleDecodeURL(this._inheritsFrom.id) :
- null;
+ this.singleDecodeURL(this._inheritsFrom.id) :
+ null;
const inheritFromChanged =
// Inherit from changed
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 8b1de6e..1660088 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
@@ -676,7 +676,7 @@
Polymer.dom(element.$$('gr-access-section').root).querySelectorAll(
'gr-permission')[2];
newPermission._handleAddRuleItem(
- {detail: {value: {id: 'Maintainers'}}});
+ {detail: {value: {id: 'Maintainers'}}});
assert.deepEqual(element._computeAddAndRemove(), expectedInput);
// Modify a section reference.
@@ -909,7 +909,7 @@
Polymer.dom(element.$$('gr-access-section').root).querySelectorAll(
'gr-permission')[1];
readPermission._handleAddRuleItem(
- {detail: {value: {id: 'Maintainers'}}});
+ {detail: {value: {id: 'Maintainers'}}});
expectedInput = {
add: {
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.js b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.js
index 92f15b0..95faaf8 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo-commands/gr-repo-commands.js
@@ -101,17 +101,17 @@
_handleEditRepoConfig() {
return this.$.restAPI.createChange(this.repo, CONFIG_BRANCH,
EDIT_CONFIG_SUBJECT, undefined, false, true).then(change => {
- const message = change ?
- CREATE_CHANGE_SUCCEEDED_MESSAGE :
- CREATE_CHANGE_FAILED_MESSAGE;
- this.dispatchEvent(new CustomEvent(
- 'show-alert',
- {detail: {message}, bubbles: true, composed: true}));
- if (!change) { return; }
+ const message = change ?
+ CREATE_CHANGE_SUCCEEDED_MESSAGE :
+ CREATE_CHANGE_FAILED_MESSAGE;
+ this.dispatchEvent(new CustomEvent(
+ 'show-alert',
+ {detail: {message}, bubbles: true, composed: true}));
+ if (!change) { return; }
- Gerrit.Nav.navigateToRelativeUrl(Gerrit.Nav.getEditUrlForDiff(
- change, CONFIG_PATH, INITIAL_PATCHSET));
- });
+ Gerrit.Nav.navigateToRelativeUrl(Gerrit.Nav.getEditUrlForDiff(
+ change, CONFIG_PATH, INITIAL_PATCHSET));
+ });
},
});
})();
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards.js b/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards.js
index 6e38566..71cc571 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo-dashboards/gr-repo-dashboards.js
@@ -49,7 +49,7 @@
// Group by ref and sort by id.
const dashboards = res.concat.apply([], res).sort((a, b) =>
- a.id < b.id ? -1 : 1);
+ a.id < b.id ? -1 : 1);
const dashboardsByRef = {};
dashboards.forEach(d => {
if (!dashboardsByRef[d.ref]) {
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.js b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.js
index da8ef52..9052322 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list.js
@@ -91,7 +91,7 @@
_determineIfOwner(repo) {
return this.$.restAPI.getRepoAccess(repo)
.then(access =>
- this._isOwner = access && !!access[repo].is_owner);
+ this._isOwner = access && !!access[repo].is_owner);
},
_paramsChanged(params) {
@@ -125,17 +125,17 @@
if (detailType === DETAIL_TYPES.BRANCHES) {
return this.$.restAPI.getRepoBranches(
filter, repo, itemsPerPage, offset, errFn).then(items => {
- if (!items) { return; }
- this._items = items;
- this._loading = false;
- });
+ if (!items) { return; }
+ this._items = items;
+ this._loading = false;
+ });
} else if (detailType === DETAIL_TYPES.TAGS) {
return this.$.restAPI.getRepoTags(
filter, repo, itemsPerPage, offset, errFn).then(items => {
- if (!items) { return; }
- this._items = items;
- this._loading = false;
- });
+ if (!items) { return; }
+ this._items = items;
+ this._loading = false;
+ });
}
},
@@ -174,7 +174,7 @@
_computeCanEditClass(ref, detailType, isOwner) {
return isOwner && this._stripRefs(ref, detailType) === 'HEAD' ?
- 'canEdit' : '';
+ 'canEdit' : '';
},
_handleEditRevision(e) {
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.html b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.html
index d8d4f7c..44d9b27 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-detail-list/gr-repo-detail-list_test.html
@@ -155,9 +155,9 @@
const cancelBtn = Polymer.dom(element.root).querySelector('.cancelBtn');
const editBtn = Polymer.dom(element.root).querySelector('.editBtn');
const revisionNoEditing = Polymer.dom(element.root)
- .querySelector('.revisionNoEditing');
+ .querySelector('.revisionNoEditing');
const revisionWithEditing = Polymer.dom(element.root)
- .querySelector('.revisionWithEditing');
+ .querySelector('.revisionWithEditing');
sandbox.stub(element, '_getLoggedIn').returns(Promise.resolve(true));
sandbox.stub(element.$.restAPI, 'getRepoAccess').returns(
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.html b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.html
index 52db4c2..5e82c1e 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-list/gr-repo-list.html
@@ -29,6 +29,20 @@
<template>
<style include="shared-styles"></style>
<style include="gr-table-styles"></style>
+ <style>
+ .genericList tr td:last-of-type {
+ text-align: left;
+ }
+ .genericList tr th:last-of-type {
+ text-align: left;
+ }
+ .readOnly {
+ text-align: center;
+ }
+ .changesLink, .name, .repositoryBrowser, .readOnly {
+ white-space:nowrap;
+ }
+ </style>
<gr-list-view
create-new=[[_createNewCapability]]
filter="[[_filter]]"
@@ -41,10 +55,10 @@
<table id="list" class="genericList">
<tr class="headerRow">
<th class="name topHeader">Repository Name</th>
- <th class="description topHeader">Repository Description</th>
- <th class="changesLink topHeader">Changes</th>
<th class="repositoryBrowser topHeader">Repository Browser</th>
- <th class="readOnly topHeader">Read only</th>
+ <th class="changesLink topHeader">Changes</th>
+ <th class="topHeader readOnly">Read only</th>
+ <th class="description topHeader">Repository Description</th>
</tr>
<tr id="loading" class$="loadingMsg [[computeLoadingClass(_loading)]]">
<td>Loading...</td>
@@ -55,8 +69,6 @@
<td class="name">
<a href$="[[_computeRepoUrl(item.name)]]">[[item.name]]</a>
</td>
- <td class="description">[[item.description]]</td>
- <td class="changesLink"><a href$="[[_computeChangesLink(item.name)]]">(view all)</a></td>
<td class="repositoryBrowser">
<template is="dom-repeat"
items="[[_computeWeblink(item)]]" as="link">
@@ -64,11 +76,13 @@
class="webLink"
rel="noopener"
target="_blank">
- ([[link.name]])
+ [[link.name]]
</a>
</template>
</td>
+ <td class="changesLink"><a href$="[[_computeChangesLink(item.name)]]">view all</a></td>
<td class="readOnly">[[_readOnly(item)]]</td>
+ <td class="description">[[item.description]]</td>
</tr>
</template>
</tbody>
diff --git a/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config_test.html b/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config_test.html
index 07da7c7..0a6846f 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo-plugin-config/gr-repo-plugin-config_test.html
@@ -54,7 +54,7 @@
{base: {config: {}}}), []);
assert.deepEqual(element._computePluginConfigOptions(
{base: {config: {testKey: 'testInfo'}}}),
- [{_key: 'testKey', info: 'testInfo'}]);
+ [{_key: 'testKey', info: 'testInfo'}]);
});
test('_computeDisabled', () => {
diff --git a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.js b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.js
index 918efba..153a137 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.js
+++ b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo.js
@@ -283,8 +283,8 @@
_handleSaveRepoConfig() {
return this.$.restAPI.saveRepoConfig(this.repo,
this._formatRepoConfigForSave(this._repoConfig)).then(() => {
- this._configChanged = false;
- });
+ this._configChanged = false;
+ });
},
_handleConfigChanged() {
@@ -327,7 +327,7 @@
command: commandObj[title]
.replace(/\$\{project\}/gi, encodeURI(repo))
.replace(/\$\{project-base-name\}/gi,
- encodeURI(repo.substring(repo.lastIndexOf('/') + 1))),
+ encodeURI(repo.substring(repo.lastIndexOf('/') + 1))),
});
}
return commands;
diff --git a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo_test.html b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo_test.html
index f22c5a5..4e81565 100644
--- a/polygerrit-ui/app/elements/admin/gr-repo/gr-repo_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-repo/gr-repo_test.html
@@ -367,7 +367,7 @@
element.$.matchAuthoredDateWithCommitterDateSelect.bindValue =
configInputObj.match_author_to_committer_date;
const inputElement = Polymer.Element ?
- element.$.maxGitObjSizeIronInput : element.$.maxGitObjSizeInput;
+ element.$.maxGitObjSizeIronInput : element.$.maxGitObjSizeInput;
inputElement.bindValue = configInputObj.max_object_size_limit;
element.$.contributorAgreementSelect.bindValue =
configInputObj.use_contributor_agreements;
diff --git a/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor_test.html b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor_test.html
index 4ea3817..6d533af 100644
--- a/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor_test.html
+++ b/polygerrit-ui/app/elements/admin/gr-rule-editor/gr-rule-editor_test.html
@@ -125,7 +125,7 @@
let permission = 'priority';
let label;
assert.deepEqual(element._getDefaultRuleValues(permission, label),
- {action: 'BATCH'});
+ {action: 'BATCH'});
permission = 'label-Code-Review';
label = {values: [
{value: -2, text: 'This shall not be merged'},
@@ -139,7 +139,7 @@
permission = 'push';
label = undefined;
assert.deepEqual(element._getDefaultRuleValues(permission, label),
- {action: 'ALLOW', force: false});
+ {action: 'ALLOW', force: false});
permission = 'submit';
assert.deepEqual(element._getDefaultRuleValues(permission, label),
{action: 'ALLOW'});
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html
index 1cddbc5..12b4202 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-item/gr-change-list-item_test.html
@@ -57,33 +57,34 @@
'cell label u-gray-background');
assert.equal(element._computeLabelClass(
{labels: {}}, 'Verified'), 'cell label u-gray-background');
- assert.equal(element._computeLabelClass(
- {labels: {Verified: {approved: true, value: 1}}}, 'Verified'),
+ assert.equal(
+ element._computeLabelClass(
+ {labels: {Verified: {approved: true, value: 1}}}, 'Verified'),
'cell label u-green u-monospace');
assert.equal(element._computeLabelClass(
{labels: {Verified: {rejected: true, value: -1}}}, 'Verified'),
- 'cell label u-monospace u-red');
+ 'cell label u-monospace u-red');
assert.equal(element._computeLabelClass(
{labels: {'Code-Review': {value: 1}}}, 'Code-Review'),
- 'cell label u-green u-monospace');
+ 'cell label u-green u-monospace');
assert.equal(element._computeLabelClass(
{labels: {'Code-Review': {value: -1}}}, 'Code-Review'),
- 'cell label u-monospace u-red');
+ 'cell label u-monospace u-red');
assert.equal(element._computeLabelClass(
{labels: {'Code-Review': {value: -1}}}, 'Verified'),
- 'cell label u-gray-background');
+ 'cell label u-gray-background');
assert.equal(element._computeLabelTitle({labels: {}}, 'Verified'),
'Label not applicable');
assert.equal(element._computeLabelTitle(
{labels: {Verified: {approved: {name: 'Diffy'}}}}, 'Verified'),
- 'Verified\nby Diffy');
+ 'Verified\nby Diffy');
assert.equal(element._computeLabelTitle(
{labels: {Verified: {approved: {name: 'Diffy'}}}}, 'Code-Review'),
- 'Label not applicable');
+ 'Label not applicable');
assert.equal(element._computeLabelTitle(
{labels: {Verified: {rejected: {name: 'Diffy'}}}}, 'Verified'),
- 'Verified\nby Diffy');
+ 'Verified\nby Diffy');
assert.equal(element._computeLabelTitle(
{labels: {'Code-Review': {disliked: {name: 'Diffy'}, value: -1}}},
'Code-Review'), 'Code-Review\nby Diffy');
@@ -93,19 +94,19 @@
assert.equal(element._computeLabelTitle(
{labels: {'Code-Review': {recommended: {name: 'Diffy'},
rejected: {name: 'Admin'}}}}, 'Code-Review'),
- 'Code-Review\nby Admin');
+ 'Code-Review\nby Admin');
assert.equal(element._computeLabelTitle(
{labels: {'Code-Review': {approved: {name: 'Diffy'},
rejected: {name: 'Admin'}}}}, 'Code-Review'),
- 'Code-Review\nby Admin');
+ 'Code-Review\nby Admin');
assert.equal(element._computeLabelTitle(
{labels: {'Code-Review': {recommended: {name: 'Diffy'},
disliked: {name: 'Admin'}, value: -1}}}, 'Code-Review'),
- 'Code-Review\nby Admin');
+ 'Code-Review\nby Admin');
assert.equal(element._computeLabelTitle(
{labels: {'Code-Review': {approved: {name: 'Diffy'},
disliked: {name: 'Admin'}, value: -1}}}, 'Code-Review'),
- 'Code-Review\nby Diffy');
+ 'Code-Review\nby Diffy');
assert.equal(element._computeLabelValue({labels: {}}), '');
assert.equal(element._computeLabelValue({labels: {}}, 'Verified'), '');
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.js b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.js
index e32c773..5006f1e 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.js
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list.js
@@ -169,7 +169,7 @@
this.showNumber = !!(preferences &&
preferences.legacycid_in_change_table);
this.visibleChangeTableColumns = preferences.change_table.length > 0 ?
- this.getVisibleColumns(preferences.change_table) : this.columnNames;
+ this.getVisibleColumns(preferences.change_table) : this.columnNames;
} else {
// Not logged in.
this.showNumber = false;
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html
index 87b1665..817de6f 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list/gr-change-list_test.html
@@ -113,7 +113,7 @@
test('computed fields', () => {
assert.equal(element._computeLabelNames(
- [{results: [{_number: 0, labels: {}}]}]).length, 0);
+ [{results: [{_number: 0, labels: {}}]}]).length, 0);
assert.equal(element._computeLabelNames([
{results: [
{_number: 0, labels: {Verified: {approved: {}}}},
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js
index aab0cba..b762ab3 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js
@@ -109,21 +109,21 @@
};
return this.$.restAPI.getDashboard(
project, dashboard, errFn).then(response => {
- if (!response) {
- return;
- }
+ if (!response) {
+ return;
+ }
+ return {
+ title: response.title,
+ sections: response.sections.map(section => {
+ const suffix = response.foreach ? ' ' + response.foreach : '';
return {
- title: response.title,
- sections: response.sections.map(section => {
- const suffix = response.foreach ? ' ' + response.foreach : '';
- return {
- name: section.name,
- query: (section.query + suffix).replace(
- PROJECT_PLACEHOLDER_PATTERN, project),
- };
- }),
+ name: section.name,
+ query: (section.query + suffix).replace(
+ PROJECT_PLACEHOLDER_PATTERN, project),
};
- });
+ }),
+ };
+ });
},
_computeTitle(user) {
@@ -156,11 +156,11 @@
this._loading = true;
const {project, dashboard, title, user, sections} = this.params;
const dashboardPromise = project ?
- this._getProjectDashboard(project, dashboard) :
- Promise.resolve(Gerrit.Nav.getUserDashboard(
- user,
- sections,
- title || this._computeTitle(user)));
+ this._getProjectDashboard(project, dashboard) :
+ Promise.resolve(Gerrit.Nav.getUserDashboard(
+ user,
+ sections,
+ title || this._computeTitle(user)));
const checkForNewUser = !project && user === 'self';
return dashboardPromise
@@ -194,8 +194,8 @@
const queries = res.sections
.map(section => section.suffixForDashboard ?
- section.query + ' ' + section.suffixForDashboard :
- section.query);
+ section.query + ' ' + section.suffixForDashboard :
+ section.query);
if (checkForNewUser) {
queries.push('owner:self limit:1');
@@ -215,7 +215,7 @@
results,
isOutgoing: res.sections[i].isOutgoing,
})).filter((section, i) => i < res.sections.length && (
- !res.sections[i].hideIfEmpty ||
+ !res.sections[i].hideIfEmpty ||
section.results.length));
});
},
diff --git a/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header.js b/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header.js
index 6942705..6afc169 100644
--- a/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header.js
+++ b/polygerrit-ui/app/elements/change-list/gr-user-header/gr-user-header.js
@@ -89,7 +89,7 @@
_computeDashboardLinkClass(showDashboardLink, loggedIn) {
return showDashboardLink && loggedIn ?
- 'dashboardLink' : 'dashboardLink hide';
+ 'dashboardLink' : 'dashboardLink hide';
},
});
})();
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
index 46b79b1..4783509 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.html
@@ -36,6 +36,7 @@
<link rel="import" href="../gr-confirm-move-dialog/gr-confirm-move-dialog.html">
<link rel="import" href="../gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.html">
<link rel="import" href="../gr-confirm-revert-dialog/gr-confirm-revert-dialog.html">
+<link rel="import" href="../gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog.html">
<link rel="import" href="../gr-confirm-submit-dialog/gr-confirm-submit-dialog.html">
<link rel="import" href="../../../styles/shared-styles.html">
@@ -208,6 +209,11 @@
on-confirm="_handleRevertDialogConfirm"
on-cancel="_handleConfirmDialogCancel"
hidden></gr-confirm-revert-dialog>
+ <gr-confirm-revert-submission-dialog id="confirmRevertSubmissionDialog"
+ class="confirmDialog"
+ on-confirm="_handleRevertSubmissionDialogConfirm"
+ on-cancel="_handleConfirmDialogCancel"
+ hidden></gr-confirm-revert-submission-dialog>
<gr-confirm-abandon-dialog id="confirmAbandonDialog"
class="confirmDialog"
on-confirm="_handleAbandonDialogConfirm"
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
index fe70239..0158627 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
@@ -64,6 +64,7 @@
REBASE_EDIT: 'rebaseEdit',
RESTORE: 'restore',
REVERT: 'revert',
+ REVERT_SUBMISSION: 'revert_submission',
REVIEWED: 'reviewed',
STOP_EDIT: 'stopEdit',
UNIGNORE: 'unignore',
@@ -86,6 +87,7 @@
rebase: 'Rebasing...',
restore: 'Restoring...',
revert: 'Reverting...',
+ revert_submission: 'Reverting Submission...',
submit: 'Submitting...',
};
@@ -180,6 +182,7 @@
ChangeActions.REBASE_EDIT,
ChangeActions.RESTORE,
ChangeActions.REVERT,
+ ChangeActions.REVERT_SUBMISSION,
ChangeActions.STOP_EDIT,
QUICK_APPROVE_ACTION.key,
RevisionActions.REBASE,
@@ -434,7 +437,7 @@
_getRebaseAction(revisionActions) {
return this._getRevisionAction(revisionActions, 'rebase',
- {rebaseOnCurrent: null}
+ {rebaseOnCurrent: null}
);
},
@@ -625,7 +628,7 @@
}
},
- /**
+ /**
* @param {string=} actionName
*/
_deleteAndNotify(actionName) {
@@ -906,6 +909,19 @@
this._showActionDialog(this.$.confirmRevertDialog);
},
+ _modifyRevertSubmissionMsg() {
+ return this.$.jsAPI.modifyRevertSubmissionMsg(this.change,
+ this.$.confirmRevertSubmissionDialog.message, this.commitMessage);
+ },
+
+ showRevertSubmissionDialog() {
+ this.$.confirmRevertSubmissionDialog.populateRevertSubmissionMessage(
+ this.commitMessage, this.change.current_revision);
+ this.$.confirmRevertSubmissionDialog.message =
+ this._modifyRevertSubmissionMsg();
+ this._showActionDialog(this.$.confirmRevertSubmissionDialog);
+ },
+
_handleActionTap(e) {
e.preventDefault();
let el = Polymer.dom(e).localTarget;
@@ -956,6 +972,9 @@
case ChangeActions.REVERT:
this.showRevertDialog();
break;
+ case ChangeActions.REVERT_SUBMISSION:
+ this.showRevertSubmissionDialog();
+ break;
case ChangeActions.ABANDON:
this._showActionDialog(this.$.confirmAbandonDialog);
break;
@@ -1118,6 +1137,14 @@
{message: el.message});
},
+ _handleRevertSubmissionDialogConfirm() {
+ const el = this.$.confirmRevertSubmissionDialog;
+ this.$.overlay.close();
+ el.hidden = true;
+ this._fireAction('/revert_submission', this.actions.revert_submission,
+ false, {message: el.message});
+ },
+
_handleAbandonDialogConfirm() {
const el = this.$.confirmAbandonDialog;
this.$.overlay.close();
@@ -1448,9 +1475,9 @@
_filterPrimaryActions(_topLevelActions) {
this._topLevelPrimaryActions = _topLevelActions.filter(action =>
- action.__primary);
+ action.__primary);
this._topLevelSecondaryActions = _topLevelActions.filter(action =>
- !action.__primary);
+ !action.__primary);
},
_computeMenuActions(actionRecord, hiddenActionsRecord) {
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
index 37201ac..c686127 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
@@ -378,7 +378,7 @@
element._handleRebaseConfirm({detail: {base: '1234'}});
rebaseAction.rebaseOnCurrent = true;
assert.deepEqual(fireActionStub.lastCall.args,
- ['/rebase', rebaseAction, true, {base: '1234'}]);
+ ['/rebase', rebaseAction, true, {base: '1234'}]);
done();
});
});
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata-it_test.html b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata-it_test.html
index 86f8aaf..b8bea9ca 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata-it_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata-it_test.html
@@ -89,6 +89,7 @@
teardown(() => {
sandbox.restore();
+ Gerrit._testOnly_resetPlugins();
});
suite('by default', () => {
@@ -113,7 +114,7 @@
js_resource_paths: [],
html_resource_paths: [
new URL('test/plugin.html?' + Math.random(),
- window.location.href).toString(),
+ window.location.href).toString(),
],
},
};
@@ -139,9 +140,9 @@
setup(() => {
Gerrit.install(p => plugin = p, '0.1',
new URL('test/plugin.html?' + Math.random(),
- window.location.href).toString());
+ window.location.href).toString());
sandbox.stub(Gerrit, '_arePluginsLoaded').returns(true);
- Gerrit._setPluginsPending([]);
+ Gerrit._loadPlugins([]);
element = createElement();
});
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
index 07eb59e..310a1a6 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
@@ -228,12 +228,12 @@
this._newHashtag = '';
this.$.restAPI.setChangeHashtag(
this.change._number, {add: [newHashtag]}).then(newHashtag => {
- this.set(['change', 'hashtags'], newHashtag);
- if (newHashtag !== lastHashtag) {
- this.dispatchEvent(new CustomEvent(
- 'hashtag-changed', {bubbles: true, composed: true}));
- }
- });
+ this.set(['change', 'hashtags'], newHashtag);
+ if (newHashtag !== lastHashtag) {
+ this.dispatchEvent(new CustomEvent(
+ 'hashtag-changed', {bubbles: true, composed: true}));
+ }
+ });
},
_computeTopicReadOnly(mutable, change) {
@@ -344,7 +344,7 @@
if (!this.change || !this.change.status) return '';
return Gerrit.Nav.getUrlForBranch(branch, project,
this.change.status == this.ChangeStatus.NEW ? 'open' :
- this.change.status.toLowerCase());
+ this.change.status.toLowerCase());
},
_computeTopicURL(topic) {
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
index fe09869..6f06fc8 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata_test.html
@@ -426,7 +426,7 @@
{current_revision: '789', revisions: {456: {commit: {parents}}}}));
assert.equal(element._computeParents(
{current_revision: '456', revisions: {456: {commit: {parents}}}}),
- parents);
+ parents);
});
test('_computeParentsLabel', () => {
@@ -725,7 +725,7 @@
},
'0.1',
'http://some/plugins/url.html');
- Gerrit._setPluginsCount(0);
+ Gerrit._loadPlugins([]);
flush(() => {
assert.strictEqual(hookEl.plugin, plugin);
assert.strictEqual(hookEl.change, element.change);
diff --git a/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.js b/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.js
index 8ec00dd..dfdcd59 100644
--- a/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.js
+++ b/polygerrit-ui/app/elements/change/gr-change-requirements/gr-change-requirements.js
@@ -135,8 +135,8 @@
_computeShowHideIcon(showOptionalLabels) {
return showOptionalLabels ?
- 'gr-icons:expand-less' :
- 'gr-icons:expand-more';
+ 'gr-icons:expand-less' :
+ 'gr-icons:expand-more';
},
_computeSectionClass(show) {
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
index e6ceea2..a293551 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
@@ -184,7 +184,7 @@
computed:
'_computeChangeIdCommitMessageError(_latestCommitMessage, _change)',
},
- /** @type {?} */
+ /** @type {?} */
_patchRange: {
type: Object,
},
@@ -456,16 +456,16 @@
this.$.commitMessageEditor.disabled = true;
this.$.restAPI.putChangeCommitMessage(
this._changeNum, message).then(resp => {
- this.$.commitMessageEditor.disabled = false;
- if (!resp.ok) { return; }
+ this.$.commitMessageEditor.disabled = false;
+ if (!resp.ok) { return; }
- this._latestCommitMessage = this._prepareCommitMsgForLinkify(
- message);
- this._editingCommitMessage = false;
- this._reloadWindow();
- }).catch(err => {
- this.$.commitMessageEditor.disabled = false;
- });
+ this._latestCommitMessage = this._prepareCommitMsgForLinkify(
+ message);
+ this._editingCommitMessage = false;
+ this._reloadWindow();
+ }).catch(err => {
+ this.$.commitMessageEditor.disabled = false;
+ });
},
_reloadWindow() {
@@ -819,7 +819,7 @@
_viewStateChanged(viewState) {
this._numFilesShown = viewState.numFilesShown ?
- viewState.numFilesShown : DEFAULT_NUM_FILES_SHOWN;
+ viewState.numFilesShown : DEFAULT_NUM_FILES_SHOWN;
},
_numFilesShownChanged(numFilesShown) {
@@ -937,7 +937,7 @@
// check that there is at least 2 parents otherwise fall back to 1,
// which means there is only one parent.
const parentCount = parentCounts.hasOwnProperty(1) ?
- parentCounts[1] : 1;
+ parentCounts[1] : 1;
const preferFirst = this._prefs &&
this._prefs.default_base_for_merges === 'FIRST_PARENT';
@@ -1327,10 +1327,10 @@
_getLatestCommitMessage() {
return this.$.restAPI.getChangeCommitInfo(this._changeNum,
this.computeLatestPatchNum(this._allPatchSets)).then(commitInfo => {
- if (!commitInfo) return Promise.resolve();
- this._latestCommitMessage =
+ if (!commitInfo) return Promise.resolve();
+ this._latestCommitMessage =
this._prepareCommitMsgForLinkify(commitInfo.message);
- });
+ });
},
_getLatestRevisionSHA(change) {
@@ -1376,7 +1376,7 @@
this._changeComments = comments;
this._diffDrafts = Object.assign({}, this._changeComments.drafts);
this._commentThreads = this._changeComments.getAllThreadsForChange()
- .map(c => Object.assign({}, c));
+ .map(c => Object.assign({}, c));
});
},
@@ -1800,7 +1800,7 @@
*/
_handleEditTap() {
const editInfo = Object.values(this._change.revisions).find(info =>
- info._number === this.EDIT_NAME);
+ info._number === this.EDIT_NAME);
if (editInfo) {
Gerrit.Nav.navigateToChange(this._change, this.EDIT_NAME);
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
index f9c7798..e00bab5 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
@@ -81,7 +81,7 @@
});
element = fixture('basic');
sandbox.stub(element.$.actions, 'reload').returns(Promise.resolve());
- Gerrit._setPluginsCount(0);
+ Gerrit._loadPlugins([]);
});
teardown(done => {
@@ -176,7 +176,7 @@
assert.isFalse(element.$.replyOverlay.opened);
assert(openSpy.lastCall.calledWithExactly(
element.$.replyDialog.FocusTarget.ANY),
- '_openReplyDialog should have been passed ANY');
+ '_openReplyDialog should have been passed ANY');
assert.equal(openSpy.callCount, 1);
done();
});
@@ -1046,7 +1046,7 @@
MockInteractions.tap(element.$.replyBtn);
assert(openStub.lastCall.calledWithExactly(
element.$.replyDialog.FocusTarget.ANY),
- '_openReplyDialog should have been passed ANY');
+ '_openReplyDialog should have been passed ANY');
assert.equal(openStub.callCount, 1);
});
@@ -1058,7 +1058,7 @@
{message: {message: 'text'}});
assert(openStub.lastCall.calledWithExactly(
element.$.replyDialog.FocusTarget.BODY),
- '_openReplyDialog should have been passed BODY');
+ '_openReplyDialog should have been passed BODY');
assert.equal(openStub.callCount, 1);
done();
});
@@ -1483,7 +1483,7 @@
test('_computeEditMode', () => {
const callCompute = (range, params) =>
- element._computeEditMode({base: range}, {base: params});
+ element._computeEditMode({base: range}, {base: params});
assert.isFalse(callCompute({}, {}));
assert.isTrue(callCompute({}, {edit: true}));
assert.isFalse(callCompute({basePatchNum: 'PARENT', patchNum: 1}, {}));
@@ -1703,7 +1703,7 @@
element._patchRange = {patchNum: 1};
element.$.actions.dispatchEvent(new CustomEvent('stop-edit-tap',
- {bubbles: false}));
+ {bubbles: false}));
});
suite('plugin endpoints', () => {
diff --git a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.js b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.js
index cbc7e42..42cb976 100644
--- a/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.js
+++ b/polygerrit-ui/app/elements/change/gr-comment-list/gr-comment-list.js
@@ -45,7 +45,7 @@
_computeDiffLineURL(file, changeNum, patchNum, comment) {
const basePatchNum = comment.hasOwnProperty('parent') ?
- -comment.parent : null;
+ -comment.parent : null;
return Gerrit.Nav.getUrlForDiffById(this.changeNum, this.projectName,
file, patchNum, basePatchNum, comment.line,
this._isOnParent(comment));
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.js b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.js
index bf84003..5278540 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-confirm-cherrypick-dialog/gr-confirm-cherrypick-dialog.js
@@ -98,21 +98,21 @@
}
return this.$.restAPI.getRepoBranches(
input, this.project, SUGGESTIONS_LIMIT).then(response => {
- const branches = [];
- let branch;
- for (const key in response) {
- if (!response.hasOwnProperty(key)) { continue; }
- if (response[key].ref.startsWith('refs/heads/')) {
- branch = response[key].ref.substring('refs/heads/'.length);
- } else {
- branch = response[key].ref;
- }
- branches.push({
- name: branch,
- });
- }
- return branches;
+ const branches = [];
+ let branch;
+ for (const key in response) {
+ if (!response.hasOwnProperty(key)) { continue; }
+ if (response[key].ref.startsWith('refs/heads/')) {
+ branch = response[key].ref.substring('refs/heads/'.length);
+ } else {
+ branch = response[key].ref;
+ }
+ branches.push({
+ name: branch,
});
+ }
+ return branches;
+ });
},
});
})();
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.js b/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.js
index 23123c3..c6b0adf 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-confirm-move-dialog/gr-confirm-move-dialog.js
@@ -68,21 +68,21 @@
}
return this.$.restAPI.getRepoBranches(
input, this.project, SUGGESTIONS_LIMIT).then(response => {
- const branches = [];
- let branch;
- for (const key in response) {
- if (!response.hasOwnProperty(key)) { continue; }
- if (response[key].ref.startsWith('refs/heads/')) {
- branch = response[key].ref.substring('refs/heads/'.length);
- } else {
- branch = response[key].ref;
- }
- branches.push({
- name: branch,
- });
- }
- return branches;
+ const branches = [];
+ let branch;
+ for (const key in response) {
+ if (!response.hasOwnProperty(key)) { continue; }
+ if (response[key].ref.startsWith('refs/heads/')) {
+ branch = response[key].ref.substring('refs/heads/'.length);
+ } else {
+ branch = response[key].ref;
+ }
+ branches.push({
+ name: branch,
});
+ }
+ return branches;
+ });
},
});
})();
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.js b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.js
index 1b884e7..54ce271 100644
--- a/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-confirm-rebase-dialog/gr-confirm-rebase-dialog.js
@@ -82,7 +82,7 @@
_getChangeSuggestions(input) {
return this._getRecentChanges().then(changes =>
- this._filterChanges(input, changes));
+ this._filterChanges(input, changes));
},
_filterChanges(input, changes) {
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog.html b/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog.html
new file mode 100644
index 0000000..b334989
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog.html
@@ -0,0 +1,71 @@
+<!--
+@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.
+-->
+
+<link rel="import" href="/bower_components/iron-autogrow-textarea/iron-autogrow-textarea.html">
+<link rel="import" href="/bower_components/polymer/polymer.html">
+<link rel="import" href="../../../behaviors/fire-behavior/fire-behavior.html">
+<link rel="import" href="../../shared/gr-dialog/gr-dialog.html">
+<link rel="import" href="../../../styles/shared-styles.html">
+
+<dom-module id="gr-confirm-revert-submission-dialog">
+ <template>
+ <!-- TODO(taoalpha): move all shared styles to a style module. -->
+ <style include="shared-styles">
+ :host {
+ display: block;
+ }
+ :host([disabled]) {
+ opacity: .5;
+ pointer-events: none;
+ }
+ label {
+ cursor: pointer;
+ display: block;
+ width: 100%;
+ }
+ iron-autogrow-textarea {
+ font-family: var(--monospace-font-family);
+ padding: 0;
+ width: 73ch; /* Add a char to account for the border. */
+
+ --iron-autogrow-textarea {
+ border: 1px solid var(--border-color);
+ box-sizing: border-box;
+ font-family: var(--monospace-font-family);
+ }
+ }
+ </style>
+ <gr-dialog
+ confirm-label="Revert Submission"
+ on-confirm="_handleConfirmTap"
+ on-cancel="_handleCancelTap">
+ <div class="header" slot="header">Revert Submission</div>
+ <div class="main" slot="main">
+ <label for="messageInput">
+ Revert Commit Message
+ </label>
+ <iron-autogrow-textarea
+ id="messageInput"
+ class="message"
+ autocomplete="on"
+ max-rows="15"
+ bind-value="{{message}}"></iron-autogrow-textarea>
+ </div>
+ </gr-dialog>
+ </template>
+ <script src="gr-confirm-revert-submission-dialog.js"></script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog.js b/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog.js
new file mode 100644
index 0000000..6cbbb37
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog.js
@@ -0,0 +1,69 @@
+/**
+ * @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() {
+ 'use strict';
+
+ const ERR_COMMIT_NOT_FOUND =
+ 'Unable to find the commit hash of this change.';
+
+ Polymer({
+ is: 'gr-confirm-revert-submission-dialog',
+
+ /**
+ * Fired when the confirm button is pressed.
+ *
+ * @event confirm
+ */
+
+ /**
+ * Fired when the cancel button is pressed.
+ *
+ * @event cancel
+ */
+
+ properties: {
+ message: String,
+ },
+
+ behaviors: [
+ Gerrit.FireBehavior,
+ ],
+
+ populateRevertSubmissionMessage(message, commitHash) {
+ // Follow the same convention of the revert
+ const revertTitle = 'Revert submission';
+ if (!commitHash) {
+ this.fire('show-alert', {message: ERR_COMMIT_NOT_FOUND});
+ return;
+ }
+ this.message = `${revertTitle}\n\n` +
+ `Reason for revert: <INSERT REASONING HERE>\n`;
+ },
+
+ _handleConfirmTap(e) {
+ e.preventDefault();
+ e.stopPropagation();
+ this.fire('confirm', null, {bubbles: false});
+ },
+
+ _handleCancelTap(e) {
+ e.preventDefault();
+ e.stopPropagation();
+ this.fire('cancel', null, {bubbles: false});
+ },
+ });
+})();
diff --git a/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog_test.html b/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog_test.html
new file mode 100644
index 0000000..c0f2dc3
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-confirm-revert-submission-dialog/gr-confirm-revert-submission-dialog_test.html
@@ -0,0 +1,100 @@
+<!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-confirm-revert-submission-dialog</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="gr-confirm-revert-submission-dialog.html">
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+ <template>
+ <gr-confirm-revert-submission-dialog>
+ </gr-confirm-revert-submission-dialog>
+ </template>
+</test-fixture>
+
+<script>
+ suite('gr-confirm-revert-submission-dialog tests', () => {
+ let element;
+ let sandbox;
+
+ setup(() => {
+ element = fixture('basic');
+ sandbox =sinon.sandbox.create();
+ });
+
+ teardown(() => sandbox.restore());
+
+ test('no match', () => {
+ assert.isNotOk(element.message);
+ const alertStub = sandbox.stub();
+ element.addEventListener('show-alert', alertStub);
+ element.populateRevertSubmissionMessage(
+ 'not a commitHash in sight'
+ );
+ assert.isTrue(alertStub.calledOnce);
+ });
+
+ test('single line', () => {
+ assert.isNotOk(element.message);
+ element.populateRevertSubmissionMessage(
+ 'one line commit\n\nChange-Id: abcdefg\n',
+ 'abcd123');
+ const expected = 'Revert submission\n\n' +
+ 'Reason for revert: <INSERT REASONING HERE>\n';
+ assert.equal(element.message, expected);
+ });
+
+ test('multi line', () => {
+ assert.isNotOk(element.message);
+ element.populateRevertSubmissionMessage(
+ 'many lines\ncommit\n\nmessage\n\nChange-Id: abcdefg\n',
+ 'abcd123');
+ const expected = 'Revert submission\n\n' +
+ 'Reason for revert: <INSERT REASONING HERE>\n';
+ assert.equal(element.message, expected);
+ });
+
+ test('issue above change id', () => {
+ assert.isNotOk(element.message);
+ element.populateRevertSubmissionMessage(
+ 'test \nvery\n\ncommit\n\nBug: Issue 42\nChange-Id: abcdefg\n',
+ 'abcd123');
+ const expected = 'Revert submission\n\n' +
+ 'Reason for revert: <INSERT REASONING HERE>\n';
+ assert.equal(element.message, expected);
+ });
+
+ test('revert a revert', () => {
+ assert.isNotOk(element.message);
+ element.populateRevertSubmissionMessage(
+ 'Revert "one line commit"\n\nChange-Id: abcdefg\n',
+ 'abcd123');
+ const expected = 'Revert submission\n\n' +
+ 'Reason for revert: <INSERT REASONING HERE>\n';
+ assert.equal(element.message, expected);
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.js b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.js
index 90cc60f..b297a14 100644
--- a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog.js
@@ -157,7 +157,7 @@
for (const rev of Object.values(change.revisions || {})) {
if (this.patchNumEquals(rev._number, patchNum)) {
const parentLength = rev.commit && rev.commit.parents ?
- rev.commit.parents.length : 0;
+ rev.commit.parents.length : 0;
return parentLength == 0;
}
}
diff --git a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.html b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.html
index de02923..82574808 100644
--- a/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-download-dialog/gr-download-dialog_test.html
@@ -182,7 +182,7 @@
test('computed fields', () => {
assert.equal(element._computeArchiveDownloadLink(
{project: 'test/project', _number: 123}, 2, 'tgz'),
- '/changes/test%2Fproject~123/revisions/2/archive?format=tgz');
+ '/changes/test%2Fproject~123/revisions/2/archive?format=tgz');
});
test('close event', done => {
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.js b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.js
index 2126433..b8095bf 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.js
+++ b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.js
@@ -148,7 +148,7 @@
const rev = this.getRevisionByPatchNum(change.revisions, patchNum);
this._patchsetDescription = (rev && rev.description) ?
- rev.description.substring(0, PATCH_DESC_MAX_LENGTH) : '';
+ rev.description.substring(0, PATCH_DESC_MAX_LENGTH) : '';
},
_handleDescriptionRemoved(e) {
@@ -257,7 +257,7 @@
_computeUploadHelpContainerClass(change, account) {
const changeIsMerged = change && change.status === MERGED_STATUS;
const ownerId = change && change.owner && change.owner._account_id ?
- change.owner._account_id : null;
+ change.owner._account_id : null;
const userId = account && account._account_id;
const userIsOwner = ownerId && userId && ownerId === userId;
const hideContainer = !userIsOwner || changeIsMerged;
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
index 4903467..6c44694 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
@@ -839,7 +839,7 @@
_computeShowHideIcon(path, expandedFilesRecord) {
return this._isFileExpanded(path, expandedFilesRecord) ?
- 'gr-icons:expand-less' : 'gr-icons:expand-more';
+ 'gr-icons:expand-less' : 'gr-icons:expand-more';
},
_computeFiles(filesByPath, changeComments, patchRange, reviewed, loading) {
@@ -879,7 +879,7 @@
}
const previousNumFilesShown = this._shownFiles ?
- this._shownFiles.length : 0;
+ this._shownFiles.length : 0;
const filesShown = files.slice(0, numFilesShown);
this.fire('files-shown-changed', {length: filesShown.length});
@@ -954,7 +954,7 @@
const rev = this.getRevisionByPatchNum(revisions, patchNum);
return (rev && rev.description) ?
- rev.description.substring(0, PATCH_DESC_MAX_LENGTH) : '';
+ rev.description.substring(0, PATCH_DESC_MAX_LENGTH) : '';
},
/**
@@ -966,7 +966,7 @@
_computeFileStatusLabel(status) {
const statusCode = this._computeFileStatus(status);
return FileStatus.hasOwnProperty(statusCode) ?
- FileStatus[statusCode] : 'Status Unknown';
+ FileStatus[statusCode] : 'Status Unknown';
},
_isFileExpanded(path, expandedFilesRecord) {
@@ -998,7 +998,7 @@
// Clear content for any diffs that are not open so if they get re-opened
// the stale content does not flash before it is cleared and reloaded.
const collapsedDiffs = this.diffs.filter(diff =>
- this._expandedFilePaths.indexOf(diff.path) === -1);
+ this._expandedFilePaths.indexOf(diff.path) === -1);
this._clearCollapsedDiffs(collapsedDiffs);
if (!record) { return; } // Happens after "Collapse all" clicked.
@@ -1008,9 +1008,9 @@
// Find the paths introduced by the new index splices:
const newPaths = record.indexSplices
- .map(splice => splice.object.slice(
- splice.index, splice.index + splice.addedCount))
- .reduce((acc, paths) => acc.concat(paths), []);
+ .map(splice => splice.object.slice(
+ splice.index, splice.index + splice.addedCount))
+ .reduce((acc, paths) => acc.concat(paths), []);
// Required so that the newly created diff view is included in this.diffs.
Polymer.dom.flush();
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
index 122291e..4c102a2 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
@@ -440,10 +440,10 @@
'/COMMIT_MSG', 'comment'), '3 comments (1 unresolved)');
assert.equal(
element._computeCommentsStringMobile(element.changeComments, parentTo1
- , '/COMMIT_MSG'), '2c');
+ , '/COMMIT_MSG'), '2c');
assert.equal(
element._computeCommentsStringMobile(element.changeComments, _1To2
- , '/COMMIT_MSG'), '3c');
+ , '/COMMIT_MSG'), '3c');
assert.equal(
element._computeDraftsString(element.changeComments, parentTo1,
'unresolved.file'), '1 draft');
@@ -639,7 +639,7 @@
assert(navStub.lastCall.calledWith(element.change,
'file_added_in_rev2.txt', '2'),
- 'Should navigate to /c/42/2/file_added_in_rev2.txt');
+ 'Should navigate to /c/42/2/file_added_in_rev2.txt');
MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
@@ -1639,7 +1639,7 @@
element.set('_filesByPath', _filesByPath);
flushAsynchronousOperations();
- // Navigates when a file is selected.
+ // Navigates when a file is selected.
element._openSelectedFile();
assert.isTrue(navStub.called);
});
@@ -1701,7 +1701,7 @@
const editControls =
Array.from(
Polymer.dom(element.root)
- .querySelectorAll('.row:not(.header-row)'))
+ .querySelectorAll('.row:not(.header-row)'))
.map(row => row.querySelector('gr-edit-file-controls'));
assert.isTrue(editControls[0].classList.contains('invisible'));
});
diff --git a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.js b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.js
index 0b888c4..76e6e64 100644
--- a/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.js
+++ b/polygerrit-ui/app/elements/change/gr-label-score-row/gr-label-score-row.js
@@ -123,7 +123,7 @@
if (!labels[label.name]) { return null; }
const labelValue = this._getLabelValue(labels, permittedLabels, label);
const len = permittedLabels[label.name] != null ?
- permittedLabels[label.name].length : 0;
+ permittedLabels[label.name].length : 0;
for (let i = 0; i < len; i++) {
const val = permittedLabels[label.name][i];
if (val === labelValue) {
@@ -154,7 +154,7 @@
_computeHiddenClass(permittedLabels, label) {
return !this._computeAnyPermittedLabelValues(permittedLabels, label) ?
- 'hidden' : '';
+ 'hidden' : '';
},
_computePermittedLabelValues(permittedLabels, label) {
diff --git a/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores_test.html b/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores_test.html
index f986a58..b8d471c 100644
--- a/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores_test.html
+++ b/polygerrit-ui/app/elements/change/gr-label-scores/gr-label-scores_test.html
@@ -123,7 +123,7 @@
const labelName = 'Code-Review';
assert.strictEqual(element._getVoteForAccount(
element.change.labels, labelName, element.account),
- '+1');
+ '+1');
});
test('_computeColumns', () => {
@@ -187,10 +187,10 @@
{name: 'Verified', value: null}
]);
element.set(['change', 'labels', 'Verified', 'all'],
- [{_account_id: 123, value: 1}]);
+ [{_account_id: 123, value: 1}]);
assert.deepEqual(element._labels, [
- {name: 'Code-Review', value: null},
- {name: 'Verified', value: '+1'},
+ {name: 'Code-Review', value: null},
+ {name: 'Verified', value: '+1'},
]);
});
});
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js
index ba4a1a9..d90132d 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list.js
@@ -102,8 +102,8 @@
el.set('message.expanded', true);
let top = el.offsetTop;
for (let offsetParent = el.offsetParent;
- offsetParent;
- offsetParent = offsetParent.offsetParent) {
+ offsetParent;
+ offsetParent = offsetParent.offsetParent) {
top += offsetParent.offsetTop;
}
window.scrollTo(0, top);
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js
index 76c5980..18a136e 100644
--- a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js
+++ b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js
@@ -368,7 +368,7 @@
_computeSubmittedTogetherClass(submittedTogether) {
if (!submittedTogether || (
- submittedTogether.changes.length === 0 &&
+ submittedTogether.changes.length === 0 &&
!submittedTogether.non_visible_changes)) {
return 'hidden';
}
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.html b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.html
index 06b7a5d..f04d40e 100644
--- a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list_test.html
@@ -321,7 +321,7 @@
sandbox.stub(element, '_getCherryPicks')
.returns(Promise.resolve());
conflictsStub = sandbox.stub(element, '_getConflicts')
- .returns(Promise.resolve());
+ .returns(Promise.resolve());
});
test('request conflicts if open and mergeable', () => {
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
index cc7fbe6..b2b1588 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
@@ -87,7 +87,7 @@
* @event comment-refresh
*/
- /**
+ /**
* Fires when the state of the send button (enabled/disabled) changes.
*
* @event send-disabled-changed
@@ -249,7 +249,7 @@
this.fetchChangeUpdates(this.change, this.$.restAPI)
.then(result => {
this.knownLatestState = result.isLatest ?
- LatestPatchState.LATEST : LatestPatchState.NOT_LATEST;
+ LatestPatchState.LATEST : LatestPatchState.NOT_LATEST;
});
this._focusOn(opt_focusTarget);
@@ -394,16 +394,16 @@
return this.$.restAPI.removeChangeReviewer(this.change._number,
account._account_id).then(response => {
- if (!response.ok) { return response; }
+ if (!response.ok) { return response; }
- const reviewers = this.change.reviewers[type] || [];
- for (let i = 0; i < reviewers.length; i++) {
- if (reviewers[i]._account_id == account._account_id) {
- this.splice(`change.reviewers.${type}`, i, 1);
- break;
- }
- }
- });
+ const reviewers = this.change.reviewers[type] || [];
+ for (let i = 0; i < reviewers.length; i++) {
+ if (reviewers[i]._account_id == account._account_id) {
+ this.splice(`change.reviewers.${type}`, i, 1);
+ break;
+ }
+ }
+ });
},
_mapReviewer(reviewer) {
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
index db98041..76f6f0b 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog_test.html
@@ -414,8 +414,8 @@
}).then(() => {
assert.isFalse(isVisible(element.$.reviewerConfirmationOverlay));
const additions = cc ?
- element.$.ccs.additions() :
- element.$.reviewers.additions();
+ element.$.ccs.additions() :
+ element.$.reviewers.additions();
assert.deepEqual(
additions,
[
@@ -843,7 +843,7 @@
// Send and purge and verify moves, delete cc3.
element.send()
.then(keepReviewers =>
- element._purgeReviewersPendingRemove(false, keepReviewers))
+ element._purgeReviewersPendingRemove(false, keepReviewers))
.then(() => {
assert.deepEqual(
mutations, [
@@ -1084,7 +1084,7 @@
/* reviewersMutated= */ false,
/* labelsChanged= */ false,
/* includeComments= */ false,
- /* disabled= */ false,
+ /* disabled= */ false
));
assert.isTrue(fn(
/* buttonLabel= */ 'Send',
@@ -1093,7 +1093,7 @@
/* reviewersMutated= */ false,
/* labelsChanged= */ false,
/* includeComments= */ false,
- /* disabled= */ false,
+ /* disabled= */ false
));
// Mock nonempty comment draft array, with seding comments.
assert.isFalse(fn(
@@ -1103,7 +1103,7 @@
/* reviewersMutated= */ false,
/* labelsChanged= */ false,
/* includeComments= */ true,
- /* disabled= */ false,
+ /* disabled= */ false
));
// Mock nonempty comment draft array, without seding comments.
assert.isTrue(fn(
@@ -1113,7 +1113,7 @@
/* reviewersMutated= */ false,
/* labelsChanged= */ false,
/* includeComments= */ false,
- /* disabled= */ false,
+ /* disabled= */ false
));
// Mock nonempty change message.
assert.isFalse(fn(
@@ -1123,7 +1123,7 @@
/* reviewersMutated= */ false,
/* labelsChanged= */ false,
/* includeComments= */ false,
- /* disabled= */ false,
+ /* disabled= */ false
));
// Mock reviewers mutated.
assert.isFalse(fn(
@@ -1133,7 +1133,7 @@
/* reviewersMutated= */ true,
/* labelsChanged= */ false,
/* includeComments= */ false,
- /* disabled= */ false,
+ /* disabled= */ false
));
// Mock labels changed.
assert.isFalse(fn(
@@ -1143,7 +1143,7 @@
/* reviewersMutated= */ false,
/* labelsChanged= */ true,
/* includeComments= */ false,
- /* disabled= */ false,
+ /* disabled= */ false
));
// Whole dialog is disabled.
assert.isTrue(fn(
@@ -1153,7 +1153,7 @@
/* reviewersMutated= */ false,
/* labelsChanged= */ true,
/* includeComments= */ false,
- /* disabled= */ true,
+ /* disabled= */ true
));
});
diff --git a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_test.html b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_test.html
index 80359e0..e3601be 100644
--- a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_test.html
@@ -196,7 +196,7 @@
element.maxReviewersDisplayed = 5;
for (let i = 0; i < 6; i++) {
reviewers.push(
- {email: i+'reviewer@google.com', name: 'reviewer-' + i});
+ {email: i+'reviewer@google.com', name: 'reviewer-' + i});
}
element.ccsOnly = true;
@@ -219,7 +219,7 @@
element.maxReviewersDisplayed = 5;
for (let i = 0; i < 7; i++) {
reviewers.push(
- {email: i+'reviewer@google.com', name: 'reviewer-' + i});
+ {email: i+'reviewer@google.com', name: 'reviewer-' + i});
}
element.ccsOnly = true;
@@ -242,7 +242,7 @@
const reviewers = [];
for (let i = 0; i < 7; i++) {
reviewers.push(
- {email: i+'reviewer@google.com', name: 'reviewer-' + i});
+ {email: i+'reviewer@google.com', name: 'reviewer-' + i});
}
element.ccsOnly = true;
@@ -265,7 +265,7 @@
element.maxReviewersDisplayed = 5;
for (let i = 0; i < 100; i++) {
reviewers.push(
- {email: i+'reviewer@google.com', name: 'reviewer-' + i});
+ {email: i+'reviewer@google.com', name: 'reviewer-' + i});
}
element.ccsOnly = true;
@@ -298,7 +298,7 @@
},
Bar: {
all: [{_account_id: 1, permitted_voting_range: {max: 1}},
- {_account_id: 7, permitted_voting_range: {max: 1}}],
+ {_account_id: 7, permitted_voting_range: {max: 1}}],
},
FooBar: {
all: [{_account_id: 7, value: 0}],
diff --git a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.js b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.js
index be2f0ea..518a013 100644
--- a/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.js
+++ b/polygerrit-ui/app/elements/change/gr-thread-list/gr-thread-list.js
@@ -132,8 +132,8 @@
const lastNonDraftComment =
(lastComment.__draft && thread.comments.length > 1) ?
- thread.comments[thread.comments.length - 2] :
- lastComment;
+ thread.comments[thread.comments.length - 2] :
+ lastComment;
return {
thread,
diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.html b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.html
index ef14370..ea0056f 100644
--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.html
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header_test.html
@@ -78,7 +78,7 @@
'none');
assert.notEqual(getComputedStyle(element.$$('gr-account-dropdown'))
.display,
- 'none');
+ 'none');
assert.notEqual(getComputedStyle(element.$$('.settingsButton')).display,
'none');
});
@@ -116,28 +116,28 @@
/* userLinks= */[],
adminLinks,
/* topMenus= */[],
- /* docBaseUrl= */ '',
+ /* docBaseUrl= */ ''
),
- defaultLinks.concat({
- title: 'Browse',
- links: adminLinks,
- }));
+ defaultLinks.concat({
+ title: 'Browse',
+ links: adminLinks,
+ }));
assert.deepEqual(element._computeLinks(
defaultLinks,
userLinks,
adminLinks,
/* topMenus= */[],
/* docBaseUrl= */ ''
- ),
- defaultLinks.concat([
- {
- title: 'Your',
- links: userLinks,
- },
- {
- title: 'Browse',
- links: adminLinks,
- }])
+ ),
+ defaultLinks.concat([
+ {
+ title: 'Your',
+ links: userLinks,
+ },
+ {
+ title: 'Browse',
+ links: adminLinks,
+ }])
);
});
@@ -185,7 +185,7 @@
/* userLinks= */ [],
adminLinks,
topMenus,
- /* baseDocUrl= */ '',
+ /* baseDocUrl= */ ''
), [{
title: 'Browse',
links: adminLinks,
@@ -221,7 +221,7 @@
/* userLinks= */ [],
adminLinks,
topMenus,
- /* baseDocUrl= */ '',
+ /* baseDocUrl= */ ''
), [{
title: 'Browse',
links: adminLinks,
@@ -260,7 +260,7 @@
/* userLinks= */ [],
adminLinks,
topMenus,
- /* baseDocUrl= */ '',
+ /* baseDocUrl= */ ''
), [{
title: 'Browse',
links: adminLinks,
@@ -297,7 +297,7 @@
/* userLinks= */ [],
/* adminLinks= */ [],
topMenus,
- /* baseDocUrl= */ '',
+ /* baseDocUrl= */ ''
), [{
title: 'Faves',
links: defaultLinks[0].links.concat([{
@@ -328,7 +328,7 @@
userLinks,
/* adminLinks= */ [],
topMenus,
- /* baseDocUrl= */ '',
+ /* baseDocUrl= */ ''
), [{
title: 'Your',
links: userLinks.concat([{
@@ -359,7 +359,7 @@
/* userLinks= */ [],
adminLinks,
topMenus,
- /* baseDocUrl= */ '',
+ /* baseDocUrl= */ ''
), [{
title: 'Browse',
links: adminLinks.concat([{
diff --git a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html
index 9eaf603..1d8f04b 100644
--- a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html
+++ b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html
@@ -732,11 +732,11 @@
getUserDashboard(user = 'self', sections = DEFAULT_SECTIONS,
title = '') {
sections = sections
- .filter(section => (user === 'self' || !section.selfOnly))
- .map(section => Object.assign({}, section, {
- name: section.name,
- query: section.query.replace(USER_PLACEHOLDER_PATTERN, user),
- }));
+ .filter(section => (user === 'self' || !section.selfOnly))
+ .map(section => Object.assign({}, section, {
+ name: section.name,
+ query: section.query.replace(USER_PLACEHOLDER_PATTERN, user),
+ }));
return {title, sections};
},
};
diff --git a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js
index 30d322e..6306933 100644
--- a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js
+++ b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting.js
@@ -41,14 +41,6 @@
DETECTED: 'Extension detected',
};
- // Page visibility related constants.
- const PAGE_VISIBILITY = {
- TYPE: 'lifecycle',
- CATEGORY: 'Page Visibility',
- // Reported events - alphabetize below.
- STARTED_HIDDEN: 'hidden',
- };
-
// Navigation reporting constants.
const NAVIGATION = {
TYPE: 'nav-report',
@@ -107,8 +99,10 @@
const pending = [];
+ // Variables that hold context info in global scope
const loadedPlugins = [];
const detectedExtensions = [];
+ let reportRepoName = undefined;
const onError = function(oldOnError, msg, url, line, column, error) {
if (oldOnError) {
@@ -188,7 +182,13 @@
reporter(...args) {
const report = (this._isMetricsPluginLoaded() && !pending.length) ?
this.defaultReporter : this.cachingReporter;
- args.splice(4, 0, loadedPlugins, detectedExtensions);
+ const contextInfo = {
+ loadedPlugins,
+ detectedExtensions,
+ repoName: reportRepoName,
+ isInBackgroundTab: document.visibilityState === 'hidden',
+ };
+ args.splice(4, 0, contextInfo);
report.apply(this, args);
},
@@ -198,22 +198,27 @@
* @param {string} category
* @param {string} eventName
* @param {string|number} eventValue
- * @param {Array} plugins
- * @param {Array} extensions
+ * @param {Object} contextInfo
* @param {boolean|undefined} opt_noLog If true, the event will not be
* logged to the JS console.
*/
- defaultReporter(type, category, eventName, eventValue,
- loadedPlugins, detectedExtensions, opt_noLog) {
+ defaultReporter(type, category, eventName, eventValue, contextInfo,
+ opt_noLog) {
const detail = {
type,
category,
name: eventName,
value: eventValue,
};
- if (category === TIMING.CATEGORY_UI_LATENCY) {
- detail.loadedPlugins = loadedPlugins;
- detail.detectedExtensions = detectedExtensions;
+ if (category === TIMING.CATEGORY_UI_LATENCY && contextInfo) {
+ detail.loadedPlugins = contextInfo.loadedPlugins;
+ detail.detectedExtensions = contextInfo.detectedExtensions;
+ }
+ if (contextInfo && contextInfo.repoName) {
+ detail.repoName = contextInfo.repoName;
+ }
+ if (contextInfo && contextInfo.isInBackgroundTab !== undefined) {
+ detail.inBackgroundTab = contextInfo.isInBackgroundTab;
}
document.dispatchEvent(new CustomEvent(type, {detail}));
if (opt_noLog) { return; }
@@ -235,39 +240,34 @@
* @param {string} category
* @param {string} eventName
* @param {string|number} eventValue
- * @param {Array} plugins
- * @param {Array} extensions
+ * @param {Object} contextInfo
* @param {boolean|undefined} opt_noLog If true, the event will not be
* logged to the JS console.
*/
- cachingReporter(type, category, eventName, eventValue,
- plugins, extensions, opt_noLog) {
+ cachingReporter(type, category, eventName, eventValue, contextInfo,
+ opt_noLog) {
if (type === ERROR.TYPE && category === ERROR.CATEGORY) {
console.error(eventValue && eventValue.error || eventName);
}
if (this._isMetricsPluginLoaded()) {
if (pending.length) {
for (const args of pending.splice(0)) {
- this.reporter(...args);
+ this.defaultReporter(...args);
}
}
- this.reporter(type, category, eventName, eventValue,
- plugins, extensions, opt_noLog);
+ this.defaultReporter(type, category, eventName, eventValue, contextInfo,
+ opt_noLog);
} else {
- pending.push([type, category, eventName, eventValue,
- plugins, extensions, opt_noLog]);
+ pending.push([type, category, eventName, eventValue, contextInfo,
+ opt_noLog]);
}
},
/**
* User-perceived app start time, should be reported when the app is ready.
*/
- appStarted(hidden) {
+ appStarted() {
this.timeEnd(TIMING.APP_STARTED);
- if (hidden) {
- this.reporter(PAGE_VISIBILITY.TYPE, PAGE_VISIBILITY.CATEGORY,
- PAGE_VISIBILITY.STARTED_HIDDEN);
- }
},
/**
@@ -296,6 +296,7 @@
this.time(TIMER.DIFF_VIEW_DISPLAYED);
this.time(TIMER.DIFF_VIEW_LOAD_FULL);
this.time(TIMER.FILE_LIST_DISPLAYED);
+ reportRepoName = undefined;
},
locationChanged(page) {
@@ -518,6 +519,10 @@
this.reporter(ERROR_DIALOG.TYPE, ERROR_DIALOG.CATEGORY,
'ErrorDialog: ' + message, {error: new Error(message)});
},
+
+ setRepoName(repoName) {
+ reportRepoName = repoName;
+ },
});
window.GrReporting = GrReporting;
diff --git a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html
index c357979..163bba5 100644
--- a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html
+++ b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html
@@ -64,15 +64,11 @@
test('appStarted', () => {
sandbox.stub(element, 'now').returns(42);
- element.appStarted(true);
+ element.appStarted();
assert.isTrue(
element.reporter.calledWithExactly(
'timing-report', 'UI Latency', 'App Started', 42
- ));
- assert.isTrue(
- element.reporter.calledWithExactly(
- 'lifecycle', 'Page Visibility', 'hidden'
- ));
+ ));
});
test('WebComponentsReady', () => {
@@ -289,9 +285,9 @@
// element.pluginLoaded('foo');
element.time('timeAction');
element.timeEnd('timeAction');
- assert.isTrue(element.defaultReporter.getCall(1).calledWith(
+ assert.isTrue(element.defaultReporter.getCall(1).calledWithMatch(
'timing-report', 'UI Latency', 'timeAction', 0,
- ['metrics-xyz1']
+ {loadedPlugins: ['metrics-xyz1']}
));
});
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router.js b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
index b0fc5b3..6b979e6 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.js
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
@@ -461,8 +461,8 @@
// If there is a repo name provided, make sure to substitute it into the
// ${repo} (or legacy ${project}) query tokens.
const query = opt_repoName ?
- section.query.replace(REPO_TOKEN_PATTERN, opt_repoName) :
- section.query;
+ section.query.replace(REPO_TOKEN_PATTERN, opt_repoName) :
+ section.query;
return encodeURIComponent(section.name) + '=' +
encodeURIComponent(query);
});
@@ -1042,11 +1042,13 @@
},
_handleProjectDashboardRoute(data) {
+ const project = data.params[0];
this._setParams({
view: Gerrit.Nav.View.DASHBOARD,
- project: data.params[0],
+ project,
dashboard: decodeURIComponent(data.params[1]),
});
+ this.$.reporting.setRepoName(project);
},
_handleGroupInfoRoute(data) {
@@ -1121,27 +1123,33 @@
},
_handleRepoCommandsRoute(data) {
+ const repo = data.params[0];
this._setParams({
view: Gerrit.Nav.View.REPO,
detail: Gerrit.Nav.RepoDetailView.COMMANDS,
- repo: data.params[0],
+ repo,
});
+ this.$.reporting.setRepoName(repo);
},
_handleRepoAccessRoute(data) {
+ const repo = data.params[0];
this._setParams({
view: Gerrit.Nav.View.REPO,
detail: Gerrit.Nav.RepoDetailView.ACCESS,
- repo: data.params[0],
+ repo,
});
+ this.$.reporting.setRepoName(repo);
},
_handleRepoDashboardsRoute(data) {
+ const repo = data.params[0];
this._setParams({
view: Gerrit.Nav.View.REPO,
detail: Gerrit.Nav.RepoDetailView.DASHBOARDS,
- repo: data.params[0],
+ repo,
});
+ this.$.reporting.setRepoName(repo);
},
_handleBranchListOffsetRoute(data) {
@@ -1242,10 +1250,12 @@
},
_handleRepoRoute(data) {
+ const repo = data.params[0];
this._setParams({
view: Gerrit.Nav.View.REPO,
- repo: data.params[0],
+ repo,
});
+ this.$.reporting.setRepoName(repo);
},
_handlePluginListOffsetRoute(data) {
@@ -1307,6 +1317,7 @@
view: Gerrit.Nav.View.CHANGE,
};
+ this.$.reporting.setRepoName(params.project);
this._redirectOrNavigate(params);
},
@@ -1326,7 +1337,7 @@
params.leftSide = address.leftSide;
params.lineNum = address.lineNum;
}
-
+ this.$.reporting.setRepoName(params.project);
this._redirectOrNavigate(params);
},
@@ -1368,24 +1379,28 @@
_handleDiffEditRoute(ctx) {
// Parameter order is based on the regex group number matched.
+ const project = ctx.params[0];
this._redirectOrNavigate({
- project: ctx.params[0],
+ project,
changeNum: ctx.params[1],
patchNum: ctx.params[2],
path: ctx.params[3],
view: Gerrit.Nav.View.EDIT,
});
+ this.$.reporting.setRepoName(project);
},
_handleChangeEditRoute(ctx) {
// Parameter order is based on the regex group number matched.
+ const project = ctx.params[0];
this._redirectOrNavigate({
- project: ctx.params[0],
+ project,
changeNum: ctx.params[1],
patchNum: ctx.params[3],
view: Gerrit.Nav.View.CHANGE,
edit: true,
});
+ this.$.reporting.setRepoName(project);
},
/**
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.html b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.html
index 2fe940c..a4927c3 100644
--- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.html
+++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar_test.html
@@ -61,8 +61,8 @@
getActiveElement = () => {
return document.activeElement.shadowRoot ?
- document.activeElement.shadowRoot.activeElement :
- document.activeElement;
+ document.activeElement.shadowRoot.activeElement :
+ document.activeElement;
};
test('enter in search input fires event', done => {
diff --git a/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search.js b/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search.js
index 94de00c..7dff30b 100644
--- a/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search.js
+++ b/polygerrit-ui/app/elements/core/gr-smart-search/gr-smart-search.js
@@ -144,8 +144,8 @@
return accounts.map(account => ({
label: account.name || '',
text: account.email ?
- `${predicate}:${account.email}` :
- `${predicate}:"${this._accountOrAnon(account)}"`,
+ `${predicate}:${account.email}` :
+ `${predicate}:"${this._accountOrAnon(account)}"`,
}));
},
});
diff --git a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.js b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.js
index efed78d..1ac307f 100644
--- a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.js
+++ b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api.js
@@ -248,9 +248,9 @@
const all = comments.concat(drafts).concat(robotComments);
const baseComments = all.filter(c =>
- this._isInBaseOfPatchRange(c, patchRange));
+ this._isInBaseOfPatchRange(c, patchRange));
const revisionComments = all.filter(c =>
- this._isInRevisionOfPatchRange(c, patchRange));
+ this._isInRevisionOfPatchRange(c, patchRange));
return {
meta: {
@@ -348,7 +348,7 @@
const threads = this.getCommentThreads(this._sortComments(comments));
const unresolvedThreads = threads
- .filter(thread =>
+ .filter(thread =>
thread.comments.length &&
thread.comments[thread.comments.length - 1].unresolved);
@@ -491,7 +491,7 @@
return Promise.all(promises).then(([comments, robotComments, drafts]) => {
this._changeComments = new ChangeComments(comments,
- robotComments, drafts, changeNum);
+ robotComments, drafts, changeNum);
return this._changeComments;
});
},
diff --git a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api_test.html b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api_test.html
index c44e8c4..47181f9 100644
--- a/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-comment-api/gr-comment-api_test.html
@@ -109,7 +109,7 @@
let draftStub;
setup(() => {
commentStub = sandbox.stub(element.$.restAPI, 'getDiffComments')
- .returns(Promise.resolve({}));
+ .returns(Promise.resolve({}));
robotCommentStub = sandbox.stub(element.$.restAPI,
'getDiffRobotComments').returns(Promise.resolve({}));
draftStub = sandbox.stub(element.$.restAPI, 'getDiffDrafts')
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.js b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.js
index 0a51d92..144cc56 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.js
@@ -105,7 +105,7 @@
let tr = content.parentElement.parentElement;
while (tr = tr.nextSibling) {
if (tr.classList.contains('both') || (
- (side === 'left' && tr.classList.contains('remove')) ||
+ (side === 'left' && tr.classList.contains('remove')) ||
(side === 'right' && tr.classList.contains('add')))) {
return tr.querySelector('.contentText');
}
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html
index 69c2419..2a57162 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.html
@@ -219,8 +219,8 @@
getLineNumberByChild(node) {
const lineEl = this.getLineElByChild(node);
return lineEl ?
- parseInt(lineEl.getAttribute('data-value'), 10) :
- null;
+ parseInt(lineEl.getAttribute('data-value'), 10) :
+ null;
},
getContentByLine(lineNumber, opt_side, opt_root) {
@@ -249,7 +249,7 @@
getSideByLineEl(lineEl) {
return lineEl.classList.contains(GrDiffBuilder.Side.RIGHT) ?
- GrDiffBuilder.Side.RIGHT : GrDiffBuilder.Side.LEFT;
+ GrDiffBuilder.Side.RIGHT : GrDiffBuilder.Side.LEFT;
},
emitGroup(group, sectionEl) {
@@ -304,7 +304,7 @@
let builder = null;
if (this.isImageDiff) {
builder = new GrDiffBuilderImage(diff, prefs, this.diffElement,
- this.baseImage, this.revisionImage);
+ this.baseImage, this.revisionImage);
} else if (diff.binary) {
// If the diff is binary, but not an image.
return new GrDiffBuilderBinary(diff, prefs, this.diffElement);
@@ -352,8 +352,8 @@
// If endIndex isn't present, continue to the end of the line.
const endIndex = highlight.endIndex === undefined ?
- line.text.length :
- highlight.endIndex;
+ line.text.length :
+ highlight.endIndex;
GrAnnotation.annotateElement(
contentEl,
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js
index c25e90c..5eb8b09 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder.js
@@ -183,7 +183,7 @@
continue;
}
const lineNumber = opt_side === 'left' ?
- line.beforeNumber : line.afterNumber;
+ line.beforeNumber : line.afterNumber;
if (lineNumber < start || lineNumber > end) { continue; }
if (out_lines) { out_lines.push(line); }
@@ -292,6 +292,7 @@
e.detail = {
groups,
section,
+ numLines,
};
// Let it bubble up the DOM tree.
});
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.js b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.js
index a53b86e..67c562c 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.js
@@ -196,7 +196,7 @@
if (!this.diffRow) return null;
const hostOwner = Polymer.dom(/** @type {Node} */ (this.diffRow))
- .getOwnerRoot();
+ .getOwnerRoot();
if (hostOwner && hostOwner.host &&
hostOwner.host.tagName === 'GR-DIFF') {
return hostOwner.host;
@@ -232,8 +232,8 @@
if (!this.diffRow) {
// does not scroll during init unless requested
const scrollingBehaviorForInit = this.initialLineNumber ?
- ScrollBehavior.KEEP_VISIBLE :
- ScrollBehavior.NEVER;
+ ScrollBehavior.KEEP_VISIBLE :
+ ScrollBehavior.NEVER;
this._scrollBehavior = scrollingBehaviorForInit;
this.reInitCursor();
}
@@ -315,7 +315,7 @@
if (this._getViewMode() === DiffViewMode.SIDE_BY_SIDE &&
this._isTargetBlank()) {
this.side = this.side === DiffSides.LEFT ?
- DiffSides.RIGHT : DiffSides.LEFT;
+ DiffSides.RIGHT : DiffSides.LEFT;
}
},
@@ -391,15 +391,15 @@
splice = changeRecord.indexSplices[spliceIdx];
for (i = splice.index;
- i < splice.index + splice.addedCount;
- i++) {
+ i < splice.index + splice.addedCount;
+ i++) {
this.listen(this.diffs[i], 'render-start', '_handleDiffRenderStart');
this.listen(this.diffs[i], 'render-content', 'handleDiffUpdate');
}
for (i = 0;
- i < splice.removed && splice.removed.length;
- i++) {
+ i < splice.removed && splice.removed.length;
+ i++) {
this.unlisten(splice.removed[i],
'render-start', '_handleDiffRenderStart');
this.unlisten(splice.removed[i],
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-annotation_test.html b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-annotation_test.html
index 86d5e45..c1bf3ed 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-annotation_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-annotation_test.html
@@ -168,7 +168,7 @@
assert.equal(layer4[0].textContent +
layer4[1].textContent +
layer4[2].textContent,
- layers[3]);
+ layers[3]);
});
test('splitTextNode', () => {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.js b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.js
index 5472489..82c3a8b 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight.js
@@ -137,7 +137,7 @@
}
return this.commentRanges.findIndex(commentRange =>
- commentRange.side === side && rangesEqual(commentRange.range, range));
+ commentRange.side === side && rangesEqual(commentRange.range, range));
},
/**
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.html b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.html
index 7ca5f5e..c929e1e 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-highlight/gr-diff-highlight_test.html
@@ -272,12 +272,12 @@
};
const getActionRange = () =>
- Polymer.dom(element.root).querySelector(
- 'gr-selection-action-box').range;
+ Polymer.dom(element.root).querySelector(
+ 'gr-selection-action-box').range;
const getActionSide = () =>
- Polymer.dom(element.root).querySelector(
- 'gr-selection-action-box').side;
+ Polymer.dom(element.root).querySelector(
+ 'gr-selection-action-box').side;
const getLineElByChild = node => {
const stubs = contentStubs.find(stub => stub.contentTd.contains(node));
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.js b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.js
index 54398b8..6167e88 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host.js
@@ -250,6 +250,7 @@
'render-content': '_handleRenderContent',
'normalize-range': '_handleNormalizeRange',
+ 'diff-context-expanded': '_handleDiffContextExpanded',
},
observers: [
@@ -271,11 +272,11 @@
},
/**
- * @param {boolean=} haveParamsChanged ends reporting events that started
- * on location change.
+ * @param {boolean=} shouldReportMetric indicate a new Diff Page. This is a
+ * signal to report metrics event that started on location change.
* @return {!Promise}
**/
- reload(haveParamsChanged) {
+ reload(shouldReportMetric) {
this._loading = true;
this._errorMessage = null;
const whitespaceLevel = this._getIgnoreWhitespace();
@@ -288,7 +289,7 @@
}
this._layers = layers;
- if (haveParamsChanged) {
+ if (shouldReportMetric) {
// We listen on render viewport only on DiffPage (on paramsChanged)
this._listenToViewportRender();
}
@@ -349,6 +350,11 @@
resolve();
}
this.removeEventListener('render', callback);
+ if (shouldReportMetric) {
+ // We report diffViewContentDisplayed only on reload caused
+ // by params changed - expected only on Diff Page.
+ this.$.reporting.diffViewContentDisplayed();
+ }
};
this.addEventListener('render', callback);
this.diff = diff;
@@ -511,7 +517,7 @@
// digits. Diffs with no delta are considered 0%.
const totalDelta = rebaseDelta + nonRebaseDelta;
const percentRebaseDelta = !totalDelta ? 0 :
- Math.round(100 * rebaseDelta / totalDelta);
+ Math.round(100 * rebaseDelta / totalDelta);
// Report the due_to_rebase percentage in the "diff" category when
// applicable.
@@ -588,7 +594,7 @@
// thread and append to it.
if (comment.in_reply_to) {
const thread = threads.find(thread =>
- thread.comments.some(c => c.id === comment.in_reply_to));
+ thread.comments.some(c => c.id === comment.in_reply_to));
if (thread) {
thread.comments.push(comment);
continue;
@@ -729,7 +735,7 @@
}
function matchesRange(threadEl) {
const threadRange = /** @type {!Gerrit.Range} */(
- JSON.parse(threadEl.getAttribute('range')));
+ JSON.parse(threadEl.getAttribute('range')));
return Gerrit.rangesEqual(threadRange, range);
}
@@ -780,7 +786,7 @@
matchers.push(matchesFileComment);
}
return threadEls.filter(threadEl =>
- matchers.some(matcher => matcher(threadEl)));
+ matchers.some(matcher => matcher(threadEl)));
},
_getIgnoreWhitespace() {
@@ -832,7 +838,7 @@
*/
_computeParentIndex(patchRangeRecord) {
return this.isMergeParent(patchRangeRecord.base.basePatchNum) ?
- this.getParentIndex(patchRangeRecord.base.basePatchNum) : null;
+ this.getParentIndex(patchRangeRecord.base.basePatchNum) : null;
},
_handleCommentSave(e) {
@@ -926,8 +932,8 @@
if (!diff) return false;
return diff.content.some(section => {
const lines = section.ab ?
- section.ab :
- (section.a || []).concat(section.b || []);
+ section.ab :
+ (section.a || []).concat(section.b || []);
return lines.some(line => line.length >= SYNTAX_MAX_LINE_LENGTH);
});
},
@@ -950,7 +956,6 @@
_handleRenderContent() {
this.$.reporting.timeEnd(TimingLabel.CONTENT);
- this.$.reporting.diffViewContentDisplayed();
},
_handleNormalizeRange(event) {
@@ -958,5 +963,10 @@
`Modified invalid comment range on l. ${event.detail.lineNum}` +
` of the ${event.detail.side} side`);
},
+
+ _handleDiffContextExpanded(event) {
+ this.$.reporting.reportInteraction(
+ 'diff-context-expanded', event.detail.numLines);
+ },
});
})();
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.html b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.html
index 16b7728..88b1e1c 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-host/gr-diff-host_test.html
@@ -320,7 +320,7 @@
element.patchRange = {};
element.$.restAPI.getDiffPreferences().then(prefs => {
element.prefs = prefs;
- return element.reload();
+ return element.reload(true);
});
// Multiple cascading microtasks are scheduled.
setTimeout(() => {
@@ -509,7 +509,7 @@
'getB64FileContents',
(changeId, patchNum, path, opt_parentIndex) => {
return Promise.resolve(opt_parentIndex === 1 ? mockFile1 :
- mockFile2);
+ mockFile2);
});
element.patchRange = {basePatchNum: 'PARENT', patchNum: 1};
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.js b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.js
index 4716544..58cc46f 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-processor/gr-diff-processor.js
@@ -290,7 +290,7 @@
if (this.context !== WHOLE_FILE) {
const hiddenStart = state.chunkIndex === 0 ? 0 : this.context;
const hiddenEnd = lineCount - (
- firstUncollapsibleChunkIndex === chunks.length ?
+ firstUncollapsibleChunkIndex === chunks.length ?
0 : this.context);
groups = GrDiffGroup.hideInContextControl(
groups, hiddenStart, hiddenEnd);
@@ -483,7 +483,7 @@
if (chunk.common && chunk.a.length != chunk.b.length) {
throw new Error(
- 'DiffContent with common=true must always have equal length');
+ 'DiffContent with common=true must always have equal length');
}
const numLines = this._commonChunkLength(chunk);
const chunkEnds = this._findChunkEndsAtKeyLocations(
@@ -494,7 +494,7 @@
if (chunk.ab) {
result.push(...this._splitAtChunkEnds(chunk.ab, chunkEnds)
.map(({lines, keyLocation}) =>
- Object.assign({}, chunk, {ab: lines, keyLocation})));
+ Object.assign({}, chunk, {ab: lines, keyLocation})));
} else if (chunk.common) {
const aChunks = this._splitAtChunkEnds(chunk.a, chunkEnds);
const bChunks = this._splitAtChunkEnds(chunk.b, chunkEnds);
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js
index 055b200..c35c304 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-selection/gr-diff-selection.js
@@ -79,8 +79,8 @@
this._setClasses([
SelectionClass.COMMENT,
node.commentSide === 'left' ?
- SelectionClass.LEFT :
- SelectionClass.RIGHT,
+ SelectionClass.LEFT :
+ SelectionClass.RIGHT,
]);
return true;
}
@@ -106,8 +106,8 @@
const side = this.diffBuilder.getSideByLineEl(lineEl);
targetClasses.push(side === 'left' ?
- SelectionClass.LEFT :
- SelectionClass.RIGHT);
+ SelectionClass.LEFT :
+ SelectionClass.RIGHT);
if (commentSelected) {
targetClasses.push(SelectionClass.COMMENT);
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
index 8158c96..a3ddaf7 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
@@ -272,8 +272,8 @@
const patchRange = patchRangeRecord.base;
return this.$.restAPI.getChangeFilePathsAsSpeciallySortedArray(
changeNum, patchRange).then(files => {
- this._fileList = files;
- });
+ this._fileList = files;
+ });
},
_getDiffPreferences() {
@@ -566,8 +566,8 @@
let idx = fileList.indexOf(path);
if (idx === -1) {
const file = direction > 0 ?
- fileList[0] :
- fileList[fileList.length - 1];
+ fileList[0] :
+ fileList[fileList.length - 1];
return {path: file};
}
@@ -706,8 +706,8 @@
// is specified.
this._getReviewedStatus(this.editMode, this._changeNum,
this._patchRange.patchNum, this._path).then(status => {
- this.$.reviewed.checked = status;
- });
+ this.$.reviewed.checked = status;
+ });
return;
}
@@ -1138,7 +1138,7 @@
// so we resolve the right "next" file.
const unreviewedFiles = this._fileList
.filter(file =>
- (file === this._path || !this._reviewedFiles.has(file)));
+ (file === this._path || !this._reviewedFiles.has(file)));
this._navToFile(this._path, unreviewedFiles, 1);
},
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
index 6427844..573d75f 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
@@ -269,28 +269,28 @@
assert.isTrue(element._loading);
assert(diffNavStub.lastCall.calledWithExactly(element._change,
'wheatley.md', '10', '5'),
- 'Should navigate to /c/42/5..10/wheatley.md');
+ 'Should navigate to /c/42/5..10/wheatley.md');
element._path = 'wheatley.md';
MockInteractions.pressAndReleaseKeyOn(element, 219, null, '[');
assert.isTrue(element._loading);
assert(diffNavStub.lastCall.calledWithExactly(element._change,
'glados.txt', '10', '5'),
- 'Should navigate to /c/42/5..10/glados.txt');
+ 'Should navigate to /c/42/5..10/glados.txt');
element._path = 'glados.txt';
MockInteractions.pressAndReleaseKeyOn(element, 219, null, '[');
assert.isTrue(element._loading);
assert(diffNavStub.lastCall.calledWithExactly(element._change, 'chell.go',
'10', '5'),
- 'Should navigate to /c/42/5..10/chell.go');
+ 'Should navigate to /c/42/5..10/chell.go');
element._path = 'chell.go';
MockInteractions.pressAndReleaseKeyOn(element, 219, null, '[');
assert.isTrue(element._loading);
assert(changeNavStub.lastCall.calledWithExactly(element._change, '10',
'5'),
- 'Should navigate to /c/42/5..10');
+ 'Should navigate to /c/42/5..10');
});
test('keyboard shortcuts with old patch number', () => {
@@ -332,13 +332,13 @@
MockInteractions.pressAndReleaseKeyOn(element, 221, null, ']');
assert(diffNavStub.lastCall.calledWithExactly(element._change,
'wheatley.md', '1', PARENT),
- 'Should navigate to /c/42/1/wheatley.md');
+ 'Should navigate to /c/42/1/wheatley.md');
element._path = 'wheatley.md';
MockInteractions.pressAndReleaseKeyOn(element, 219, null, '[');
assert(diffNavStub.lastCall.calledWithExactly(element._change,
'glados.txt', '1', PARENT),
- 'Should navigate to /c/42/1/glados.txt');
+ 'Should navigate to /c/42/1/glados.txt');
element._path = 'glados.txt';
MockInteractions.pressAndReleaseKeyOn(element, 219, null, '[');
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-group.js b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-group.js
index 02ca7e5..7c42fa2 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-group.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-group.js
@@ -185,11 +185,11 @@
if (before.length) {
beforeGroups.push(before.length === group.lines.length ?
- group : group.cloneWithLines(before));
+ group : group.cloneWithLines(before));
}
if (after.length) {
afterGroups.push(after.length === group.lines.length ?
- group : group.cloneWithLines(after));
+ group : group.cloneWithLines(after));
}
}
return [beforeGroups, afterGroups];
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 fc3862b..9c38718 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
@@ -118,6 +118,14 @@
* @event render
*/
+ /**
+ * Fired for interaction reporting when a diff context is expanded.
+ * Contains an event.detail with numLines about the number of lines that
+ * were expanded.
+ *
+ * @event diff-context-expanded
+ */
+
properties: {
changeNum: String,
noAutoRender: {
@@ -169,7 +177,7 @@
observer: '_viewModeObserver',
},
- /** @type ?Gerrit.LineOfInterest */
+ /** @type ?Gerrit.LineOfInterest */
lineOfInterest: Object,
loading: {
@@ -324,8 +332,8 @@
// up the diff, because they are in the shadow DOM of the gr-diff element.
// This takes the shadow DOM selection if one exists.
return this.root.getSelection ?
- this.root.getSelection() :
- document.getSelection();
+ this.root.getSelection() :
+ document.getSelection();
},
_observeNodes() {
@@ -471,6 +479,9 @@
const el = Polymer.dom(e).localTarget;
if (el.classList.contains('showContext')) {
+ this.fire('diff-context-expanded', {
+ numLines: e.detail.numLines,
+ });
this.$.diffBuilder.showContext(e.detail.groups, e.detail.section);
} else if (el.classList.contains('lineNum')) {
this.addDraftAtLine(el);
@@ -524,8 +535,8 @@
return false;
}
const patchNum = el.classList.contains(DiffSide.LEFT) ?
- this.patchRange.basePatchNum :
- this.patchRange.patchNum;
+ this.patchRange.basePatchNum :
+ this.patchRange.patchNum;
const isEdit = this.patchNumEquals(patchNum, this.EDIT_NAME);
const isEditBase = this.patchNumEquals(patchNum, this.PARENT_NAME) &&
@@ -892,8 +903,8 @@
chunkIndex--;
chunk = diff.content[chunkIndex];
} while (
- // We haven't reached the beginning.
- chunkIndex >= 0 &&
+ // We haven't reached the beginning.
+ chunkIndex >= 0 &&
// The chunk doesn't have both sides.
!chunk.ab &&
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 09342e1..ec2f5f1 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
@@ -1070,7 +1070,7 @@
/* loading= */ false,
element.prefs,
element._diffLength
- ));
+ ));
});
test('do not show the message if still loading', () => {
@@ -1079,7 +1079,7 @@
/* loading= */ true,
element.prefs,
element._diffLength
- ));
+ ));
});
test('do not show the message if contains valid changes', () => {
@@ -1098,7 +1098,7 @@
/* loading= */ false,
element.prefs,
element._diffLength
- ));
+ ));
});
test('do not show message if ignore whitespace is disabled', () => {
@@ -1116,7 +1116,7 @@
/* loading= */ false,
element.prefs,
element._diffLength
- ));
+ ));
});
});
@@ -1129,7 +1129,7 @@
element = fixture('basic');
element.prefs = {};
renderStub = sandbox.stub(element.$.diffBuilder, 'render')
- .returns(Promise.resolve());
+ .returns(Promise.resolve());
element.addEventListener('render', event => {
assert.isTrue(event.detail.contentRendered);
done();
diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.js b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.js
index cdc5f30..2dae7ed 100644
--- a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.js
+++ b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select.js
@@ -80,7 +80,7 @@
const parentCounts = revisionInfo.getParentCountMap();
const currentParentCount = parentCounts.hasOwnProperty(patchNum) ?
- parentCounts[patchNum] : 1;
+ parentCounts[patchNum] : 1;
const maxParents = revisionInfo.getMaxParents();
const isMerge = currentParentCount > 1;
@@ -246,7 +246,7 @@
_computePatchSetDescription(revisions, patchNum, opt_addFrontSpace) {
const rev = this.getRevisionByPatchNum(revisions, patchNum);
return (rev && rev.description) ?
- (opt_addFrontSpace ? ' ' : '') +
+ (opt_addFrontSpace ? ' ' : '') +
rev.description.substring(0, PATCH_DESC_MAX_LENGTH) : '';
},
diff --git a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.html b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.html
index 66fa974..ee893ee 100644
--- a/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-patch-range-select/gr-patch-range-select_test.html
@@ -185,7 +185,7 @@
assert.deepEqual(element._computeBaseDropdownContent(availablePatches,
patchNum, sortedRevisions, element.changeComments,
element.revisionInfo),
- expectedResult);
+ expectedResult);
});
test('_computeBaseDropdownContent called when patchNum updates', () => {
@@ -344,7 +344,7 @@
assert.deepEqual(element._computePatchDropdownContent(availablePatches,
basePatchNum, sortedRevisions, element.changeComments),
- expectedResult);
+ expectedResult);
});
test('filesWeblinks', () => {
diff --git a/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer.js b/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer.js
index c7c9b9d..2e74ff4 100644
--- a/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer.js
+++ b/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer.js
@@ -66,12 +66,12 @@
annotate(el, lineNumberEl, line) {
let ranges = [];
if (line.type === GrDiffLine.Type.REMOVE || (
- line.type === GrDiffLine.Type.BOTH &&
+ line.type === GrDiffLine.Type.BOTH &&
el.getAttribute('data-side') !== 'right')) {
ranges = ranges.concat(this._getRangesForLine(line, 'left'));
}
if (line.type === GrDiffLine.Type.ADD || (
- line.type === GrDiffLine.Type.BOTH &&
+ line.type === GrDiffLine.Type.BOTH &&
el.getAttribute('data-side') !== 'left')) {
ranges = ranges.concat(this._getRangesForLine(line, 'right'));
}
@@ -134,7 +134,7 @@
this._updateRangesMap(
side, range, hovering, (forLine, start, end, hovering) => {
const index = forLine.findIndex(lineRange =>
- lineRange.start === start && lineRange.end === end);
+ lineRange.start === start && lineRange.end === end);
forLine[index].hovering = hovering;
});
}
@@ -147,7 +147,7 @@
this._updateRangesMap(
side, range, hovering, (forLine, start, end) => {
const index = forLine.findIndex(lineRange =>
- lineRange.start === start && lineRange.end === end);
+ lineRange.start === start && lineRange.end === end);
forLine.splice(index, 1);
});
}
diff --git a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.js b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.js
index 5375ef8..6c7d582 100644
--- a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.js
+++ b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.js
@@ -192,11 +192,11 @@
// Determine the side.
let side;
if (line.type === GrDiffLine.Type.REMOVE || (
- line.type === GrDiffLine.Type.BOTH &&
+ line.type === GrDiffLine.Type.BOTH &&
el.getAttribute('data-side') !== 'right')) {
side = 'left';
} else if (line.type === GrDiffLine.Type.ADD || (
- el.getAttribute('data-side') !== 'left')) {
+ el.getAttribute('data-side') !== 'left')) {
side = 'right';
}
@@ -260,12 +260,14 @@
lastNotify: {left: 1, right: 1},
};
+ const rangesCache = new Map();
+
this._processPromise = util.makeCancelable(this._loadHLJS()
.then(() => {
return new Promise(resolve => {
const nextStep = () => {
this._processHandle = null;
- this._processNextLine(state);
+ this._processNextLine(state, rangesCache);
// Move to the next line in the section.
state.lineIndex++;
@@ -322,12 +324,21 @@
* Highlight.js emits and emit a list of text ranges and classes for the
* markers.
* @param {string} str The string of HTML.
+ * @param {Map<string, !Array<!Object>>} rangesCache A map for caching
+ * ranges for each string. A cache is read and written by this method.
+ * Since diff is mostly comparing same file on two sides, there is good rate
+ * of duplication at least for parts that are on left and right parts.
* @return {!Array<!Object>} The list of ranges.
*/
- _rangesFromString(str) {
+ _rangesFromString(str, rangesCache) {
+ const cached = rangesCache.get(str);
+ if (cached) return cached;
+
const div = document.createElement('div');
div.innerHTML = str;
- return this._rangesFromElement(div, 0);
+ const ranges = this._rangesFromElement(div, 0);
+ rangesCache.set(str, ranges);
+ return ranges;
},
_rangesFromElement(elem, offset) {
@@ -358,7 +369,7 @@
* lines).
* @param {!Object} state The processing state for the layer.
*/
- _processNextLine(state) {
+ _processNextLine(state, rangesCache) {
let baseLine;
let revisionLine;
@@ -387,7 +398,8 @@
baseLine = this._workaround(this._baseLanguage, baseLine);
result = this._hljs.highlight(this._baseLanguage, baseLine, true,
state.baseContext);
- this.push('_baseRanges', this._rangesFromString(result.value));
+ this.push('_baseRanges',
+ this._rangesFromString(result.value, rangesCache));
state.baseContext = result.top;
}
@@ -396,7 +408,8 @@
revisionLine = this._workaround(this._revisionLanguage, revisionLine);
result = this._hljs.highlight(this._revisionLanguage, revisionLine,
true, state.revisionContext);
- this.push('_revisionRanges', this._rangesFromString(result.value));
+ this.push('_revisionRanges',
+ this._rangesFromString(result.value, rangesCache));
state.revisionContext = result.top;
}
},
diff --git a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.html b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.html
index 472db21..4e3492f 100644
--- a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.html
@@ -388,10 +388,20 @@
'<span class="non-whtelisted-class">',
'<span class="gr-diff gr-syntax gr-syntax-keyword">public</span>',
'</span>'].join('');
- const result = element._rangesFromString(str);
+ const result = element._rangesFromString(str, new Map());
assert.notEqual(result.length, 0);
});
+ test('_rangesFromString cache same syntax markers', () => {
+ sandbox.spy(element, '_rangesFromElement');
+ const str =
+ '<span class="gr-diff gr-syntax gr-syntax-keyword">public</span>';
+ const cacheMap = new Map();
+ element._rangesFromString(str, cacheMap);
+ element._rangesFromString(str, cacheMap);
+ assert.isTrue(element._rangesFromElement.calledOnce);
+ });
+
test('_isSectionDone', () => {
let state = {sectionIndex: 0, lineIndex: 0};
assert.isFalse(element._isSectionDone(state));
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.js b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.js
index 5aed3e4..32aad5a 100644
--- a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.js
+++ b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.js
@@ -214,17 +214,17 @@
const dialog = this._getDialogFromEvent(e);
return this.$.restAPI.renameFileInChangeEdit(this.change._number,
this._path, this._newPath).then(res => {
- if (!res.ok) { return; }
- this._closeDialog(dialog, true);
- Gerrit.Nav.navigateToChange(this.change);
- });
+ if (!res.ok) { return; }
+ this._closeDialog(dialog, true);
+ Gerrit.Nav.navigateToChange(this.change);
+ });
},
_queryFiles(input) {
return this.$.restAPI.queryChangeFiles(this.change._number,
this.patchNum, input).then(res => res.map(file => {
- return {name: file};
- }));
+ return {name: file};
+ }));
},
_computeIsInvisible(id, hiddenActions) {
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.html b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.html
index df029ed..dd8cb74 100644
--- a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.html
+++ b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.html
@@ -192,8 +192,8 @@
let renameStub;
let renameAutocomplete;
const inputSelector = Polymer.Element ?
- '.newPathIronInput' :
- '.newPathInput';
+ '.newPathIronInput' :
+ '.newPathInput';
setup(() => {
navStub = sandbox.stub(Gerrit.Nav, 'navigateToChange');
diff --git a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.js b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.js
index 95cfaf6..bbcb90c 100644
--- a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.js
+++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.js
@@ -141,11 +141,11 @@
}
return this.$.restAPI.renameFileInChangeEdit(this._changeNum,
this._path, path).then(res => {
- if (!res.ok) { return; }
+ if (!res.ok) { return; }
- this._successfulSave = true;
- this._viewEditInChangeView();
- });
+ this._successfulSave = true;
+ this._viewEditInChangeView();
+ });
},
_viewEditInChangeView() {
@@ -191,13 +191,13 @@
this.$.storage.eraseEditableContentItem(this.storageKey);
return this.$.restAPI.saveChangeEdit(this._changeNum, this._path,
this._newContent).then(res => {
- this._saving = false;
- this._showAlert(res.ok ? SAVED_MESSAGE : SAVE_FAILED_MSG);
- if (!res.ok) { return; }
+ this._saving = false;
+ this._showAlert(res.ok ? SAVED_MESSAGE : SAVE_FAILED_MSG);
+ if (!res.ok) { return; }
- this._content = this._newContent;
- this._successfulSave = true;
- });
+ this._content = this._newContent;
+ this._successfulSave = true;
+ });
},
_showAlert(message) {
diff --git a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.html b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.html
index abb8131..226472f 100644
--- a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.html
+++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.html
@@ -106,7 +106,7 @@
// Calling with the same path should not navigate.
return element._handlePathChanged({detail: mockParams.path}).then(() => {
assert.isFalse(savePathStub.called);
- // !ok response
+ // !ok response
element._handlePathChanged({detail: 'newPath'}).then(() => {
assert.isTrue(savePathStub.called);
assert.isFalse(navigateStub.called);
diff --git a/polygerrit-ui/app/elements/gr-app-element.js b/polygerrit-ui/app/elements/gr-app-element.js
index e61bbfe..b8f789b 100644
--- a/polygerrit-ui/app/elements/gr-app-element.js
+++ b/polygerrit-ui/app/elements/gr-app-element.js
@@ -118,7 +118,7 @@
},
ready() {
- this.$.reporting.appStarted(document.visibilityState === 'hidden');
+ this.$.reporting.appStarted();
this.$.router.start();
this.$.restAPI.getAccount().then(account => {
@@ -426,7 +426,8 @@
},
_computePluginScreenName({plugin, screen}) {
- return Gerrit._getPluginScreenName(plugin, screen);
+ if (!plugin || !screen) return '';
+ return `${plugin}-screen-${screen}`;
},
_logWelcome() {
diff --git a/polygerrit-ui/app/elements/plugins/gr-admin-api/gr-admin-api_test.html b/polygerrit-ui/app/elements/plugins/gr-admin-api/gr-admin-api_test.html
index 9cdcf76..2537a37 100644
--- a/polygerrit-ui/app/elements/plugins/gr-admin-api/gr-admin-api_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-admin-api/gr-admin-api_test.html
@@ -39,7 +39,7 @@
let plugin;
Gerrit.install(p => { plugin = p; }, '0.1',
'http://test.com/plugins/testplugin/static/test.js');
- sandbox.stub(Gerrit, '_arePluginsLoaded').returns(true);
+ Gerrit._loadPlugins([]);
adminApi = plugin.admin();
});
diff --git a/polygerrit-ui/app/elements/plugins/gr-change-metadata-api/gr-change-metadata-api.js b/polygerrit-ui/app/elements/plugins/gr-change-metadata-api/gr-change-metadata-api.js
index b550f73..0454767 100644
--- a/polygerrit-ui/app/elements/plugins/gr-change-metadata-api/gr-change-metadata-api.js
+++ b/polygerrit-ui/app/elements/plugins/gr-change-metadata-api/gr-change-metadata-api.js
@@ -31,7 +31,7 @@
this._createHook();
}
this._hook.onAttached(element =>
- this.plugin.attributeHelper(element).bind('labels', callback));
+ this.plugin.attributeHelper(element).bind('labels', callback));
return this;
};
diff --git a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.js b/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.js
index 448e090..ba49291 100644
--- a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.js
+++ b/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator.js
@@ -95,15 +95,15 @@
return helper.get('value').then(
value => helper.bind('value',
value => plugin.attributeHelper(el).set(paramName, value))
- );
+ );
});
let timeoutId;
const timeout = new Promise(
- resolve => timeoutId = setTimeout(() => {
- console.warn(
- 'Timeout waiting for endpoint properties initialization: ' +
+ resolve => timeoutId = setTimeout(() => {
+ console.warn(
+ 'Timeout waiting for endpoint properties initialization: ' +
`plugin ${plugin.getPluginName()}, endpoint ${this.name}`);
- }, INIT_PROPERTIES_TIMEOUT_MS));
+ }, INIT_PROPERTIES_TIMEOUT_MS));
return Promise.race([timeout, Promise.all(expectProperties)])
.then(() => {
clearTimeout(timeoutId);
diff --git a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.html b/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.html
index 994d666..b0ad585 100644
--- a/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.html
@@ -67,7 +67,7 @@
replacementHook = plugin.registerCustomComponent(
'second', 'other-module', {replace: true});
// Mimic all plugins loaded.
- Gerrit._setPluginsPending([]);
+ Gerrit._loadPlugins([]);
flush(done);
});
diff --git a/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host.js b/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host.js
index bdc7652..21da106 100644
--- a/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host.js
+++ b/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host.js
@@ -27,33 +27,29 @@
},
},
- behaviors: [
- Gerrit.BaseUrlBehavior,
- ],
-
_configChanged(config) {
const plugins = config.plugin;
- const htmlPlugins = (plugins.html_resource_paths || [])
- .map(p => this._urlFor(p))
- .filter(p => !Gerrit._isPluginPreloaded(p));
+ const htmlPlugins = (plugins.html_resource_paths || []);
const jsPlugins =
- this._handleMigrations(plugins.js_resource_paths || [], htmlPlugins)
- .map(p => this._urlFor(p))
- .filter(p => !Gerrit._isPluginPreloaded(p));
+ this._handleMigrations(plugins.js_resource_paths || [], htmlPlugins);
+
const shouldLoadTheme = config.default_theme &&
!Gerrit._isPluginPreloaded('preloaded:gerrit-theme');
- const defaultTheme =
- shouldLoadTheme ? this._urlFor(config.default_theme) : null;
+ const themeToLoad =
+ shouldLoadTheme ? [config.default_theme] : [];
+
+ // Theme should be loaded first if has one to have better UX
const pluginsPending =
- [].concat(jsPlugins, htmlPlugins, defaultTheme || []);
- Gerrit._setPluginsPending(pluginsPending);
- if (defaultTheme) {
- // Make theme first to be first to load.
- // Load sync to work around rare theme loading race condition.
- this._importHtmlPlugins([defaultTheme], true);
+ themeToLoad.concat(jsPlugins, htmlPlugins);
+
+ const pluginOpts = {};
+
+ if (shouldLoadTheme) {
+ // Theme needs to be loaded synchronous.
+ pluginOpts[config.default_theme] = {sync: true};
}
- this._loadJsPlugins(jsPlugins);
- this._importHtmlPlugins(htmlPlugins);
+
+ Gerrit._loadPlugins(pluginsPending, pluginOpts);
},
/**
@@ -66,53 +62,5 @@
return !htmlPlugins.includes(counterpart);
});
},
-
- /**
- * @suppress {checkTypes}
- * States that it expects no more than 3 parameters, but that's not true.
- * @todo (beckysiegel) check Polymer annotations and submit change.
- * @param {Array} plugins
- * @param {boolean=} opt_sync
- */
- _importHtmlPlugins(plugins, opt_sync) {
- const async = !opt_sync;
- for (const url of plugins) {
- // onload (second param) needs to be a function. When null or undefined
- // were passed, plugins were not loaded correctly.
- (this.importHref || Polymer.importHref)(
- this._urlFor(url), () => {},
- Gerrit._pluginInstallError.bind(null, `${url} import error`),
- async);
- }
- },
-
- _loadJsPlugins(plugins) {
- for (const url of plugins) {
- this._createScriptTag(this._urlFor(url));
- }
- },
-
- _createScriptTag(url) {
- const el = document.createElement('script');
- el.defer = true;
- el.src = url;
- el.onerror = Gerrit._pluginInstallError.bind(null, `${url} load error`);
- return document.body.appendChild(el);
- },
-
- _urlFor(pathOrUrl) {
- if (!pathOrUrl) {
- return pathOrUrl;
- }
- if (pathOrUrl.startsWith('preloaded:') ||
- pathOrUrl.startsWith('http')) {
- // Plugins are loaded from another domain or preloaded.
- return pathOrUrl;
- }
- if (!pathOrUrl.startsWith('/')) {
- pathOrUrl = '/' + pathOrUrl;
- }
- return window.location.origin + this.getBaseUrl() + pathOrUrl;
- },
});
})();
diff --git a/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host_test.html b/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host_test.html
index e577182..3a8e4d8 100644
--- a/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-plugin-host/gr-plugin-host_test.html
@@ -38,195 +38,57 @@
suite('gr-plugin-host tests', () => {
let element;
let sandbox;
- let url;
setup(() => {
element = fixture('basic');
sandbox = sinon.sandbox.create();
sandbox.stub(document.body, 'appendChild');
sandbox.stub(element, 'importHref');
- url = window.location.origin;
});
teardown(() => {
sandbox.restore();
});
- test('counts plugins', () => {
- sandbox.stub(Gerrit, '_setPluginsCount');
+ test('load plugins should be called', () => {
+ sandbox.stub(Gerrit, '_loadPlugins');
element.config = {
plugin: {
html_resource_paths: ['plugins/foo/bar', 'plugins/baz'],
js_resource_paths: ['plugins/42'],
},
};
- assert.isTrue(Gerrit._setPluginsCount.calledWith(3));
+ assert.isTrue(Gerrit._loadPlugins.calledOnce);
+ assert.isTrue(Gerrit._loadPlugins.calledWith([
+ 'plugins/42', 'plugins/foo/bar', 'plugins/baz',
+ ], {}));
});
- test('imports relative html plugins from config', () => {
- sandbox.stub(Gerrit, '_pluginInstallError');
+ test('theme plugins should be loaded if enabled', () => {
+ sandbox.stub(Gerrit, '_loadPlugins');
element.config = {
- plugin: {html_resource_paths: ['foo/bar', 'baz']},
- };
- assert.equal(element.importHref.firstCall.args[0], url + '/foo/bar');
- assert.isTrue(element.importHref.firstCall.args[3]);
-
- assert.equal(element.importHref.secondCall.args[0], url + '/baz');
- assert.isTrue(element.importHref.secondCall.args[3]);
-
- assert.equal(Gerrit._pluginInstallError.callCount, 0);
- element.importHref.firstCall.args[2]();
- assert.equal(Gerrit._pluginInstallError.callCount, 1);
- element.importHref.secondCall.args[2]();
- assert.equal(Gerrit._pluginInstallError.callCount, 2);
- });
-
- test('imports relative html plugins from config with a base url', () => {
- sandbox.stub(Gerrit, '_pluginInstallError');
- sandbox.stub(element, 'getBaseUrl').returns('/the-base');
- element.config = {
- plugin: {html_resource_paths: ['foo/bar', 'baz']}};
- assert.equal(element.importHref.firstCall.args[0],
- url + '/the-base/foo/bar');
- assert.isTrue(element.importHref.firstCall.args[3]);
-
- assert.equal(element.importHref.secondCall.args[0],
- url + '/the-base/baz');
- assert.isTrue(element.importHref.secondCall.args[3]);
- assert.equal(Gerrit._pluginInstallError.callCount, 0);
- element.importHref.firstCall.args[2]();
- assert.equal(Gerrit._pluginInstallError.callCount, 1);
- element.importHref.secondCall.args[2]();
- assert.equal(Gerrit._pluginInstallError.callCount, 2);
- });
-
- test('importHref is not called with null callback functions', () => {
- const plugins = ['path/to/plugin'];
- element._importHtmlPlugins(plugins);
- assert.isTrue(element.importHref.calledOnce);
- assert.isFunction(element.importHref.lastCall.args[1]);
- assert.isFunction(element.importHref.lastCall.args[2]);
- });
-
- test('imports absolute html plugins from config', () => {
- sandbox.stub(Gerrit, '_pluginInstallError');
- element.config = {
+ default_theme: 'gerrit-theme.html',
plugin: {
- html_resource_paths: [
- 'http://example.com/foo/bar',
- 'https://example.com/baz',
- ],
+ html_resource_paths: ['plugins/foo/bar', 'plugins/baz'],
+ js_resource_paths: ['plugins/42'],
},
};
- assert.equal(element.importHref.firstCall.args[0],
- 'http://example.com/foo/bar');
- assert.isTrue(element.importHref.firstCall.args[3]);
-
- assert.equal(element.importHref.secondCall.args[0],
- 'https://example.com/baz');
- assert.isTrue(element.importHref.secondCall.args[3]);
- assert.equal(Gerrit._pluginInstallError.callCount, 0);
- element.importHref.firstCall.args[2]();
- assert.equal(Gerrit._pluginInstallError.callCount, 1);
- element.importHref.secondCall.args[2]();
- assert.equal(Gerrit._pluginInstallError.callCount, 2);
+ assert.isTrue(Gerrit._loadPlugins.calledOnce);
+ assert.isTrue(Gerrit._loadPlugins.calledWith([
+ 'gerrit-theme.html', 'plugins/42', 'plugins/foo/bar', 'plugins/baz',
+ ], {'gerrit-theme.html': {sync: true}}));
});
- test('adds js plugins from config to the body', () => {
- element.config = {plugin: {js_resource_paths: ['foo/bar', 'baz']}};
- assert.isTrue(document.body.appendChild.calledTwice);
- });
-
- test('imports relative js plugins from config', () => {
- sandbox.stub(element, '_createScriptTag');
- element.config = {plugin: {js_resource_paths: ['foo/bar', 'baz']}};
- assert.isTrue(element._createScriptTag.calledWith(url + '/foo/bar'));
- assert.isTrue(element._createScriptTag.calledWith(url + '/baz'));
- });
-
- test('imports relative html plugins from config with a base url', () => {
- sandbox.stub(element, '_createScriptTag');
- sandbox.stub(element, 'getBaseUrl').returns('/the-base');
- element.config = {plugin: {js_resource_paths: ['foo/bar', 'baz']}};
- assert.isTrue(element._createScriptTag.calledWith(
- url + '/the-base/foo/bar'));
- assert.isTrue(element._createScriptTag.calledWith(
- url + '/the-base/baz'));
- });
-
- test('imports absolute html plugins from config', () => {
- sandbox.stub(element, '_createScriptTag');
- element.config = {
- plugin: {
- js_resource_paths: [
- 'http://example.com/foo/bar',
- 'https://example.com/baz',
- ],
- },
- };
- assert.isTrue(element._createScriptTag.calledWith(
- 'http://example.com/foo/bar'));
- assert.isTrue(element._createScriptTag.calledWith(
- 'https://example.com/baz'));
- });
-
- test('default theme is loaded with html plugins', () => {
- sandbox.stub(Gerrit, '_pluginInstallError');
- element.config = {
- default_theme: '/oof',
- plugin: {
- html_resource_paths: ['some'],
- },
- };
- assert.equal(element.importHref.firstCall.args[0], url + '/oof');
- assert.isFalse(element.importHref.firstCall.args[3]);
-
- assert.equal(element.importHref.secondCall.args[0], url + '/some');
- assert.isTrue(element.importHref.secondCall.args[3]);
- assert.equal(Gerrit._pluginInstallError.callCount, 0);
- element.importHref.firstCall.args[2]();
- assert.equal(Gerrit._pluginInstallError.callCount, 1);
- element.importHref.secondCall.args[2]();
- assert.equal(Gerrit._pluginInstallError.callCount, 2);
- });
-
- test('default theme is loaded with html plugins', () => {
- sandbox.stub(Gerrit, '_setPluginsPending');
- element.config = {
- default_theme: '/oof',
- plugin: {},
- };
- assert.isTrue(Gerrit._setPluginsPending.calledWith([url + '/oof']));
- });
-
- test('skips default theme loading if preloaded', () => {
+ test('skip theme if preloaded', () => {
sandbox.stub(Gerrit, '_isPluginPreloaded')
.withArgs('preloaded:gerrit-theme').returns(true);
- sandbox.stub(Gerrit, '_setPluginsPending');
+ sandbox.stub(Gerrit, '_loadPlugins');
element.config = {
default_theme: '/oof',
plugin: {},
};
- assert.isFalse(element.importHref.calledWith(url + '/oof'));
- });
-
- test('skips preloaded plugins', () => {
- sandbox.stub(Gerrit, '_isPluginPreloaded')
- .withArgs(url + '/plugins/foo/bar').returns(true)
- .withArgs(url + '/plugins/42').returns(true);
- sandbox.stub(Gerrit, '_setPluginsCount');
- sandbox.stub(Gerrit, '_setPluginsPending');
- sandbox.stub(element, '_createScriptTag');
- element.config = {
- plugin: {
- html_resource_paths: ['plugins/foo/bar', 'plugins/baz'],
- js_resource_paths: ['plugins/42'],
- },
- };
- assert.isTrue(
- Gerrit._setPluginsPending.calledWith([url + '/plugins/baz']));
- assert.equal(element._createScriptTag.callCount, 0);
- assert.isTrue(element.importHref.calledWith(url + '/plugins/baz'));
+ assert.isTrue(Gerrit._loadPlugins.calledOnce);
+ assert.isTrue(Gerrit._loadPlugins.calledWith([], {}));
});
});
</script>
diff --git a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface.js b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface.js
index 556cfd8..e3f8694 100644
--- a/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface.js
+++ b/polygerrit-ui/app/elements/plugins/gr-popup-interface/gr-popup-interface.js
@@ -46,17 +46,17 @@
if (!this._openingPromise) {
this._openingPromise =
this.plugin.hook('plugin-overlay').getLastAttached()
- .then(hookEl => {
- const popup = document.createElement('gr-plugin-popup');
- if (this._moduleName) {
- const el = Polymer.dom(popup).appendChild(
- document.createElement(this._moduleName));
- el.plugin = this.plugin;
- }
- this._popup = Polymer.dom(hookEl).appendChild(popup);
- Polymer.dom.flush();
- return this._popup.open().then(() => this);
- });
+ .then(hookEl => {
+ const popup = document.createElement('gr-plugin-popup');
+ if (this._moduleName) {
+ const el = Polymer.dom(popup).appendChild(
+ document.createElement(this._moduleName));
+ el.plugin = this.plugin;
+ }
+ this._popup = Polymer.dom(hookEl).appendChild(popup);
+ Polymer.dom.flush();
+ return this._popup.open().then(() => this);
+ });
}
return this._openingPromise;
};
diff --git a/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-repo-api_test.html b/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-repo-api_test.html
index 7c7564b..0b32f8a 100644
--- a/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-repo-api_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-repo-api/gr-repo-api_test.html
@@ -46,7 +46,7 @@
let plugin;
Gerrit.install(p => { plugin = p; }, '0.1',
'http://test.com/plugins/testplugin/static/test.js');
- sandbox.stub(Gerrit, '_arePluginsLoaded').returns(true);
+ Gerrit._loadPlugins([]);
repoApi = plugin.project();
});
diff --git a/polygerrit-ui/app/elements/plugins/gr-settings-api/gr-settings-api_test.html b/polygerrit-ui/app/elements/plugins/gr-settings-api/gr-settings-api_test.html
index d34ca94..cbc2de6 100644
--- a/polygerrit-ui/app/elements/plugins/gr-settings-api/gr-settings-api_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-settings-api/gr-settings-api_test.html
@@ -48,7 +48,7 @@
let plugin;
Gerrit.install(p => { plugin = p; }, '0.1',
'http://test.com/plugins/testplugin/static/test.js');
- sandbox.stub(Gerrit, '_arePluginsLoaded').returns(true);
+ Gerrit._loadPlugins([]);
settingsApi = plugin.settings();
});
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
index 1de8283..7aca389 100644
--- 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
@@ -38,7 +38,7 @@
*/
GrStyleObject.prototype.getClassName = function(element) {
let rootNode = Polymer.Settings.useShadow
- ? element.getRootNode() : document.body;
+ ? element.getRootNode() : document.body;
if (rootNode === document) {
rootNode = document.head;
}
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
index d67a309..46bda6d 100644
--- 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
@@ -46,7 +46,7 @@
let plugin;
Gerrit.install(p => { plugin = p; }, '0.1',
'http://test.com/plugins/testplugin/static/test.js');
- sandbox.stub(Gerrit, '_arePluginsLoaded').returns(true);
+ Gerrit._loadPlugins([]);
stylesApi = plugin.styles();
});
@@ -76,7 +76,7 @@
let plugin;
Gerrit.install(p => { plugin = p; }, '0.1',
'http://test.com/plugins/testplugin/static/test.js');
- sandbox.stub(Gerrit, '_arePluginsLoaded').returns(true);
+ Gerrit._loadPlugins([]);
stylesApi = plugin.styles();
displayInlineStyle = stylesApi.css('display: inline');
displayNoneStyle = stylesApi.css('display: none');
diff --git a/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-theme-api_test.html b/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-theme-api_test.html
index 82eb0f8..6332b91 100644
--- a/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-theme-api_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-theme-api/gr-theme-api_test.html
@@ -67,7 +67,7 @@
stub('gr-custom-plugin-header', {
ready() { customHeader = this; },
});
- Gerrit._setPluginsPending([]);
+ Gerrit._loadPlugins([]);
});
test('sets logo and title', done => {
diff --git a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.js b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.js
index b0c7661..3ba3a80 100644
--- a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.js
+++ b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info.js
@@ -127,20 +127,20 @@
_maybeSetName() {
return this._hasNameChange && this.nameMutable ?
- this.$.restAPI.setAccountName(this._account.name) :
- Promise.resolve();
+ this.$.restAPI.setAccountName(this._account.name) :
+ Promise.resolve();
},
_maybeSetUsername() {
return this._hasUsernameChange && this.usernameMutable ?
- this.$.restAPI.setAccountUsername(this._username) :
- Promise.resolve();
+ this.$.restAPI.setAccountUsername(this._username) :
+ Promise.resolve();
},
_maybeSetStatus() {
return this._hasStatusChange ?
- this.$.restAPI.setAccountStatus(this._account.status) :
- Promise.resolve();
+ this.$.restAPI.setAccountStatus(this._account.status) :
+ Promise.resolve();
},
_computeHasUnsavedChanges(nameChanged, usernameChanged, statusChanged) {
diff --git a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.html b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.html
index de222a9..a35c1f0 100644
--- a/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-account-info/gr-account-info_test.html
@@ -152,7 +152,7 @@
usernameChangedSpy = sandbox.spy(element, '_usernameChanged');
statusChangedSpy = sandbox.spy(element, '_statusChanged');
element.set('_serverConfig',
- {auth: {editable_account_fields: ['FULL_NAME', 'USER_NAME']}});
+ {auth: {editable_account_fields: ['FULL_NAME', 'USER_NAME']}});
nameStub = sandbox.stub(element.$.restAPI, 'setAccountName',
name => Promise.resolve());
@@ -280,7 +280,7 @@
setup(() => {
statusChangedSpy = sandbox.spy(element, '_statusChanged');
element.set('_serverConfig',
- {auth: {editable_account_fields: []}});
+ {auth: {editable_account_fields: []}});
statusStub = sandbox.stub(element.$.restAPI, 'setAccountStatus',
status => Promise.resolve());
diff --git a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.js b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.js
index 3eb1a22..41a9800 100644
--- a/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.js
+++ b/polygerrit-ui/app/elements/settings/gr-cla-view/gr-cla-view.js
@@ -123,7 +123,7 @@
_hideAggreements(item, groups, signedAgreements) {
return this._disableAggreements(item, groups, signedAgreements) ?
- '' : 'hide';
+ '' : 'hide';
},
_disableAgreementsText(text) {
diff --git a/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor.js b/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor.js
index 78025d1..890061e 100644
--- a/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor.js
+++ b/polygerrit-ui/app/elements/settings/gr-gpg-editor/gr-gpg-editor.js
@@ -46,11 +46,11 @@
return;
}
this._keys = Object.keys(keys)
- .map(key => {
- const gpgKey = keys[key];
- gpgKey.id = key;
- return gpgKey;
- });
+ .map(key => {
+ const gpgKey = keys[key];
+ gpgKey.id = key;
+ return gpgKey;
+ });
});
},
diff --git a/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.js b/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.js
index 92836a8..b2e0973 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.js
+++ b/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry.js
@@ -94,7 +94,7 @@
_inputTextChanged(text) {
if (text.length && this.allowAnyInput) {
this.dispatchEvent(new CustomEvent(
- 'account-text-changed', {bubbles: true, composed: true}));
+ 'account-text-changed', {bubbles: true, composed: true}));
}
},
});
diff --git a/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry_test.html b/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry_test.html
index 59792a7..6896af9 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-account-entry/gr-account-entry_test.html
@@ -92,7 +92,7 @@
test('account-text-changed not fired when input text changed without ' +
'allowAnyInput', () => {
- // Spy on query, as that is called when _updateSuggestions proceeds.
+ // Spy on query, as that is called when _updateSuggestions proceeds.
const changeStub = sandbox.stub();
element.querySuggestions = input => Promise.resolve([]);
element.addEventListener('account-text-changed', changeStub);
diff --git a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.html b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.html
index 740dfcf..f0620d3 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-account-label/gr-account-label_test.html
@@ -64,19 +64,21 @@
});
test('computed fields', () => {
- assert.equal(element._computeAccountTitle(
- {
+ assert.equal(
+ element._computeAccountTitle({
name: 'Andrew Bonventre',
email: 'andybons+gerrit@gmail.com',
}, /* additionalText= */ ''),
'Andrew Bonventre <andybons+gerrit@gmail.com>');
- assert.equal(element._computeAccountTitle(
- {name: 'Andrew Bonventre'}, /* additionalText= */ ''),
+ assert.equal(
+ element._computeAccountTitle({
+ name: 'Andrew Bonventre',
+ }, /* additionalText= */ ''),
'Andrew Bonventre');
- assert.equal(element._computeAccountTitle(
- {
+ assert.equal(
+ element._computeAccountTitle({
email: 'andybons+gerrit@gmail.com',
}, /* additionalText= */ ''),
'Anonymous <andybons+gerrit@gmail.com>');
@@ -94,9 +96,9 @@
assert.equal(element._computeShowEmailClass(
{name: 'Andrew Bonventre'},
- /* additionalText= */ '',
+ /* additionalText= */ ''
),
- '');
+ '');
assert.equal(element._computeShowEmailClass(undefined), '');
diff --git a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.js b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.js
index 5f1d41a..9897105 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.js
+++ b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list.js
@@ -120,7 +120,7 @@
suggestions = suggestions.filter(this.filter);
}
return suggestions.map(suggestion =>
- provider.makeSuggestionItem(suggestion));
+ provider.makeSuggestionItem(suggestion));
});
},
diff --git a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.html b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.html
index 6f265e7..f931a69 100644
--- a/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-account-list/gr-account-list_test.html
@@ -44,6 +44,7 @@
return item;
}
}
+
suite('gr-account-list tests', () => {
let _nextAccountId = 0;
const makeAccount = function() {
diff --git a/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar_test.html b/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar_test.html
index 1c2afaa..9c2aba9 100644
--- a/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-avatar/gr-avatar_test.html
@@ -49,28 +49,28 @@
});
test('methods', () => {
- assert.equal(element._buildAvatarURL(
- {
+ assert.equal(
+ element._buildAvatarURL({
_account_id: 123,
}),
'/accounts/123/avatar?s=16');
- assert.equal(element._buildAvatarURL(
- {
+ assert.equal(
+ element._buildAvatarURL({
email: 'test@example.com',
}),
'/accounts/test%40example.com/avatar?s=16');
- assert.equal(element._buildAvatarURL(
- {
+ assert.equal(
+ element._buildAvatarURL({
name: 'John Doe',
}),
'/accounts/John%20Doe/avatar?s=16');
- assert.equal(element._buildAvatarURL(
- {
+ assert.equal(
+ element._buildAvatarURL({
username: 'John_Doe',
}),
'/accounts/John_Doe/avatar?s=16');
- assert.equal(element._buildAvatarURL(
- {
+ assert.equal(
+ element._buildAvatarURL({
_account_id: 123,
avatars: [
{
@@ -88,8 +88,8 @@
],
}),
'https://cdn.example.com/s16-p/photo.jpg');
- assert.equal(element._buildAvatarURL(
- {
+ assert.equal(
+ element._buildAvatarURL({
_account_id: 123,
avatars: [
{
@@ -117,7 +117,7 @@
assert.strictEqual(element.style.backgroundImage, '');
// Emulate plugins loaded.
- Gerrit._setPluginsPending([]);
+ Gerrit._loadPlugins([]);
Promise.all([
element.$.restAPI.getConfig(),
@@ -155,7 +155,7 @@
assert.isFalse(element.hasAttribute('hidden'));
// Emulate plugins loaded.
- Gerrit._setPluginsPending([]);
+ Gerrit._loadPlugins([]);
return Promise.all([
element.$.restAPI.getConfig(),
@@ -197,7 +197,7 @@
_account_id: 123,
};
// Emulate plugins loaded.
- Gerrit._setPluginsPending([]);
+ Gerrit._loadPlugins([]);
return Promise.all([
element.$.restAPI.getConfig(),
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.js b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.js
index b7fe8b0..ff5576cb 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.js
+++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.js
@@ -35,7 +35,7 @@
* @event thread-changed
*/
- /**
+ /**
* gr-comment-thread exposes the following attributes that allow a
* diff widget like gr-diff to show the thread in the right location:
*
@@ -165,7 +165,7 @@
commentEl.collapsed = false;
} else {
const range = opt_range ? opt_range :
- lastComment ? lastComment.range : undefined;
+ lastComment ? lastComment.range : undefined;
const unresolved = lastComment ? lastComment.unresolved : undefined;
this.addDraft(opt_lineNum, range, unresolved);
}
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.html b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.html
index b6222b4..d3946e6 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.html
@@ -469,7 +469,7 @@
done();
});
draftEl.fire('comment-discard', {comment: draftEl.comment},
- {bubbles: false});
+ {bubbles: false});
});
test('first editing comment does not add __otherEditing attribute', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.js b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.js
index d41ef7f..6881929 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.js
+++ b/polygerrit-ui/app/elements/shared/gr-comment/gr-comment.js
@@ -445,7 +445,7 @@
return;
}
const timingLabel = this.comment.id ?
- REPORT_UPDATE_DRAFT : REPORT_CREATE_DRAFT;
+ REPORT_UPDATE_DRAFT : REPORT_CREATE_DRAFT;
const timer = this.$.reporting.getTimer(timingLabel);
this.set('comment.__editing', false);
return this.save().then(() => { timer.end(); });
@@ -591,13 +591,13 @@
this._showStartRequest();
return this.$.restAPI.deleteDiffDraft(this.changeNum, this.patchNum,
draft).then(result => {
- if (result.ok) {
- this._showEndRequest();
- } else {
- this._handleFailedDraftRequest();
- }
- return result;
- });
+ if (result.ok) {
+ this._showEndRequest();
+ } else {
+ this._handleFailedDraftRequest();
+ }
+ return result;
+ });
},
_getPatchNum() {
diff --git a/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js b/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js
index be40ce6..4e9ff76 100644
--- a/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js
+++ b/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js
@@ -271,8 +271,8 @@
_getTop(target) {
let top = target.offsetTop;
for (let offsetParent = target.offsetParent;
- offsetParent;
- offsetParent = offsetParent.offsetParent) {
+ offsetParent;
+ offsetParent = offsetParent.offsetParent) {
top += offsetParent.offsetTop;
}
return top;
@@ -301,7 +301,7 @@
const dims = this._getWindowDims();
const top = this._getTop(this.target);
const bottomIsVisible = this._targetHeight ?
- this._targetIsVisible(top + this._targetHeight) : true;
+ this._targetIsVisible(top + this._targetHeight) : true;
const scrollToValue = this._calculateScrollToValue(top, this.target);
if (this._targetIsVisible(top)) {
diff --git a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.js b/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.js
index 0360145..5b1ef7f 100644
--- a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.js
+++ b/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.js
@@ -167,8 +167,8 @@
_timeToSecondsFormat(timeFormat) {
return timeFormat === TimeFormats.TIME_12 ?
- TimeFormats.TIME_12_WITH_SEC :
- TimeFormats.TIME_24_WITH_SEC;
+ TimeFormats.TIME_12_WITH_SEC :
+ TimeFormats.TIME_24_WITH_SEC;
},
_computeFullDateStr(dateStr, timeFormat) {
diff --git a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter_test.html b/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter_test.html
index a0bf207..99af4e6 100644
--- a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter_test.html
@@ -89,7 +89,7 @@
suite('24 hours time format preference', () => {
setup(() => {
return stubRestAPI(
- {time_format: 'HHMM_24', relative_date_in_change_table: false}
+ {time_format: 'HHMM_24', relative_date_in_change_table: false}
).then(() => {
element = fixture('basic');
sandbox.stub(element, '_getUtcOffsetString').returns('');
@@ -139,7 +139,7 @@
setup(() => {
// relative_date_in_change_table is not set when false.
return stubRestAPI(
- {time_format: 'HHMM_12'}
+ {time_format: 'HHMM_12'}
).then(() => {
element = fixture('basic');
sandbox.stub(element, '_getUtcOffsetString').returns('');
@@ -159,7 +159,7 @@
suite('relative date preference', () => {
setup(() => {
return stubRestAPI(
- {time_format: 'HHMM_12', relative_date_in_change_table: true}
+ {time_format: 'HHMM_12', relative_date_in_change_table: true}
).then(() => {
element = fixture('basic');
sandbox.stub(element, '_getUtcOffsetString').returns('');
@@ -187,7 +187,7 @@
suite('logged in', () => {
setup(() => {
return stubRestAPI(
- {time_format: 'HHMM_12', relative_date_in_change_table: true}
+ {time_format: 'HHMM_12', relative_date_in_change_table: true}
).then(() => {
element = fixture('basic');
return element._loadPreferences();
diff --git a/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences_test.html b/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences_test.html
index f7b10e0..4d2a1a4 100644
--- a/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-diff-preferences/gr-diff-preferences_test.html
@@ -110,7 +110,7 @@
.returns(Promise.resolve());
const showTrailingWhitespaceCheckbox =
valueOf('Show trailing whitespace', 'diffPreferences')
- .firstElementChild;
+ .firstElementChild;
showTrailingWhitespaceCheckbox.checked = false;
element._handleShowTrailingWhitespaceTap();
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.js b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.js
index 89a0725..1f0d01c 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.js
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.js
@@ -118,7 +118,7 @@
});
if (!selectedObj) { return; }
this.text = selectedObj.triggerText? selectedObj.triggerText :
- selectedObj.text;
+ selectedObj.text;
this.dispatchEvent(new CustomEvent('value-change', {
detail: {value},
bubbles: false,
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.js b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.js
index 50a20d1..7273268 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.js
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.js
@@ -206,7 +206,7 @@
*/
_computeURLHelper(host, path) {
const base = path.startsWith(this.getBaseUrl()) ?
- '' : this.getBaseUrl();
+ '' : this.getBaseUrl();
return '//' + host + base + path;
},
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.js b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.js
index 1e339dc..75c0201 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.js
+++ b/polygerrit-ui/app/elements/shared/gr-editable-content/gr-editable-content.js
@@ -112,8 +112,8 @@
// TODO(wyatta) switch linkify sequence, see issue 5526.
this._newContent = this.removeZeroWidthSpace ?
- content.replace(/^R=\u200B/gm, 'R=') :
- content;
+ content.replace(/^R=\u200B/gm, 'R=') :
+ content;
},
_computeSaveDisabled(disabled, content, newContent) {
diff --git a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard.js b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard.js
index 1b31e89..3a43191 100644
--- a/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard.js
+++ b/polygerrit-ui/app/elements/shared/gr-hovercard/gr-hovercard.js
@@ -136,8 +136,8 @@
target = Polymer.dom(ownerRoot).querySelector('#' + this.for);
} else {
target = parentNode.nodeType == Node.DOCUMENT_FRAGMENT_NODE ?
- ownerRoot.host :
- parentNode;
+ ownerRoot.host :
+ parentNode;
}
return target;
},
@@ -244,7 +244,7 @@
hovercardLeft = targetLeft + (targetRect.width - thisRect.width) / 2;
hovercardTop = targetTop - thisRect.height - this.offset;
cssText += `padding-bottom:${this.offset
- }px; margin-bottom:-${this.offset}px;`;
+ }px; margin-bottom:-${this.offset}px;`;
break;
case 'bottom':
hovercardLeft = targetLeft + (targetRect.width - thisRect.width) / 2;
diff --git a/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.html b/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.html
index 7bd6f48..743923b 100644
--- a/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.html
+++ b/polygerrit-ui/app/elements/shared/gr-icons/gr-icons.html
@@ -76,6 +76,7 @@
<g id="restore"><path d="M12,8 L12,13 L16.28,15.54 L17,14.33 L13.5,12.25 L13.5,8 L12,8 Z M13,3 C8.03,3 4,7.03 4,12 L1,12 L4.89,15.89 L4.96,16.03 L9,12 L6,12 C6,8.13 9.13,5 13,5 C16.87,5 20,8.13 20,12 C20,15.87 16.87,19 13,19 C11.07,19 9.32,18.21 8.06,16.94 L6.64,18.36 C8.27,19.99 10.51,21 13,21 C17.97,21 22,16.97 22,12 C22,7.03 17.97,3 13,3 Z"></path></g>
<!-- This is a custom PolyGerrit SVG -->
<g id="revert"><path d="M12.3,8.5 C9.64999995,8.5 7.24999995,9.49 5.39999995,11.1 L1.79999995,7.5 L1.79999995,16.5 L10.8,16.5 L7.17999995,12.88 C8.56999995,11.72 10.34,11 12.3,11 C15.84,11 18.85,13.31 19.9,16.5 L22.27,15.72 C20.88,11.53 16.95,8.5 12.3,8.5"></path></g>
+ <g id="revert_submission"><path d="M12.3,8.5 C9.64999995,8.5 7.24999995,9.49 5.39999995,11.1 L1.79999995,7.5 L1.79999995,16.5 L10.8,16.5 L7.17999995,12.88 C8.56999995,11.72 10.34,11 12.3,11 C15.84,11 18.85,13.31 19.9,16.5 L22.27,15.72 C20.88,11.53 16.95,8.5 12.3,8.5"></path></g>
<!-- This is a custom PolyGerrit SVG -->
<g id="stopEdit"><path d="M4 4 20 4 20 20 4 20z"></path></g>
<!-- This is a custom PolyGerrit SVG -->
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api.js
index 98268c5..b5ff46d 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-annotation-actions-js-api.js
@@ -156,7 +156,7 @@
GrAnnotationActionsInterface.prototype.getLayer = function(
path, changeNum, patchNum) {
const annotationLayer = new AnnotationLayer(path, changeNum, patchNum,
- this._addLayerFunc);
+ this._addLayerFunc);
this._annotationLayers.push(annotationLayer);
return annotationLayer;
};
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-api-utils.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-api-utils.js
index 2c5f39c..4123f70 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-api-utils.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-api-utils.js
@@ -19,6 +19,7 @@
'use strict';
const PRELOADED_PROTOCOL = 'preloaded:';
+ const PLUGIN_LOADING_TIMEOUT_MS = 10000;
let _restAPI;
function getRestAPI() {
@@ -28,6 +29,10 @@
return _restAPI;
}
+ function getBaseUrl() {
+ return Gerrit.BaseUrlBehavior.getBaseUrl();
+ }
+
/**
* Retrieves the name of the plugin base on the url.
* @param {string|URL} url
@@ -96,6 +101,9 @@
getPluginNameFromUrl,
send,
getRestAPI,
+ getBaseUrl,
+ PRELOADED_PROTOCOL,
+ PLUGIN_LOADING_TIMEOUT_MS,
// TEST only methods
testOnly_resetInternalState,
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api_test.html
index 0131912..7332877 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-change-actions-js-api_test.html
@@ -58,13 +58,14 @@
Gerrit.install(p => { plugin = p; }, '0.1',
'http://test.com/plugins/testplugin/static/test.js');
// Mimic all plugins loaded.
- Gerrit._setPluginsPending([]);
+ Gerrit._loadPlugins([]);
changeActions = plugin.changeActions();
element = fixture('basic');
});
teardown(() => {
changeActions = null;
+ Gerrit._testOnly_resetPlugins();
});
test('does not throw', ()=> {
@@ -85,11 +86,12 @@
'http://test.com/plugins/testplugin/static/test.js');
changeActions = plugin.changeActions();
// Mimic all plugins loaded.
- Gerrit._setPluginsPending([]);
+ Gerrit._loadPlugins([]);
});
teardown(() => {
changeActions = null;
+ Gerrit._testOnly_resetPlugins();
});
test('property existence', () => {
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit.js
index a567700..546b9f3 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit.js
@@ -15,7 +15,7 @@
* limitations under the License.
*/
- /**
+/**
* This defines the Gerrit instance. All methods directly attached to Gerrit
* should be defined or linked here.
*/
@@ -23,42 +23,12 @@
(function(window) {
'use strict';
- /**
- * Hash of loaded and installed plugins, name to Plugin object.
- */
- const _plugins = {};
-
- /**
- * Array of plugin URLs to be loaded, name to url.
- */
- let _pluginsPending = {};
-
- let _pluginsInstalled = [];
-
- let _pluginsPendingCount = -1;
-
- const UNKNOWN_PLUGIN = 'unknown';
- const PRELOADED_PROTOCOL = 'preloaded:';
-
- const PLUGIN_LOADING_TIMEOUT_MS = 10000;
-
- let _reporting;
- const getReporting = () => {
- if (!_reporting) {
- _reporting = document.createElement('gr-reporting');
- }
- return _reporting;
- };
-
// Import utils methods
const {
- getPluginNameFromUrl,
- send,
- getRestAPI,
+ send,
+ getRestAPI,
} = window._apiUtils;
- const API_VERSION = '0.1';
-
/**
* Trigger the preinstalls for bundled plugins.
* This needs to happen before Gerrit as plugin bundle overrides the Gerrit.
@@ -72,9 +42,7 @@
window.Gerrit = window.Gerrit || {};
const Gerrit = window.Gerrit;
-
- let _resolveAllPluginsLoaded = null;
- let _allPluginsPromise = null;
+ Gerrit._pluginLoader = new PluginLoader();
Gerrit._endpoints = new GrPluginEndpoints();
@@ -85,20 +53,13 @@
const {
testOnly_resetInternalState,
} = window._apiUtils;
- Gerrit._testOnly_installPreloadedPlugins = installPreloadedPlugins;
+ Gerrit._testOnly_installPreloadedPlugins = (...args) => Gerrit._pluginLoader
+ .installPreloadedPlugins(...args);
Gerrit._testOnly_flushPreinstalls = flushPreinstalls;
Gerrit._testOnly_resetPlugins = () => {
- _allPluginsPromise = null;
- _pluginsInstalled = [];
- _pluginsPending = {};
- _pluginsPendingCount = -1;
- _reporting = null;
- _resolveAllPluginsLoaded = null;
testOnly_resetInternalState();
Gerrit._endpoints = new GrPluginEndpoints();
- for (const k of Object.keys(_plugins)) {
- delete _plugins[k];
- }
+ Gerrit._pluginLoader = new PluginLoader();
};
}
@@ -122,36 +83,7 @@
};
Gerrit.install = function(callback, opt_version, opt_src) {
- // HTML import polyfill adds __importElement pointing to the import tag.
- const script = document.currentScript &&
- (document.currentScript.__importElement || document.currentScript);
-
- let src = opt_src || (script && script.src);
- if (!src || src.startsWith('data:')) {
- src = script && script.baseURI;
- }
- const name = getPluginNameFromUrl(src);
-
- if (opt_version && opt_version !== API_VERSION) {
- Gerrit._pluginInstallError(`Plugin ${name} install error: only version ` +
- API_VERSION + ' is supported in PolyGerrit. ' + opt_version +
- ' was given.');
- return;
- }
-
- const existingPlugin = _plugins[name];
- const plugin = existingPlugin || new Plugin(src);
- try {
- callback(plugin);
- if (name) {
- _plugins[name] = plugin;
- }
- if (!existingPlugin) {
- Gerrit._pluginInstalled(src);
- }
- } catch (e) {
- Gerrit._pluginInstallError(`${e.name}: ${e.message}`);
- }
+ Gerrit._pluginLoader.install(callback, opt_version, opt_src);
};
Gerrit.getLoggedIn = function() {
@@ -195,96 +127,33 @@
};
Gerrit.awaitPluginsLoaded = function() {
- if (!_allPluginsPromise) {
- if (Gerrit._arePluginsLoaded()) {
- _allPluginsPromise = Promise.resolve();
- } else {
- let timeoutId;
- _allPluginsPromise =
- Promise.race([
- new Promise(resolve => _resolveAllPluginsLoaded = resolve),
- new Promise(resolve => timeoutId = setTimeout(
- Gerrit._pluginLoadingTimeout, PLUGIN_LOADING_TIMEOUT_MS)),
- ]).then(() => clearTimeout(timeoutId));
- }
- }
- return _allPluginsPromise;
+ return Gerrit._pluginLoader.awaitPluginsLoaded();
};
- Gerrit._pluginLoadingTimeout = function() {
- console.error(`Failed to load plugins: ${Object.keys(_pluginsPending)}`);
- Gerrit._setPluginsPending([]);
- };
+ // TODO(taoalpha): consider removing these proxy methods
+ // and using _pluginLoader directly
- Gerrit._setPluginsPending = function(plugins) {
- _pluginsPending = plugins.reduce((o, url) => {
- // TODO(viktard): Remove guard (@see Issue 8962)
- o[getPluginNameFromUrl(url) || UNKNOWN_PLUGIN] = url;
- return o;
- }, {});
- Gerrit._setPluginsCount(Object.keys(_pluginsPending).length);
- };
-
- Gerrit._setPluginsCount = function(count) {
- _pluginsPendingCount = count;
- if (Gerrit._arePluginsLoaded()) {
- getReporting().pluginsLoaded(_pluginsInstalled);
- if (_resolveAllPluginsLoaded) {
- _resolveAllPluginsLoaded();
- }
- }
- };
-
- Gerrit._pluginInstallError = function(message) {
- document.dispatchEvent(new CustomEvent('show-alert', {
- detail: {
- message: `Plugin install error: ${message}`,
- },
- }));
- console.info(`Plugin install error: ${message}`);
- Gerrit._setPluginsCount(_pluginsPendingCount - 1);
- };
-
- Gerrit._pluginInstalled = function(url) {
- const name = getPluginNameFromUrl(url) || UNKNOWN_PLUGIN;
- if (!_pluginsPending[name]) {
- console.warn(`Unexpected plugin ${name} installed from ${url}.`);
- } else {
- delete _pluginsPending[name];
- _pluginsInstalled.push(name);
- Gerrit._setPluginsCount(_pluginsPendingCount - 1);
- getReporting().pluginLoaded(name);
- console.log(`Plugin ${name} installed.`);
- }
+ Gerrit._loadPlugins = function(plugins, opt_option) {
+ Gerrit._pluginLoader.loadPlugins(plugins, opt_option);
};
Gerrit._arePluginsLoaded = function() {
- return _pluginsPendingCount === 0;
- };
-
- Gerrit._getPluginScreenName = function(pluginName, screenName) {
- return `${pluginName}-screen-${screenName}`;
+ return Gerrit._pluginLoader.arePluginsLoaded;
};
Gerrit._isPluginPreloaded = function(url) {
- const name = getPluginNameFromUrl(url);
- if (name && Gerrit._preloadedPlugins) {
- return name in Gerrit._preloadedPlugins;
- } else {
- return false;
- }
+ return Gerrit._pluginLoader.isPluginPreloaded(url);
};
- function installPreloadedPlugins() {
- if (!Gerrit._preloadedPlugins) { return; }
- for (const name in Gerrit._preloadedPlugins) {
- if (!Gerrit._preloadedPlugins.hasOwnProperty(name)) { continue; }
- const callback = Gerrit._preloadedPlugins[name];
- Gerrit.install(callback, API_VERSION, PRELOADED_PROTOCOL + name);
- }
- }
+ Gerrit._isPluginEnabled = function(pathOrUrl) {
+ return Gerrit._pluginLoader.isPluginEnabled(pathOrUrl);
+ };
+
+ Gerrit._isPluginLoaded = function(pathOrUrl) {
+ return Gerrit._pluginLoader.isPluginLoaded(pathOrUrl);
+ };
// Preloaded plugins should be installed after Gerrit.install() is set,
// since plugin preloader substitutes Gerrit.install() temporarily.
- installPreloadedPlugins();
+ Gerrit._pluginLoader.installPreloadedPlugins();
})(window);
\ No newline at end of file
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit_test.html
index 9a05454..e81b8aa 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-gerrit_test.html
@@ -37,11 +37,11 @@
<script>
suite('gr-gerrit tests', () => {
let element;
- let plugin;
let sandbox;
let sendStub;
setup(() => {
+ this.clock = sinon.useFakeTimers();
sandbox = sinon.sandbox.create();
sendStub = sandbox.stub().returns(Promise.resolve({status: 200}));
stub('gr-rest-api-interface', {
@@ -53,136 +53,48 @@
},
});
element = fixture('basic');
- Gerrit.install(p => { plugin = p; }, '0.1',
- 'http://test.com/plugins/testplugin/static/test.js');
- Gerrit._setPluginsPending([]);
});
teardown(() => {
+ this.clock.restore();
sandbox.restore();
element._removeEventCallbacks();
- plugin = null;
+ Gerrit._testOnly_resetPlugins();
});
- test('reuse plugin for install calls', () => {
- let otherPlugin;
- Gerrit.install(p => { otherPlugin = p; }, '0.1',
- 'http://test.com/plugins/testplugin/static/test.js');
- assert.strictEqual(plugin, otherPlugin);
- });
-
- test('flushes preinstalls if provided', () => {
- assert.doesNotThrow(() => {
- Gerrit._testOnly_flushPreinstalls();
+ suite('proxy methods', () => {
+ test('Gerrit._isPluginEnabled proxy to pluginLoader', () => {
+ const stubFn = sandbox.stub();
+ sandbox.stub(
+ Gerrit._pluginLoader,
+ 'isPluginEnabled',
+ (...args) => stubFn(...args)
+ );
+ Gerrit._isPluginEnabled('test_plugin');
+ assert.isTrue(stubFn.calledWith('test_plugin'));
});
- window.Gerrit.flushPreinstalls = sandbox.stub();
- Gerrit._testOnly_flushPreinstalls();
- assert.isTrue(window.Gerrit.flushPreinstalls.calledOnce);
- delete window.Gerrit.flushPreinstalls;
- });
- test('versioning', () => {
- const callback = sandbox.spy();
- Gerrit.install(callback, '0.0pre-alpha');
- assert(callback.notCalled);
- });
-
- test('_setPluginsCount', done => {
- stub('gr-reporting', {
- pluginsLoaded() {
- done();
- },
+ test('Gerrit._isPluginLoaded proxy to pluginLoader', () => {
+ const stubFn = sandbox.stub();
+ sandbox.stub(
+ Gerrit._pluginLoader,
+ 'isPluginLoaded',
+ (...args) => stubFn(...args)
+ );
+ Gerrit._isPluginLoaded('test_plugin');
+ assert.isTrue(stubFn.calledWith('test_plugin'));
});
- Gerrit._setPluginsCount(0);
- });
- test('_arePluginsLoaded', () => {
- assert.isTrue(Gerrit._arePluginsLoaded());
- Gerrit._setPluginsCount(1);
- assert.isFalse(Gerrit._arePluginsLoaded());
- Gerrit._setPluginsCount(0);
- assert.isTrue(Gerrit._arePluginsLoaded());
- });
-
- test('_pluginInstalled', () => {
- const pluginsLoadedStub = sandbox.stub();
- stub('gr-reporting', {
- pluginsLoaded: (...args) => pluginsLoadedStub(...args),
+ test('Gerrit._isPluginPreloaded proxy to pluginLoader', () => {
+ const stubFn = sandbox.stub();
+ sandbox.stub(
+ Gerrit._pluginLoader,
+ 'isPluginPreloaded',
+ (...args) => stubFn(...args)
+ );
+ Gerrit._isPluginPreloaded('test_plugin');
+ assert.isTrue(stubFn.calledWith('test_plugin'));
});
- const plugins = [
- 'http://test.com/plugins/foo/static/test.js',
- 'http://test.com/plugins/bar/static/test.js',
- ];
- Gerrit._setPluginsPending(plugins);
- Gerrit._pluginInstalled(plugins[0]);
- Gerrit._pluginInstalled(plugins[1]);
- assert.isTrue(pluginsLoadedStub.calledWithExactly(['foo', 'bar']));
- });
-
- test('install calls _pluginInstalled', () => {
- sandbox.stub(Gerrit, '_pluginInstalled');
- Gerrit.install(p => { plugin = p; }, '0.1',
- 'http://test.com/plugins/testplugin/static/test.js');
-
- // testplugin has already been installed once (in setup).
- assert.isFalse(Gerrit._pluginInstalled.called);
-
- // testplugin2 plugin has not yet been installed.
- Gerrit.install(p => { plugin = p; }, '0.1',
- 'http://test.com/plugins/testplugin2/static/test.js');
- assert.isTrue(Gerrit._pluginInstalled.calledOnce);
- });
-
- test('plugin install errors mark plugins as loaded', () => {
- Gerrit._setPluginsCount(1);
- Gerrit.install(() => {}, '0.0pre-alpha');
- return Gerrit.awaitPluginsLoaded();
- });
-
- test('multiple ui plugins per java plugin', () => {
- const file1 = 'http://test.com/plugins/qaz/static/foo.nocache.js';
- const file2 = 'http://test.com/plugins/qaz/static/bar.js';
- Gerrit._setPluginsPending([file1, file2]);
- Gerrit.install(() => {}, '0.1', file1);
- Gerrit.install(() => {}, '0.1', file2);
- return Gerrit.awaitPluginsLoaded();
- });
-
- test('plugin install errors shows toasts', () => {
- const alertStub = sandbox.stub();
- document.addEventListener('show-alert', alertStub);
- Gerrit._setPluginsCount(1);
- Gerrit.install(() => {}, '0.0pre-alpha');
- return Gerrit.awaitPluginsLoaded().then(() => {
- assert.isTrue(alertStub.calledOnce);
- });
- });
-
- test('Gerrit._isPluginPreloaded', () => {
- Gerrit._preloadedPlugins = {baz: ()=>{}};
- assert.isFalse(Gerrit._isPluginPreloaded('plugins/foo/bar'));
- assert.isFalse(Gerrit._isPluginPreloaded('http://a.com/42'));
- assert.isTrue(Gerrit._isPluginPreloaded('preloaded:baz'));
- Gerrit._preloadedPlugins = null;
- });
-
- test('preloaded plugins are installed', () => {
- const installStub = sandbox.stub();
- Gerrit._preloadedPlugins = {foo: installStub};
- Gerrit._testOnly_installPreloadedPlugins();
- assert.isTrue(installStub.called);
- const pluginApi = installStub.lastCall.args[0];
- assert.strictEqual(pluginApi.getPluginName(), 'foo');
- });
-
- test('installing preloaded plugin', () => {
- let plugin;
- window.ASSETS_PATH = 'http://blips.com/chitz';
- Gerrit.install(p => { plugin = p; }, '0.1', 'preloaded:foo');
- assert.strictEqual(plugin.getPluginName(), 'foo');
- assert.strictEqual(plugin.url('/some/thing.html'),
- 'http://blips.com/chitz/plugins/foo/some/thing.html');
- delete window.ASSETS_PATH;
});
});
</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 7fa2250..dc81545 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
@@ -31,6 +31,13 @@
<link rel="import" href="../gr-rest-api-interface/gr-rest-api-interface.html">
<dom-module id="gr-js-api-interface">
+ <!--
+ Note: the order matters as files depend on each other.
+ 1. gr-api-utils will be used in multiple files below.
+ 2. gr-gerrit depends on gr-plugin-loader, gr-public-js-api and
+ also gr-plugin-endpoints
+ 3. gr-public-js-api depends on gr-plugin-rest-api
+ -->
<script src="gr-api-utils.js"></script>
<script src="gr-annotation-actions-context.js"></script>
<script src="gr-annotation-actions-js-api.js"></script>
@@ -41,5 +48,6 @@
<script src="gr-plugin-action-context.js"></script>
<script src="gr-plugin-rest-api.js"></script>
<script src="gr-public-js-api.js"></script>
+ <script src="gr-plugin-loader.js"></script>
<script src="gr-gerrit.js"></script>
</dom-module>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.js
index 70a7a01..3206b21 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface.js
@@ -25,6 +25,7 @@
COMMIT_MSG_EDIT: 'commitmsgedit',
COMMENT: 'comment',
REVERT: 'revert',
+ REVERT_SUBMISSION: 'revert_submission',
POST_REVERT: 'postrevert',
ANNOTATE_DIFF: 'annotatediff',
ADMIN_MENU_LINKS: 'admin-menu-links',
@@ -213,10 +214,21 @@
return revertMsg;
},
+ modifyRevertSubmissionMsg(change, revertSubmissionMsg, origMsg) {
+ for (const cb of this._getEventCallbacks(EventType.REVERT_SUBMISSION)) {
+ try {
+ revertSubmissionMsg = cb(change, revertSubmissionMsg, origMsg);
+ } catch (err) {
+ console.error(err);
+ }
+ }
+ return revertSubmissionMsg;
+ },
+
getDiffLayers(path, changeNum, patchNum) {
const layers = [];
for (const annotationApi of
- this._getEventCallbacks(EventType.ANNOTATE_DIFF)) {
+ this._getEventCallbacks(EventType.ANNOTATE_DIFF)) {
try {
const layer = annotationApi.getLayer(path, changeNum, patchNum);
layers.push(layer);
@@ -243,7 +255,7 @@
getCoverageRanges(changeNum, path, basePatchNum, patchNum) {
return Gerrit.awaitPluginsLoaded().then(() => {
for (const annotationApi of
- this._getEventCallbacks(EventType.ANNOTATE_DIFF)) {
+ this._getEventCallbacks(EventType.ANNOTATE_DIFF)) {
const provider = annotationApi.getCoverageProvider();
// Only one coverage provider makes sense. If there are more, then we
// simply ignore them.
@@ -258,7 +270,7 @@
getAdminMenuLinks() {
const links = [];
for (const adminApi of
- this._getEventCallbacks(EventType.ADMIN_MENU_LINKS)) {
+ this._getEventCallbacks(EventType.ADMIN_MENU_LINKS)) {
links.push(...adminApi.getMenuLinks());
}
return links;
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html
index 330310f..ae12940 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-js-api-interface_test.html
@@ -35,6 +35,7 @@
</test-fixture>
<script>
+ const {PLUGIN_LOADING_TIMEOUT_MS} = window._apiUtils;
suite('gr-js-api-interface tests', () => {
let element;
let plugin;
@@ -48,6 +49,7 @@
};
setup(() => {
+ this.clock = sinon.useFakeTimers();
sandbox = sinon.sandbox.create();
getResponseObjectStub = sandbox.stub().returns(Promise.resolve());
sendStub = sandbox.stub().returns(Promise.resolve({status: 200}));
@@ -64,10 +66,11 @@
errorStub = sandbox.stub(console, 'error');
Gerrit.install(p => { plugin = p; }, '0.1',
'http://test.com/plugins/testplugin/static/test.js');
- Gerrit._setPluginsPending([]);
+ Gerrit._loadPlugins([]);
});
teardown(() => {
+ this.clock.restore();
sandbox.restore();
element._removeEventCallbacks();
plugin = null;
@@ -194,12 +197,15 @@
revisions: {def: {_number: 2}, abc: {_number: 1}},
};
const spy = sandbox.spy();
- Gerrit._setPluginsCount(1);
+ Gerrit._loadPlugins(['plugins/test.html']);
plugin.on(element.EventType.SHOW_CHANGE, spy);
element.handleEvent(element.EventType.SHOW_CHANGE,
{change: testChange, patchNum: 1});
assert.isFalse(spy.called);
- Gerrit._setPluginsCount(0);
+
+ // Timeout on loading plugins
+ this.clock.tick(PLUGIN_LOADING_TIMEOUT_MS * 2);
+
flush(() => {
assert.isTrue(spy.called);
done();
@@ -334,7 +340,6 @@
setup(() => {
sandbox.stub(Gerrit.BaseUrlBehavior, 'getBaseUrl').returns('/r');
- Gerrit._setPluginsCount(1);
Gerrit.install(p => { baseUrlPlugin = p; }, '0.1',
'http://test.com/r/plugins/baseurlplugin/static/test.js');
});
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-action-context_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-action-context_test.html
index 229c020..6da117f 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-action-context_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-action-context_test.html
@@ -42,7 +42,6 @@
setup(() => {
sandbox = sinon.sandbox.create();
- Gerrit._setPluginsCount(1);
Gerrit.install(p => { plugin = p; }, '0.1',
'http://test.com/plugins/testplugin/static/test.js');
instance = new GrPluginActionContext(plugin);
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints.js
index 8832a3f..4b7778c 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints.js
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-endpoints.js
@@ -33,7 +33,7 @@
GrPluginEndpoints.prototype._getOrCreateModuleInfo = function(plugin,
endpoint, type, moduleName, domHook) {
const existingModule = this._endpoints[endpoint].find(info =>
- info.plugin === plugin &&
+ info.plugin === plugin &&
info.moduleName === moduleName &&
info.domHook === domHook
);
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader.js
new file mode 100644
index 0000000..4be38b6
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader.js
@@ -0,0 +1,393 @@
+/**
+ * @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';
+
+ // Import utils methods
+ const {
+ PLUGIN_LOADING_TIMEOUT_MS,
+ PRELOADED_PROTOCOL,
+ getPluginNameFromUrl,
+ getBaseUrl,
+ } = window._apiUtils;
+
+ /**
+ * @enum {string}
+ */
+ const PluginState = {
+ /**
+ * State that indicates the plugin is pending to be loaded.
+ */
+ PENDING: 'PENDING',
+
+ /**
+ * State that indicates the plugin is already loaded.
+ */
+ LOADED: 'LOADED',
+
+ /**
+ * State that indicates the plugin is already loaded.
+ */
+ PRE_LOADED: 'PRE_LOADED',
+
+ /**
+ * State that indicates the plugin failed to load.
+ */
+ LOAD_FAILED: 'LOAD_FAILED',
+ };
+
+ // Prefix for any unrecognized plugin urls.
+ // Url should match following patterns:
+ // /plugins/PLUGINNAME/static/SCRIPTNAME.(html|js)
+ // /plugins/PLUGINNAME.(js|html)
+ const UNKNOWN_PLUGIN_PREFIX = '__$$__';
+
+ // Current API version for Plugin,
+ // plugins with incompatible version will not be laoded.
+ const API_VERSION = '0.1';
+
+ /**
+ * PluginLoader, responsible for:
+ *
+ * Loading all plugins and handling errors etc.
+ * Recording plugin state.
+ * Reporting on plugin loading status.
+ * Retrieve plugin.
+ * Check plugin status and if all plugins loaded.
+ */
+ class PluginLoader {
+ constructor() {
+ this._pluginListLoaded = false;
+
+ /** @type {Map<string,PluginLoader.PluginObject>} */
+ this._plugins = new Map();
+
+ this._reporting = null;
+
+ // Promise that resolves when all plugins loaded
+ this._loadingPromise = null;
+
+ // Resolver to resolve _loadingPromise once all plugins loaded
+ this._loadingResolver = null;
+ }
+
+ _getReporting() {
+ if (!this._reporting) {
+ this._reporting = document.createElement('gr-reporting');
+ }
+ return this._reporting;
+ }
+
+ /**
+ * Use the plugin name or use the full url if not recognized.
+ * @see gr-api-utils#getPluginNameFromUrl
+ * @param {string|URL} url
+ */
+ _getPluginKeyFromUrl(url) {
+ return getPluginNameFromUrl(url) ||
+ `${UNKNOWN_PLUGIN_PREFIX}${url}`;
+ }
+
+ /**
+ * Load multiple plugins with certain options.
+ *
+ * @param {Array<string>} plugins
+ * @param {Object<string, PluginLoader.PluginOption>} opts
+ */
+ loadPlugins(plugins = [], opts = {}) {
+ this._pluginListLoaded = true;
+
+ plugins.forEach(path => {
+ const url = this._urlFor(path);
+ // Skip if preloaded, for bundling.
+ if (this.isPluginPreloaded(url)) return;
+
+ const pluginKey = this._getPluginKeyFromUrl(url);
+ // Skip if already installed.
+ if (this._plugins.has(pluginKey)) return;
+ this._plugins.set(pluginKey, {
+ name: pluginKey,
+ url,
+ state: PluginState.PENDING,
+ plugin: null,
+ });
+
+ if (this._isPathEndsWith(url, '.html')) {
+ this._importHtmlPlugin(url, opts && opts[path]);
+ } else if (this._isPathEndsWith(url, '.js')) {
+ this._loadJsPlugin(url);
+ } else {
+ this._failToLoad(`Unrecognized plugin url ${url}`, url);
+ }
+ });
+
+ this.awaitPluginsLoaded().then(() => {
+ console.info('Plugins loaded');
+ this._getReporting().pluginsLoaded(this._getAllInstalledPluginNames());
+ });
+ }
+
+ _isPathEndsWith(url, suffix) {
+ if (!(url instanceof URL)) {
+ try {
+ url = new URL(url);
+ } catch (e) {
+ console.warn(e);
+ return false;
+ }
+ }
+
+ return url.pathname && url.pathname.endsWith(suffix);
+ }
+
+ _getAllInstalledPluginNames() {
+ const installedPlugins = [];
+ for (const plugin of this._plugins.values()) {
+ if (plugin.state === PluginState.LOADED) {
+ installedPlugins.push(plugin.name);
+ }
+ }
+ return installedPlugins;
+ }
+
+ install(callback, opt_version, opt_src) {
+ // HTML import polyfill adds __importElement pointing to the import tag.
+ const script = document.currentScript &&
+ (document.currentScript.__importElement || document.currentScript);
+ let src = opt_src || (script && script.src);
+ if (!src || src.startsWith('data:')) {
+ src = script && script.baseURI;
+ }
+
+ if (opt_version && opt_version !== API_VERSION) {
+ this._failToLoad(`Plugin ${src} install error: only version ` +
+ API_VERSION + ' is supported in PolyGerrit. ' + opt_version +
+ ' was given.', src);
+ return;
+ }
+
+ const pluginObject = this.getPlugin(src);
+ let plugin = pluginObject && pluginObject.plugin;
+ if (!plugin) {
+ plugin = new Plugin(src);
+ }
+ try {
+ callback(plugin);
+ this._pluginInstalled(src, plugin);
+ } catch (e) {
+ this._failToLoad(`${e.name}: ${e.message}`, src);
+ }
+ }
+
+ get arePluginsLoaded() {
+ // As the size of plugins is relatively small,
+ // so the performance of this check should be reasonable
+ if (!this._pluginListLoaded) return false;
+ for (const plugin of this._plugins.values()) {
+ if (plugin.state === PluginState.PENDING) return false;
+ }
+ return true;
+ }
+
+ _checkIfCompleted() {
+ if (this.arePluginsLoaded && this._loadingResolver) {
+ this._loadingResolver();
+ this._loadingResolver = null;
+ this._loadingPromise = null;
+ }
+ }
+
+ _timeout() {
+ const pendingPlugins = [];
+ for (const plugin of this._plugins.values()) {
+ if (plugin.state === PluginState.PENDING) {
+ this._updatePluginState(plugin.url, PluginState.LOAD_FAILED);
+ this._checkIfCompleted();
+ pendingPlugins.push(plugin.url);
+ }
+ }
+ return `Timeout when loading plugins: ${pendingPlugins.join(',')}`;
+ }
+
+ _failToLoad(message, pluginUrl) {
+ // Show an alert with the error
+ document.dispatchEvent(new CustomEvent('show-alert', {
+ detail: {
+ message: `Plugin install error: ${message} from ${pluginUrl}`,
+ },
+ }));
+ this._updatePluginState(pluginUrl, PluginState.LOAD_FAILED);
+ this._checkIfCompleted();
+ }
+
+ _updatePluginState(pluginUrl, state) {
+ const key = this._getPluginKeyFromUrl(pluginUrl);
+ if (this._plugins.has(key)) {
+ this._plugins.get(key).state = state;
+ } else {
+ // Plugin is not recorded for some reason.
+ console.warn(`Plugin loaded separately: ${pluginUrl}`);
+ this._plugins.set(key, {
+ name: key,
+ url: pluginUrl,
+ state,
+ plugin: null,
+ });
+ }
+ return this._plugins.get(key);
+ }
+
+ _pluginInstalled(url, plugin) {
+ const pluginObj = this._updatePluginState(url, PluginState.LOADED);
+ pluginObj.plugin = plugin;
+ this._getReporting().pluginLoaded(plugin.getPluginName() || url);
+ console.log(`Plugin ${plugin.getPluginName() || url} installed.`);
+ this._checkIfCompleted();
+ }
+
+ installPreloadedPlugins() {
+ if (!window.Gerrit || !window.Gerrit._preloadedPlugins) { return; }
+ const Gerrit = window.Gerrit;
+ for (const name in Gerrit._preloadedPlugins) {
+ if (!Gerrit._preloadedPlugins.hasOwnProperty(name)) { continue; }
+ const callback = Gerrit._preloadedPlugins[name];
+ this.install(callback, API_VERSION, PRELOADED_PROTOCOL + name);
+ }
+ }
+
+ isPluginPreloaded(pathOrUrl) {
+ const url = this._urlFor(pathOrUrl);
+ const name = getPluginNameFromUrl(url);
+ if (name && window.Gerrit._preloadedPlugins) {
+ return window.Gerrit._preloadedPlugins.hasOwnProperty(name);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Checks if given plugin path/url is enabled or not.
+ * @param {string} pathOrUrl
+ */
+ isPluginEnabled(pathOrUrl) {
+ const url = this._urlFor(pathOrUrl);
+ if (this.isPluginPreloaded(url)) return true;
+ const key = this._getPluginKeyFromUrl(url);
+ return this._plugins.has(key);
+ }
+
+ /**
+ * Returns the plugin object with a given url.
+ * @param {string} pathOrUrl
+ */
+ getPlugin(pathOrUrl) {
+ const key = this._getPluginKeyFromUrl(this._urlFor(pathOrUrl));
+ return this._plugins.get(key);
+ }
+
+ /**
+ * Checks if given plugin path/url is loaded or not.
+ * @param {string} pathOrUrl
+ */
+ isPluginLoaded(pathOrUrl) {
+ const url = this._urlFor(pathOrUrl);
+ const key = this._getPluginKeyFromUrl(url);
+ return this._plugins.has(key) ?
+ this._plugins.get(key).state === PluginState.LOADED :
+ false;
+ }
+
+ _importHtmlPlugin(pluginUrl, opts = {}) {
+ // onload (second param) needs to be a function. When null or undefined
+ // were passed, plugins were not loaded correctly.
+ (Polymer.importHref || Polymer.Base.importHref)(
+ this._urlFor(pluginUrl), () => {},
+ () => this._failToLoad(`${pluginUrl} import error`, pluginUrl),
+ !opts.sync);
+ }
+
+ _loadJsPlugin(pluginUrl) {
+ this._createScriptTag(this._urlFor(pluginUrl));
+ }
+
+ _createScriptTag(url) {
+ const el = document.createElement('script');
+ el.defer = true;
+ el.setAttribute('src', url);
+ el.onerror = () => this._failToLoad(`${url} load error`, url);
+ return document.body.appendChild(el);
+ }
+
+ _urlFor(pathOrUrl) {
+ if (!pathOrUrl) {
+ return pathOrUrl;
+ }
+ if (pathOrUrl.startsWith(PRELOADED_PROTOCOL) ||
+ pathOrUrl.startsWith('http')) {
+ // Plugins are loaded from another domain or preloaded.
+ return pathOrUrl;
+ }
+ if (!pathOrUrl.startsWith('/')) {
+ pathOrUrl = '/' + pathOrUrl;
+ }
+ return window.location.origin + getBaseUrl() + pathOrUrl;
+ }
+
+ awaitPluginsLoaded() {
+ // Resolve if completed.
+ this._checkIfCompleted();
+
+ if (this.arePluginsLoaded) {
+ return Promise.resolve();
+ }
+ if (!this._loadingPromise) {
+ let timerId;
+ this._loadingPromise =
+ Promise.race([
+ new Promise(resolve => this._loadingResolver = resolve),
+ new Promise((_, reject) => timerId = setTimeout(
+ () => {
+ reject(this._timeout());
+ }, PLUGIN_LOADING_TIMEOUT_MS)),
+ ]).then(() => {
+ if (timerId) clearTimeout(timerId);
+ });
+ }
+ return this._loadingPromise;
+ }
+ }
+
+ /**
+ * @typedef {{
+ * name:string,
+ * url:string,
+ * state:PluginState,
+ * plugin:Object
+ * }}
+ */
+ PluginLoader.PluginObject;
+
+ /**
+ * @typedef {{
+ * sync:boolean,
+ * }}
+ */
+ PluginLoader.PluginOption;
+
+ window.PluginLoader = PluginLoader;
+})(window);
\ No newline at end of file
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader_test.html
new file mode 100644
index 0000000..ee54319
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-loader_test.html
@@ -0,0 +1,502 @@
+<!DOCTYPE html>
+<!--
+@license
+Copyright (C) 2017 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-plugin-host</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="gr-js-api-interface.html">
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+ <template>
+ <gr-js-api-interface></gr-js-api-interface>
+ </template>
+</test-fixture>
+
+<script>
+ const {PRELOADED_PROTOCOL, PLUGIN_LOADING_TIMEOUT_MS} = window._apiUtils;
+ suite('gr-plugin-loader tests', () => {
+ let plugin;
+ let sandbox;
+ let url;
+ let sendStub;
+
+ setup(() => {
+ this.clock = sinon.useFakeTimers();
+ sandbox = sinon.sandbox.create();
+ sendStub = sandbox.stub().returns(Promise.resolve({status: 200}));
+ stub('gr-rest-api-interface', {
+ getAccount() {
+ return Promise.resolve({name: 'Judy Hopps'});
+ },
+ send(...args) {
+ return sendStub(...args);
+ },
+ });
+ sandbox.stub(document.body, 'appendChild');
+ fixture('basic');
+ url = window.location.origin;
+ });
+
+ teardown(() => {
+ sandbox.restore();
+ this.clock.restore();
+ Gerrit._testOnly_resetPlugins();
+ });
+
+ test('reuse plugin for install calls', () => {
+ Gerrit.install(p => { plugin = p; }, '0.1',
+ 'http://test.com/plugins/testplugin/static/test.js');
+
+ let otherPlugin;
+ Gerrit.install(p => { otherPlugin = p; }, '0.1',
+ 'http://test.com/plugins/testplugin/static/test.js');
+ assert.strictEqual(plugin, otherPlugin);
+ });
+
+ test('flushes preinstalls if provided', () => {
+ assert.doesNotThrow(() => {
+ Gerrit._testOnly_flushPreinstalls();
+ });
+ window.Gerrit.flushPreinstalls = sandbox.stub();
+ Gerrit._testOnly_flushPreinstalls();
+ assert.isTrue(window.Gerrit.flushPreinstalls.calledOnce);
+ delete window.Gerrit.flushPreinstalls;
+ });
+
+ test('versioning', () => {
+ const callback = sandbox.spy();
+ Gerrit.install(callback, '0.0pre-alpha');
+ assert(callback.notCalled);
+ });
+
+ test('report pluginsLoaded', done => {
+ stub('gr-reporting', {
+ pluginsLoaded() {
+ done();
+ },
+ });
+ Gerrit._loadPlugins([]);
+ });
+
+ test('arePluginsLoaded', done => {
+ assert.isFalse(Gerrit._arePluginsLoaded());
+ const plugins = [
+ 'http://test.com/plugins/foo/static/test.js',
+ 'http://test.com/plugins/bar/static/test.js',
+ ];
+
+ Gerrit._loadPlugins(plugins);
+ assert.isFalse(Gerrit._arePluginsLoaded());
+ // Timeout on loading plugins
+ this.clock.tick(PLUGIN_LOADING_TIMEOUT_MS * 2);
+
+ flush(() => {
+ assert.isTrue(Gerrit._arePluginsLoaded());
+ done();
+ });
+ });
+
+ test('plugins installed successfully', done => {
+ sandbox.stub(Gerrit._pluginLoader, '_loadJsPlugin', url => {
+ Gerrit.install(() => void 0, undefined, url);
+ });
+ const pluginsLoadedStub = sandbox.stub();
+ stub('gr-reporting', {
+ pluginsLoaded: (...args) => pluginsLoadedStub(...args),
+ });
+
+ const plugins = [
+ 'http://test.com/plugins/foo/static/test.js',
+ 'http://test.com/plugins/bar/static/test.js',
+ ];
+ Gerrit._loadPlugins(plugins);
+
+ flush(() => {
+ assert.isTrue(pluginsLoadedStub.calledWithExactly(['foo', 'bar']));
+ assert.isTrue(Gerrit._arePluginsLoaded());
+ done();
+ });
+ });
+
+ test('isPluginEnabled and isPluginLoaded', done => {
+ sandbox.stub(Gerrit._pluginLoader, '_loadJsPlugin', url => {
+ Gerrit.install(() => void 0, undefined, url);
+ });
+ const pluginsLoadedStub = sandbox.stub();
+ stub('gr-reporting', {
+ pluginsLoaded: (...args) => pluginsLoadedStub(...args),
+ });
+
+ const plugins = [
+ 'http://test.com/plugins/foo/static/test.js',
+ 'http://test.com/plugins/bar/static/test.js',
+ 'bar/static/test.js',
+ ];
+ Gerrit._loadPlugins(plugins);
+ assert.isTrue(
+ plugins.every(plugin => Gerrit._pluginLoader.isPluginEnabled(plugin))
+ );
+
+ flush(() => {
+ assert.isTrue(Gerrit._arePluginsLoaded());
+ assert.isTrue(
+ plugins.every(plugin => Gerrit._pluginLoader.isPluginLoaded(plugin))
+ );
+
+ done();
+ });
+ });
+
+ test('plugins installed mixed result, 1 fail 1 succeed', done => {
+ const plugins = [
+ 'http://test.com/plugins/foo/static/test.js',
+ 'http://test.com/plugins/bar/static/test.js',
+ ];
+
+ const alertStub = sandbox.stub();
+ document.addEventListener('show-alert', alertStub);
+
+ sandbox.stub(Gerrit._pluginLoader, '_loadJsPlugin', url => {
+ Gerrit.install(() => {
+ if (url === plugins[0]) {
+ throw new Error('failed');
+ }
+ }, undefined, url);
+ });
+
+ const pluginsLoadedStub = sandbox.stub();
+ stub('gr-reporting', {
+ pluginsLoaded: (...args) => pluginsLoadedStub(...args),
+ });
+
+ Gerrit._loadPlugins(plugins);
+
+ flush(() => {
+ assert.isTrue(pluginsLoadedStub.calledWithExactly(['bar']));
+ assert.isTrue(Gerrit._arePluginsLoaded());
+ assert.isTrue(alertStub.calledOnce);
+ done();
+ });
+ });
+
+ test('isPluginEnabled and isPluginLoaded for mixed results', done => {
+ const plugins = [
+ 'http://test.com/plugins/foo/static/test.js',
+ 'http://test.com/plugins/bar/static/test.js',
+ ];
+
+ const alertStub = sandbox.stub();
+ document.addEventListener('show-alert', alertStub);
+
+ sandbox.stub(Gerrit._pluginLoader, '_loadJsPlugin', url => {
+ Gerrit.install(() => {
+ if (url === plugins[0]) {
+ throw new Error('failed');
+ }
+ }, undefined, url);
+ });
+
+ const pluginsLoadedStub = sandbox.stub();
+ stub('gr-reporting', {
+ pluginsLoaded: (...args) => pluginsLoadedStub(...args),
+ });
+
+ Gerrit._loadPlugins(plugins);
+ assert.isTrue(
+ plugins.every(plugin => Gerrit._pluginLoader.isPluginEnabled(plugin))
+ );
+
+ flush(() => {
+ assert.isTrue(pluginsLoadedStub.calledWithExactly(['bar']));
+ assert.isTrue(Gerrit._arePluginsLoaded());
+ assert.isTrue(alertStub.calledOnce);
+ assert.isTrue(Gerrit._pluginLoader.isPluginLoaded(plugins[1]));
+ assert.isFalse(Gerrit._pluginLoader.isPluginLoaded(plugins[0]));
+ done();
+ });
+ });
+
+ test('plugins installed all failed', done => {
+ const plugins = [
+ 'http://test.com/plugins/foo/static/test.js',
+ 'http://test.com/plugins/bar/static/test.js',
+ ];
+
+ const alertStub = sandbox.stub();
+ document.addEventListener('show-alert', alertStub);
+
+ sandbox.stub(Gerrit._pluginLoader, '_loadJsPlugin', url => {
+ Gerrit.install(() => {
+ throw new Error('failed');
+ }, undefined, url);
+ });
+
+ const pluginsLoadedStub = sandbox.stub();
+ stub('gr-reporting', {
+ pluginsLoaded: (...args) => pluginsLoadedStub(...args),
+ });
+
+ Gerrit._loadPlugins(plugins);
+
+ flush(() => {
+ assert.isTrue(pluginsLoadedStub.calledWithExactly([]));
+ assert.isTrue(Gerrit._arePluginsLoaded());
+ assert.isTrue(alertStub.calledTwice);
+ done();
+ });
+ });
+
+ test('plugins installed failed becasue of wrong version', done => {
+ const plugins = [
+ 'http://test.com/plugins/foo/static/test.js',
+ 'http://test.com/plugins/bar/static/test.js',
+ ];
+
+ const alertStub = sandbox.stub();
+ document.addEventListener('show-alert', alertStub);
+
+ sandbox.stub(Gerrit._pluginLoader, '_loadJsPlugin', url => {
+ Gerrit.install(() => {
+ }, url === plugins[0] ? '' : 'alpha', url);
+ });
+
+ const pluginsLoadedStub = sandbox.stub();
+ stub('gr-reporting', {
+ pluginsLoaded: (...args) => pluginsLoadedStub(...args),
+ });
+
+ Gerrit._loadPlugins(plugins);
+
+ flush(() => {
+ assert.isTrue(pluginsLoadedStub.calledWithExactly(['foo']));
+ assert.isTrue(Gerrit._arePluginsLoaded());
+ assert.isTrue(alertStub.calledOnce);
+ done();
+ });
+ });
+
+ test('multiple assets for same plugin installed successfully', done => {
+ sandbox.stub(Gerrit._pluginLoader, '_loadJsPlugin', url => {
+ Gerrit.install(() => void 0, undefined, url);
+ });
+ const pluginsLoadedStub = sandbox.stub();
+ stub('gr-reporting', {
+ pluginsLoaded: (...args) => pluginsLoadedStub(...args),
+ });
+
+ const plugins = [
+ 'http://test.com/plugins/foo/static/test.js',
+ 'http://test.com/plugins/foo/static/test2.js',
+ 'http://test.com/plugins/bar/static/test.js',
+ ];
+ Gerrit._loadPlugins(plugins);
+
+ flush(() => {
+ assert.isTrue(pluginsLoadedStub.calledWithExactly(['foo', 'bar']));
+ assert.isTrue(Gerrit._arePluginsLoaded());
+ done();
+ });
+ });
+
+ suite('plugin path and url', () => {
+ let importHtmlPluginStub;
+ let loadJsPluginStub;
+ setup(() => {
+ importHtmlPluginStub = sandbox.stub();
+ sandbox.stub(Gerrit._pluginLoader, '_importHtmlPlugin', url => {
+ importHtmlPluginStub(url);
+ });
+ loadJsPluginStub = sandbox.stub();
+ sandbox.stub(Gerrit._pluginLoader, '_loadJsPlugin', url => {
+ loadJsPluginStub(url);
+ });
+ });
+
+ test('invalid plugin path', () => {
+ const failToLoadStub = sandbox.stub();
+ sandbox.stub(Gerrit._pluginLoader, '_failToLoad', (...args) => {
+ failToLoadStub(...args);
+ });
+
+ Gerrit._loadPlugins([
+ 'foo/bar',
+ ]);
+
+ assert.isTrue(failToLoadStub.calledOnce);
+ assert.isTrue(failToLoadStub.calledWithExactly(
+ `Unrecognized plugin url ${url}/foo/bar`,
+ `${url}/foo/bar`
+ ));
+ });
+
+ test('relative path for plugins', () => {
+ Gerrit._loadPlugins([
+ 'foo/bar.js',
+ 'foo/bar.html',
+ ]);
+
+ assert.isTrue(importHtmlPluginStub.calledOnce);
+ assert.isTrue(
+ importHtmlPluginStub.calledWithExactly(`${url}/foo/bar.html`)
+ );
+ assert.isTrue(loadJsPluginStub.calledOnce);
+ assert.isTrue(
+ loadJsPluginStub.calledWithExactly(`${url}/foo/bar.js`)
+ );
+ });
+
+
+ test('relative path should honor getBaseUrl', () => {
+ const testUrl = '/test';
+ sandbox.stub(Gerrit.BaseUrlBehavior, 'getBaseUrl', () => {
+ return testUrl;
+ });
+
+ Gerrit._loadPlugins([
+ 'foo/bar.js',
+ 'foo/bar.html',
+ ]);
+
+ assert.isTrue(importHtmlPluginStub.calledOnce);
+ assert.isTrue(loadJsPluginStub.calledOnce);
+ assert.isTrue(
+ importHtmlPluginStub.calledWithExactly(
+ `${url}${testUrl}/foo/bar.html`
+ )
+ );
+ assert.isTrue(
+ loadJsPluginStub.calledWithExactly(`${url}${testUrl}/foo/bar.js`)
+ );
+ });
+
+ test('absolute path for plugins', () => {
+ Gerrit._loadPlugins([
+ 'http://e.com/foo/bar.js',
+ 'http://e.com/foo/bar.html',
+ ]);
+
+ assert.isTrue(importHtmlPluginStub.calledOnce);
+ assert.isTrue(
+ importHtmlPluginStub.calledWithExactly(`http://e.com/foo/bar.html`)
+ );
+ assert.isTrue(loadJsPluginStub.calledOnce);
+ assert.isTrue(
+ loadJsPluginStub.calledWithExactly(`http://e.com/foo/bar.js`)
+ );
+ });
+ });
+
+ test('adds js plugins will call the body', () => {
+ Gerrit._loadPlugins([
+ 'http://e.com/foo/bar.js',
+ 'http://e.com/bar/foo.js',
+ ]);
+ assert.isTrue(document.body.appendChild.calledTwice);
+ });
+
+ test('can call awaitPluginsLoaded multiple times', done => {
+ const plugins = [
+ 'http://e.com/foo/bar.js',
+ 'http://e.com/bar/foo.js',
+ ];
+
+ let installed = false;
+ function pluginCallback(url) {
+ if (url === plugins[1]) {
+ installed = true;
+ }
+ }
+ sandbox.stub(Gerrit._pluginLoader, '_loadJsPlugin', url => {
+ Gerrit.install(() => pluginCallback(url), undefined, url);
+ });
+
+ Gerrit._loadPlugins(plugins);
+
+ Gerrit.awaitPluginsLoaded().then(() => {
+ assert.isTrue(installed);
+
+ Gerrit.awaitPluginsLoaded().then(() => {
+ done();
+ });
+ });
+ });
+
+ suite('preloaded plugins', () => {
+ test('skips preloaded plugins when load plugins', () => {
+ const importHtmlPluginStub = sandbox.stub();
+ sandbox.stub(Gerrit._pluginLoader, '_importHtmlPlugin', url => {
+ importHtmlPluginStub(url);
+ });
+ const loadJsPluginStub = sandbox.stub();
+ sandbox.stub(Gerrit._pluginLoader, '_loadJsPlugin', url => {
+ loadJsPluginStub(url);
+ });
+
+ Gerrit._preloadedPlugins = {
+ foo: () => void 0,
+ bar: () => void 0,
+ };
+
+ Gerrit._loadPlugins([
+ 'http://e.com/plugins/foo.js',
+ 'plugins/bar.html',
+ 'http://e.com/plugins/test/foo.js',
+ ]);
+
+ assert.isTrue(importHtmlPluginStub.notCalled);
+ assert.isTrue(loadJsPluginStub.calledOnce);
+ });
+
+ test('isPluginPreloaded', () => {
+ Gerrit._preloadedPlugins = {baz: ()=>{}};
+ assert.isFalse(Gerrit._pluginLoader.isPluginPreloaded('plugins/foo/bar'));
+ assert.isFalse(Gerrit._pluginLoader.isPluginPreloaded('http://a.com/42'));
+ assert.isTrue(
+ Gerrit._pluginLoader.isPluginPreloaded(PRELOADED_PROTOCOL + 'baz')
+ );
+ Gerrit._preloadedPlugins = null;
+ });
+
+ test('preloaded plugins are installed', () => {
+ const installStub = sandbox.stub();
+ Gerrit._preloadedPlugins = {foo: installStub};
+ Gerrit._pluginLoader.installPreloadedPlugins();
+ assert.isTrue(installStub.called);
+ const pluginApi = installStub.lastCall.args[0];
+ assert.strictEqual(pluginApi.getPluginName(), 'foo');
+ });
+
+ test('installing preloaded plugin', () => {
+ let plugin;
+ window.ASSETS_PATH = 'http://blips.com/chitz';
+ Gerrit.install(p => { plugin = p; }, '0.1', 'preloaded:foo');
+ assert.strictEqual(plugin.getPluginName(), 'foo');
+ assert.strictEqual(plugin.url('/some/thing.html'),
+ 'http://blips.com/chitz/plugins/foo/some/thing.html');
+ delete window.ASSETS_PATH;
+ });
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api_test.html b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api_test.html
index 05f84c0..bcbd961 100644
--- a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-plugin-rest-api_test.html
@@ -50,7 +50,6 @@
a[k] = (...args) => restApiStub[k](...args);
return a;
}, {}));
- Gerrit._setPluginsCount(1);
Gerrit.install(p => { plugin = p; }, '0.1',
'http://test.com/plugins/testplugin/static/test.js');
instance = new GrPluginRestApi();
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 d44acea..6c306d9 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
@@ -26,8 +26,8 @@
// Import utils methods
const {
- getPluginNameFromUrl,
- send,
+ getPluginNameFromUrl,
+ send,
} = window._apiUtils;
/**
@@ -113,7 +113,7 @@
Plugin.prototype._registerCustomComponent = function(
endpointName, opt_moduleName, opt_options, dynamicEndpoint) {
const type = opt_options && opt_options.replace ?
- EndpointType.REPLACE : EndpointType.DECORATE;
+ EndpointType.REPLACE : EndpointType.DECORATE;
const hook = this._domHooks.getDomHook(endpointName, opt_moduleName);
const moduleName = opt_moduleName || hook.getModuleName();
Gerrit._endpoints.registerModule(
@@ -184,14 +184,14 @@
Plugin.prototype.changeActions = function() {
return new GrChangeActionsInterface(this,
- Plugin._sharedAPIElement.getElement(
- Plugin._sharedAPIElement.Element.CHANGE_ACTIONS));
+ Plugin._sharedAPIElement.getElement(
+ Plugin._sharedAPIElement.Element.CHANGE_ACTIONS));
};
Plugin.prototype.changeReply = function() {
return new GrChangeReplyInterface(this,
- Plugin._sharedAPIElement.getElement(
- Plugin._sharedAPIElement.Element.REPLY_DIALOG));
+ Plugin._sharedAPIElement.getElement(
+ Plugin._sharedAPIElement.Element.REPLY_DIALOG));
};
Plugin.prototype.changeView = function() {
@@ -267,10 +267,14 @@
return;
}
return this.registerCustomComponent(
- Gerrit._getPluginScreenName(this.getPluginName(), screenName),
+ this._getScreenName(screenName),
opt_moduleName);
};
+ Plugin.prototype._getScreenName = function(screenName) {
+ return `${this.getPluginName()}-screen-${screenName}`;
+ };
+
const deprecatedAPI = {
_loadedGwt: ()=> {},
@@ -321,7 +325,7 @@
'Please use strings for patterns.');
return;
}
- this.hook(Gerrit._getPluginScreenName(this.getPluginName(), pattern))
+ this.hook(this._getScreenName(pattern))
.onAttached(el => {
el.style.display = 'none';
callback({
diff --git a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.js b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.js
index 3eb44e6..3c27c94 100644
--- a/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.js
+++ b/polygerrit-ui/app/elements/shared/gr-label-info/gr-label-info.js
@@ -125,14 +125,14 @@
const accountID = parseInt(target.getAttribute('data-account-id'), 10);
this._xhrPromise =
this.$.restAPI.deleteVote(this.change._number, accountID, this.label)
- .then(response => {
- target.disabled = false;
- if (!response.ok) { return; }
- Gerrit.Nav.navigateToChange(this.change);
- }).catch(err => {
- target.disabled = false;
- return;
- });
+ .then(response => {
+ target.disabled = false;
+ if (!response.ok) { return; }
+ Gerrit.Nav.navigateToChange(this.change);
+ }).catch(err => {
+ target.disabled = false;
+ return;
+ });
},
_computeValueTooltip(labelInfo, score) {
diff --git a/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav.js b/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav.js
index 9ccff600..2e05607 100644
--- a/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav.js
+++ b/polygerrit-ui/app/elements/shared/gr-page-nav/gr-page-nav.js
@@ -36,8 +36,8 @@
if (this._headerHeight === undefined) {
let top = this._getOffsetTop(this);
for (let offsetParent = this.offsetParent;
- offsetParent;
- offsetParent = this._getOffsetParent(offsetParent)) {
+ offsetParent;
+ offsetParent = this._getOffsetParent(offsetParent)) {
top += this._getOffsetTop(offsetParent);
}
this._headerHeight = top;
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-auth.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-auth.js
index 43e3922..0084932 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-auth.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-auth.js
@@ -76,7 +76,8 @@
}, this._defaultOptions, opt_options);
if (this._type === Gerrit.Auth.TYPE.ACCESS_TOKEN) {
return this._getAccessToken().then(
- accessToken => this._fetchWithAccessToken(url, options, accessToken)
+ accessToken =>
+ this._fetchWithAccessToken(url, options, accessToken)
);
} else {
return this._fetchWithXsrfToken(url, options);
@@ -146,7 +147,7 @@
params.push(`access_token=${accessToken}`);
const baseUrl = Gerrit.BaseUrlBehavior.getBaseUrl();
const pathname = baseUrl ?
- url.substring(url.indexOf(baseUrl) + baseUrl.length) : url;
+ url.substring(url.indexOf(baseUrl) + baseUrl.length) : url;
if (!pathname.startsWith('/a/')) {
url = url.replace(pathname, '/a' + pathname);
}
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-auth_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-auth_test.html
index cfdc6ee..dc07d0f 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-auth_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-auth_test.html
@@ -111,8 +111,8 @@
test('getToken calls are cached', () => {
return Promise.all([
auth.fetch('/url-one'), auth.fetch('/url-two')]).then(() => {
- assert.equal(getToken.callCount, 1);
- });
+ assert.equal(getToken.callCount, 1);
+ });
});
test('getToken refreshes token', () => {
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 6233a33..b72f47c 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
@@ -887,7 +887,7 @@
return Promise.resolve({
changes_per_page: 25,
default_diff_view: this._isNarrowScreen() ?
- DiffViewMode.UNIFIED : DiffViewMode.SIDE_BY_SIDE,
+ DiffViewMode.UNIFIED : DiffViewMode.SIDE_BY_SIDE,
diff_view: 'SIDE_BY_SIDE',
size_bar_in_change_table: true,
});
@@ -1097,8 +1097,8 @@
}
const payloadPromise = response ?
- this._restApiHelper.readResponsePayload(response) :
- Promise.resolve(null);
+ this._restApiHelper.readResponsePayload(response) :
+ Promise.resolve(null);
return payloadPromise.then(payload => {
if (!payload) { return null; }
@@ -1186,7 +1186,7 @@
getChangeOrEditFiles(changeNum, patchRange) {
if (this.patchNumEquals(patchRange.patchNum, this.EDIT_NAME)) {
return this.getChangeEditFiles(changeNum, patchRange).then(res =>
- res.files);
+ res.files);
}
return this.getChangeFiles(changeNum, patchRange);
},
@@ -1737,8 +1737,8 @@
return res;
};
const promise = this.patchNumEquals(patchNum, this.EDIT_NAME) ?
- this._getFileInChangeEdit(changeNum, path) :
- this._getFileInRevision(changeNum, path, patchNum, suppress404s);
+ this._getFileInChangeEdit(changeNum, path) :
+ this._getFileInRevision(changeNum, path, patchNum, suppress404s);
return promise.then(res => {
if (!res.ok) { return res; }
@@ -2197,7 +2197,7 @@
*/
getB64FileContents(changeId, patchNum, path, opt_parentIndex) {
const parent = typeof opt_parentIndex === 'number' ?
- '?parent=' + opt_parentIndex : '';
+ '?parent=' + opt_parentIndex : '';
return this._changeBaseURL(changeId, patchNum).then(url => {
url = `${url}/files/${encodeURIComponent(path)}/content${parent}`;
return this._fetchB64File(url);
@@ -2256,8 +2256,8 @@
// TODO(kaspern): For full slicer migration, app should warn with a call
// stack every time _changeBaseURL is called without a project.
const projectPromise = opt_project ?
- Promise.resolve(opt_project) :
- this.getFromProjectLookup(changeNum);
+ Promise.resolve(opt_project) :
+ this.getFromProjectLookup(changeNum);
return projectPromise.then(project => {
let url = `/changes/${encodeURIComponent(project)}~${changeNum}`;
if (opt_patchNum) {
@@ -2589,9 +2589,9 @@
*/
_getChangeURLAndSend(req) {
const anonymizedBaseUrl = req.patchNum ?
- ANONYMIZED_REVISION_BASE_URL : ANONYMIZED_CHANGE_BASE_URL;
+ ANONYMIZED_REVISION_BASE_URL : ANONYMIZED_CHANGE_BASE_URL;
const anonymizedEndpoint = req.reportEndpointAsIs ?
- req.endpoint : req.anonymizedEndpoint;
+ req.endpoint : req.anonymizedEndpoint;
return this._changeBaseURL(req.changeNum, req.patchNum).then(url => {
return this._restApiHelper.send({
@@ -2603,7 +2603,7 @@
headers: req.headers,
parseResponse: req.parseResponse,
anonymizedUrl: anonymizedEndpoint ?
- (anonymizedBaseUrl + anonymizedEndpoint) : undefined,
+ (anonymizedBaseUrl + anonymizedEndpoint) : undefined,
});
});
},
@@ -2615,9 +2615,9 @@
*/
_getChangeURLAndFetch(req) {
const anonymizedEndpoint = req.reportEndpointAsIs ?
- req.endpoint : req.anonymizedEndpoint;
+ req.endpoint : req.anonymizedEndpoint;
const anonymizedBaseUrl = req.patchNum ?
- ANONYMIZED_REVISION_BASE_URL : ANONYMIZED_CHANGE_BASE_URL;
+ ANONYMIZED_REVISION_BASE_URL : ANONYMIZED_CHANGE_BASE_URL;
return this._changeBaseURL(req.changeNum, req.patchNum).then(url => {
return this._restApiHelper.fetchJSON({
url: url + req.endpoint,
@@ -2625,7 +2625,7 @@
params: req.params,
fetchOptions: req.fetchOptions,
anonymizedUrl: anonymizedEndpoint ?
- (anonymizedBaseUrl + anonymizedEndpoint) : undefined,
+ (anonymizedBaseUrl + anonymizedEndpoint) : undefined,
});
});
},
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 ea71522..1781ce7 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
@@ -691,7 +691,7 @@
test('setAccountStatus', () => {
const sendStub = sandbox.stub(element._restApiHelper, 'send')
- .returns(Promise.resolve('OOO'));
+ .returns(Promise.resolve('OOO'));
element._cache.set('/accounts/self/detail', {});
return element.setAccountStatus('OOO').then(() => {
assert.isTrue(sendStub.calledOnce);
@@ -702,7 +702,7 @@
{status: 'OOO'});
assert.deepEqual(element._restApiHelper
._cache.get('/accounts/self/detail'),
- {status: 'OOO'});
+ {status: 'OOO'});
});
});
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper.js
index 5cea96b..c9cdfb4 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-apis/gr-rest-api-helper.js
@@ -147,7 +147,7 @@
*/
_logCall(req, startTime, status) {
const method = (req.fetchOptions && req.fetchOptions.method) ?
- req.fetchOptions.method : 'GET';
+ req.fetchOptions.method : 'GET';
const endTime = Date.now();
const elapsed = (endTime - startTime);
const startAt = new Date(startTime);
@@ -339,7 +339,7 @@
options.headers.set(
'Content-Type', req.contentType || 'application/json');
options.body = typeof req.body === 'string' ?
- req.body : JSON.stringify(req.body);
+ req.body : JSON.stringify(req.body);
}
if (req.headers) {
if (!options.headers) { options.headers = new Headers(); }
@@ -349,7 +349,7 @@
}
}
const url = req.url.startsWith('http') ?
- req.url : this.getBaseUrl() + req.url;
+ req.url : this.getBaseUrl() + req.url;
const fetchReq = {
url,
fetchOptions: options,
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser.js
index e14b955..601f7d9 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser.js
@@ -203,7 +203,7 @@
messages.forEach((message, index) => {
const messageDate = util.parseDate(message.date).getTime();
const nextMessageDate = index === messages.length - 1 ? null :
- util.parseDate(messages[index + 1].date).getTime();
+ util.parseDate(messages[index + 1].date).getTime();
for (const update of updates) {
const date = util.parseDate(update.date).getTime();
if (date >= messageDate
@@ -211,7 +211,7 @@
const timestamp = util.parseDate(update.date).getTime() -
GrReviewerUpdatesParser.MESSAGE_REVIEWERS_THRESHOLD_MILLIS;
update.date = new Date(timestamp)
- .toISOString().replace('T', ' ').replace('Z', '000000');
+ .toISOString().replace('T', ' ').replace('Z', '000000');
}
if (nextMessageDate && date > nextMessageDate) {
break;
diff --git a/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.js b/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.js
index 9ae77d9..f6ade6e 100644
--- a/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.js
+++ b/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.js
@@ -77,9 +77,9 @@
_getDraftKey(location) {
const range = location.range ?
- `${location.range.start_line}-${location.range.start_character}` +
+ `${location.range.start_line}-${location.range.start_character}` +
`-${location.range.end_character}-${location.range.end_line}` :
- null;
+ null;
let key = ['draft', location.changeNum, location.patchNum, location.path,
location.line || ''].join(':');
if (range) {
diff --git a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.html b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.html
index ae1de02..b884ecd 100644
--- a/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-textarea/gr-textarea_test.html
@@ -209,17 +209,17 @@
assert.isTrue(formatSpy.lastCall.calledWithExactly(
[{dataValue: '😂', value: '😂', match: 'tears :\')',
text: '😂 tears :\')'},
- {dataValue: '😢', value: '😢', match: 'tear', text: '😢 tear'},
+ {dataValue: '😢', value: '😢', match: 'tear', text: '😢 tear'},
]));
});
test('_formatSuggestions', () => {
const matchedSuggestions = [{value: '😢', match: 'tear'},
- {value: '😂', match: 'tears'}];
+ {value: '😂', match: 'tears'}];
element._formatSuggestions(matchedSuggestions);
assert.deepEqual(
[{value: '😢', dataValue: '😢', match: 'tear', text: '😢 tear'},
- {value: '😂', dataValue: '😂', match: 'tears', text: '😂 tears'}],
+ {value: '😂', dataValue: '😂', match: 'tears', text: '😂 tears'}],
element._suggestions);
});
diff --git a/polygerrit-ui/app/elements/shared/revision-info/revision-info.html b/polygerrit-ui/app/elements/shared/revision-info/revision-info.html
index fca8ae1..75b8ac3 100644
--- a/polygerrit-ui/app/elements/shared/revision-info/revision-info.html
+++ b/polygerrit-ui/app/elements/shared/revision-info/revision-info.html
@@ -74,7 +74,7 @@
*/
RevisionInfo.prototype.getParentId = function(patchNum, parentIndex) {
const rev = Object.values(this._change.revisions).find(rev =>
- Gerrit.PatchSetBehavior.patchNumEquals(rev._number, patchNum));
+ Gerrit.PatchSetBehavior.patchNumEquals(rev._number, patchNum));
return rev.commit.parents[parentIndex].commit;
};
diff --git a/polygerrit-ui/app/samples/coverage-plugin.html b/polygerrit-ui/app/samples/coverage-plugin.html
index f5f9c6e..d1d96a8 100644
--- a/polygerrit-ui/app/samples/coverage-plugin.html
+++ b/polygerrit-ui/app/samples/coverage-plugin.html
@@ -39,16 +39,16 @@
annotationApi.addLayer(context => {
if (Object.keys(coverageData).length === 0) {
- // Coverage data is not ready yet.
+ // Coverage data is not ready yet.
return;
}
const path = context.path;
const line = context.line;
- // Highlight lines missing coverage with this background color if
- // coverage should be displayed, else do nothing.
+ // Highlight lines missing coverage with this background color if
+ // coverage should be displayed, else do nothing.
const annotationStyle = displayCoverage
- ? coverageStyle
- : emptyStyle;
+ ? coverageStyle
+ : emptyStyle;
if (coverageData[path] &&
coverageData[path].changeNum === context.changeNum &&
coverageData[path].patchNum === context.patchNum) {
diff --git a/polygerrit-ui/app/scripts/gr-email-suggestions-provider/gr-email-suggestions-provider_test.html b/polygerrit-ui/app/scripts/gr-email-suggestions-provider/gr-email-suggestions-provider_test.html
index fb6b5d4..0266ab9 100644
--- a/polygerrit-ui/app/scripts/gr-email-suggestions-provider/gr-email-suggestions-provider_test.html
+++ b/polygerrit-ui/app/scripts/gr-email-suggestions-provider/gr-email-suggestions-provider_test.html
@@ -68,7 +68,7 @@
test('getSuggestions', done => {
const getSuggestedAccountsStub =
sandbox.stub(restAPI, 'getSuggestedAccounts')
- .returns(Promise.resolve([account1, account2]));
+ .returns(Promise.resolve([account1, account2]));
provider.getSuggestions('Some input').then(res => {
assert.deepEqual(res, [account1, account2]);
diff --git a/polygerrit-ui/app/scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider.js b/polygerrit-ui/app/scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider.js
index c83e5a2..6b97cf6 100644
--- a/polygerrit-ui/app/scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider.js
+++ b/polygerrit-ui/app/scripts/gr-reviewer-suggestions-provider/gr-reviewer-suggestions-provider.js
@@ -35,14 +35,15 @@
switch (usersType) {
case Gerrit.SUGGESTIONS_PROVIDERS_USERS_TYPES.REVIEWER:
return new GrReviewerSuggestionsProvider(restApi, changeNumber,
- input => restApi.getChangeSuggestedReviewers(changeNumber, input));
+ input =>
+ restApi.getChangeSuggestedReviewers(changeNumber, input));
case Gerrit.SUGGESTIONS_PROVIDERS_USERS_TYPES.CC:
return new GrReviewerSuggestionsProvider(restApi, changeNumber,
- input => restApi.getChangeSuggestedCCs(changeNumber, input));
+ input => restApi.getChangeSuggestedCCs(changeNumber, input));
case Gerrit.SUGGESTIONS_PROVIDERS_USERS_TYPES.ANY:
return new GrReviewerSuggestionsProvider(restApi, changeNumber,
- input => restApi.getSuggestedAccounts(
- `cansee:${changeNumber} ${input}`));
+ input => restApi.getSuggestedAccounts(
+ `cansee:${changeNumber} ${input}`));
default:
throw new Error(`Unknown users type: ${usersType}`);
}
@@ -65,9 +66,9 @@
this._loggedIn = loggedIn;
});
this._initPromise = Promise.all([getConfigPromise, getLoggedInPromise])
- .then(() => {
- this._initialized = true;
- });
+ .then(() => {
+ this._initialized = true;
+ });
return this._initPromise;
}
diff --git a/polygerrit-ui/app/test/index.html b/polygerrit-ui/app/test/index.html
index a1c3ae6..cb1fcef 100644
--- a/polygerrit-ui/app/test/index.html
+++ b/polygerrit-ui/app/test/index.html
@@ -188,6 +188,7 @@
'shared/gr-js-api-interface/gr-js-api-interface_test.html',
'shared/gr-js-api-interface/gr-gerrit_test.html',
'shared/gr-js-api-interface/gr-plugin-action-context_test.html',
+ 'shared/gr-js-api-interface/gr-plugin-loader_test.html',
'shared/gr-js-api-interface/gr-plugin-endpoints_test.html',
'shared/gr-js-api-interface/gr-plugin-rest-api_test.html',
'shared/gr-fixed-panel/gr-fixed-panel_test.html',
diff --git a/polygerrit-ui/server.go b/polygerrit-ui/server.go
index 1a2d299..35bdefd 100644
--- a/polygerrit-ui/server.go
+++ b/polygerrit-ui/server.go
@@ -43,6 +43,8 @@
host = flag.String("host", "gerrit-review.googlesource.com", "Host to proxy requests to")
scheme = flag.String("scheme", "https", "URL scheme")
cdnPattern = regexp.MustCompile("https://cdn.googlesource.com/polygerrit_ui/[0-9.]*")
+ webComponentPattern = regexp.MustCompile("webcomponentsjs-p2")
+ grAppPattern = regexp.MustCompile("gr-app-p2")
bundledPluginsPattern = regexp.MustCompile("https://cdn.googlesource.com/polygerrit_assets/[0-9.]*")
)
@@ -184,9 +186,13 @@
buf.ReadFrom(reader)
original := buf.String()
+ // Replace the webcomponentsjs-p2 with webcomponentsjs
+ replaced := webComponentPattern.ReplaceAllString(original, "webcomponentsjs")
+ replaced = grAppPattern.ReplaceAllString(replaced, "gr-app")
+
// Simply remove all CDN references, so files are loaded from the local file system or the proxy
// server instead.
- replaced := cdnPattern.ReplaceAllString(original, "")
+ replaced = cdnPattern.ReplaceAllString(replaced, "")
// Modify window.INITIAL_DATA so that it has the same effect as injectLocalPlugins. To achieve
// this let's add JavaScript lines at the end of the <script>...</script> snippet that also
diff --git a/prologtests/examples/BUILD b/prologtests/examples/BUILD
index f4ebe90..ebf2c68 100644
--- a/prologtests/examples/BUILD
+++ b/prologtests/examples/BUILD
@@ -1,15 +1,12 @@
package(default_visibility = ["//visibility:public"])
-DUMMY = ["dummy.sh"]
-
-# Enable prologtests on newer Java versions again, when this Bazel bug is fixed:
-# https://github.com/bazelbuild/bazel/issues/9391
sh_test(
name = "test_examples",
- srcs = select({
- "//:java11": DUMMY,
- "//:java_next": DUMMY,
- "//conditions:default": ["run.sh"],
- }),
- data = glob(["*.pl"]) + ["//:gerrit.war"],
+ srcs = ["run.sh"],
+ args = ["$(JAVA)"],
+ data = glob(["*.pl"]) + [
+ "//:gerrit.war",
+ "@bazel_tools//tools/jdk:current_host_java_runtime",
+ ],
+ toolchains = ["@bazel_tools//tools/jdk:current_host_java_runtime"],
)
diff --git a/prologtests/examples/run.sh b/prologtests/examples/run.sh
index 947c153..b2883ebe 100755
--- a/prologtests/examples/run.sh
+++ b/prologtests/examples/run.sh
@@ -1,5 +1,14 @@
#!/bin/bash
+# TODO(davido): Figure out what to do if running alone and not invoked from bazel
+# $1 is equal to the $(JAVABASE)/bin/java make variable
+JAVA=$1
+
+# Checks whether or not the $1 is starting with a slash: '/' and thus considered to be
+# an absolute path. If it is, then it is left as is, if it isn't then "$PWD/ is prepended
+# (in sh_test case it is relative and thus the runfiles directory is prepended).
+[[ "$JAVA" =~ ^(/|[^/]+$) ]] || JAVA="$PWD/$JAVA"
+
TESTS="t1 t2 t3"
# Note that both t1.pl and t2.pl test code in rules.pl.
@@ -36,7 +45,7 @@
# Unit tests do not need to define clauses in packages.
# Use one prolog-shell per unit test, to avoid name collision.
echo "### Running test ${T}.pl"
- echo "[$T]." | java -jar ${GERRIT_WAR} prolog-shell -q -s load.pl
+ echo "[$T]." | "${JAVA}" -jar ${GERRIT_WAR} prolog-shell -q -s load.pl
if [ "x$?" != "x0" ]; then
echo "### Test ${T}.pl failed."
diff --git a/tools/maven/gerrit-acceptance-framework_pom.xml b/tools/maven/gerrit-acceptance-framework_pom.xml
index b080ddf..4240a9b 100644
--- a/tools/maven/gerrit-acceptance-framework_pom.xml
+++ b/tools/maven/gerrit-acceptance-framework_pom.xml
@@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-acceptance-framework</artifactId>
- <version>3.1.0-rc3</version>
+ <version>3.2.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Gerrit Code Review - Acceptance Test Framework</name>
<description>Framework for Gerrit's acceptance tests</description>
diff --git a/tools/maven/gerrit-extension-api_pom.xml b/tools/maven/gerrit-extension-api_pom.xml
index 8ce2b90..cf2b080 100644
--- a/tools/maven/gerrit-extension-api_pom.xml
+++ b/tools/maven/gerrit-extension-api_pom.xml
@@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-extension-api</artifactId>
- <version>3.1.0-rc3</version>
+ <version>3.2.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Gerrit Code Review - Extension API</name>
<description>API for Gerrit Extensions</description>
diff --git a/tools/maven/gerrit-plugin-api_pom.xml b/tools/maven/gerrit-plugin-api_pom.xml
index 410e9db..7d3c4f0 100644
--- a/tools/maven/gerrit-plugin-api_pom.xml
+++ b/tools/maven/gerrit-plugin-api_pom.xml
@@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-plugin-api</artifactId>
- <version>3.1.0-rc3</version>
+ <version>3.2.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Gerrit Code Review - Plugin API</name>
<description>API for Gerrit Plugins</description>
diff --git a/tools/maven/gerrit-war_pom.xml b/tools/maven/gerrit-war_pom.xml
index f308048..9478283 100644
--- a/tools/maven/gerrit-war_pom.xml
+++ b/tools/maven/gerrit-war_pom.xml
@@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-war</artifactId>
- <version>3.1.0-rc3</version>
+ <version>3.2.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>Gerrit Code Review - WAR</name>
<description>Gerrit WAR</description>
diff --git a/version.bzl b/version.bzl
index a8988f4..fb1e5ca 100644
--- a/version.bzl
+++ b/version.bzl
@@ -2,4 +2,4 @@
# Used by :api_install and :api_deploy targets
# when talking to the destination repository.
#
-GERRIT_VERSION = "3.1.0-rc3"
+GERRIT_VERSION = "3.2.0-SNAPSHOT"