Merge branch 'stable-2.15'
* stable-2.15:
Documentation: List all ciphers/MACs available and add some recommendations
PolyGerrit: Reduce the threshold in gr-watched-projects-editor
Update polymer to 1.11.0
Change-Id: I3928a1759d3270a0b92dc26e347e07a4b04c0013
diff --git a/Documentation/cmd-review.txt b/Documentation/cmd-review.txt
index 3f6acf6..121fcad 100644
--- a/Documentation/cmd-review.txt
+++ b/Documentation/cmd-review.txt
@@ -124,12 +124,6 @@
or invalid value) and votes that are not permitted for the user are
silently ignored.
---strict-labels::
- Require ability to vote on all specified labels before reviewing change.
- If the vote is invalid (invalid label or invalid name), the vote is not
- permitted for the user, or the vote is on an outdated or closed patch set,
- return an error instead of silently discarding the vote.
-
--tag::
-t::
Apply a 'TAG' to the change message, votes, and inline comments. The 'TAG'
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index f0ec02f..abaf314 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -4600,9 +4600,8 @@
[[upload]]
=== Section upload
-Sets the group of users allowed to execute 'upload-pack' on the
-server, 'upload-pack' is what runs on the server during a user's
-fetch, clone or repo sync command.
+Options to control the behavior of `upload-pack` on the server side,
+which handles a user's fetch, clone, or repo sync command.
----
[upload]
@@ -4612,8 +4611,8 @@
[[upload.allowGroup]]upload.allowGroup::
+
-Name of the groups of users that are allowed to execute 'upload-pack'
-on the server. One or more groups can be set.
+Name of the groups of users that are allowed to execute 'upload-pack'.
+One or more groups can be set.
+
If no groups are added, any user will be allowed to execute
'upload-pack' on the server.
diff --git a/Documentation/config-mail.txt b/Documentation/config-mail.txt
index af2bd98..3b810e0 100644
--- a/Documentation/config-mail.txt
+++ b/Documentation/config-mail.txt
@@ -7,8 +7,9 @@
them and easily modify them to tweak their contents.
*Compatibility Note:* previously, Velocity Template Language (VTL) was used as
-the template language for Gerrit emails. VTL has now been deprecated in favor of
-Soy, but Velocity templates that modify text emails remain supported for now.
+the template language for Gerrit emails. Support for VTL has now been removed
+in favor of Soy, and Velocity templates that modify text emails are no longer
+supported.
== Template Locations and Extensions:
@@ -231,6 +232,14 @@
+
The refname of the patch set.
+$patchSetInfo.authorName::
++
+The name of the author of the patch set.
+
+$patchSetInfo.authorEmail::
++
+The email address of the author of the patch set.
+
GERRIT
------
Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/dev-bazel.txt b/Documentation/dev-bazel.txt
index dc81f44..ad2f947 100644
--- a/Documentation/dev-bazel.txt
+++ b/Documentation/dev-bazel.txt
@@ -358,25 +358,48 @@
`lib/jgit/jgit.bzl` setting LOCAL_JGIT_REPO to a directory holding a
JGit repository.
-[[clean-cache]]
+[[clean-download-cache]]
=== Cleaning The download cache
-The cache for the Gerrit Code Review project is located in
-`~/.gerritcodereview/buck-cache/locally-built-artifacts`.
+The cache for downloaded artifacts is located in
+`~/.gerritcodereview/buck-cache/downloaded-artifacts`.
-If you really do need to clean the cache manually, then:
+If you really do need to clean the download cache manually, then:
----
- rm -rf ~/.gerritcodereview/buck-cache/locally-built-artifacts
+ rm -rf ~/.gerritcodereview/buck-cache/downloaded-artifacts
----
-Note that the root `buck-cache` folder should not be deleted as it also contains
-the `downloaded-artifacts` directory, which holds the artifacts that got
-downloaded (not built locally).
-
[NOTE] When building with Bazel the artifacts are still cached in
-`~/.gerritcodereview/buck-cache/`. This allows Bazel to make use of
-libraries that were previously downloaded by Buck.
+`~/.gerritcodereview/buck-cache/downloaded-artifacts`. This allows Bazel to
+make use of libraries that were previously downloaded by Buck.
+
+[[local-action-cache]]
+
+To accelerate builds, local action cache can be activated. Note, that this
+experimental feature is not activated per default and only available since
+Bazel version 0.7.
+
+To activate the local action cache, create accessible cache directory:
+
+----
+ mkdir -p ~/.gerritcodereview/bazel-cache/cas
+----
+
+and add these lines to your `~/.bazelrc` file:
+
+----
+build --experimental_local_disk_cache_path=/home/<user>/.gerritcodereview/bazel-cache/cas
+build --experimental_local_disk_cache
+build --experimental_strict_action_env
+test --experimental_local_disk_cache_path=/home/user>/.gerritcodereview/bazel-cache/cas
+test --experimental_local_disk_cache
+test --experimental_strict_action_env
+----
+
+[NOTE] `experimental_local_disk_cache_path` must be absolute path. Expansion of `~` is
+unfortunately not supported yet. This is also the reason why we can't activate this
+feature by default yet (by adjusting tools/bazel.rc file).
GERRIT
------
diff --git a/Documentation/dev-plugin-pg-styling.txt b/Documentation/dev-plugin-pg-styling.txt
deleted file mode 100644
index 618d984..0000000
--- a/Documentation/dev-plugin-pg-styling.txt
+++ /dev/null
@@ -1,61 +0,0 @@
-= Gerrit Code Review - PolyGerrit Plugin Styling
-
-CAUTION: Work in progress. Hard hat area. +
-This document will be populated with details along with implementation. +
-link:https://groups.google.com/d/topic/repo-discuss/vb8WJ4m0hK0/discussion[Join the discussion.]
-
-== Plugin styles
-
-Plugins may provide link:https://www.polymer-project.org/2.0/docs/devguide/style-shadow-dom#style-modules[Polymer style modules] for UI CSS-based customization.
-
-PolyGerrit UI implements number of styling endpoints, which apply CSS mixins link:https://tabatkins.github.io/specs/css-apply-rule/[using @apply] to its direct contents.
-
-NOTE: Only items (ie CSS properties and mixin targets) documented here are guaranteed to work in the long term, since they are covered by integration tests. +
-When there is a need to add new property or endpoint, please link:https://bugs.chromium.org/p/gerrit/issues/entry?template=PolyGerrit%20Issue[file a bug] stating your usecase to track and maintain for future releases.
-
-Plugin should be html-based and imported following PolyGerrit's link:dev-plugins-pg.html#loading[dev guide].
-
-Plugin should provide Style Module, for example:
-
-``` html
- <dom-module id="some-style">
- <style>
- :root {
- --css-mixin-name: {
- property: value;
- }
- }
- </style>
- </dom-module>
-```
-
-Plugin should register style module with a styling endpoint using `Plugin.prototype.registerStyleModule(endpointName, styleModuleName)`, for example:
-
-``` js
- Gerrit.install(function(plugin) {
- plugin.registerStyleModule('some-endpoint', 'some-style');
- });
-```
-
-== Available styling endpoints
-=== change-metadata
-Following custom css mixins are recognized:
-
-* `--change-metadata-assignee`
-+
-is applied to `gr-change-metadata section.assignee`
-* `--change-metadata-label-status`
-+
-is applied to `gr-change-metadata section.labelStatus`
-* `--change-metadata-strategy`
-+
-is applied to `gr-change-metadata section.strategy`
-* `--change-metadata-topic`
-+
-is applied to `gr-change-metadata section.topic`
-
-Following CSS properties have link:https://gerrit.googlesource.com/gerrit/+/master/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata-it_test.html[long-term support via integration test]:
-
-* `display`
-+
-can be set to `none` to hide a section.
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index 1a026d1..f7028e0 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -407,6 +407,10 @@
+
Update of the group secondary index
+* `com.google.gerrit.server.extensions.events.ProjectIndexedListener`:
++
+Update of the project secondary index
+
* `com.google.gerrit.httpd.WebLoginListener`:
+
User login or logout interactively on the Web user interface.
@@ -479,6 +483,15 @@
submitted by Rebase Always and Cherry Pick submit strategies as well as
change being queried with COMMIT_FOOTERS option.
+[[merge-super-set-computation]]
+== Merge Super Set Computation
+
+The algorithm to compute the merge super set to detect changes that
+should be submitted together can be customized by implementing
+`com.google.gerrit.server.git.MergeSuperSetComputation`.
+MergeSuperSetComputation is a DynamicItem, so Gerrit may only have one
+implementation.
+
[[receive-pack]]
== Receive Pack Initializers
diff --git a/Documentation/dev-release.txt b/Documentation/dev-release.txt
index 039d545..53ded48 100644
--- a/Documentation/dev-release.txt
+++ b/Documentation/dev-release.txt
@@ -73,47 +73,32 @@
== Create the Actual Release
-To create a Gerrit release the following steps have to be done:
-
-. link:#build-gerrit[Build the Gerrit Release]
-. link:#publish-gerrit[Publish the Gerrit Release]
-.. link:#publish-to-maven-central[Publish the Gerrit artifacts to Maven Central]
-.. link:#publish-to-google-storage[Publish the Gerrit WAR to Google Storage]
-.. link:#push-stable[Push the Stable Branch]
-.. link:#push-tag[Push the Release Tag]
-.. link:#upload-documentation[Upload the Documentation]
-.. link:#finalize-release-notes[Finalize Release Notes]
-.. link:#update-issues[Update the Issues]
-.. link:#announce[Announce on Mailing List]
-. link:#increase-version[Increase Gerrit Version for Current Development]
-. link:#merge-stable[Merge `stable` into `master`]
-
-
[[update-versions]]
=== Update Versions and Create Release Tag
Before doing the release build, the `GERRIT_VERSION` in the `version.bzl`
-file must be updated, e.g. change it from `2.5-SNAPSHOT` to `2.5`.
+file must be updated, e.g. change it from `$version-SNAPSHOT` to `$version`.
-In addition the version must be updated in a number of pom.xml files.
+In addition the version must be updated in a number of `*_pom.xml` files.
To do this run the `./tools/version.py` script and provide the new
version as parameter, e.g.:
----
- ./tools/version.py 2.5
+ version=2.15
+ ./tools/version.py $version
----
Commit the changes and create a signed release tag on the new commit:
----
- git tag -s -m "v2.5" v2.5
+ git tag -s -m "v$version" "v$version"
----
Tag the plugins:
----
- git submodule foreach git tag -s -m "v2.5" v2.5
+ git submodule foreach git tag -s -m "v$version" "v$version"
----
[[build-gerrit]]
@@ -126,8 +111,12 @@
./tools/maven/api.sh install
----
-* Sanity check WAR
-* Test the new Gerrit version
+* Verify the WAR version:
++
+----
+ java -jar ~/dl/gerrit-$version.war --version
+----
+* Try upgrading a test site and launching the daemon
* Verify plugin versions
+
@@ -148,7 +137,7 @@
configuration] for deploying to Maven Central
* Make sure that the version is updated in the `version.bzl` file and in
-the `pom.xml` files as described in the link:#update-versions[Update
+the `*_pom.xml` files as described in the link:#update-versions[Update
Versions and Create Release Tag] section.
* Push the WAR to Maven Central:
@@ -203,7 +192,7 @@
+
Use this URL for further testing of the artifacts in this repository,
e.g. to try building a plugin against the plugin API in this repository
-update the version in the `pom.xml` and configure the repository:
+update the version in the `*_pom.xml` and configure the repository:
+
----
<repositories>
@@ -257,11 +246,11 @@
[[push-stable]]
==== Push the Stable Branch
-* Create the stable branch `stable-2.5` in the `gerrit` project via the
+* Create the stable branch `stable-$version` in the `gerrit` project via the
link:https://gerrit-review.googlesource.com/#/admin/projects/gerrit,branches[
Gerrit Web UI] or by push.
-* Push the commits done on `stable-2.5` to `refs/for/stable-2.5` and
+* Push the commits done on `stable-$version` to `refs/for/stable-$version` and
get them merged
@@ -271,13 +260,13 @@
Push the new Release Tag:
----
- git push gerrit-review tag v2.5
+ git push gerrit-review tag v$version
----
Push the new Release Tag on the plugins:
----
- git submodule foreach git push gerrit-review tag v2.5
+ git submodule foreach git push gerrit-review tag v$version
----
@@ -314,11 +303,11 @@
Update the issues by hand. There is no script for this.
Our current process is an issue should be updated to say `Status =
-Submitted, FixedIn-2.5` once the change is submitted, but before the
+Submitted, FixedIn-$version` once the change is submitted, but before the
release.
After the release is actually made, you can search in Google Code for
-`Status=Submitted FixedIn=2.5` and then batch update these changes
+`Status=Submitted FixedIn=$version` and then batch update these changes
to say `Status=Released`. Make sure the pulldown says `All Issues`
because `Status=Submitted` is considered a closed issue.
diff --git a/Documentation/dev-plugins-pg.txt b/Documentation/pg-plugin-dev.txt
similarity index 66%
rename from Documentation/dev-plugins-pg.txt
rename to Documentation/pg-plugin-dev.txt
index e1bf39e..92c52f6 100644
--- a/Documentation/dev-plugins-pg.txt
+++ b/Documentation/pg-plugin-dev.txt
@@ -1,10 +1,5 @@
= Gerrit Code Review - PolyGerrit Plugin Development
-CAUTION: Work in progress. Hard hat area. +
-This document will be populated with details along with implementation. +
-link:https://groups.google.com/d/topic/repo-discuss/vb8WJ4m0hK0/discussion[Join
-the discussion.]
-
[[loading]]
== Plugin loading and initialization
@@ -33,8 +28,8 @@
</dom-module>
```
-[[low-level-api]]
-== Low-level DOM API
+[[low-level-api-concepts]]
+== Low-level DOM API concepts
Basically, the DOM is the API surface. Low-level API provides methods for
decorating, replacing, and styling DOM elements exposed through a set of
@@ -126,3 +121,119 @@
</style>
</dom-module>
```
+
+[[high-level-api-concepts]]
+== High-level DOM API concepts
+
+High leve API is based on low-level DOM API and is essentially a standartized
+way for doing common tasks. It's less flexible, but will be a bit more stable.
+
+Common way to access high-leve API is through `plugin` instance passed into
+setup callback parameter of `Gerrit.install()`, also sometimes referred as
+`self`.
+
+[[low-level-api]]
+== Low-level DOM API
+
+Low-level DOM API methods are the base of all UI customization.
+
+=== attributeHelper
+`plugin.attributeHelper(element)`
+
+Note: TODO
+
+=== eventHelper
+`plugin.eventHelper(element)`
+
+Note: TODO
+
+=== hook
+`plugin.hook(endpointName, opt_options)`
+
+Note: TODO
+
+=== registerCustomComponent
+`plugin.registerCustomComponent(endpointName, opt_moduleName, opt_options)`
+
+Note: TODO
+
+=== registerStyleModule
+`plugin.registerStyleModule(endpointName, moduleName)`
+
+Note: TODO
+
+[[high-level-api]]
+== High-level API
+
+Plugin instance provides access to number of more specific APIs and methods
+to be used by plugin authors.
+
+=== changeReply
+`plugin.changeReply()`
+
+Note: TODO
+
+=== changeView
+`plugin.changeView()`
+
+Note: TODO
+
+=== delete
+`plugin.delete(url, opt_callback)`
+
+Note: TODO
+
+=== get
+`plugin.get(url, opt_callback)`
+
+Note: TODO
+
+=== getPluginName
+`plugin.getPluginName()`
+
+Note: TODO
+
+=== getServerInfo
+`plugin.getServerInfo()`
+
+Note: TODO
+
+=== on
+`plugin.on(eventName, callback)`
+
+Note: TODO
+
+=== popup
+`plugin.popup(moduleName)`
+
+Note: TODO
+
+=== post
+`plugin.post(url, payload, opt_callback)`
+
+Note: TODO
+
+[plugin-project]
+=== project
+`plugin.project()`
+
+.Params:
+- none
+
+.Returns:
+- Instance of link:pg-plugin-project-api.html[GrProjectApi].
+
+=== put
+`plugin.put(url, payload, opt_callback)`
+
+Note: TODO
+
+=== theme
+`plugin.theme()`
+
+Note: TODO
+
+=== url
+`plugin.url(opt_path)`
+
+Note: TODO
diff --git a/Documentation/pg-plugin-project-api.txt b/Documentation/pg-plugin-project-api.txt
new file mode 100644
index 0000000..897430c
--- /dev/null
+++ b/Documentation/pg-plugin-project-api.txt
@@ -0,0 +1,36 @@
+= Gerrit Code Review - Project admin customization API
+
+This API is provided by link:pg-plugin-dev.html#plugin-project[plugin.project()]
+and provides customization to admin page.
+
+== createCommand
+`projectApi.createCommand(title, checkVisibleCallback)`
+
+Create a project command in the admin panel.
+
+.Params
+- *title* String title.
+- *checkVisibleCallback* function to configure command visibility.
+
+.Returns
+- GrProjectApi for chainging.
+
+`checkVisibleCallback(projectName, projectConfig)`
+
+.Params
+- *projectName* String project name.
+- *projectConfig* Object REST API response for project config.
+
+.Returns
+- `false` to hide the command for the specific project.
+
+== onTap
+`projectApi.onTap(tapCalback)`
+
+Add a command tap callback.
+
+.Params
+- *tapCallback* function that's excuted on command tap.
+
+.Returns
+- Nothing
diff --git a/Documentation/pg-plugin-styling.txt b/Documentation/pg-plugin-styling.txt
new file mode 100644
index 0000000..58b6d7a
--- /dev/null
+++ b/Documentation/pg-plugin-styling.txt
@@ -0,0 +1,69 @@
+= Gerrit Code Review - PolyGerrit Plugin Styling
+
+== Plugin styles
+
+Plugins may provide
+link:https://www.polymer-project.org/2.0/docs/devguide/style-shadow-dom#style-modules[Polymer
+style modules] for UI CSS-based customization.
+
+PolyGerrit UI implements number of styling endpoints, which apply CSS mixins
+link:https://tabatkins.github.io/specs/css-apply-rule/[using @apply] to its
+direct contents.
+
+NOTE: Only items (ie CSS properties and mixin targets) documented here are
+guaranteed to work in the long term, since they are covered by integration
+tests. + When there is a need to add new property or endpoint, please
+link:https://bugs.chromium.org/p/gerrit/issues/entry?template=PolyGerrit%20Issue[file
+a bug] stating your usecase to track and maintain for future releases.
+
+Plugin should be html-based and imported following PolyGerrit's
+link:pg-plugin-dev.html#loading[dev guide].
+
+Plugin should provide Style Module, for example:
+
+``` html
+ <dom-module id="some-style">
+ <style>
+ :root {
+ --css-mixin-name: {
+ property: value;
+ }
+ }
+ </style>
+ </dom-module>
+```
+
+Plugin should register style module with a styling endpoint using
+`Plugin.prototype.registerStyleModule(endpointName, styleModuleName)`, for
+example:
+
+``` js
+ Gerrit.install(function(plugin) {
+ plugin.registerStyleModule('some-endpoint', 'some-style');
+ });
+```
+
+== Available styling endpoints
+=== change-metadata
+Following custom css mixins are recognized:
+
+* `--change-metadata-assignee`
++
+is applied to `gr-change-metadata section.assignee`
+* `--change-metadata-label-status`
++
+is applied to `gr-change-metadata section.labelStatus`
+* `--change-metadata-strategy`
++
+is applied to `gr-change-metadata section.strategy`
+* `--change-metadata-topic`
++
+is applied to `gr-change-metadata section.topic`
+
+Following CSS properties have
+link:https://gerrit.googlesource.com/gerrit/+/master/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata-it_test.html[long-term
+support via integration test]:
+
+* `display`
++
+can be set to `none` to hide a section.
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 50afe40..638d793 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -5201,6 +5201,9 @@
Cherry picks a revision to a destination branch.
+To cherry pick a commit with no change-id associated with it, see
+link:rest-api-projects.html#cherry-pick-commit[CherryPickCommit].
+
The commit message and destination branch must be provided in the request body inside a
link:#cherrypick-input[CherryPickInput] entity. If the commit message
does not specify a Change-Id, a new one is picked for the destination change.
@@ -6693,13 +6696,6 @@
|`robot_comments` |optional|
The robot comments that should be added as a map that maps a file path
to a list of link:#robot-comment-input[RobotCommentInput] entities.
-|`strict_labels` |`true` if not set|
-Whether all labels are required to be within the user's permitted ranges
-based on access controls. +
-If `true`, attempting to use a label not granted to the user will fail
-the entire modify operation early. +
-If `false`, the operation will execute anyway, but the proposed labels
-will be modified to be the "best" value allowed by the access controls.
|`drafts` |optional|
Draft handling that defines how draft comments are handled that are
already in the database but that were not also described in this
diff --git a/Documentation/rest-api-groups.txt b/Documentation/rest-api-groups.txt
index 7eac992..45c5e34 100644
--- a/Documentation/rest-api-groups.txt
+++ b/Documentation/rest-api-groups.txt
@@ -124,6 +124,40 @@
* `MEMBERS`: include list of direct group members.
--
+==== Find groups that are owned by another group
+
+By setting `ownedBy` and specifying the link:#group-id[\{group-id\}] of another
+group, it is possible to find all the groups for which the owning group is the
+given group.
+
+.Request
+----
+ GET /groups/?ownedBy=7ca042f4d5847936fcb90ca91057673157fd06fc HTTP/1.0
+----
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ {
+ "MyProject-Committers": {
+ "id": "9999c971bb4ab872aab759d8c49833ee6b9ff320",
+ "url": "#/admin/groups/uuid-9999c971bb4ab872aab759d8c49833ee6b9ff320",
+ "options": {
+ "visible_to_all": true
+ },
+ "description":"contains all committers for MyProject",
+ "group_id": 551,
+ "owner": "MyProject-Owners",
+ "owner_id": "7ca042f4d5847936fcb90ca91057673157fd06fc",
+ "created_on": "2013-02-01 09:59:32.126000000"
+ }
+ }
+----
+
==== Check if a group is owned by the calling user
By setting the option `owned` and specifying a group to inspect with
the option `group`/`g`, it is possible to find out if this group is
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index 9d76d34..a7c26b8 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -324,6 +324,65 @@
}
----
+[[query-projects]]
+=== Query Projects
+--
+'GET /projects/?query=<query>'
+--
+
+Queries projects visible to the caller. The
+link:user-search-projects.html#_search_operators[query string] must be
+provided by the `query` parameter. The `start` and `limit` parameters
+can be used to skip/limit results.
+
+As result a list of link:#project-info[ProjectInfo] entities is returned.
+
+.Request
+----
+ GET /projects/?query=name:test HTTP/1.0
+----
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Disposition: attachment
+ Content-Type: application/json; charset=UTF-8
+
+ )]}'
+ {
+ "test": {
+ "id": "test",
+ "description": "\u003chtml\u003e is escaped"
+ }
+ }
+----
+
+[[project-query-limit]]
+==== Project Limit
+The `/projects/?query=<query>` URL also accepts a limit integer in the
+`limit` parameter. This limits the results to `limit` projects.
+
+Query the first 25 projects in project list.
+----
+ GET /projects/?query=<query>&limit=25 HTTP/1.0
+----
+
+The `/projects/` URL also accepts a start integer in the `start`
+parameter. The results will skip `start` groups from project list.
+
+Query 25 projects starting from index 50.
+----
+ GET /groups/?query=<query>&limit=25&start=50 HTTP/1.0
+----
+
+[[project-query-options]]
+==== Project Options
+Additional fields can be obtained by adding `o` parameters. Each option
+requires more lookups and slows down the query response time to the
+client so they are generally disabled by default. The supported fields
+are described in the context of the link:#project-options[List Projects]
+REST endpoint.
+
[[get-project]]
=== Get Project
--
@@ -2306,6 +2365,8 @@
Cherry-picks a commit of a project to a destination branch.
+To cherry pick a change revision, see link:rest-api-changes.html#cherry-pick[CherryPick].
+
The destination branch must be provided in the request body inside a
link:rest-api-changes.html#cherrypick-input[CherryPickInput] entity.
If the commit message is not set, the commit message of the source
diff --git a/Documentation/user-search-projects.txt b/Documentation/user-search-projects.txt
new file mode 100644
index 0000000..ba20adb
--- /dev/null
+++ b/Documentation/user-search-projects.txt
@@ -0,0 +1,48 @@
+= Gerrit Code Review - Searching Projects
+
+[[search-operators]]
+== Search Operators
+
+Operators act as restrictions on the search. As more operators
+are added to the same query string, they further restrict the
+returned results.
+
+[[name]]
+name:'NAME'::
++
+Matches projects that have exactly the name 'NAME'.
+
+[[inname]]
+inname:'NAME'::
++
+Matches projects that a name part that starts with 'NAME' (case
+insensitive).
+
+[[description]]
+description:'DESCRIPTION'::
++
+Matches projects whose description contains 'DESCRIPTION', using a
+full-text search.
+
+== Magical Operators
+
+[[is-visible]]
+is:visible::
++
+Magical internal flag to prove the current user has access to read
+the projects and all the refs. This flag is always added to any query.
+
+[[limit]]
+limit:'CNT'::
++
+Limit the returned results to no more than 'CNT' records. This is
+automatically set to the page size configured in the current user's
+preferences. Including it in a web query may lead to unpredictable
+results with regards to pagination.
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
+
+SEARCHBOX
+---------
diff --git a/WORKSPACE b/WORKSPACE
index ad1bc8a..2857854 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -1,5 +1,9 @@
workspace(name = "gerrit")
+load("//:version.bzl", "check_version")
+
+check_version("0.5.3")
+
load("//tools/bzl:maven_jar.bzl", "maven_jar", "GERRIT", "MAVEN_LOCAL")
load("//lib/codemirror:cm.bzl", "CM_VERSION", "DIFF_MATCH_PATCH_VERSION")
load("//plugins:external_plugin_deps.bzl", "external_plugin_deps")
@@ -175,8 +179,8 @@
maven_jar(
name = "gson",
- artifact = "com.google.code.gson:gson:2.8.0",
- sha1 = "c4ba5371a29ac9b2ad6129b1d39ea38750043eff",
+ artifact = "com.google.code.gson:gson:2.8.2",
+ sha1 = "3edcfe49d2c6053a70a2a47e4e1c2f94998a49cf",
)
maven_jar(
@@ -188,20 +192,8 @@
maven_jar(
name = "protobuf",
- artifact = "com.google.protobuf:protobuf-java:3.0.0-beta-2",
- sha1 = "de80fe047052445869b96f6def6baca7182c95af",
-)
-
-maven_jar(
- name = "joda_time",
- artifact = "joda-time:joda-time:2.9.9",
- sha1 = "f7b520c458572890807d143670c9b24f4de90897",
-)
-
-maven_jar(
- name = "joda_convert",
- artifact = "org.joda:joda-convert:1.8.1",
- sha1 = "675642ac208e0b741bc9118dcbcae44c271b992a",
+ artifact = "com.google.protobuf:protobuf-java:3.4.0",
+ sha1 = "b32aba0cbe737a4ca953f71688725972e3ee927c",
)
load("//lib:guava.bzl", "GUAVA_VERSION", "GUAVA_BIN_SHA1")
@@ -213,12 +205,6 @@
)
maven_jar(
- name = "velocity",
- artifact = "org.apache.velocity:velocity:1.7",
- sha1 = "2ceb567b8f3f21118ecdec129fe1271dbc09aa7a",
-)
-
-maven_jar(
name = "jsch",
artifact = "com.jcraft:jsch:0.1.54",
sha1 = "da3584329a263616e277e15462b387addd1b208d",
@@ -364,12 +350,12 @@
sha1 = "2e35862b0435c1b027a21f3d6eecbe50e6e08d54",
)
-GREENMAIL_VERS = "1.5.3"
+GREENMAIL_VERS = "1.5.5"
maven_jar(
name = "greenmail",
artifact = "com.icegreen:greenmail:" + GREENMAIL_VERS,
- sha1 = "afabf8178312f7f220f74f1558e457bf54fa4253",
+ sha1 = "9ea96384ad2cb8118c22f493b529eb72c212691c",
)
MAIL_VERS = "1.5.6"
@@ -440,8 +426,8 @@
maven_jar(
name = "tukaani_xz",
- artifact = "org.tukaani:xz:1.4",
- sha1 = "18a9a2ce6abf32ea1b5fd31dae5210ad93f4e5e3",
+ artifact = "org.tukaani:xz:1.6",
+ sha1 = "05b6f921f1810bdf90e25471968f741f87168b64",
)
# When upgrading Lucene, make sure it's compatible with Elasticsearch
@@ -591,8 +577,8 @@
# Keep this version of Soy synchronized with the version used in Gitiles.
maven_jar(
name = "soy",
- artifact = "com.google.template:soy:2017-04-23",
- sha1 = "52f32a5a3801ab97e0909373ef7f73a3460d0802",
+ artifact = "com.google.template:soy:2017-08-08",
+ sha1 = "792aa49e3ec3f61e793e56b499f0724df1c1e16c",
)
maven_jar(
@@ -609,8 +595,8 @@
maven_jar(
name = "dropwizard_core",
- artifact = "io.dropwizard.metrics:metrics-core:3.2.4",
- sha1 = "36af4975e38bb39686a63ba5139dce8d3f410669",
+ artifact = "io.dropwizard.metrics:metrics-core:3.2.5",
+ sha1 = "ea2316646e9787c5b2d14ca97f4ef7ad5c6b94e9",
)
# When updading Bouncy Castle, also update it in bazlets.
@@ -717,18 +703,18 @@
sha1 = "4785a3c21320980282f9f33d0d1264a69040538f",
)
-TRUTH_VERS = "0.35"
+TRUTH_VERS = "0.36"
maven_jar(
name = "truth",
artifact = "com.google.truth:truth:" + TRUTH_VERS,
- sha1 = "c08a7fde45e058323bcfa3f510d4fe1e2b028f37",
+ sha1 = "7485219d2c1d341097a19382c02bde07e69ff5d2",
)
maven_jar(
name = "truth-java8-extension",
artifact = "com.google.truth.extensions:truth-java8-extension:" + TRUTH_VERS,
- sha1 = "5457fdf91b1e954b070ad7f2db9bea5505da4bca",
+ sha1 = "dcc60988c8f9a051840766ef192a2ef41e7992f1",
)
# When bumping the easymock version number, make sure to also move powermock to a compatible version
@@ -922,8 +908,8 @@
# When upgrading Elasticsearch, make sure it's compatible with Lucene
maven_jar(
name = "elasticsearch",
- artifact = "org.elasticsearch:elasticsearch:2.4.5",
- sha1 = "daafe48ae06592029a2fedca1fe2ac0f5eec3185",
+ artifact = "org.elasticsearch:elasticsearch:2.4.6",
+ sha1 = "d2954e1173a608a9711f132d1768a676a8b1fb81",
)
# Java REST client for Elasticsearch.
@@ -942,6 +928,18 @@
)
maven_jar(
+ name = "joda_time",
+ artifact = "joda-time:joda-time:2.9.9",
+ sha1 = "f7b520c458572890807d143670c9b24f4de90897",
+)
+
+maven_jar(
+ name = "joda_convert",
+ artifact = "org.joda:joda-convert:1.8.1",
+ sha1 = "675642ac208e0b741bc9118dcbcae44c271b992a",
+)
+
+maven_jar(
name = "compress_lzf",
artifact = "com.ning:compress-lzf:1.0.2",
sha1 = "62896e6fca184c79cc01a14d143f3ae2b4f4b4ae",
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index 958eeb9..adcb57e 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -21,6 +21,8 @@
import static com.google.gerrit.reviewdb.client.Patch.COMMIT_MSG;
import static com.google.gerrit.reviewdb.client.Patch.MERGE_LIST;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static com.google.gerrit.server.project.Util.category;
+import static com.google.gerrit.server.project.Util.value;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.stream.Collectors.toList;
import static org.eclipse.jgit.lib.Constants.HEAD;
@@ -37,6 +39,9 @@
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.ContributorAgreement;
import com.google.gerrit.common.data.GroupReference;
+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.Permission;
import com.google.gerrit.common.data.PermissionRule;
import com.google.gerrit.extensions.api.GerritApi;
@@ -1007,7 +1012,6 @@
gApi.changes().id(chId).submittedTogether(EnumSet.of(NON_VISIBLE_CHANGES));
assertThat(info.nonVisibleChanges).isEqualTo(0);
- assertThat(actual).hasSize(expected.length);
assertThat(changeIds(actual)).containsExactly((Object[]) expected).inOrder();
assertThat(changeIds(info.changes)).containsExactly((Object[]) expected).inOrder();
}
@@ -1383,4 +1387,19 @@
return rw.parseCommit(
ObjectId.fromString(get(changeId, ListChangesOption.CURRENT_REVISION).currentRevision));
}
+
+ protected void configLabel(String label, LabelFunction func) throws Exception {
+ configLabel(
+ project, label, func, value(1, "Passes"), value(0, "No score"), value(-1, "Failed"));
+ }
+
+ protected void configLabel(
+ Project.NameKey project, String label, LabelFunction func, LabelValue... value)
+ throws Exception {
+ ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
+ LabelType labelType = category(label, value);
+ labelType.setFunction(func);
+ cfg.getLabelSections().put(labelType.getName(), labelType);
+ saveProjectConfig(project, cfg);
+ }
}
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractNotificationTest.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractNotificationTest.java
index b2e7415..decc471 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractNotificationTest.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractNotificationTest.java
@@ -21,9 +21,8 @@
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
-import com.google.common.truth.FailureStrategy;
+import com.google.common.truth.FailureMetadata;
import com.google.common.truth.Subject;
-import com.google.common.truth.SubjectFactory;
import com.google.common.truth.Truth;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.api.changes.RecipientType;
@@ -62,18 +61,8 @@
gApi.projects().name(project.get()).config(conf);
}
- private static final SubjectFactory<FakeEmailSenderSubject, FakeEmailSender>
- FAKE_EMAIL_SENDER_SUBJECT_FACTORY =
- new SubjectFactory<FakeEmailSenderSubject, FakeEmailSender>() {
- @Override
- public FakeEmailSenderSubject getSubject(
- FailureStrategy failureStrategy, FakeEmailSender target) {
- return new FakeEmailSenderSubject(failureStrategy, target);
- }
- };
-
protected static FakeEmailSenderSubject assertThat(FakeEmailSender sender) {
- return assertAbout(FAKE_EMAIL_SENDER_SUBJECT_FACTORY).that(sender);
+ return assertAbout(FakeEmailSenderSubject::new).that(sender);
}
protected void setEmailStrategy(TestAccount account, EmailStrategy strategy) throws Exception {
@@ -98,8 +87,8 @@
private Map<RecipientType, List<String>> recipients = new HashMap<>();
private Set<String> accountedFor = new HashSet<>();
- FakeEmailSenderSubject(FailureStrategy failureStrategy, FakeEmailSender target) {
- super(failureStrategy, target);
+ FakeEmailSenderSubject(FailureMetadata failureMetadata, FakeEmailSender target) {
+ super(failureMetadata, target);
}
public FakeEmailSenderSubject notSent() {
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GitUtil.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GitUtil.java
index c9a474f..e11651f 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GitUtil.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/GitUtil.java
@@ -196,7 +196,7 @@
PushCommand pushCmd = testRepo.git().push();
pushCmd.setForce(force);
pushCmd.setPushOptions(pushOptions);
- pushCmd.setRefSpecs(new RefSpec(source + ":" + target));
+ pushCmd.setRefSpecs(new RefSpec((source != null ? source : "") + ":" + target));
if (pushTags) {
pushCmd.setPushTags();
}
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/InProcessProtocol.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/InProcessProtocol.java
index e2e29c9..629c6bd 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/InProcessProtocol.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/InProcessProtocol.java
@@ -39,9 +39,7 @@
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.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.util.RequestContext;
import com.google.gerrit.server.util.RequestScopePropagator;
@@ -286,7 +284,7 @@
private static class Receive implements ReceivePackFactory<Context> {
private final Provider<CurrentUser> userProvider;
- private final ProjectControl.GenericFactory projectControlFactory;
+ private final ProjectCache projectCache;
private final AsyncReceiveCommits.Factory factory;
private final TransferConfig config;
private final DynamicSet<ReceivePackInitializer> receivePackInitializers;
@@ -297,7 +295,7 @@
@Inject
Receive(
Provider<CurrentUser> userProvider,
- ProjectControl.GenericFactory projectControlFactory,
+ ProjectCache projectCache,
AsyncReceiveCommits.Factory factory,
TransferConfig config,
DynamicSet<ReceivePackInitializer> receivePackInitializers,
@@ -305,7 +303,7 @@
ThreadLocalRequestContext threadContext,
PermissionBackend permissionBackend) {
this.userProvider = userProvider;
- this.projectControlFactory = projectControlFactory;
+ this.projectCache = projectCache;
this.factory = factory;
this.config = config;
this.receivePackInitializers = receivePackInitializers;
@@ -333,8 +331,14 @@
throw new RuntimeException(e);
}
try {
- ProjectControl ctl = projectControlFactory.controlFor(req.project, userProvider.get());
- AsyncReceiveCommits arc = factory.create(ctl, db, null, ImmutableSetMultimap.of());
+ IdentifiedUser identifiedUser = userProvider.get().asIdentifiedUser();
+ ProjectState projectState = projectCache.checkedGet(req.project);
+ if (projectState == null) {
+ throw new RuntimeException(String.format("project %s not found", req.project));
+ }
+
+ AsyncReceiveCommits arc =
+ factory.create(projectState, identifiedUser, db, null, ImmutableSetMultimap.of());
ReceivePack rp = arc.getReceivePack();
Capable r = arc.canUpload();
@@ -342,17 +346,17 @@
throw new ServiceNotAuthorizedException();
}
- rp.setRefLogIdent(ctl.getUser().asIdentifiedUser().newRefLogIdent());
+ rp.setRefLogIdent(identifiedUser.newRefLogIdent());
rp.setTimeout(config.getTimeout());
rp.setMaxObjectSizeLimit(config.getMaxObjectSizeLimit());
for (ReceivePackInitializer initializer : receivePackInitializers) {
- initializer.init(ctl.getProject().getNameKey(), rp);
+ initializer.init(projectState.getNameKey(), rp);
}
rp.setPostReceiveHook(PostReceiveHookChain.newChain(Lists.newArrayList(postReceiveHooks)));
return rp;
- } catch (NoSuchProjectException | IOException e) {
+ } catch (IOException | PermissionBackendException e) {
throw new RuntimeException(e);
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
index baa0a68..7ca424d 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -69,6 +69,7 @@
import com.google.gerrit.acceptance.TestProjectInput;
import com.google.gerrit.common.FooterConstants;
import com.google.gerrit.common.TimeUtil;
+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.extensions.api.changes.AddReviewerInput;
@@ -132,6 +133,7 @@
import com.google.gerrit.server.config.AnonymousCowardNameProvider;
import com.google.gerrit.server.git.ChangeMessageModifier;
import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
import com.google.gerrit.server.project.Util;
import com.google.gerrit.server.update.BatchUpdate;
@@ -1582,7 +1584,6 @@
// Exact request format made by GWT UI at ddc6b7160fe416fed9e7e3180489d44c82fd64f8.
ReviewInput in = new ReviewInput();
in.labels = ImmutableMap.of("Code-Review", (short) 0);
- in.strictLabels = true;
in.drafts = DraftHandling.PUBLISH_ALL_REVISIONS;
in.message = "comment";
gApi.changes().id(r.getChangeId()).revision(r.getCommit().name()).review(in);
@@ -3189,6 +3190,68 @@
gApi.changes().id(changeId).topic(topic);
}
+ @Test
+ public void submittableAfterLosingPermissions_MaxWithBlock() throws Exception {
+ configLabel("Label", LabelFunction.MAX_WITH_BLOCK);
+ submittableAfterLosingPermissions("Label");
+ }
+
+ @Test
+ public void submittableAfterLosingPermissions_AnyWithBlock() throws Exception {
+ configLabel("Label", LabelFunction.ANY_WITH_BLOCK);
+ submittableAfterLosingPermissions("Label");
+ }
+
+ public void submittableAfterLosingPermissions(String label) throws Exception {
+ String codeReviewLabel = "Code-Review";
+ ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
+ AccountGroup.UUID registered = SystemGroupBackend.REGISTERED_USERS;
+ Util.allow(cfg, Permission.forLabel(label), -1, +1, registered, "refs/heads/*");
+ Util.allow(cfg, Permission.forLabel(codeReviewLabel), -2, +2, registered, "refs/heads/*");
+ saveProjectConfig(cfg);
+
+ setApiUser(user);
+ PushOneCommit.Result r = createChange();
+ String changeId = r.getChangeId();
+
+ // Verify user's permitted range.
+ ChangeInfo change = gApi.changes().id(changeId).get();
+ assertPermitted(change, label, -1, 0, 1);
+ assertPermitted(change, codeReviewLabel, -2, -1, 0, 1, 2);
+
+ ReviewInput input = new ReviewInput();
+ input.label(codeReviewLabel, 2);
+ input.label(label, 1);
+ gApi.changes().id(changeId).current().review(input);
+
+ assertThat(gApi.changes().id(changeId).current().reviewer(user.email).votes().keySet())
+ .containsExactly(codeReviewLabel, label);
+ assertThat(gApi.changes().id(changeId).current().reviewer(user.email).votes().values())
+ .containsExactly((short) 2, (short) 1);
+ assertThat(gApi.changes().id(changeId).get().submittable).isTrue();
+
+ setApiUser(admin);
+ // Remove user's permission for 'Label'.
+ Util.remove(cfg, Permission.forLabel(label), registered, "refs/heads/*");
+ // Update user's permitted range for 'Code-Review' to be -1...+1.
+ Util.remove(cfg, Permission.forLabel(codeReviewLabel), registered, "refs/heads/*");
+ Util.allow(cfg, Permission.forLabel(codeReviewLabel), -1, +1, registered, "refs/heads/*");
+ saveProjectConfig(cfg);
+
+ // Verify user's new permitted range.
+ setApiUser(user);
+ change = gApi.changes().id(changeId).get();
+ assertPermitted(change, label);
+ assertPermitted(change, codeReviewLabel, -1, 0, 1);
+
+ assertThat(gApi.changes().id(changeId).current().reviewer(user.email).votes().values())
+ .containsExactly((short) 2, (short) 1);
+ assertThat(gApi.changes().id(changeId).get().submittable).isTrue();
+
+ setApiUser(admin);
+ gApi.changes().id(changeId).current().submit();
+ }
+
private String getCommitMessage(String changeId) throws RestApiException, IOException {
return gApi.changes().id(changeId).current().file("/COMMIT_MSG").content().asString();
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/group/GroupsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/group/GroupsIT.java
index 305a2b0..2118f29 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/group/GroupsIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/group/GroupsIT.java
@@ -491,6 +491,33 @@
}
@Test
+ public void getGroupsByOwner() throws Exception {
+ String parent = createGroup("test-parent");
+ List<String> children =
+ Arrays.asList(createGroup("test-child1", parent), createGroup("test-child2", parent));
+
+ // By UUID
+ List<GroupInfo> owned =
+ gApi.groups().list().withOwnedBy(getFromCache(parent).getGroupUUID().get()).get();
+ assertThat(owned.stream().map(g -> g.name).collect(toList()))
+ .containsExactlyElementsIn(children);
+
+ // By name
+ owned = gApi.groups().list().withOwnedBy(parent).get();
+ assertThat(owned.stream().map(g -> g.name).collect(toList()))
+ .containsExactlyElementsIn(children);
+
+ // By group that does not own any others
+ owned = gApi.groups().list().withOwnedBy(owned.get(0).id).get();
+ assertThat(owned).isEmpty();
+
+ // By non-existing group
+ exception.expect(UnprocessableEntityException.class);
+ exception.expectMessage("Group Not Found: does-not-exist");
+ gApi.groups().list().withOwnedBy("does-not-exist").get();
+ }
+
+ @Test
public void onlyVisibleGroupsReturned() throws Exception {
String newGroupName = name("newGroup");
GroupInput in = new GroupInput();
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/plugin/PluginIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/plugin/PluginIT.java
index 0fa09af..9b12069f 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/plugin/PluginIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/plugin/PluginIT.java
@@ -28,6 +28,7 @@
import com.google.gerrit.extensions.api.plugins.Plugins.ListRequest;
import com.google.gerrit.extensions.common.InstallPluginInput;
import com.google.gerrit.extensions.common.PluginInfo;
+import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.RawInput;
@@ -45,7 +46,7 @@
private static final RawInput HTML_PLUGIN_CONTENT =
RawInputUtil.create(HTML_PLUGIN.getBytes(UTF_8));
- private static final List<String> PLUGINS =
+ private static final ImmutableList<String> PLUGINS =
ImmutableList.of(
"plugin-a.js", "plugin-b.html", "plugin-c.js", "plugin-d.html", "plugin_e.js");
@@ -107,12 +108,21 @@
api = gApi.plugins().name("plugin-a");
assertThat(api.get().disabled).isNull();
assertPlugins(list().get(), PLUGINS);
+
+ // Non-admin cannot disable
+ setApiUser(user);
+ try {
+ gApi.plugins().name("plugin-a").disable();
+ fail("Expected AuthException");
+ } catch (AuthException expected) {
+ // Expected
+ }
}
@Test
public void installNotAllowed() throws Exception {
exception.expect(MethodNotAllowedException.class);
- exception.expectMessage("remote installation is disabled");
+ exception.expectMessage("remote plugin administration is disabled");
gApi.plugins().install("test.js", new InstallPluginInput());
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/DashboardIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/DashboardIT.java
index b140a6e..6f4495e 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/DashboardIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/DashboardIT.java
@@ -16,12 +16,15 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+import static java.util.stream.Collectors.toList;
+import com.google.common.collect.ImmutableList;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.api.projects.DashboardInfo;
+import com.google.gerrit.extensions.api.projects.DashboardSectionInfo;
import com.google.gerrit.extensions.api.projects.ProjectApi;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -56,20 +59,40 @@
@Test
public void getDashboard() throws Exception {
- assertThat(dashboards()).isEmpty();
- DashboardInfo info = createDashboard(DashboardsCollection.DEFAULT_DASHBOARD_NAME, "test");
+ DashboardInfo info = createTestDashboard();
DashboardInfo result = project().dashboard(info.id).get();
- assertThat(result.id).isEqualTo(info.id);
- assertThat(result.path).isEqualTo(info.path);
- assertThat(result.ref).isEqualTo(info.ref);
- assertThat(result.project).isEqualTo(project.get());
- assertThat(result.definingProject).isEqualTo(project.get());
- assertThat(dashboards()).hasSize(1);
+ assertDashboardInfo(result, info);
+ }
+
+ @Test
+ public void getDashboardWithNoDescription() throws Exception {
+ DashboardInfo info = newDashboardInfo(DashboardsCollection.DEFAULT_DASHBOARD_NAME, "test");
+ info.description = null;
+ DashboardInfo created = createDashboard(info);
+ assertThat(created.description).isNull();
+ DashboardInfo result = project().dashboard(created.id).get();
+ assertThat(result.description).isNull();
+ }
+
+ @Test
+ public void getDashboardNonDefault() throws Exception {
+ DashboardInfo info = createTestDashboard("my", "test");
+ DashboardInfo result = project().dashboard(info.id).get();
+ assertDashboardInfo(result, info);
+ }
+
+ @Test
+ public void listDashboards() throws Exception {
+ assertThat(dashboards()).isEmpty();
+ DashboardInfo info1 = createTestDashboard(DashboardsCollection.DEFAULT_DASHBOARD_NAME, "test1");
+ DashboardInfo info2 = createTestDashboard(DashboardsCollection.DEFAULT_DASHBOARD_NAME, "test2");
+ assertThat(dashboards().stream().map(d -> d.id).collect(toList()))
+ .containsExactly(info1.id, info2.id);
}
@Test
public void setDefaultDashboard() throws Exception {
- DashboardInfo info = createDashboard(DashboardsCollection.DEFAULT_DASHBOARD_NAME, "test");
+ DashboardInfo info = createTestDashboard();
assertThat(info.isDefault).isNull();
project().dashboard(info.id).setDefault();
assertThat(project().dashboard(info.id).get().isDefault).isTrue();
@@ -78,7 +101,7 @@
@Test
public void setDefaultDashboardByProject() throws Exception {
- DashboardInfo info = createDashboard(DashboardsCollection.DEFAULT_DASHBOARD_NAME, "test");
+ DashboardInfo info = createTestDashboard();
assertThat(info.isDefault).isNull();
project().defaultDashboard(info.id);
assertThat(project().dashboard(info.id).get().isDefault).isTrue();
@@ -93,8 +116,8 @@
@Test
public void replaceDefaultDashboard() throws Exception {
- DashboardInfo d1 = createDashboard(DashboardsCollection.DEFAULT_DASHBOARD_NAME, "test1");
- DashboardInfo d2 = createDashboard(DashboardsCollection.DEFAULT_DASHBOARD_NAME, "test2");
+ DashboardInfo d1 = createTestDashboard(DashboardsCollection.DEFAULT_DASHBOARD_NAME, "test1");
+ DashboardInfo d2 = createTestDashboard(DashboardsCollection.DEFAULT_DASHBOARD_NAME, "test2");
assertThat(d1.isDefault).isNull();
assertThat(d2.isDefault).isNull();
project().dashboard(d1.id).setDefault();
@@ -109,12 +132,28 @@
@Test
public void cannotGetDashboardWithInheritedForNonDefault() throws Exception {
- DashboardInfo info = createDashboard(DashboardsCollection.DEFAULT_DASHBOARD_NAME, "test");
+ DashboardInfo info = createTestDashboard();
exception.expect(BadRequestException.class);
exception.expectMessage("inherited flag can only be used with default");
project().dashboard(info.id).get(true);
}
+ private void assertDashboardInfo(DashboardInfo actual, DashboardInfo expected) throws Exception {
+ assertThat(actual.id).isEqualTo(expected.id);
+ assertThat(actual.path).isEqualTo(expected.path);
+ assertThat(actual.ref).isEqualTo(expected.ref);
+ assertThat(actual.project).isEqualTo(project.get());
+ assertThat(actual.definingProject).isEqualTo(project.get());
+ assertThat(actual.description).isEqualTo(expected.description);
+ assertThat(actual.title).isEqualTo(expected.title);
+ assertThat(actual.foreach).isEqualTo(expected.foreach);
+ if (expected.sections == null) {
+ assertThat(actual.sections).isNull();
+ } else {
+ assertThat(actual.sections.size()).isEqualTo(expected.sections.size());
+ }
+ }
+
private List<DashboardInfo> dashboards() throws Exception {
return project().dashboards().get();
}
@@ -123,8 +162,27 @@
return gApi.projects().name(project.get());
}
- private DashboardInfo createDashboard(String ref, String path) throws Exception {
+ private DashboardInfo newDashboardInfo(String ref, String path) {
DashboardInfo info = DashboardsCollection.newDashboardInfo(ref, path);
+ info.title = "Reviewer";
+ info.description = "Own review requests";
+ info.foreach = "owner:self";
+ DashboardSectionInfo section = new DashboardSectionInfo();
+ section.name = "Open";
+ section.query = "is:open";
+ info.sections = ImmutableList.of(section);
+ return info;
+ }
+
+ private DashboardInfo createTestDashboard() throws Exception {
+ return createTestDashboard(DashboardsCollection.DEFAULT_DASHBOARD_NAME, "test");
+ }
+
+ private DashboardInfo createTestDashboard(String ref, String path) throws Exception {
+ return createDashboard(newDashboardInfo(ref, path));
+ }
+
+ private DashboardInfo createDashboard(DashboardInfo info) throws Exception {
String canonicalRef = DashboardsCollection.normalizeDashboardRef(info.ref);
try {
project().branch(canonicalRef).create(new BranchInput());
@@ -137,13 +195,23 @@
try (Repository r = repoManager.openRepository(project)) {
TestRepository<Repository>.CommitBuilder cb =
new TestRepository<>(r).branch(canonicalRef).commit();
- String content =
- "[dashboard]\n"
- + "Description = Test\n"
- + "foreach = owner:self\n"
- + "[section \"Mine\"]\n"
- + "query = is:open";
- cb.add(info.path, content);
+ StringBuilder content = new StringBuilder("[dashboard]\n");
+ if (info.title != null) {
+ content.append("title = ").append(info.title).append("\n");
+ }
+ if (info.description != null) {
+ content.append("description = ").append(info.description).append("\n");
+ }
+ if (info.foreach != null) {
+ content.append("foreach = ").append(info.foreach).append("\n");
+ }
+ if (info.sections != null) {
+ for (DashboardSectionInfo section : info.sections) {
+ content.append("[section \"").append(section.name).append("\"]\n");
+ content.append("query = ").append(section.query).append("\n");
+ }
+ }
+ cb.add(info.path, content.toString());
RevCommit c = cb.create();
project().commit(c.name());
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/ProjectIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/ProjectIT.java
index 94dcf31..aba5c7d 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/ProjectIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/ProjectIT.java
@@ -15,10 +15,14 @@
package com.google.gerrit.acceptance.api.project;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
+import static java.util.stream.Collectors.toSet;
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.AtomicLongMap;
import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.GitUtil;
import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.api.projects.ConfigInfo;
@@ -28,31 +32,82 @@
import com.google.gerrit.extensions.client.InheritableBoolean;
import com.google.gerrit.extensions.client.ProjectState;
import com.google.gerrit.extensions.client.SubmitType;
+import com.google.gerrit.extensions.events.ProjectIndexedListener;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.extensions.registration.RegistrationHandle;
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.UnprocessableEntityException;
import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.inject.Inject;
import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.transport.PushResult;
+import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
+import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
@NoHttpd
public class ProjectIT extends AbstractDaemonTest {
+ @Inject private DynamicSet<ProjectIndexedListener> projectIndexedListeners;
+
+ private ProjectIndexedCounter projectIndexedCounter;
+ private RegistrationHandle projectIndexedCounterHandle;
+
+ @Before
+ public void addProjectIndexedCounter() {
+ projectIndexedCounter = new ProjectIndexedCounter();
+ projectIndexedCounterHandle = projectIndexedListeners.add(projectIndexedCounter);
+ }
+
+ @After
+ public void removeProjectIndexedCounter() {
+ if (projectIndexedCounterHandle != null) {
+ projectIndexedCounterHandle.remove();
+ }
+ }
@Test
public void createProject() throws Exception {
String name = name("foo");
- assertThat(name).isEqualTo(gApi.projects().create(name).get().name);
+ assertThat(gApi.projects().create(name).get().name).isEqualTo(name);
RevCommit head = getRemoteHead(name, RefNames.REFS_CONFIG);
eventRecorder.assertRefUpdatedEvents(name, RefNames.REFS_CONFIG, null, head);
eventRecorder.assertRefUpdatedEvents(name, "refs/heads/master", new String[] {});
+ projectIndexedCounter.assertReindexOf(name);
+ }
+
+ @Test
+ public void createProjectWithInitialBranches() throws Exception {
+ String name = name("foo");
+ ProjectInput input = new ProjectInput();
+ input.name = name;
+ input.createEmptyCommit = true;
+ input.branches = ImmutableList.of("master", "foo");
+ assertThat(gApi.projects().create(input).get().name).isEqualTo(name);
+ assertThat(
+ gApi.projects().name(name).branches().get().stream().map(b -> b.ref).collect(toSet()))
+ .containsExactly("refs/heads/foo", "refs/heads/master", "HEAD", RefNames.REFS_CONFIG);
+
+ RevCommit head = getRemoteHead(name, RefNames.REFS_CONFIG);
+ eventRecorder.assertRefUpdatedEvents(name, RefNames.REFS_CONFIG, null, head);
+
+ head = getRemoteHead(name, "refs/heads/foo");
+ eventRecorder.assertRefUpdatedEvents(name, "refs/heads/foo", null, head);
+
+ head = getRemoteHead(name, "refs/heads/master");
+ eventRecorder.assertRefUpdatedEvents(name, "refs/heads/master", null, head);
+
+ projectIndexedCounter.assertReindexOf(name);
}
@Test
public void createProjectWithGitSuffix() throws Exception {
String name = name("foo");
- assertThat(name).isEqualTo(gApi.projects().create(name + ".git").get().name);
+ assertThat(gApi.projects().create(name + ".git").get().name).isEqualTo(name);
RevCommit head = getRemoteHead(name, RefNames.REFS_CONFIG);
eventRecorder.assertRefUpdatedEvents(name, RefNames.REFS_CONFIG, null, head);
@@ -66,7 +121,7 @@
ProjectInput input = new ProjectInput();
input.name = name;
input.createEmptyCommit = true;
- assertThat(name).isEqualTo(gApi.projects().create(input).get().name);
+ assertThat(gApi.projects().create(input).get().name).isEqualTo(name);
RevCommit head = getRemoteHead(name, RefNames.REFS_CONFIG);
eventRecorder.assertRefUpdatedEvents(name, RefNames.REFS_CONFIG, null, head);
@@ -103,9 +158,34 @@
}
@Test
- public void createBranch() throws Exception {
- allow("refs/*", Permission.READ, ANONYMOUS_USERS);
+ public void createAndDeleteBranch() throws Exception {
+ assertThat(getRemoteHead(project.get(), "foo")).isNull();
+
gApi.projects().name(project.get()).branch("foo").create(new BranchInput());
+ assertThat(getRemoteHead(project.get(), "foo")).isNotNull();
+ projectIndexedCounter.assertNoReindex();
+
+ gApi.projects().name(project.get()).branch("foo").delete();
+ assertThat(getRemoteHead(project.get(), "foo")).isNull();
+ projectIndexedCounter.assertNoReindex();
+ }
+
+ @Test
+ public void createAndDeleteBranchByPush() throws Exception {
+ grant(project, "refs/*", Permission.PUSH, true);
+ projectIndexedCounter.clear();
+
+ assertThat(getRemoteHead(project.get(), "foo")).isNull();
+
+ PushOneCommit.Result r = pushTo("refs/heads/foo");
+ r.assertOkStatus();
+ assertThat(getRemoteHead(project.get(), "foo")).isEqualTo(r.getCommit());
+ projectIndexedCounter.assertNoReindex();
+
+ PushResult r2 = GitUtil.pushOne(testRepo, null, "refs/heads/foo", false, true, null);
+ assertThat(r2.getRemoteUpdate("refs/heads/foo").getStatus()).isEqualTo(Status.OK);
+ assertThat(getRemoteHead(project.get(), "foo")).isNull();
+ projectIndexedCounter.assertNoReindex();
}
@Test
@@ -204,10 +284,45 @@
ConfigInput input = createTestConfigInput();
setApiUser(user);
exception.expect(AuthException.class);
- exception.expectMessage("restricted to project owner");
+ exception.expectMessage("write config not permitted");
gApi.projects().name(project.get()).config(input);
}
+ @Test
+ public void setHead() throws Exception {
+ assertThat(gApi.projects().name(project.get()).head()).isEqualTo("refs/heads/master");
+ gApi.projects().name(project.get()).branch("test1").create(new BranchInput());
+ gApi.projects().name(project.get()).branch("test2").create(new BranchInput());
+ for (String head : new String[] {"test1", "refs/heads/test2"}) {
+ gApi.projects().name(project.get()).head(head);
+ assertThat(gApi.projects().name(project.get()).head()).isEqualTo(RefNames.fullName(head));
+ }
+ }
+
+ @Test
+ public void setHeadToNonexistentBranch() throws Exception {
+ exception.expect(UnprocessableEntityException.class);
+ gApi.projects().name(project.get()).head("does-not-exist");
+ }
+
+ @Test
+ public void setHeadToSameBranch() throws Exception {
+ gApi.projects().name(project.get()).branch("test").create(new BranchInput());
+ for (String head : new String[] {"test", "refs/heads/test"}) {
+ gApi.projects().name(project.get()).head(head);
+ assertThat(gApi.projects().name(project.get()).head()).isEqualTo(RefNames.fullName(head));
+ }
+ }
+
+ @Test
+ public void setHeadNotAllowed() throws Exception {
+ gApi.projects().name(project.get()).branch("test").create(new BranchInput());
+ setApiUser(user);
+ exception.expect(AuthException.class);
+ exception.expectMessage("set head not permitted");
+ gApi.projects().name(project.get()).head("test");
+ }
+
private ConfigInput createTestConfigInput() {
ConfigInput input = new ConfigInput();
input.description = "some description";
@@ -224,4 +339,35 @@
input.state = ProjectState.HIDDEN;
return input;
}
+
+ private static class ProjectIndexedCounter implements ProjectIndexedListener {
+ private final AtomicLongMap<String> countsByProject = AtomicLongMap.create();
+
+ @Override
+ public void onProjectIndexed(String project) {
+ countsByProject.incrementAndGet(project);
+ }
+
+ void clear() {
+ countsByProject.clear();
+ }
+
+ long getCount(String projectName) {
+ return countsByProject.get(projectName);
+ }
+
+ void assertReindexOf(String projectName) {
+ assertReindexOf(projectName, 1);
+ }
+
+ void assertReindexOf(String projectName, int expectedCount) {
+ assertThat(getCount(projectName)).isEqualTo(expectedCount);
+ assertThat(countsByProject).hasSize(1);
+ clear();
+ }
+
+ void assertNoReindex() {
+ assertThat(countsByProject).isEmpty();
+ }
+ }
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/SetParentIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/SetParentIT.java
new file mode 100644
index 0000000..486a29e
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/project/SetParentIT.java
@@ -0,0 +1,89 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.api.project;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.config.AllProjectsNameProvider;
+import org.junit.Test;
+
+@NoHttpd
+public class SetParentIT extends AbstractDaemonTest {
+ @Test
+ public void setParentNotAllowed() throws Exception {
+ String parent = createProject("parent", null, true).get();
+ setApiUser(user);
+ exception.expect(AuthException.class);
+ gApi.projects().name(project.get()).parent(parent);
+ }
+
+ @Test
+ public void setParent() throws Exception {
+ String parent = createProject("parent", null, true).get();
+
+ gApi.projects().name(project.get()).parent(parent);
+ assertThat(gApi.projects().name(project.get()).parent()).isEqualTo(parent);
+
+ // When the parent name is not explicitly set, it should be
+ // set to "All-Projects".
+ gApi.projects().name(project.get()).parent(null);
+ assertThat(gApi.projects().name(project.get()).parent())
+ .isEqualTo(AllProjectsNameProvider.DEFAULT);
+ }
+
+ @Test
+ public void setParentForAllProjectsNotAllowed() throws Exception {
+ exception.expect(ResourceConflictException.class);
+ exception.expectMessage("cannot set parent of " + AllProjectsNameProvider.DEFAULT);
+ gApi.projects().name(allProjects.get()).parent(project.get());
+ }
+
+ @Test
+ public void setParentToSelfNotAllowed() throws Exception {
+ exception.expect(ResourceConflictException.class);
+ exception.expectMessage("cannot set parent to self");
+ gApi.projects().name(project.get()).parent(project.get());
+ }
+
+ @Test
+ public void setParentToOwnChildNotAllowed() throws Exception {
+ String child = createProject("child", project, true).get();
+ exception.expect(ResourceConflictException.class);
+ exception.expectMessage("cycle exists between");
+ gApi.projects().name(project.get()).parent(child);
+ }
+
+ @Test
+ public void setParentToGrandchildNotAllowed() throws Exception {
+ Project.NameKey child = createProject("child", project, true);
+ String grandchild = createProject("grandchild", child, true).get();
+ exception.expect(ResourceConflictException.class);
+ exception.expectMessage("cycle exists between");
+ gApi.projects().name(project.get()).parent(grandchild);
+ }
+
+ @Test
+ public void setParentToNonexistentProject() throws Exception {
+ exception.expect(UnprocessableEntityException.class);
+ exception.expectMessage("not found");
+ gApi.projects().name(project.get()).parent("non-existing");
+ }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/BUILD b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/BUILD
index 990bad6..6bfecfa 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/BUILD
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/BUILD
@@ -4,7 +4,4 @@
srcs = ["ChangeEditIT.java"],
group = "edit",
labels = ["edit"],
- deps = [
- "//lib/joda:joda-time",
- ],
)
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/BUILD b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/BUILD
index 43ec5bc..897b99f 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/BUILD
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/BUILD
@@ -16,7 +16,6 @@
srcs = ["AbstractPushForReview.java"],
deps = [
"//gerrit-acceptance-tests:lib",
- "//lib/joda:joda-time",
],
)
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/ImpersonationIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/ImpersonationIT.java
index 36843a5..9b88e0d 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/ImpersonationIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/ImpersonationIT.java
@@ -146,7 +146,6 @@
ReviewInput in = new ReviewInput();
in.onBehalfOf = user.id.toString();
- in.strictLabels = true;
in.label("Not-A-Label", 5);
exception.expect(BadRequestException.class);
@@ -155,23 +154,6 @@
}
@Test
- public void voteOnBehalfOfInvalidLabelIgnoredWithoutStrictLabels() throws Exception {
- allowCodeReviewOnBehalfOf();
- PushOneCommit.Result r = createChange();
- RevisionApi revision = gApi.changes().id(r.getChangeId()).current();
-
- ReviewInput in = new ReviewInput();
- in.onBehalfOf = user.id.toString();
- in.strictLabels = false;
- in.label("Code-Review", 1);
- in.label("Not-A-Label", 5);
-
- revision.review(in);
-
- assertThat(gApi.changes().id(r.getChangeId()).get().labels).doesNotContainKey("Not-A-Label");
- }
-
- @Test
public void voteOnBehalfOfLabelNotPermitted() throws Exception {
ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
LabelType verified = Util.verified();
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/PutUsernameIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/PutUsernameIT.java
index 7de9d70..1558988 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/PutUsernameIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/PutUsernameIT.java
@@ -18,13 +18,14 @@
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
-import com.google.gerrit.server.account.PutUsername;
+import com.google.gerrit.extensions.api.accounts.UsernameInput;
+
import org.junit.Test;
public class PutUsernameIT extends AbstractDaemonTest {
@Test
public void set() throws Exception {
- PutUsername.Input in = new PutUsername.Input();
+ UsernameInput in = new UsernameInput();
in.username = "myUsername";
RestResponse r =
adminRestSession.put("/accounts/" + accountCreator.create().id.get() + "/username", in);
@@ -34,7 +35,7 @@
@Test
public void setExisting_Conflict() throws Exception {
- PutUsername.Input in = new PutUsername.Input();
+ UsernameInput in = new UsernameInput();
in.username = admin.username;
adminRestSession
.put("/accounts/" + accountCreator.create().id.get() + "/username", in)
@@ -43,7 +44,7 @@
@Test
public void setNew_MethodNotAllowed() throws Exception {
- PutUsername.Input in = new PutUsername.Input();
+ UsernameInput in = new UsernameInput();
in.username = "newUsername";
adminRestSession.put("/accounts/" + admin.username + "/username", in).assertMethodNotAllowed();
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUILD b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUILD
index b7ed2e8..49f00f9 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUILD
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUILD
@@ -15,7 +15,6 @@
labels = ["rest"],
deps = [
":submit_util",
- "//lib/joda:joda-time",
],
)
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/MoveChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/MoveChangeIT.java
index 8388ed0..37d3e1e 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/MoveChangeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/MoveChangeIT.java
@@ -22,6 +22,7 @@
import com.google.gerrit.acceptance.GitUtil;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.PushOneCommit;
+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.extensions.api.changes.MoveInput;
@@ -33,6 +34,7 @@
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.gerrit.server.project.Util;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.PersonIdent;
@@ -227,6 +229,59 @@
move(r.getChangeId(), newBranch.get());
}
+ @Test
+ public void moveChangeOnlyKeepVetoVotes() throws Exception {
+ // A vote for a label will be kept after moving if the label's function is *WithBlock and the
+ // vote holds the minimum value.
+ createBranch(new Branch.NameKey(project, "foo"));
+
+ String codeReviewLabel = "Code-Review"; // 'Code-Review' uses 'MaxWithBlock' function.
+ String testLabelA = "Label-A";
+ String testLabelB = "Label-B";
+ String testLabelC = "Label-C";
+ configLabel(testLabelA, LabelFunction.ANY_WITH_BLOCK);
+ configLabel(testLabelB, LabelFunction.MAX_NO_BLOCK);
+ configLabel(testLabelC, LabelFunction.NO_BLOCK);
+
+ AccountGroup.UUID registered = SystemGroupBackend.REGISTERED_USERS;
+ ProjectConfig cfg = projectCache.checkedGet(project).getConfig();
+ Util.allow(cfg, Permission.forLabel(testLabelA), -1, +1, registered, "refs/heads/*");
+ Util.allow(cfg, Permission.forLabel(testLabelB), -1, +1, registered, "refs/heads/*");
+ Util.allow(cfg, Permission.forLabel(testLabelC), -1, +1, registered, "refs/heads/*");
+ saveProjectConfig(cfg);
+
+ String changeId = createChange().getChangeId();
+ gApi.changes().id(changeId).current().review(ReviewInput.reject());
+
+ amendChange(changeId);
+
+ ReviewInput input = new ReviewInput();
+ input.label(testLabelA, -1);
+ input.label(testLabelB, -1);
+ input.label(testLabelC, -1);
+ gApi.changes().id(changeId).current().review(input);
+
+ assertThat(gApi.changes().id(changeId).current().reviewer(admin.email).votes().keySet())
+ .containsExactly(codeReviewLabel, testLabelA, testLabelB, testLabelC);
+ assertThat(gApi.changes().id(changeId).current().reviewer(admin.email).votes().values())
+ .containsExactly((short) -2, (short) -1, (short) -1, (short) -1);
+
+ // Move the change to the 'foo' branch.
+ assertThat(gApi.changes().id(changeId).get().branch).isEqualTo("master");
+ move(changeId, "foo");
+ assertThat(gApi.changes().id(changeId).get().branch).isEqualTo("foo");
+
+ // 'Code-Review -2' and 'Label-A -1' will be kept.
+ assertThat(gApi.changes().id(changeId).current().reviewer(admin.email).votes().values())
+ .containsExactly((short) -2, (short) -1, (short) 0, (short) 0);
+
+ // Move the change back to 'master'.
+ move(changeId, "master");
+ assertThat(gApi.changes().id(changeId).get().branch).isEqualTo("master");
+ assertThat(gApi.changes().id(changeId).current().reviewer(admin.email).votes().values())
+ .containsExactly((short) -2, (short) -1, (short) 0, (short) 0);
+ }
+
private void move(int changeNum, String destination) throws RestApiException {
gApi.changes().id(changeNum).move(destination);
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BanCommitIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BanCommitIT.java
index 90d51e0..14fa715 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BanCommitIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/BanCommitIT.java
@@ -21,7 +21,7 @@
import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.RestResponse;
-import com.google.gerrit.server.project.BanCommit;
+import com.google.gerrit.extensions.api.projects.BanCommitInput;
import com.google.gerrit.server.project.BanCommit.BanResultInfo;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.transport.RemoteRefUpdate;
@@ -35,7 +35,7 @@
RestResponse r =
adminRestSession.put(
- "/projects/" + project.get() + "/ban/", BanCommit.Input.fromCommits(c.name()));
+ "/projects/" + project.get() + "/ban/", BanCommitInput.fromCommits(c.name()));
r.assertOK();
BanResultInfo info = newGson().fromJson(r.getReader(), BanResultInfo.class);
assertThat(Iterables.getOnlyElement(info.newlyBanned)).isEqualTo(c.name());
@@ -54,13 +54,13 @@
RestResponse r =
adminRestSession.put(
"/projects/" + project.get() + "/ban/",
- BanCommit.Input.fromCommits("a8a477efffbbf3b44169bb9a1d3a334cbbd9aa96"));
+ BanCommitInput.fromCommits("a8a477efffbbf3b44169bb9a1d3a334cbbd9aa96"));
r.consume();
r =
adminRestSession.put(
"/projects/" + project.get() + "/ban/",
- BanCommit.Input.fromCommits("a8a477efffbbf3b44169bb9a1d3a334cbbd9aa96"));
+ BanCommitInput.fromCommits("a8a477efffbbf3b44169bb9a1d3a334cbbd9aa96"));
r.assertOK();
BanResultInfo info = newGson().fromJson(r.getReader(), BanResultInfo.class);
assertThat(Iterables.getOnlyElement(info.alreadyBanned))
@@ -74,7 +74,7 @@
userRestSession
.put(
"/projects/" + project.get() + "/ban/",
- BanCommit.Input.fromCommits("a8a477efffbbf3b44169bb9a1d3a334cbbd9aa96"))
+ BanCommitInput.fromCommits("a8a477efffbbf3b44169bb9a1d3a334cbbd9aa96"))
.assertForbidden();
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java
index 0409fbc..b47b51a 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java
@@ -43,6 +43,12 @@
import com.google.gerrit.server.project.ProjectState;
import java.util.Collections;
import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
import org.apache.http.HttpStatus;
import org.apache.http.message.BasicHeader;
import org.eclipse.jgit.lib.Constants;
@@ -85,6 +91,30 @@
}
@Test
+ public void createSameProjectFromTwoConcurrentRequests() throws Exception {
+ ExecutorService executor = Executors.newFixedThreadPool(2);
+ try {
+ for (int i = 0; i < 10; i++) {
+ String newProjectName = name("foo" + i);
+ CyclicBarrier sync = new CyclicBarrier(2);
+ Callable<RestResponse> createProjectFoo =
+ () -> {
+ sync.await();
+ return adminRestSession.put("/projects/" + newProjectName);
+ };
+
+ Future<RestResponse> r1 = executor.submit(createProjectFoo);
+ Future<RestResponse> r2 = executor.submit(createProjectFoo);
+ assertThat(ImmutableList.of(r1.get().getStatusCode(), r2.get().getStatusCode()))
+ .containsAllOf(HttpStatus.SC_CREATED, HttpStatus.SC_CONFLICT);
+ }
+ } finally {
+ executor.shutdown();
+ executor.awaitTermination(5, TimeUnit.SECONDS);
+ }
+ }
+
+ @Test
@UseLocalDisk
public void createProjectHttpWithUnreasonableName_BadRequest() throws Exception {
ImmutableList<String> forbiddenStrings =
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/SetParentIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/SetParentIT.java
deleted file mode 100644
index 841e398..0000000
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/SetParentIT.java
+++ /dev/null
@@ -1,105 +0,0 @@
-// Copyright (C) 2013 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.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.RestResponse;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.config.AllProjectsNameProvider;
-import com.google.gerrit.server.project.SetParent;
-import org.junit.Test;
-
-public class SetParentIT extends AbstractDaemonTest {
- @Test
- public void setParent_Forbidden() throws Exception {
- String parent = createProject("parent", null, true).get();
- RestResponse r =
- userRestSession.put("/projects/" + project.get() + "/parent", newParentInput(parent));
- r.assertForbidden();
- r.consume();
- }
-
- @Test
- public void setParent() throws Exception {
- String parent = createProject("parent", null, true).get();
- RestResponse r =
- adminRestSession.put("/projects/" + project.get() + "/parent", newParentInput(parent));
- r.assertOK();
- r.consume();
-
- r = adminRestSession.get("/projects/" + project.get() + "/parent");
- r.assertOK();
- String newParent = newGson().fromJson(r.getReader(), String.class);
- assertThat(newParent).isEqualTo(parent);
- r.consume();
-
- // When the parent name is not explicitly set, it should be
- // set to "All-Projects".
- r = adminRestSession.put("/projects/" + project.get() + "/parent", newParentInput(null));
- r.assertOK();
- r.consume();
-
- r = adminRestSession.get("/projects/" + project.get() + "/parent");
- r.assertOK();
- newParent = newGson().fromJson(r.getReader(), String.class);
- assertThat(newParent).isEqualTo(AllProjectsNameProvider.DEFAULT);
- r.consume();
- }
-
- @Test
- public void setParentForAllProjects_Conflict() throws Exception {
- RestResponse r =
- adminRestSession.put(
- "/projects/" + allProjects.get() + "/parent", newParentInput(project.get()));
- r.assertConflict();
- r.consume();
- }
-
- @Test
- public void setInvalidParent_Conflict() throws Exception {
- RestResponse r =
- adminRestSession.put(
- "/projects/" + project.get() + "/parent", newParentInput(project.get()));
- r.assertConflict();
- r.consume();
-
- Project.NameKey child = createProject("child", project, true);
- r = adminRestSession.put("/projects/" + project.get() + "/parent", newParentInput(child.get()));
- r.assertConflict();
- r.consume();
-
- String grandchild = createProject("grandchild", child, true).get();
- r = adminRestSession.put("/projects/" + project.get() + "/parent", newParentInput(grandchild));
- r.assertConflict();
- r.consume();
- }
-
- @Test
- public void setNonExistingParent_UnprocessibleEntity() throws Exception {
- RestResponse r =
- adminRestSession.put(
- "/projects/" + project.get() + "/parent", newParentInput("non-existing"));
- r.assertUnprocessableEntity();
- r.consume();
- }
-
- SetParent.Input newParentInput(String project) {
- SetParent.Input in = new SetParent.Input();
- in.parent = project;
- return in;
- }
-}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/SubmittedTogetherIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/SubmittedTogetherIT.java
index 49588e7..81ad9df 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/SubmittedTogetherIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/SubmittedTogetherIT.java
@@ -54,7 +54,6 @@
assertThat(info.changes.get(0).currentRevision).isEqualTo(c2_1.name());
assertThat(info.changes.get(1).currentRevision).isEqualTo(c1_1.name());
- assertThat(info.changes.get(0).currentRevision).isEqualTo(c2_1.name());
RevisionInfo rev = info.changes.get(0).revisions.get(c2_1.name());
assertThat(rev.files).isNull();
}
@@ -75,7 +74,6 @@
assertThat(info.changes.get(0).currentRevision).isEqualTo(c2_1.name());
assertThat(info.changes.get(1).currentRevision).isEqualTo(c1_1.name());
- assertThat(info.changes.get(0).currentRevision).isEqualTo(c2_1.name());
RevisionInfo rev = info.changes.get(0).revisions.get(c2_1.name());
assertThat(rev).isNotNull();
FileInfo file = rev.files.get("b.txt");
@@ -108,7 +106,7 @@
}
@Test
- public void respectsWholeTopicAndAncestors() throws Exception {
+ public void respectWholeTopic() throws Exception {
RevCommit initialHead = getRemoteHead();
// Create two independent commits and push.
RevCommit c1_1 = commitBuilder().add("a.txt", "1").message("subject: 1").create();
@@ -154,7 +152,7 @@
@Test
public void topicChaining() throws Exception {
RevCommit initialHead = getRemoteHead();
- // Create two independent commits and push.
+
RevCommit c1_1 = commitBuilder().add("a.txt", "1").message("subject: 1").create();
String id1 = getChangeId(c1_1);
pushHead(testRepo, "refs/for/master/" + name("connectingTopic"), false);
@@ -164,7 +162,7 @@
String id2 = getChangeId(c2_1);
pushHead(testRepo, "refs/for/master/" + name("connectingTopic"), false);
- RevCommit c3_1 = commitBuilder().add("b.txt", "2").message("subject: 2").create();
+ RevCommit c3_1 = commitBuilder().add("b.txt", "3").message("subject: 3").create();
String id3 = getChangeId(c3_1);
pushHead(testRepo, "refs/for/master/" + name("unrelated-topic"), false);
@@ -180,6 +178,53 @@
}
@Test
+ public void respectTopicsOnAncestors() throws Exception {
+ RevCommit initialHead = getRemoteHead();
+
+ RevCommit c1_1 = commitBuilder().add("a.txt", "1").message("subject: 1").create();
+ String id1 = getChangeId(c1_1);
+ pushHead(testRepo, "refs/for/master/" + name("connectingTopic"), false);
+
+ testRepo.reset(initialHead);
+ RevCommit c2_1 = commitBuilder().add("b.txt", "2").message("subject: 2").create();
+ String id2 = getChangeId(c2_1);
+ pushHead(testRepo, "refs/for/master/" + name("otherConnectingTopic"), false);
+
+ RevCommit c3_1 = commitBuilder().add("b.txt", "3").message("subject: 3").create();
+ String id3 = getChangeId(c3_1);
+ pushHead(testRepo, "refs/for/master/" + name("connectingTopic"), false);
+
+ RevCommit c4_1 = commitBuilder().add("b.txt", "4").message("subject: 4").create();
+ String id4 = getChangeId(c4_1);
+ pushHead(testRepo, "refs/for/master", false);
+
+ testRepo.reset(initialHead);
+ RevCommit c5_1 = commitBuilder().add("c.txt", "5").message("subject: 5").create();
+ String id5 = getChangeId(c5_1);
+ pushHead(testRepo, "refs/for/master", false);
+
+ RevCommit c6_1 = commitBuilder().add("c.txt", "6").message("subject: 6").create();
+ String id6 = getChangeId(c6_1);
+ pushHead(testRepo, "refs/for/master/" + name("otherConnectingTopic"), false);
+
+ if (isSubmitWholeTopicEnabled()) {
+ assertSubmittedTogether(id1, id6, id5, id3, id2, id1);
+ assertSubmittedTogether(id2, id6, id5, id2);
+ assertSubmittedTogether(id3, id6, id5, id3, id2, id1);
+ assertSubmittedTogether(id4, id6, id5, id4, id3, id2, id1);
+ assertSubmittedTogether(id5);
+ assertSubmittedTogether(id6, id6, id5, id2);
+ } else {
+ assertSubmittedTogether(id1);
+ assertSubmittedTogether(id2);
+ assertSubmittedTogether(id3, id3, id2);
+ assertSubmittedTogether(id4, id4, id3, id2);
+ assertSubmittedTogether(id5);
+ assertSubmittedTogether(id6, id6, id5);
+ }
+ }
+
+ @Test
public void newBranchTwoChangesTogether() throws Exception {
Project.NameKey p1 = createProject("a-new-project", null, false);
TestRepository<?> repo1 = cloneProject(p1);
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/mail/AbstractMailIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/mail/AbstractMailIT.java
index 6f4bdab..32f1ce5 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/mail/AbstractMailIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/mail/AbstractMailIT.java
@@ -23,8 +23,8 @@
import com.google.gerrit.extensions.client.Comment;
import com.google.gerrit.extensions.client.Side;
import com.google.gerrit.server.mail.receive.MailMessage;
+import java.time.Instant;
import java.util.HashMap;
-import org.joda.time.DateTime;
import org.junit.Ignore;
@Ignore
@@ -36,7 +36,7 @@
b.from(user.emailAddress);
b.addTo(user.emailAddress); // Not evaluated
b.subject("");
- b.dateReceived(new DateTime());
+ b.dateReceived(Instant.now());
return b;
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/mail/BUILD b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/mail/BUILD
index 71a6135..c3a4e20 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/mail/BUILD
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/mail/BUILD
@@ -2,7 +2,6 @@
DEPS = [
"//lib/greenmail",
- "//lib/joda:joda-time",
"//lib/mail",
]
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/mail/MailSenderIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/mail/MailSenderIT.java
index 43f046a..4f51e1f 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/mail/MailSenderIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/mail/MailSenderIT.java
@@ -18,6 +18,7 @@
import com.google.gerrit.acceptance.GerritConfig;
import com.google.gerrit.server.mail.send.EmailHeader;
+import java.net.URI;
import java.util.Map;
import org.junit.Test;
@@ -31,9 +32,7 @@
// Check that the custom address was added as Reply-To
assertThat(sender.getMessages()).hasSize(1);
Map<String, EmailHeader> headers = sender.getMessages().iterator().next().headers();
- assertThat(headers.get("Reply-To")).isInstanceOf(EmailHeader.String.class);
- assertThat(((EmailHeader.String) headers.get("Reply-To")).getString())
- .isEqualTo("custom@gerritcodereview.com");
+ assertThat(headerString(headers, "Reply-To")).isEqualTo("custom@gerritcodereview.com");
}
@Test
@@ -42,7 +41,30 @@
// Check that the user's email was added as Reply-To
assertThat(sender.getMessages()).hasSize(1);
Map<String, EmailHeader> headers = sender.getMessages().iterator().next().headers();
- assertThat(headers.get("Reply-To")).isInstanceOf(EmailHeader.String.class);
- assertThat(((EmailHeader.String) headers.get("Reply-To")).getString()).contains(user.email);
+ assertThat(headerString(headers, "Reply-To")).contains(user.email);
+ }
+
+ @Test
+ public void outgoingMailHasListHeaders() throws Exception {
+ String changeId = createChangeWithReview(user);
+ // Check that the mail has the expected headers
+ assertThat(sender.getMessages()).hasSize(1);
+ Map<String, EmailHeader> headers = sender.getMessages().iterator().next().headers();
+ String hostname = URI.create(canonicalWebUrl.get()).getHost();
+ String listId = String.format("<gerrit-%s.%s>", project.get(), hostname);
+ String unsubscribeLink = String.format("<%ssettings>", canonicalWebUrl.get());
+ String threadId =
+ String.format(
+ "<gerrit.%s.%s@%s>",
+ gApi.changes().id(changeId).get().created.getTime(), changeId, hostname);
+ assertThat(headerString(headers, "List-Id")).isEqualTo(listId);
+ assertThat(headerString(headers, "List-Unsubscribe")).isEqualTo(unsubscribeLink);
+ assertThat(headerString(headers, "In-Reply-To")).isEqualTo(threadId);
+ }
+
+ private String headerString(Map<String, EmailHeader> headers, String name) {
+ EmailHeader header = headers.get(name);
+ assertThat(header).isInstanceOf(EmailHeader.String.class);
+ return ((EmailHeader.String) header).getString();
}
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java
index 0324ffa..e60abc6 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java
@@ -43,6 +43,7 @@
import com.google.gerrit.extensions.api.changes.ReviewInput.DraftHandling;
import com.google.gerrit.extensions.client.Side;
import com.google.gerrit.extensions.common.CommentInfo;
+import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.reviewdb.client.Account;
@@ -326,7 +327,7 @@
public void restApiNotFoundWhenNoteDbDisabled() throws Exception {
PushOneCommit.Result r = createChange();
exception.expect(ResourceNotFoundException.class);
- rebuildHandler.apply(parseChangeResource(r.getChangeId()), new Rebuild.Input());
+ rebuildHandler.apply(parseChangeResource(r.getChangeId()), new Input());
}
@Test
@@ -336,7 +337,7 @@
setNotesMigration(true, false);
checker.assertNoChangeRef(project, id);
- rebuildHandler.apply(parseChangeResource(r.getChangeId()), new Rebuild.Input());
+ rebuildHandler.apply(parseChangeResource(r.getChangeId()), new Input());
checker.checkChanges(id);
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/CustomLabelIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/CustomLabelIT.java
index 38ff3c7..75e7553 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/CustomLabelIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/project/CustomLabelIT.java
@@ -15,6 +15,10 @@
package com.google.gerrit.acceptance.server.project;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.common.data.LabelFunction.ANY_WITH_BLOCK;
+import static com.google.gerrit.common.data.LabelFunction.MAX_NO_BLOCK;
+import static com.google.gerrit.common.data.LabelFunction.NO_BLOCK;
+import static com.google.gerrit.common.data.LabelFunction.NO_OP;
import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
import static com.google.gerrit.server.project.Util.category;
import static com.google.gerrit.server.project.Util.value;
@@ -80,11 +84,11 @@
@Test
public void customLabelNoOp_NegativeVoteNotBlock() throws Exception {
- label.setFunctionName("NoOp");
+ label.setFunction(NO_OP);
saveLabelConfig();
PushOneCommit.Result r = createChange();
revision(r).review(new ReviewInput().label(label.getName(), -1));
- ChangeInfo c = get(r.getChangeId());
+ ChangeInfo c = getWithLabels(r);
LabelInfo q = c.labels.get(label.getName());
assertThat(q.all).hasSize(1);
assertThat(q.rejected).isNotNull();
@@ -93,11 +97,11 @@
@Test
public void customLabelNoBlock_NegativeVoteNotBlock() throws Exception {
- label.setFunctionName("NoBlock");
+ label.setFunction(NO_BLOCK);
saveLabelConfig();
PushOneCommit.Result r = createChange();
revision(r).review(new ReviewInput().label(label.getName(), -1));
- ChangeInfo c = get(r.getChangeId());
+ ChangeInfo c = getWithLabels(r);
LabelInfo q = c.labels.get(label.getName());
assertThat(q.all).hasSize(1);
assertThat(q.rejected).isNotNull();
@@ -106,11 +110,11 @@
@Test
public void customLabelMaxNoBlock_NegativeVoteNotBlock() throws Exception {
- label.setFunctionName("MaxNoBlock");
+ label.setFunction(MAX_NO_BLOCK);
saveLabelConfig();
PushOneCommit.Result r = createChange();
revision(r).review(new ReviewInput().label(label.getName(), -1));
- ChangeInfo c = get(r.getChangeId());
+ ChangeInfo c = getWithLabels(r);
LabelInfo q = c.labels.get(label.getName());
assertThat(q.all).hasSize(1);
assertThat(q.rejected).isNotNull();
@@ -119,11 +123,11 @@
@Test
public void customLabelAnyWithBlock_NegativeVoteBlock() throws Exception {
- label.setFunctionName("AnyWithBlock");
+ label.setFunction(ANY_WITH_BLOCK);
saveLabelConfig();
PushOneCommit.Result r = createChange();
revision(r).review(new ReviewInput().label(label.getName(), -1));
- ChangeInfo c = get(r.getChangeId());
+ ChangeInfo c = getWithLabels(r);
LabelInfo q = c.labels.get(label.getName());
assertThat(q.all).hasSize(1);
assertThat(q.disliked).isNull();
@@ -133,7 +137,7 @@
@Test
public void customLabelAnyWithBlock_Addreviewer_ZeroVote() throws Exception {
- P.setFunctionName("AnyWithBlock");
+ P.setFunction(ANY_WITH_BLOCK);
saveLabelConfig();
PushOneCommit.Result r = createChange();
AddReviewerInput in = new AddReviewerInput();
@@ -144,7 +148,7 @@
input.message = "foo";
revision(r).review(input);
- ChangeInfo c = get(r.getChangeId());
+ ChangeInfo c = getWithLabels(r);
LabelInfo q = c.labels.get(P.getName());
assertThat(q.all).hasSize(2);
assertThat(q.disliked).isNull();
@@ -158,7 +162,7 @@
saveLabelConfig();
PushOneCommit.Result r = createChange();
revision(r).review(new ReviewInput().label(label.getName(), -1));
- ChangeInfo c = get(r.getChangeId());
+ ChangeInfo c = getWithLabels(r);
LabelInfo q = c.labels.get(label.getName());
assertThat(q.all).hasSize(1);
assertThat(q.disliked).isNull();
@@ -168,16 +172,16 @@
@Test
public void customLabel_DisallowPostSubmit() throws Exception {
- label.setFunctionName("NoOp");
+ label.setFunction(NO_OP);
label.setAllowPostSubmit(false);
- P.setFunctionName("NoOp");
+ P.setFunction(NO_OP);
saveLabelConfig();
PushOneCommit.Result r = createChange();
revision(r).review(ReviewInput.approve());
revision(r).submit();
- ChangeInfo info = get(r.getChangeId(), ListChangesOption.DETAILED_LABELS);
+ ChangeInfo info = getWithLabels(r);
assertPermitted(info, "Code-Review", 2);
assertPermitted(info, P.getName(), 0, 1);
assertPermitted(info, label.getName());
@@ -199,4 +203,8 @@
cfg.getLabelSections().put(P.getName(), P);
saveProjectConfig(project, cfg);
}
+
+ private ChangeInfo getWithLabels(PushOneCommit.Result r) throws Exception {
+ return get(r.getChangeId(), ListChangesOption.LABELS, ListChangesOption.DETAILED_LABELS);
+ }
}
diff --git a/gerrit-common/BUILD b/gerrit-common/BUILD
index 4389080..d9d4392 100644
--- a/gerrit-common/BUILD
+++ b/gerrit-common/BUILD
@@ -28,7 +28,6 @@
"//lib:gwtorm_client",
"//lib:servlet-api-3_1",
"//lib/jgit/org.eclipse.jgit:jgit",
- "//lib/joda:joda-time",
"//lib/log:api",
],
gwt_xml = SRC + "Common.gwt.xml",
@@ -53,7 +52,6 @@
"//lib:gwtorm",
"//lib:servlet-api-3_1",
"//lib/jgit/org.eclipse.jgit:jgit",
- "//lib/joda:joda-time",
"//lib/log:api",
],
)
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/TimeUtil.java b/gerrit-common/src/main/java/com/google/gerrit/common/TimeUtil.java
index a8e40c6..b1697dc 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/TimeUtil.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/TimeUtil.java
@@ -15,14 +15,21 @@
package com.google.gerrit.common;
import com.google.common.annotations.GwtIncompatible;
+import com.google.common.annotations.VisibleForTesting;
import java.sql.Timestamp;
-import org.joda.time.DateTimeUtils;
+import java.util.function.LongSupplier;
/** Static utility methods for dealing with dates and times. */
-@GwtIncompatible("Unemulated org.joda.time.DateTimeUtils")
+@GwtIncompatible("Unemulated Java 8 functionalities")
public class TimeUtil {
+ private static final LongSupplier SYSTEM_CURRENT_MILLIS_SUPPLIER = System::currentTimeMillis;
+
+ private static volatile LongSupplier currentMillisSupplier = SYSTEM_CURRENT_MILLIS_SUPPLIER;
+
public static long nowMs() {
- return DateTimeUtils.currentTimeMillis();
+ // We should rather use Instant.now(Clock).toEpochMilli() instead but this would require some
+ // changes in our testing code as we wouldn't have clock steps anymore.
+ return currentMillisSupplier.getAsLong();
}
public static Timestamp nowTs() {
@@ -33,5 +40,15 @@
return new Timestamp((t.getTime() / 1000) * 1000);
}
+ @VisibleForTesting
+ public static void setCurrentMillisSupplier(LongSupplier customCurrentMillisSupplier) {
+ currentMillisSupplier = customCurrentMillisSupplier;
+ }
+
+ @VisibleForTesting
+ public static void resetCurrentMillisSupplier() {
+ currentMillisSupplier = SYSTEM_CURRENT_MILLIS_SUPPLIER;
+ }
+
private TimeUtil() {}
}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/LabelFunction.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/LabelFunction.java
new file mode 100644
index 0000000..0ce2c29
--- /dev/null
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/LabelFunction.java
@@ -0,0 +1,71 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.common.data;
+
+import com.google.gerrit.common.Nullable;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * Functions for determining submittability based on label votes.
+ *
+ * <p>Only describes built-in label functions. Admins can extend the logic arbitrarily using Prolog
+ * rules, in which case the choice of function in the project config is ignored.
+ *
+ * <p>Function semantics are documented in {@code config-labels.txt}, and actual behavior is
+ * implemented in Prolog in {@code gerrit_common.pl}.
+ */
+public enum LabelFunction {
+ MAX_WITH_BLOCK("MaxWithBlock", true),
+ ANY_WITH_BLOCK("AnyWithBlock", true),
+ MAX_NO_BLOCK("MaxNoBlock", false),
+ NO_BLOCK("NoBlock", false),
+ NO_OP("NoOp", false),
+ PATCH_SET_LOCK("PatchSetLock", false);
+
+ public static final Map<String, LabelFunction> ALL;
+
+ static {
+ Map<String, LabelFunction> all = new LinkedHashMap<>();
+ for (LabelFunction f : values()) {
+ all.put(f.getFunctionName(), f);
+ }
+ ALL = Collections.unmodifiableMap(all);
+ }
+
+ public static Optional<LabelFunction> parse(@Nullable String str) {
+ return Optional.ofNullable(ALL.get(str));
+ }
+
+ private final String name;
+ private final boolean isBlock;
+
+ private LabelFunction(String name, boolean isBlock) {
+ this.name = name;
+ this.isBlock = isBlock;
+ }
+
+ /** The function name as defined in documentation and {@code project.config}. */
+ public String getFunctionName() {
+ return name;
+ }
+
+ /** Whether the label is a "block" label, meaning a minimum vote will prevent submission. */
+ public boolean isBlock() {
+ return isBlock;
+ }
+}
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/LabelType.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/LabelType.java
index c90e1fd..7bfd22e 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/LabelType.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/LabelType.java
@@ -14,6 +14,7 @@
package com.google.gerrit.common.data;
+import com.google.gerrit.common.Nullable;
import com.google.gerrit.reviewdb.client.LabelId;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import java.util.ArrayList;
@@ -22,6 +23,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
public class LabelType {
public static final boolean DEF_ALLOW_POST_SUBMIT = true;
@@ -97,7 +99,9 @@
protected String name;
+ // String rather than LabelFunction for backwards compatibility with GWT JSON interface.
protected String functionName;
+
protected boolean copyMinScore;
protected boolean copyMaxScore;
protected boolean copyAllScoresOnMergeFirstParentUpdate;
@@ -124,7 +128,7 @@
values = sortValues(valueList);
defaultValue = 0;
- functionName = "MaxWithBlock";
+ functionName = LabelFunction.MAX_WITH_BLOCK.getFunctionName();
maxNegative = Short.MIN_VALUE;
maxPositive = Short.MAX_VALUE;
@@ -154,12 +158,19 @@
return psa.getLabelId().get().equalsIgnoreCase(name);
}
- public String getFunctionName() {
- return functionName;
+ public LabelFunction getFunction() {
+ if (functionName == null) {
+ return null;
+ }
+ Optional<LabelFunction> f = LabelFunction.parse(functionName);
+ if (!f.isPresent()) {
+ throw new IllegalStateException("Unsupported functionName: " + functionName);
+ }
+ return f.get();
}
- public void setFunctionName(String functionName) {
- this.functionName = functionName;
+ public void setFunction(@Nullable LabelFunction function) {
+ this.functionName = function != null ? function.getFunctionName() : null;
}
public boolean canOverride() {
diff --git a/gerrit-elasticsearch/BUILD b/gerrit-elasticsearch/BUILD
index fb86aaf..d278bcf 100644
--- a/gerrit-elasticsearch/BUILD
+++ b/gerrit-elasticsearch/BUILD
@@ -17,10 +17,10 @@
"//lib/elasticsearch",
"//lib/elasticsearch:jest",
"//lib/elasticsearch:jest-common",
+ "//lib/elasticsearch:joda-time",
"//lib/guice",
"//lib/guice:guice-assistedinject",
"//lib/jgit/org.eclipse.jgit:jgit",
- "//lib/joda:joda-time",
"//lib/log:api",
"//lib/lucene:lucene-analyzers-common",
"//lib/lucene:lucene-core",
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java
index 7868443..2d04e11 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java
@@ -26,6 +26,7 @@
import com.google.gerrit.server.index.account.AccountIndex;
import com.google.gerrit.server.index.change.ChangeIndex;
import com.google.gerrit.server.index.group.GroupIndex;
+import com.google.gerrit.server.index.project.ProjectIndex;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import com.google.inject.Singleton;
@@ -75,6 +76,10 @@
new FactoryModuleBuilder()
.implement(GroupIndex.class, ElasticGroupIndex.class)
.build(GroupIndex.Factory.class));
+ install(
+ new FactoryModuleBuilder()
+ .implement(ProjectIndex.class, ElasticProjectIndex.class)
+ .build(ProjectIndex.Factory.class));
install(new IndexModule(threads));
if (singleVersions == null) {
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticProjectIndex.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticProjectIndex.java
new file mode 100644
index 0000000..780f023
--- /dev/null
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticProjectIndex.java
@@ -0,0 +1,215 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.elasticsearch;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.gerrit.elasticsearch.ElasticMapping.MappingProperties;
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.query.DataSource;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.index.IndexUtils;
+import com.google.gerrit.server.index.project.ProjectField;
+import com.google.gerrit.server.index.project.ProjectIndex;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectData;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.ResultSet;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.assistedinject.Assisted;
+import io.searchbox.client.JestResult;
+import io.searchbox.core.Bulk;
+import io.searchbox.core.Bulk.Builder;
+import io.searchbox.core.Search;
+import io.searchbox.core.search.sort.Sort;
+import io.searchbox.core.search.sort.Sort.Sorting;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import org.eclipse.jgit.lib.Config;
+import org.elasticsearch.index.query.QueryBuilder;
+import org.elasticsearch.search.builder.SearchSourceBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ElasticProjectIndex extends AbstractElasticIndex<Project.NameKey, ProjectData>
+ implements ProjectIndex {
+ static class ProjectMapping {
+ MappingProperties projects;
+
+ ProjectMapping(Schema<ProjectData> schema) {
+ this.projects = ElasticMapping.createMapping(schema);
+ }
+ }
+
+ static final String PROJECTS = "projects";
+ static final String PROJECTS_PREFIX = PROJECTS + "_";
+
+ private static final Logger log = LoggerFactory.getLogger(ElasticProjectIndex.class);
+
+ private final ProjectMapping mapping;
+ private final Provider<ProjectCache> projectCache;
+
+ @Inject
+ ElasticProjectIndex(
+ @GerritServerConfig Config cfg,
+ SitePaths sitePaths,
+ Provider<ProjectCache> projectCache,
+ JestClientBuilder clientBuilder,
+ @Assisted Schema<ProjectData> schema) {
+ super(cfg, sitePaths, schema, clientBuilder, PROJECTS_PREFIX);
+ this.projectCache = projectCache;
+ this.mapping = new ProjectMapping(schema);
+ }
+
+ @Override
+ public void replace(ProjectData projectState) throws IOException {
+ Bulk bulk =
+ new Bulk.Builder()
+ .defaultIndex(indexName)
+ .defaultType(PROJECTS)
+ .addAction(insert(PROJECTS, projectState))
+ .refresh(true)
+ .build();
+ JestResult result = client.execute(bulk);
+ if (!result.isSucceeded()) {
+ throw new IOException(
+ String.format(
+ "Failed to replace project %s in index %s: %s",
+ projectState.getProject().getName(), indexName, result.getErrorMessage()));
+ }
+ }
+
+ @Override
+ public DataSource<ProjectData> getSource(Predicate<ProjectData> p, QueryOptions opts)
+ throws QueryParseException {
+ return new QuerySource(p, opts);
+ }
+
+ @Override
+ protected Builder addActions(Builder builder, Project.NameKey nameKey) {
+ return builder.addAction(delete(PROJECTS, nameKey));
+ }
+
+ @Override
+ protected String getMappings() {
+ ImmutableMap<String, ProjectMapping> mappings = ImmutableMap.of("mappings", mapping);
+ return gson.toJson(mappings);
+ }
+
+ @Override
+ protected String getId(ProjectData projectState) {
+ return projectState.getProject().getName();
+ }
+
+ private class QuerySource implements DataSource<ProjectData> {
+ private final Search search;
+ private final Set<String> fields;
+
+ QuerySource(Predicate<ProjectData> p, QueryOptions opts) throws QueryParseException {
+ QueryBuilder qb = queryBuilder.toQueryBuilder(p);
+ fields = IndexUtils.projectFields(opts);
+ SearchSourceBuilder searchSource =
+ new SearchSourceBuilder()
+ .query(qb)
+ .from(opts.start())
+ .size(opts.limit())
+ .fields(Lists.newArrayList(fields));
+
+ Sort sort = new Sort(ProjectField.NAME.getName(), Sorting.ASC);
+ sort.setIgnoreUnmapped();
+
+ search =
+ new Search.Builder(searchSource.toString())
+ .addType(PROJECTS)
+ .addIndex(indexName)
+ .addSort(ImmutableList.of(sort))
+ .build();
+ }
+
+ @Override
+ public int getCardinality() {
+ return 10;
+ }
+
+ @Override
+ public ResultSet<ProjectData> read() throws OrmException {
+ try {
+ List<ProjectData> results = Collections.emptyList();
+ JestResult result = client.execute(search);
+ if (result.isSucceeded()) {
+ JsonObject obj = result.getJsonObject().getAsJsonObject("hits");
+ if (obj.get("hits") != null) {
+ JsonArray json = obj.getAsJsonArray("hits");
+ results = Lists.newArrayListWithCapacity(json.size());
+ for (int i = 0; i < json.size(); i++) {
+ results.add(toProjectData(json.get(i)));
+ }
+ }
+ } else {
+ log.error(result.getErrorMessage());
+ }
+ final List<ProjectData> r = Collections.unmodifiableList(results);
+ return new ResultSet<ProjectData>() {
+ @Override
+ public Iterator<ProjectData> iterator() {
+ return r.iterator();
+ }
+
+ @Override
+ public List<ProjectData> toList() {
+ return r;
+ }
+
+ @Override
+ public void close() {
+ // Do nothing.
+ }
+ };
+ } catch (IOException e) {
+ throw new OrmException(e);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return search.toString();
+ }
+
+ private ProjectData toProjectData(JsonElement json) {
+ JsonElement source = json.getAsJsonObject().get("_source");
+ if (source == null) {
+ source = json.getAsJsonObject().get("fields");
+ }
+
+ Project.NameKey nameKey =
+ new Project.NameKey(
+ source.getAsJsonObject().get(ProjectField.NAME.getName()).getAsString());
+ return projectCache.get().get(nameKey).toProjectData();
+ }
+ }
+}
diff --git a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticQueryProjectsTest.java b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticQueryProjectsTest.java
new file mode 100644
index 0000000..4af53e3
--- /dev/null
+++ b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticQueryProjectsTest.java
@@ -0,0 +1,65 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.elasticsearch;
+
+import com.google.gerrit.elasticsearch.ElasticTestUtils.ElasticNodeInfo;
+import com.google.gerrit.server.query.project.AbstractQueryProjectsTest;
+import com.google.gerrit.testutil.InMemoryModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import java.util.concurrent.ExecutionException;
+import org.eclipse.jgit.lib.Config;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+
+public class ElasticQueryProjectsTest extends AbstractQueryProjectsTest {
+ private static ElasticNodeInfo nodeInfo;
+
+ @BeforeClass
+ public static void startIndexService() throws InterruptedException, ExecutionException {
+ if (nodeInfo != null) {
+ // do not start Elasticsearch twice
+ return;
+ }
+ nodeInfo = ElasticTestUtils.startElasticsearchNode();
+ ElasticTestUtils.createAllIndexes(nodeInfo);
+ }
+
+ @AfterClass
+ public static void stopElasticsearchServer() {
+ if (nodeInfo != null) {
+ nodeInfo.node.close();
+ nodeInfo.elasticDir.delete();
+ nodeInfo = null;
+ }
+ }
+
+ @After
+ public void cleanupIndex() {
+ if (nodeInfo != null) {
+ ElasticTestUtils.deleteAllIndexes(nodeInfo);
+ ElasticTestUtils.createAllIndexes(nodeInfo);
+ }
+ }
+
+ @Override
+ protected Injector createInjector() {
+ Config elasticsearchConfig = new Config(config);
+ InMemoryModule.setDefaults(elasticsearchConfig);
+ ElasticTestUtils.configure(elasticsearchConfig, nodeInfo.port);
+ return Guice.createInjector(new InMemoryModule(elasticsearchConfig, notesMigration));
+ }
+}
diff --git a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticTestUtils.java b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticTestUtils.java
index fac10eb..c37a8ec 100644
--- a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticTestUtils.java
+++ b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticTestUtils.java
@@ -20,12 +20,14 @@
import static com.google.gerrit.elasticsearch.ElasticChangeIndex.CLOSED_CHANGES;
import static com.google.gerrit.elasticsearch.ElasticChangeIndex.OPEN_CHANGES;
import static com.google.gerrit.elasticsearch.ElasticGroupIndex.GROUPS_PREFIX;
+import static com.google.gerrit.elasticsearch.ElasticProjectIndex.PROJECTS_PREFIX;
import com.google.common.base.Strings;
import com.google.common.io.Files;
import com.google.gerrit.elasticsearch.ElasticAccountIndex.AccountMapping;
import com.google.gerrit.elasticsearch.ElasticChangeIndex.ChangeMapping;
import com.google.gerrit.elasticsearch.ElasticGroupIndex.GroupMapping;
+import com.google.gerrit.elasticsearch.ElasticProjectIndex.ProjectMapping;
import com.google.gerrit.index.Schema;
import com.google.gerrit.server.account.AccountState;
import com.google.gerrit.server.group.InternalGroup;
@@ -33,6 +35,8 @@
import com.google.gerrit.server.index.account.AccountSchemaDefinitions;
import com.google.gerrit.server.index.change.ChangeSchemaDefinitions;
import com.google.gerrit.server.index.group.GroupSchemaDefinitions;
+import com.google.gerrit.server.index.project.ProjectSchemaDefinitions;
+import com.google.gerrit.server.project.ProjectData;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
@@ -157,6 +161,18 @@
.addMapping(ElasticGroupIndex.GROUPS, gson.toJson(groupMapping))
.execute()
.actionGet();
+
+ Schema<ProjectData> projectSchema = ProjectSchemaDefinitions.INSTANCE.getLatest();
+ ProjectMapping projectMapping = new ProjectMapping(projectSchema);
+ nodeInfo
+ .node
+ .client()
+ .admin()
+ .indices()
+ .prepareCreate(String.format("%s%04d", PROJECTS_PREFIX, projectSchema.getVersion()))
+ .addMapping(ElasticProjectIndex.PROJECTS, gson.toJson(projectMapping))
+ .execute()
+ .actionGet();
}
private static String getHttpPort(Node node) throws InterruptedException, ExecutionException {
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/GpgKeysInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/GpgKeysInput.java
new file mode 100644
index 0000000..8fb587a
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/GpgKeysInput.java
@@ -0,0 +1,22 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.api.accounts;
+
+import java.util.List;
+
+public class GpgKeysInput {
+ public List<String> add;
+ public List<String> delete;
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/SshKeyInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/SshKeyInput.java
new file mode 100644
index 0000000..46dd858
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/SshKeyInput.java
@@ -0,0 +1,21 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.api.accounts;
+
+import com.google.gerrit.extensions.restapi.RawInput;
+
+public class SshKeyInput {
+ public RawInput raw;
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/StatusInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/StatusInput.java
new file mode 100644
index 0000000..951c049
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/StatusInput.java
@@ -0,0 +1,27 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.api.accounts;
+
+import com.google.gerrit.extensions.restapi.DefaultInput;
+
+public class StatusInput {
+ public @DefaultInput String status;
+
+ public StatusInput(String status) {
+ this.status = status;
+ }
+
+ public StatusInput() {}
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/UsernameInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/UsernameInput.java
new file mode 100644
index 0000000..f774ddc
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/UsernameInput.java
@@ -0,0 +1,21 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.api.accounts;
+
+import com.google.gerrit.extensions.restapi.DefaultInput;
+
+public class UsernameInput {
+ @DefaultInput public String username;
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/TopicInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/TopicInput.java
new file mode 100644
index 0000000..12240d2
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/TopicInput.java
@@ -0,0 +1,21 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.api.changes;
+
+import com.google.gerrit.extensions.restapi.DefaultInput;
+
+public class TopicInput {
+ @DefaultInput public String topic;
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/groups/Groups.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/groups/Groups.java
index 567d9ba..0243ba3 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/groups/Groups.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/groups/Groups.java
@@ -80,6 +80,7 @@
private String substring;
private String suggest;
private String regex;
+ private String ownedBy;
public List<GroupInfo> get() throws RestApiException {
Map<String, GroupInfo> map = getAsMap();
@@ -160,6 +161,11 @@
return this;
}
+ public ListRequest withOwnedBy(String ownedBy) {
+ this.ownedBy = ownedBy;
+ return this;
+ }
+
public EnumSet<ListGroupsOption> getOptions() {
return options;
}
@@ -203,6 +209,10 @@
public String getSuggest() {
return suggest;
}
+
+ public String getOwnedBy() {
+ return ownedBy;
+ }
}
/**
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/groups/OwnerInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/groups/OwnerInput.java
new file mode 100644
index 0000000..8b0006e
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/groups/OwnerInput.java
@@ -0,0 +1,21 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.api.groups;
+
+import com.google.gerrit.extensions.restapi.DefaultInput;
+
+public class OwnerInput {
+ @DefaultInput public String owner;
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/BanCommitInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/BanCommitInput.java
new file mode 100644
index 0000000..b0f674f
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/BanCommitInput.java
@@ -0,0 +1,33 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.api.projects;
+
+import com.google.common.collect.Lists;
+import java.util.List;
+
+public class BanCommitInput {
+ public List<String> commits;
+ public String reason;
+
+ public static BanCommitInput fromCommits(String firstCommit, String... moreCommits) {
+ return fromCommits(Lists.asList(firstCommit, moreCommits));
+ }
+
+ public static BanCommitInput fromCommits(List<String> commits) {
+ BanCommitInput in = new BanCommitInput();
+ in.commits = commits;
+ return in;
+ }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/DescriptionInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/DescriptionInput.java
index 322b076..672602d 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/DescriptionInput.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/DescriptionInput.java
@@ -14,9 +14,6 @@
package com.google.gerrit.extensions.api.projects;
-import com.google.gerrit.extensions.restapi.DefaultInput;
-
-public class DescriptionInput {
- @DefaultInput public String description;
+public class DescriptionInput extends com.google.gerrit.extensions.common.DescriptionInput {
public String commitMessage;
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/HeadInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/HeadInput.java
new file mode 100644
index 0000000..606cf52
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/HeadInput.java
@@ -0,0 +1,21 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.api.projects;
+
+import com.google.gerrit.extensions.restapi.DefaultInput;
+
+public class HeadInput {
+ @DefaultInput public String ref;
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ParentInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ParentInput.java
new file mode 100644
index 0000000..6e481ae
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ParentInput.java
@@ -0,0 +1,22 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.api.projects;
+
+import com.google.gerrit.extensions.restapi.DefaultInput;
+
+public class ParentInput {
+ @DefaultInput public String parent;
+ public String commitMessage;
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
index 8320ef7..c9f47c2 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
@@ -170,6 +170,26 @@
ListDashboardsRequest dashboards() throws RestApiException;
+ /** Get the name of the branch to which {@code HEAD} points. */
+ String head() throws RestApiException;
+
+ /**
+ * Set the project's {@code HEAD}.
+ *
+ * @param head the HEAD
+ */
+ void head(String head) throws RestApiException;
+
+ /** Get the name of the project's parent. */
+ String parent() throws RestApiException;
+
+ /**
+ * Set the project's parent.
+ *
+ * @param parent the parent
+ */
+ void parent(String parent) throws RestApiException;
+
/**
* A default implementation which allows source compatibility when adding new methods to the
* interface.
@@ -304,5 +324,25 @@
public void removeDefaultDashboard() throws RestApiException {
throw new NotImplementedException();
}
+
+ @Override
+ public String head() throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public void head(String head) throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public String parent() throws RestApiException {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public void parent(String parent) throws RestApiException {
+ throw new NotImplementedException();
+ }
}
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/Projects.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/Projects.java
index e4a659c..02cce3a 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/Projects.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/Projects.java
@@ -58,6 +58,24 @@
ListRequest list();
+ /**
+ * Query projects.
+ *
+ * <p>Example code: {@code query().withQuery("name:project").get()}
+ *
+ * @return API for setting parameters and getting result.
+ */
+ QueryRequest query();
+
+ /**
+ * Query projects.
+ *
+ * <p>Shortcut API for {@code query().withQuery(String)}.
+ *
+ * @see #query()
+ */
+ QueryRequest query(String query);
+
abstract class ListRequest {
public enum FilterType {
CODE,
@@ -172,6 +190,56 @@
}
/**
+ * API for setting parameters and getting result. Used for {@code query()}.
+ *
+ * @see #query()
+ */
+ abstract class QueryRequest {
+ private String query;
+ private int limit;
+ private int start;
+
+ /** Execute query and returns the matched projects as list. */
+ public abstract List<ProjectInfo> get() throws RestApiException;
+
+ /**
+ * Set query.
+ *
+ * @param query needs to be in human-readable form.
+ */
+ public QueryRequest withQuery(String query) {
+ this.query = query;
+ return this;
+ }
+
+ /**
+ * Set limit for returned list of projects. Optional; server-default is used when not provided.
+ */
+ public QueryRequest withLimit(int limit) {
+ this.limit = limit;
+ return this;
+ }
+
+ /** Set number of projects to skip. Optional; no projects are skipped when not provided. */
+ public QueryRequest withStart(int start) {
+ this.start = start;
+ return this;
+ }
+
+ public String getQuery() {
+ return query;
+ }
+
+ public int getLimit() {
+ return limit;
+ }
+
+ public int getStart() {
+ return start;
+ }
+ }
+
+ /**
* A default implementation which allows source compatibility when adding new methods to the
* interface.
*/
@@ -195,5 +263,15 @@
public ListRequest list() {
throw new NotImplementedException();
}
+
+ @Override
+ public QueryRequest query() {
+ throw new NotImplementedException();
+ }
+
+ @Override
+ public QueryRequest query(String query) {
+ throw new NotImplementedException();
+ }
}
}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/DescriptionInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/DescriptionInput.java
new file mode 100644
index 0000000..c0733dc
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/DescriptionInput.java
@@ -0,0 +1,21 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.common;
+
+import com.google.gerrit.extensions.restapi.DefaultInput;
+
+public class DescriptionInput {
+ @DefaultInput public String description;
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/HttpPasswordInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/HttpPasswordInput.java
new file mode 100644
index 0000000..246c7cf
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/HttpPasswordInput.java
@@ -0,0 +1,20 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.common;
+
+public class HttpPasswordInput {
+ public String httpPassword;
+ public boolean generate;
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/Input.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/Input.java
new file mode 100644
index 0000000..68f864c
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/Input.java
@@ -0,0 +1,20 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.common;
+
+/** A generic empty input. */
+public class Input {
+ public Input() {}
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/NameInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/NameInput.java
new file mode 100644
index 0000000..463eee1
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/NameInput.java
@@ -0,0 +1,21 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.common;
+
+import com.google.gerrit.extensions.restapi.DefaultInput;
+
+public class NameInput {
+ @DefaultInput public String name;
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/ProjectIndexedListener.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/ProjectIndexedListener.java
new file mode 100644
index 0000000..93a610b
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/ProjectIndexedListener.java
@@ -0,0 +1,28 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.extensions.events;
+
+import com.google.gerrit.extensions.annotations.ExtensionPoint;
+
+/** Notified whenever a project is indexed */
+@ExtensionPoint
+public interface ProjectIndexedListener {
+ /**
+ * Invoked when a project is indexed
+ *
+ * @param project name of the project
+ */
+ void onProjectIndexed(String project);
+}
diff --git a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/api/GpgApiAdapterImpl.java b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/api/GpgApiAdapterImpl.java
index 49c7f67..0df0aa9 100644
--- a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/api/GpgApiAdapterImpl.java
+++ b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/api/GpgApiAdapterImpl.java
@@ -15,6 +15,7 @@
package com.google.gerrit.gpg.api;
import com.google.gerrit.extensions.api.accounts.GpgKeyApi;
+import com.google.gerrit.extensions.api.accounts.GpgKeysInput;
import com.google.gerrit.extensions.common.GpgKeyInfo;
import com.google.gerrit.extensions.common.PushCertificateInfo;
import com.google.gerrit.extensions.restapi.IdString;
@@ -75,7 +76,7 @@
public Map<String, GpgKeyInfo> putGpgKeys(
AccountResource account, List<String> add, List<String> delete)
throws RestApiException, GpgException {
- PostGpgKeys.Input in = new PostGpgKeys.Input();
+ GpgKeysInput in = new GpgKeysInput();
in.add = add;
in.delete = delete;
try {
diff --git a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/api/GpgKeyApiImpl.java b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/api/GpgKeyApiImpl.java
index 14a4c6d..25b472d 100644
--- a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/api/GpgKeyApiImpl.java
+++ b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/api/GpgKeyApiImpl.java
@@ -16,6 +16,7 @@
import com.google.gerrit.extensions.api.accounts.GpgKeyApi;
import com.google.gerrit.extensions.common.GpgKeyInfo;
+import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.gpg.server.DeleteGpgKey;
import com.google.gerrit.gpg.server.GpgKey;
@@ -55,7 +56,7 @@
@Override
public void delete() throws RestApiException {
try {
- delete.apply(rsrc, new DeleteGpgKey.Input());
+ delete.apply(rsrc, new Input());
} catch (PGPException | OrmException | IOException | ConfigInvalidException e) {
throw new RestApiException("Cannot delete GPG key", e);
}
diff --git a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/DeleteGpgKey.java b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/DeleteGpgKey.java
index baf5a58..b9d89ee 100644
--- a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/DeleteGpgKey.java
+++ b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/DeleteGpgKey.java
@@ -18,11 +18,11 @@
import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_GPGKEY;
import com.google.common.io.BaseEncoding;
+import com.google.gerrit.extensions.common.Input;
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.gpg.PublicKeyStore;
-import com.google.gerrit.gpg.server.DeleteGpgKey.Input;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.server.account.externalids.ExternalIdsUpdate;
@@ -38,7 +38,6 @@
import org.eclipse.jgit.lib.RefUpdate;
public class DeleteGpgKey implements RestModifyView<GpgKey, Input> {
- public static class Input {}
private final Provider<PersonIdent> serverIdent;
private final Provider<PublicKeyStore> storeProvider;
diff --git a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/PostGpgKeys.java b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/PostGpgKeys.java
index 7d1aceed..8014574 100644
--- a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/PostGpgKeys.java
+++ b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/PostGpgKeys.java
@@ -28,6 +28,7 @@
import com.google.common.collect.Sets;
import com.google.common.io.BaseEncoding;
import com.google.gerrit.common.errors.EmailException;
+import com.google.gerrit.extensions.api.accounts.GpgKeysInput;
import com.google.gerrit.extensions.common.GpgKeyInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -38,7 +39,6 @@
import com.google.gerrit.gpg.GerritPublicKeyChecker;
import com.google.gerrit.gpg.PublicKeyChecker;
import com.google.gerrit.gpg.PublicKeyStore;
-import com.google.gerrit.gpg.server.PostGpgKeys.Input;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.GerritPersonIdent;
@@ -75,12 +75,7 @@
import org.slf4j.LoggerFactory;
@Singleton
-public class PostGpgKeys implements RestModifyView<AccountResource, Input> {
- public static class Input {
- public List<String> add;
- public List<String> delete;
- }
-
+public class PostGpgKeys implements RestModifyView<AccountResource, GpgKeysInput> {
private final Logger log = LoggerFactory.getLogger(getClass());
private final Provider<PersonIdent> serverIdent;
private final Provider<CurrentUser> self;
@@ -112,7 +107,7 @@
}
@Override
- public Map<String, GpgKeyInfo> apply(AccountResource rsrc, Input input)
+ public Map<String, GpgKeyInfo> apply(AccountResource rsrc, GpgKeysInput input)
throws ResourceNotFoundException, BadRequestException, ResourceConflictException,
PGPException, OrmException, IOException, ConfigInvalidException {
GpgKeys.checkVisible(self, rsrc);
@@ -148,7 +143,8 @@
}
}
- private Set<Fingerprint> readKeysToRemove(Input input, Collection<ExternalId> existingExtIds) {
+ private Set<Fingerprint> readKeysToRemove(
+ GpgKeysInput input, Collection<ExternalId> existingExtIds) {
if (input.delete == null || input.delete.isEmpty()) {
return ImmutableSet.of();
}
@@ -163,7 +159,7 @@
return fingerprints;
}
- private List<PGPPublicKeyRing> readKeysToAdd(Input input, Set<Fingerprint> toRemove)
+ private List<PGPPublicKeyRing> readKeysToAdd(GpgKeysInput input, Set<Fingerprint> toRemove)
throws BadRequestException, IOException {
if (input.add == null || input.add.isEmpty()) {
return ImmutableList.of();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ReviewInput.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ReviewInput.java
index 113651b..f851d5e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ReviewInput.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ReviewInput.java
@@ -60,7 +60,6 @@
private native void init() /*-{
this.labels = {};
- this.strict_labels = true;
}-*/;
public final native void prePost() /*-{
diff --git a/gerrit-httpd/BUILD b/gerrit-httpd/BUILD
index dbca10c..cc2160f 100644
--- a/gerrit-httpd/BUILD
+++ b/gerrit-httpd/BUILD
@@ -77,6 +77,5 @@
"//lib/guice:guice-servlet",
"//lib/jgit/org.eclipse.jgit:jgit",
"//lib/jgit/org.eclipse.jgit.junit:junit",
- "//lib/joda:joda-time",
],
)
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java
index 329beab..4d472da 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java
@@ -34,8 +34,8 @@
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.NoSuchProjectException;
-import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectState;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -80,7 +80,7 @@
public class GitOverHttpServlet extends GitServlet {
private static final long serialVersionUID = 1L;
- private static final String ATT_CONTROL = ProjectControl.class.getName();
+ private static final String ATT_STATE = ProjectState.class.getName();
private static final String ATT_ARC = AsyncReceiveCommits.class.getName();
private static final String ID_CACHE = "adv_bases";
@@ -145,18 +145,18 @@
private final GitRepositoryManager manager;
private final PermissionBackend permissionBackend;
private final Provider<CurrentUser> userProvider;
- private final ProjectControl.GenericFactory projectControlFactory;
+ private final ProjectCache projectCache;
@Inject
Resolver(
GitRepositoryManager manager,
PermissionBackend permissionBackend,
Provider<CurrentUser> userProvider,
- ProjectControl.GenericFactory projectControlFactory) {
+ ProjectCache projectCache) {
this.manager = manager;
this.permissionBackend = permissionBackend;
this.userProvider = userProvider;
- this.projectControlFactory = projectControlFactory;
+ this.projectCache = projectCache;
}
@Override
@@ -182,13 +182,11 @@
try {
Project.NameKey nameKey = new Project.NameKey(projectName);
- ProjectControl pc;
- try {
- pc = projectControlFactory.controlFor(nameKey, user);
- } catch (NoSuchProjectException err) {
- throw new RepositoryNotFoundException(projectName);
+ ProjectState state = projectCache.checkedGet(nameKey);
+ if (state == null) {
+ throw new RepositoryNotFoundException(nameKey.get());
}
- req.setAttribute(ATT_CONTROL, pc);
+ req.setAttribute(ATT_STATE, state);
try {
permissionBackend.user(user).project(nameKey).check(ProjectPermission.ACCESS);
@@ -231,9 +229,9 @@
up.setTimeout(config.getTimeout());
up.setPreUploadHook(PreUploadHookChain.newChain(Lists.newArrayList(preUploadHooks)));
up.setPostUploadHook(PostUploadHookChain.newChain(Lists.newArrayList(postUploadHooks)));
- ProjectControl pc = (ProjectControl) req.getAttribute(ATT_CONTROL);
+ ProjectState state = (ProjectState) req.getAttribute(ATT_STATE);
for (UploadPackInitializer initializer : uploadPackInitializers) {
- initializer.init(pc.getProject().getNameKey(), up);
+ initializer.init(state.getNameKey(), up);
}
return up;
}
@@ -243,15 +241,18 @@
private final VisibleRefFilter.Factory refFilterFactory;
private final UploadValidators.Factory uploadValidatorsFactory;
private final PermissionBackend permissionBackend;
+ private final Provider<CurrentUser> userProvider;
@Inject
UploadFilter(
VisibleRefFilter.Factory refFilterFactory,
UploadValidators.Factory uploadValidatorsFactory,
- PermissionBackend permissionBackend) {
+ PermissionBackend permissionBackend,
+ Provider<CurrentUser> userProvider) {
this.refFilterFactory = refFilterFactory;
this.uploadValidatorsFactory = uploadValidatorsFactory;
this.permissionBackend = permissionBackend;
+ this.userProvider = userProvider;
}
@Override
@@ -259,13 +260,13 @@
throws IOException, ServletException {
// The Resolver above already checked READ access for us.
Repository repo = ServletUtils.getRepository(request);
- ProjectControl pc = (ProjectControl) request.getAttribute(ATT_CONTROL);
+ ProjectState state = (ProjectState) request.getAttribute(ATT_STATE);
UploadPack up = (UploadPack) request.getAttribute(ServletUtils.ATTRIBUTE_HANDLER);
try {
permissionBackend
- .user(pc.getUser())
- .project(pc.getProject().getNameKey())
+ .user(userProvider)
+ .project(state.getNameKey())
.check(ProjectPermission.RUN_UPLOAD_PACK);
} catch (AuthException e) {
GitSmartHttpTools.sendError(
@@ -280,10 +281,10 @@
// We use getRemoteHost() here instead of getRemoteAddr() because REMOTE_ADDR
// may have been overridden by a proxy server -- we'll try to avoid this.
UploadValidators uploadValidators =
- uploadValidatorsFactory.create(pc.getProject(), repo, request.getRemoteHost());
+ uploadValidatorsFactory.create(state.getProject(), repo, request.getRemoteHost());
up.setPreUploadHook(
PreUploadHookChain.newChain(Lists.newArrayList(up.getPreUploadHook(), uploadValidators)));
- up.setAdvertiseRefsHook(refFilterFactory.create(pc.getProjectState(), repo));
+ up.setAdvertiseRefsHook(refFilterFactory.create(state, repo));
next.doFilter(request, response);
}
@@ -297,23 +298,27 @@
static class ReceiveFactory implements ReceivePackFactory<HttpServletRequest> {
private final AsyncReceiveCommits.Factory factory;
+ private final Provider<CurrentUser> userProvider;
@Inject
- ReceiveFactory(AsyncReceiveCommits.Factory factory) {
+ ReceiveFactory(AsyncReceiveCommits.Factory factory, Provider<CurrentUser> userProvider) {
this.factory = factory;
+ this.userProvider = userProvider;
}
@Override
public ReceivePack create(HttpServletRequest req, Repository db)
throws ServiceNotAuthorizedException {
- final ProjectControl pc = (ProjectControl) req.getAttribute(ATT_CONTROL);
+ final ProjectState state = (ProjectState) req.getAttribute(ATT_STATE);
- if (!(pc.getUser().isIdentifiedUser())) {
+ if (!(userProvider.get().isIdentifiedUser())) {
// Anonymous users are not permitted to push.
throw new ServiceNotAuthorizedException();
}
- AsyncReceiveCommits arc = factory.create(pc, db, null, ImmutableSetMultimap.of());
+ AsyncReceiveCommits arc =
+ factory.create(
+ state, userProvider.get().asIdentifiedUser(), db, null, ImmutableSetMultimap.of());
ReceivePack rp = arc.getReceivePack();
req.setAttribute(ATT_ARC, arc);
return rp;
@@ -331,13 +336,16 @@
static class ReceiveFilter implements Filter {
private final Cache<AdvertisedObjectsCacheKey, Set<ObjectId>> cache;
private final PermissionBackend permissionBackend;
+ private final Provider<CurrentUser> userProvider;
@Inject
ReceiveFilter(
@Named(ID_CACHE) Cache<AdvertisedObjectsCacheKey, Set<ObjectId>> cache,
- PermissionBackend permissionBackend) {
+ PermissionBackend permissionBackend,
+ Provider<CurrentUser> userProvider) {
this.cache = cache;
this.permissionBackend = permissionBackend;
+ this.userProvider = userProvider;
}
@Override
@@ -348,14 +356,15 @@
AsyncReceiveCommits arc = (AsyncReceiveCommits) request.getAttribute(ATT_ARC);
ReceivePack rp = arc.getReceivePack();
rp.getAdvertiseRefsHook().advertiseRefs(rp);
- ProjectControl pc = (ProjectControl) request.getAttribute(ATT_CONTROL);
- Project.NameKey projectName = pc.getProject().getNameKey();
+ ProjectState state = (ProjectState) request.getAttribute(ATT_STATE);
+ Capable s;
try {
permissionBackend
- .user(pc.getUser())
- .project(pc.getProject().getNameKey())
+ .user(userProvider)
+ .project(state.getNameKey())
.check(ProjectPermission.RUN_RECEIVE_PACK);
+ s = arc.canUpload();
} catch (AuthException e) {
GitSmartHttpTools.sendError(
(HttpServletRequest) request,
@@ -367,7 +376,6 @@
throw new RuntimeException(e);
}
- Capable s = arc.canUpload();
if (s != Capable.OK) {
GitSmartHttpTools.sendError(
(HttpServletRequest) request,
@@ -382,13 +390,13 @@
return;
}
- if (!(pc.getUser().isIdentifiedUser())) {
+ if (!(userProvider.get().isIdentifiedUser())) {
chain.doFilter(request, response);
return;
}
AdvertisedObjectsCacheKey cacheKey =
- AdvertisedObjectsCacheKey.create(pc.getUser().getAccountId(), projectName);
+ AdvertisedObjectsCacheKey.create(userProvider.get().getAccountId(), state.getNameKey());
if (isGet) {
cache.invalidate(cacheKey);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java
index 8ceb50a..1c7d508 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectBasicAuthFilter.java
@@ -146,7 +146,7 @@
}
if (gitBasicAuthPolicy == GitBasicAuthPolicy.HTTP) {
- return failAuthentication(rsp, username);
+ return failAuthentication(rsp, username, req);
}
AuthRequest whoAuth = AuthRequest.forUser(username);
@@ -160,15 +160,15 @@
if (who.checkPassword(password, who.getUserName())) {
return succeedAuthentication(who);
}
- log.warn("Authentication failed for " + username, e);
+ log.warn(authenticationFailedMsg(username, req), e);
rsp.sendError(SC_UNAUTHORIZED);
return false;
} catch (AuthenticationFailedException e) {
- log.warn("Authentication failed for " + username + ": " + e.getMessage());
+ log.warn(authenticationFailedMsg(username, req) + ": " + e.getMessage());
rsp.sendError(SC_UNAUTHORIZED);
return false;
} catch (AccountException e) {
- log.warn("Authentication failed for " + username, e);
+ log.warn(authenticationFailedMsg(username, req), e);
rsp.sendError(SC_UNAUTHORIZED);
return false;
}
@@ -179,13 +179,20 @@
return true;
}
- private boolean failAuthentication(Response rsp, String username) throws IOException {
+ private boolean failAuthentication(Response rsp, String username, HttpServletRequest req)
+ throws IOException {
log.warn(
- "Authentication failed for {}: password does not match the one stored in Gerrit", username);
+ authenticationFailedMsg(username, req)
+ + ": password does not match the one stored in Gerrit",
+ username);
rsp.sendError(SC_UNAUTHORIZED);
return false;
}
+ static String authenticationFailedMsg(String username, HttpServletRequest req) {
+ return String.format("Authentication from %s failed for %s", req.getRemoteAddr(), username);
+ }
+
private void setUserIdentified(Account.Id id) {
WebSession ws = session.get();
ws.setUserAccountId(id);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectOAuthFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectOAuthFilter.java
index 1f21da2..b910509 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectOAuthFilter.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/ProjectOAuthFilter.java
@@ -14,6 +14,7 @@
package com.google.gerrit.httpd;
+import static com.google.gerrit.httpd.ProjectBasicAuthFilter.authenticationFailedMsg;
import static java.nio.charset.StandardCharsets.UTF_8;
import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
@@ -153,8 +154,7 @@
AccountState who = accountCache.getByUsername(authInfo.username);
if (who == null || !who.getAccount().isActive()) {
log.warn(
- "Authentication failed for "
- + authInfo.username
+ authenticationFailedMsg(authInfo.username, req)
+ ": account inactive or not provisioned in Gerrit");
rsp.sendError(SC_UNAUTHORIZED);
return false;
@@ -175,7 +175,7 @@
ws.setAccessPathOk(AccessPath.REST_API, true);
return true;
} catch (AccountException e) {
- log.warn("Authentication failed for " + authInfo.username, e);
+ log.warn(authenticationFailedMsg(authInfo.username, req), e);
rsp.sendError(SC_UNAUTHORIZED);
return false;
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectAccess.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectAccess.java
index 2adf029..62232db 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectAccess.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectAccess.java
@@ -19,16 +19,17 @@
import com.google.gerrit.common.data.ProjectAccess;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.ContributorAgreementsChecker;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.SetParent;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -56,7 +57,6 @@
@Inject
ChangeProjectAccess(
ProjectAccessFactory.Factory projectAccessFactory,
- ProjectControl.Factory projectControlFactory,
ProjectCache projectCache,
GroupBackend groupBackend,
MetaDataUpdate.User metaDataUpdateFactory,
@@ -64,23 +64,26 @@
Provider<SetParent> setParent,
GitReferenceUpdated gitRefUpdated,
ContributorAgreementsChecker contributorAgreements,
+ Provider<CurrentUser> user,
+ PermissionBackend permissionBackend,
@Assisted("projectName") Project.NameKey projectName,
@Nullable @Assisted ObjectId base,
@Assisted List<AccessSection> sectionList,
@Nullable @Assisted("parentProjectName") Project.NameKey parentProjectName,
@Nullable @Assisted String message) {
super(
- projectControlFactory,
groupBackend,
metaDataUpdateFactory,
allProjects,
setParent,
+ user.get(),
projectName,
base,
sectionList,
parentProjectName,
message,
contributorAgreements,
+ permissionBackend,
true);
this.projectAccessFactory = projectAccessFactory;
this.projectCache = projectCache;
@@ -89,10 +92,7 @@
@Override
protected ProjectAccess updateProjectConfig(
- ProjectControl projectControl,
- ProjectConfig config,
- MetaDataUpdate md,
- boolean parentProjectUpdate)
+ ProjectConfig config, MetaDataUpdate md, boolean parentProjectUpdate)
throws IOException, NoSuchProjectException, ConfigInvalidException,
PermissionBackendException {
RevCommit commit = config.commit(md);
@@ -102,7 +102,7 @@
RefNames.REFS_CONFIG,
base,
commit.getId(),
- projectControl.getUser().asIdentifiedUser().getAccount());
+ user.asIdentifiedUser().getAccount());
projectCache.evict(config.getProject());
return projectAccessFactory.create(projectName).call();
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java
index 4cd6fa0..98f6b3f 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java
@@ -17,6 +17,7 @@
import static com.google.gerrit.server.permissions.GlobalPermission.ADMINISTRATE_SERVER;
import static com.google.gerrit.server.permissions.RefPermission.CREATE_CHANGE;
import static com.google.gerrit.server.permissions.RefPermission.READ;
+import static com.google.gerrit.server.permissions.RefPermission.WRITE_CONFIG;
import com.google.common.collect.Maps;
import com.google.gerrit.common.data.AccessSection;
@@ -47,7 +48,7 @@
import com.google.gerrit.server.permissions.RefPermission;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.project.ProjectState;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
@@ -69,7 +70,6 @@
private final ProjectCache projectCache;
private final PermissionBackend permissionBackend;
private final Provider<CurrentUser> user;
- private final ProjectControl.GenericFactory projectControlFactory;
private final GroupControl.Factory groupControlFactory;
private final MetaDataUpdate.Server metaDataUpdateFactory;
private final AllProjectsName allProjectsName;
@@ -83,7 +83,6 @@
ProjectCache projectCache,
PermissionBackend permissionBackend,
Provider<CurrentUser> user,
- ProjectControl.GenericFactory projectControlFactory,
GroupControl.Factory groupControlFactory,
MetaDataUpdate.Server metaDataUpdateFactory,
AllProjectsName allProjectsName,
@@ -93,7 +92,6 @@
this.projectCache = projectCache;
this.permissionBackend = permissionBackend;
this.user = user;
- this.projectControlFactory = projectControlFactory;
this.groupControlFactory = groupControlFactory;
this.metaDataUpdateFactory = metaDataUpdateFactory;
this.allProjectsName = allProjectsName;
@@ -106,7 +104,7 @@
public ProjectAccess call()
throws NoSuchProjectException, IOException, ConfigInvalidException,
PermissionBackendException {
- ProjectControl pc = checkProjectControl();
+ ProjectState projectState = checkProjectState();
// Load the current configuration from the repository, ensuring its the most
// recent version available. If it differs from what was in the project
@@ -119,11 +117,11 @@
md.setMessage("Update group names\n");
config.commit(md);
projectCache.evict(config.getProject());
- pc = checkProjectControl();
+ projectState = checkProjectState();
} else if (config.getRevision() != null
- && !config.getRevision().equals(pc.getProjectState().getConfig().getRevision())) {
+ && !config.getRevision().equals(projectState.getConfig().getRevision())) {
projectCache.evict(config.getProject());
- pc = checkProjectControl();
+ projectState = checkProjectState();
}
}
@@ -132,11 +130,17 @@
Map<AccountGroup.UUID, Boolean> visibleGroups = new HashMap<>();
PermissionBackend.ForProject perm = permissionBackend.user(user).project(projectName);
boolean checkReadConfig = check(perm, RefNames.REFS_CONFIG, READ);
+ boolean canWriteProjectConfig = true;
+ try {
+ perm.check(ProjectPermission.WRITE_CONFIG);
+ } catch (AuthException e) {
+ canWriteProjectConfig = false;
+ }
for (AccessSection section : config.getAccessSections()) {
String name = section.getName();
if (AccessSection.GLOBAL_CAPABILITIES.equals(name)) {
- if (pc.isOwner()) {
+ if (canWriteProjectConfig) {
local.add(section);
ownerOf.add(name);
@@ -145,7 +149,7 @@
}
} else if (RefConfigSection.isValid(name)) {
- if (pc.controlForRef(name).isOwner()) {
+ if (check(perm, name, WRITE_CONFIG)) {
local.add(section);
ownerOf.add(name);
@@ -217,11 +221,11 @@
detail.setLocal(local);
detail.setOwnerOf(ownerOf);
detail.setCanUpload(
- pc.isOwner()
+ canWriteProjectConfig
|| (checkReadConfig && perm.ref(RefNames.REFS_CONFIG).testOrFalse(CREATE_CHANGE)));
- detail.setConfigVisible(pc.isOwner() || checkReadConfig);
+ detail.setConfigVisible(canWriteProjectConfig || checkReadConfig);
detail.setGroupInfo(buildGroupInfo(local));
- detail.setLabelTypes(pc.getProjectState().getLabelTypes());
+ detail.setLabelTypes(projectState.getLabelTypes());
detail.setFileHistoryLinks(getConfigFileLogLinks(projectName.get()));
return detail;
}
@@ -251,15 +255,15 @@
return Maps.filterEntries(infos, in -> in.getValue() != null);
}
- private ProjectControl checkProjectControl()
+ private ProjectState checkProjectState()
throws NoSuchProjectException, IOException, PermissionBackendException {
- ProjectControl pc = projectControlFactory.controlFor(projectName, user.get());
+ ProjectState state = projectCache.checkedGet(projectName);
try {
permissionBackend.user(user).project(projectName).check(ProjectPermission.ACCESS);
} catch (AuthException e) {
throw new NoSuchProjectException(projectName);
}
- return pc;
+ return state;
}
private static boolean check(PermissionBackend.ForProject ctx, String ref, RefPermission perm)
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessHandler.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessHandler.java
index 3fa05ab..5bde72b 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessHandler.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessHandler.java
@@ -14,6 +14,7 @@
package com.google.gerrit.httpd.rpc.project;
+import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.gerrit.common.ProjectAccessUtil.mergeSections;
import com.google.common.base.MoreObjects;
@@ -30,15 +31,18 @@
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupBackends;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
+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.permissions.RefPermission;
import com.google.gerrit.server.project.ContributorAgreementsChecker;
import com.google.gerrit.server.project.NoSuchProjectException;
-import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.RefPattern;
import com.google.gerrit.server.project.SetParent;
import com.google.gwtorm.server.OrmException;
@@ -53,38 +57,43 @@
public abstract class ProjectAccessHandler<T> extends Handler<T> {
- private final ProjectControl.Factory projectControlFactory;
protected final GroupBackend groupBackend;
+ protected final Project.NameKey projectName;
+ protected final ObjectId base;
+ protected final CurrentUser user;
+
private final MetaDataUpdate.User metaDataUpdateFactory;
private final AllProjectsName allProjects;
private final Provider<SetParent> setParent;
private final ContributorAgreementsChecker contributorAgreements;
-
- protected final Project.NameKey projectName;
- protected final ObjectId base;
- private List<AccessSection> sectionList;
+ private final PermissionBackend permissionBackend;
private final Project.NameKey parentProjectName;
+
protected String message;
+
+ private List<AccessSection> sectionList;
private boolean checkIfOwner;
+ private Boolean canWriteConfig;
protected ProjectAccessHandler(
- ProjectControl.Factory projectControlFactory,
GroupBackend groupBackend,
MetaDataUpdate.User metaDataUpdateFactory,
AllProjectsName allProjects,
Provider<SetParent> setParent,
+ CurrentUser user,
Project.NameKey projectName,
ObjectId base,
List<AccessSection> sectionList,
Project.NameKey parentProjectName,
String message,
ContributorAgreementsChecker contributorAgreements,
+ PermissionBackend permissionBackend,
boolean checkIfOwner) {
- this.projectControlFactory = projectControlFactory;
this.groupBackend = groupBackend;
this.metaDataUpdateFactory = metaDataUpdateFactory;
this.allProjects = allProjects;
this.setParent = setParent;
+ this.user = user;
this.projectName = projectName;
this.base = base;
@@ -92,6 +101,7 @@
this.parentProjectName = parentProjectName;
this.message = message;
this.contributorAgreements = contributorAgreements;
+ this.permissionBackend = permissionBackend;
this.checkIfOwner = checkIfOwner;
}
@@ -100,10 +110,8 @@
throws NoSuchProjectException, IOException, ConfigInvalidException, InvalidNameException,
NoSuchGroupException, OrmException, UpdateParentFailedException,
PermissionDeniedException, PermissionBackendException {
- final ProjectControl projectControl = projectControlFactory.controlFor(projectName);
-
try {
- contributorAgreements.check(projectName, projectControl.getUser());
+ contributorAgreements.check(projectName, user);
} catch (AuthException e) {
throw new PermissionDeniedException(e.getMessage());
}
@@ -111,18 +119,19 @@
try (MetaDataUpdate md = metaDataUpdateFactory.create(projectName)) {
ProjectConfig config = ProjectConfig.read(md, base);
Set<String> toDelete = scanSectionNames(config);
+ PermissionBackend.ForProject forProject = permissionBackend.user(user).project(projectName);
for (AccessSection section : mergeSections(sectionList)) {
String name = section.getName();
if (AccessSection.GLOBAL_CAPABILITIES.equals(name)) {
- if (checkIfOwner && !projectControl.isOwner()) {
+ if (checkIfOwner && !canWriteConfig()) {
continue;
}
replace(config, toDelete, section);
} else if (AccessSection.isValid(name)) {
- if (checkIfOwner && !projectControl.controlForRef(name).isOwner()) {
+ if (checkIfOwner && !forProject.ref(name).test(RefPermission.WRITE_CONFIG)) {
continue;
}
@@ -134,11 +143,11 @@
for (String name : toDelete) {
if (AccessSection.GLOBAL_CAPABILITIES.equals(name)) {
- if (!checkIfOwner || projectControl.isOwner()) {
+ if (!checkIfOwner || canWriteConfig()) {
config.remove(config.getAccessSection(name));
}
- } else if (!checkIfOwner || projectControl.controlForRef(name).isOwner()) {
+ } else if (!checkIfOwner || forProject.ref(name).test(RefPermission.WRITE_CONFIG)) {
config.remove(config.getAccessSection(name));
}
}
@@ -151,8 +160,8 @@
setParent
.get()
.validateParentUpdate(
- projectControl.getProject().getNameKey(),
- projectControl.getUser().asIdentifiedUser(),
+ projectName,
+ user.asIdentifiedUser(),
MoreObjects.firstNonNull(parentProjectName, allProjects).get(),
checkIfOwner);
} catch (AuthException e) {
@@ -176,17 +185,14 @@
md.setMessage("Modify access rules\n");
}
- return updateProjectConfig(projectControl, config, md, parentProjectUpdate);
+ return updateProjectConfig(config, md, parentProjectUpdate);
} catch (RepositoryNotFoundException notFound) {
throw new NoSuchProjectException(projectName);
}
}
protected abstract T updateProjectConfig(
- ProjectControl projectControl,
- ProjectConfig config,
- MetaDataUpdate md,
- boolean parentProjectUpdate)
+ ProjectConfig config, MetaDataUpdate md, boolean parentProjectUpdate)
throws IOException, NoSuchProjectException, ConfigInvalidException, OrmException,
PermissionDeniedException, PermissionBackendException;
@@ -219,4 +225,20 @@
ref.setUUID(group.getUUID());
}
}
+
+ /** Provide a local cache for {@code ProjectPermission.WRITE_CONFIG} capability. */
+ private boolean canWriteConfig() throws PermissionBackendException {
+ checkNotNull(user);
+
+ if (canWriteConfig != null) {
+ return canWriteConfig;
+ }
+ try {
+ permissionBackend.user(user).project(projectName).check(ProjectPermission.WRITE_CONFIG);
+ canWriteConfig = true;
+ } catch (AuthException e) {
+ canWriteConfig = false;
+ }
+ return canWriteConfig;
+ }
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java
index f27b9d3..e7e0021 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ReviewProjectAccess.java
@@ -30,6 +30,7 @@
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.Sequences;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.change.ChangeInserter;
@@ -42,10 +43,10 @@
import com.google.gerrit.server.group.SystemGroupBackend;
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.permissions.RefPermission;
import com.google.gerrit.server.project.ContributorAgreementsChecker;
import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.SetParent;
import com.google.gerrit.server.update.BatchUpdate;
import com.google.gerrit.server.update.UpdateException;
@@ -82,7 +83,6 @@
@Inject
ReviewProjectAccess(
- final ProjectControl.Factory projectControlFactory,
PermissionBackend permissionBackend,
GroupBackend groupBackend,
MetaDataUpdate.User metaDataUpdateFactory,
@@ -96,23 +96,25 @@
Provider<SetParent> setParent,
Sequences seq,
ContributorAgreementsChecker contributorAgreements,
+ Provider<CurrentUser> user,
@Assisted("projectName") Project.NameKey projectName,
@Nullable @Assisted ObjectId base,
@Assisted List<AccessSection> sectionList,
@Nullable @Assisted("parentProjectName") Project.NameKey parentProjectName,
@Nullable @Assisted String message) {
super(
- projectControlFactory,
groupBackend,
metaDataUpdateFactory,
allProjects,
setParent,
+ user.get(),
projectName,
base,
sectionList,
parentProjectName,
message,
contributorAgreements,
+ permissionBackend,
false);
this.db = db;
this.permissionBackend = permissionBackend;
@@ -129,27 +131,16 @@
@SuppressWarnings("deprecation")
@Override
protected Change.Id updateProjectConfig(
- ProjectControl projectControl,
- ProjectConfig config,
- MetaDataUpdate md,
- boolean parentProjectUpdate)
+ ProjectConfig config, MetaDataUpdate md, boolean parentProjectUpdate)
throws IOException, OrmException, PermissionDeniedException, PermissionBackendException {
- PermissionBackend.ForRef metaRef =
- permissionBackend
- .user(projectControl.getUser())
- .project(projectControl.getProject().getNameKey())
- .ref(RefNames.REFS_CONFIG);
- try {
- metaRef.check(RefPermission.READ);
- } catch (AuthException denied) {
+ PermissionBackend.ForProject perm = permissionBackend.user(user).project(config.getName());
+ if (!check(perm, ProjectPermission.READ_CONFIG)) {
throw new PermissionDeniedException(RefNames.REFS_CONFIG + " not visible");
}
- if (!projectControl.isOwner()) {
- try {
- metaRef.check(RefPermission.CREATE_CHANGE);
- } catch (AuthException denied) {
- throw new PermissionDeniedException("cannot create change for " + RefNames.REFS_CONFIG);
- }
+
+ if (!check(perm, ProjectPermission.WRITE_CONFIG)
+ && !check(perm.ref(RefNames.REFS_CONFIG), RefPermission.CREATE_CHANGE)) {
+ throw new PermissionDeniedException("cannot create change for " + RefNames.REFS_CONFIG);
}
md.setInsertChangeId(true);
@@ -165,8 +156,7 @@
ObjectReader objReader = objInserter.newReader();
RevWalk rw = new RevWalk(objReader);
BatchUpdate bu =
- updateFactory.create(
- db, config.getProject().getNameKey(), projectControl.getUser(), TimeUtil.nowTs())) {
+ updateFactory.create(db, config.getProject().getNameKey(), user, TimeUtil.nowTs())) {
bu.setRepository(md.getRepository(), rw, objInserter);
bu.insertChange(
changeInserterFactory
@@ -223,4 +213,24 @@
}
}
}
+
+ private boolean check(PermissionBackend.ForRef perm, RefPermission p)
+ throws PermissionBackendException {
+ try {
+ perm.check(p);
+ return true;
+ } catch (AuthException denied) {
+ return false;
+ }
+ }
+
+ private boolean check(PermissionBackend.ForProject perm, ProjectPermission p)
+ throws PermissionBackendException {
+ try {
+ perm.check(p);
+ return true;
+ } catch (AuthException denied) {
+ return false;
+ }
+ }
}
diff --git a/gerrit-httpd/src/main/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy b/gerrit-httpd/src/main/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy
index 51c60af..b22a2a4 100644
--- a/gerrit-httpd/src/main/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy
+++ b/gerrit-httpd/src/main/resources/com/google/gerrit/httpd/raw/PolyGerritIndexHtml.soy
@@ -21,7 +21,7 @@
* @param staticResourcePath
* @param? versionInfo
*/
-{template .Index autoescape="strict" kind="html"}
+{template .Index}
<!DOCTYPE html>{\n}
<html lang="en">{\n}
<meta charset="utf-8">{\n}
diff --git a/gerrit-httpd/src/test/java/com/google/gerrit/httpd/raw/ResourceServletTest.java b/gerrit-httpd/src/test/java/com/google/gerrit/httpd/raw/ResourceServletTest.java
index 18256c6..6dd15bc 100644
--- a/gerrit-httpd/src/test/java/com/google/gerrit/httpd/raw/ResourceServletTest.java
+++ b/gerrit-httpd/src/test/java/com/google/gerrit/httpd/raw/ResourceServletTest.java
@@ -36,9 +36,11 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.FileTime;
+import java.time.LocalDateTime;
+import java.time.Month;
+import java.time.ZoneOffset;
import java.util.concurrent.atomic.AtomicLong;
import java.util.zip.GZIPInputStream;
-import org.joda.time.format.ISODateTimeFormat;
import org.junit.Before;
import org.junit.Test;
@@ -91,7 +93,12 @@
@Before
public void setUp() {
fs = Jimfs.newFileSystem(Configuration.unix());
- ts = new AtomicLong(ISODateTimeFormat.dateTime().parseMillis("2010-01-30T12:00:00.000-08:00"));
+ ts =
+ new AtomicLong(
+ LocalDateTime.of(2010, Month.JANUARY, 30, 12, 0, 0)
+ .atOffset(ZoneOffset.ofHours(-8))
+ .toInstant()
+ .toEpochMilli());
}
@Test
diff --git a/gerrit-index/src/main/java/com/google/gerrit/index/IndexConfig.java b/gerrit-index/src/main/java/com/google/gerrit/index/IndexConfig.java
index b53b59b..b5b36f1 100644
--- a/gerrit-index/src/main/java/com/google/gerrit/index/IndexConfig.java
+++ b/gerrit-index/src/main/java/com/google/gerrit/index/IndexConfig.java
@@ -61,15 +61,15 @@
public abstract static class Builder {
public abstract Builder maxLimit(int maxLimit);
- abstract int maxLimit();
+ public abstract int maxLimit();
public abstract Builder maxPages(int maxPages);
- abstract int maxPages();
+ public abstract int maxPages();
public abstract Builder maxTerms(int maxTerms);
- abstract int maxTerms();
+ public abstract int maxTerms();
public abstract Builder separateChangeSubIndexes(boolean separate);
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java
index d738540..d5d6360 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java
@@ -27,6 +27,7 @@
import com.google.gerrit.server.index.account.AccountIndex;
import com.google.gerrit.server.index.change.ChangeIndex;
import com.google.gerrit.server.index.group.GroupIndex;
+import com.google.gerrit.server.index.project.ProjectIndex;
import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import com.google.inject.Singleton;
@@ -85,7 +86,10 @@
new FactoryModuleBuilder()
.implement(GroupIndex.class, LuceneGroupIndex.class)
.build(GroupIndex.Factory.class));
-
+ install(
+ new FactoryModuleBuilder()
+ .implement(ProjectIndex.class, LuceneProjectIndex.class)
+ .build(ProjectIndex.Factory.class));
install(new IndexModule(threads));
if (singleVersions == null) {
install(new MultiVersionModule());
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneProjectIndex.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneProjectIndex.java
new file mode 100644
index 0000000..6354f61
--- /dev/null
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneProjectIndex.java
@@ -0,0 +1,200 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.lucene;
+
+import static com.google.gerrit.server.index.project.ProjectField.NAME;
+
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.query.DataSource;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.index.IndexUtils;
+import com.google.gerrit.server.index.project.ProjectIndex;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectData;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.ResultSet;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.assistedinject.Assisted;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.ScoreDoc;
+import org.apache.lucene.search.SearcherFactory;
+import org.apache.lucene.search.Sort;
+import org.apache.lucene.search.SortField;
+import org.apache.lucene.search.TopFieldDocs;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.FSDirectory;
+import org.apache.lucene.store.RAMDirectory;
+import org.eclipse.jgit.lib.Config;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class LuceneProjectIndex extends AbstractLuceneIndex<Project.NameKey, ProjectData>
+ implements ProjectIndex {
+ private static final Logger log = LoggerFactory.getLogger(LuceneProjectIndex.class);
+
+ private static final String PROJECTS = "projects";
+
+ private static final String NAME_SORT_FIELD = sortFieldName(NAME);
+
+ private static Term idTerm(ProjectData projectState) {
+ return idTerm(projectState.getProject().getNameKey());
+ }
+
+ private static Term idTerm(Project.NameKey nameKey) {
+ return QueryBuilder.stringTerm(NAME.getName(), nameKey.get());
+ }
+
+ private final GerritIndexWriterConfig indexWriterConfig;
+ private final QueryBuilder<ProjectData> queryBuilder;
+ private final Provider<ProjectCache> projectCache;
+
+ private static Directory dir(Schema<ProjectData> schema, Config cfg, SitePaths sitePaths)
+ throws IOException {
+ if (LuceneIndexModule.isInMemoryTest(cfg)) {
+ return new RAMDirectory();
+ }
+ Path indexDir = LuceneVersionManager.getDir(sitePaths, PROJECTS, schema);
+ return FSDirectory.open(indexDir);
+ }
+
+ @Inject
+ LuceneProjectIndex(
+ @GerritServerConfig Config cfg,
+ SitePaths sitePaths,
+ Provider<ProjectCache> projectCache,
+ @Assisted Schema<ProjectData> schema)
+ throws IOException {
+ super(
+ schema,
+ sitePaths,
+ dir(schema, cfg, sitePaths),
+ PROJECTS,
+ null,
+ new GerritIndexWriterConfig(cfg, PROJECTS),
+ new SearcherFactory());
+ this.projectCache = projectCache;
+
+ indexWriterConfig = new GerritIndexWriterConfig(cfg, PROJECTS);
+ queryBuilder = new QueryBuilder<>(schema, indexWriterConfig.getAnalyzer());
+ }
+
+ @Override
+ public void replace(ProjectData projectState) throws IOException {
+ try {
+ replace(idTerm(projectState), toDocument(projectState)).get();
+ } catch (ExecutionException | InterruptedException e) {
+ throw new IOException(e);
+ }
+ }
+
+ @Override
+ public void delete(Project.NameKey nameKey) throws IOException {
+ try {
+ delete(idTerm(nameKey)).get();
+ } catch (ExecutionException | InterruptedException e) {
+ throw new IOException(e);
+ }
+ }
+
+ @Override
+ public DataSource<ProjectData> getSource(Predicate<ProjectData> p, QueryOptions opts)
+ throws QueryParseException {
+ return new QuerySource(
+ opts,
+ queryBuilder.toQuery(p),
+ new Sort(new SortField(NAME_SORT_FIELD, SortField.Type.STRING, false)));
+ }
+
+ private class QuerySource implements DataSource<ProjectData> {
+ private final QueryOptions opts;
+ private final Query query;
+ private final Sort sort;
+
+ private QuerySource(QueryOptions opts, Query query, Sort sort) {
+ this.opts = opts;
+ this.query = query;
+ this.sort = sort;
+ }
+
+ @Override
+ public int getCardinality() {
+ return 10;
+ }
+
+ @Override
+ public ResultSet<ProjectData> read() throws OrmException {
+ IndexSearcher searcher = null;
+ try {
+ searcher = acquire();
+ int realLimit = opts.start() + opts.limit();
+ TopFieldDocs docs = searcher.search(query, realLimit, sort);
+ List<ProjectData> result = new ArrayList<>(docs.scoreDocs.length);
+ for (int i = opts.start(); i < docs.scoreDocs.length; i++) {
+ ScoreDoc sd = docs.scoreDocs[i];
+ Document doc = searcher.doc(sd.doc, IndexUtils.projectFields(opts));
+ result.add(toProjectData(doc));
+ }
+ final List<ProjectData> r = Collections.unmodifiableList(result);
+ return new ResultSet<ProjectData>() {
+ @Override
+ public Iterator<ProjectData> iterator() {
+ return r.iterator();
+ }
+
+ @Override
+ public List<ProjectData> toList() {
+ return r;
+ }
+
+ @Override
+ public void close() {
+ // Do nothing.
+ }
+ };
+ } catch (IOException e) {
+ throw new OrmException(e);
+ } finally {
+ if (searcher != null) {
+ try {
+ release(searcher);
+ } catch (IOException e) {
+ log.warn("cannot release Lucene searcher", e);
+ }
+ }
+ }
+ }
+ }
+
+ private ProjectData toProjectData(Document doc) {
+ Project.NameKey nameKey = new Project.NameKey(doc.getField(NAME.getName()).stringValue());
+ return projectCache.get().get(nameKey).toProjectData();
+ }
+}
diff --git a/gerrit-pgm/BUILD b/gerrit-pgm/BUILD
index 1fd3165..60663d7 100644
--- a/gerrit-pgm/BUILD
+++ b/gerrit-pgm/BUILD
@@ -22,7 +22,6 @@
"//lib/guice:guice-assistedinject",
"//lib/guice:guice-servlet",
"//lib/jgit/org.eclipse.jgit:jgit",
- "//lib/joda:joda-time",
"//lib/log:api",
"//lib/log:log4j",
]
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
index 6b5c157..4c44ef3 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
@@ -66,6 +66,7 @@
import com.google.gerrit.server.config.RestCacheAdminModule;
import com.google.gerrit.server.events.StreamEventsApiListener;
import com.google.gerrit.server.git.GarbageCollectionModule;
+import com.google.gerrit.server.git.LocalMergeSuperSetComputation;
import com.google.gerrit.server.git.SearchingChangeCacheImpl;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.git.receive.ReceiveCommitsExecutorModule;
@@ -84,6 +85,7 @@
import com.google.gerrit.server.plugins.PluginModule;
import com.google.gerrit.server.plugins.PluginRestApiModule;
import com.google.gerrit.server.project.DefaultPermissionBackendModule;
+import com.google.gerrit.server.project.DefaultProjectNameLockManager;
import com.google.gerrit.server.schema.DataSourceProvider;
import com.google.gerrit.server.schema.InMemoryAccountPatchReviewStore;
import com.google.gerrit.server.schema.JdbcAccountPatchReviewStore;
@@ -472,6 +474,8 @@
if (testSysModule != null) {
modules.add(testSysModule);
}
+ modules.add(new LocalMergeSuperSetComputation.Module());
+ modules.add(new DefaultProjectNameLockManager.Module());
return cfgInjector.createChildInjector(modules);
}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAdminUser.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAdminUser.java
index 9a81c52..453f871 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAdminUser.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitAdminUser.java
@@ -90,12 +90,12 @@
this.dbFactory = dbFactory;
}
- @Inject(optional = true)
+ @Inject
void set(AccountIndexCollection accountIndexCollection) {
this.accountIndexCollection = accountIndexCollection;
}
- @Inject(optional = true)
+ @Inject
void set(GroupIndexCollection groupIndexCollection) {
this.groupIndexCollection = groupIndexCollection;
}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitLabels.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitLabels.java
index 60fd60f..c7309f8 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitLabels.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitLabels.java
@@ -14,6 +14,8 @@
package com.google.gerrit.pgm.init;
+import static com.google.gerrit.common.data.LabelFunction.MAX_WITH_BLOCK;
+
import com.google.gerrit.pgm.init.api.AllProjectsConfig;
import com.google.gerrit.pgm.init.api.ConsoleUI;
import com.google.gerrit.pgm.init.api.InitStep;
@@ -54,7 +56,7 @@
public void postRun() throws Exception {
Config cfg = allProjectsConfig.load().getConfig();
if (installVerified) {
- cfg.setString(KEY_LABEL, LABEL_VERIFIED, KEY_FUNCTION, "MaxWithBlock");
+ cfg.setString(KEY_LABEL, LABEL_VERIFIED, KEY_FUNCTION, MAX_WITH_BLOCK.getFunctionName());
cfg.setStringList(
KEY_LABEL,
LABEL_VERIFIED,
diff --git a/gerrit-plugin-api/BUILD b/gerrit-plugin-api/BUILD
index fe9ce19..4f846b5 100644
--- a/gerrit-plugin-api/BUILD
+++ b/gerrit-plugin-api/BUILD
@@ -29,7 +29,6 @@
"//lib/httpcomponents:httpcore",
"//lib/jgit/org.eclipse.jgit.http.server:jgit-servlet",
"//lib/jgit/org.eclipse.jgit:jgit",
- "//lib/joda:joda-time",
"//lib/log:api",
"//lib/log:log4j",
"//lib/mina:sshd",
@@ -49,7 +48,6 @@
"//lib:protobuf",
"//lib:servlet-api-3_1-without-neverlink",
"//lib:soy",
- "//lib:velocity",
]
java_binary(
diff --git a/gerrit-server/BUILD b/gerrit-server/BUILD
index e124e89..e0ce904 100644
--- a/gerrit-server/BUILD
+++ b/gerrit-server/BUILD
@@ -76,7 +76,6 @@
"//lib:servlet-api-3_1",
"//lib:soy",
"//lib:tukaani-xz",
- "//lib:velocity",
"//lib/auto:auto-value",
"//lib/bouncycastle:bcpkix-neverlink",
"//lib/bouncycastle:bcprov-neverlink",
@@ -92,7 +91,6 @@
"//lib/guice:guice-servlet",
"//lib/jgit/org.eclipse.jgit.archive:jgit-archive",
"//lib/jgit/org.eclipse.jgit:jgit",
- "//lib/joda:joda-time",
"//lib/jsoup",
"//lib/log:api",
"//lib/log:jsonevent-layout",
@@ -122,7 +120,6 @@
"//lib:blame-cache",
"//lib:guava",
"//lib:soy",
- "//lib:velocity",
"//lib/guice",
"//lib/jgit/org.eclipse.jgit:jgit",
],
@@ -181,7 +178,6 @@
"//lib/guice:guice-servlet",
"//lib/jgit/org.eclipse.jgit:jgit",
"//lib/jgit/org.eclipse.jgit.junit:junit",
- "//lib/joda:joda-time",
"//lib/log:api",
"//lib/log:impl_log4j",
"//lib/log:log4j",
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalCopier.java b/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalCopier.java
index 6971b48..c1f89e2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalCopier.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalCopier.java
@@ -31,7 +31,6 @@
import com.google.gerrit.server.git.LabelNormalizer;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
-import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.query.change.ChangeData;
@@ -213,7 +212,7 @@
}
}
return labelNormalizer.normalize(notes, user, byUser.values()).getNormalized();
- } catch (IOException | PermissionBackendException e) {
+ } catch (IOException e) {
throw new OrmException(e);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/CmdLineParserModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/CmdLineParserModule.java
index 63f7202..d7f6e30 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/CmdLineParserModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/CmdLineParserModule.java
@@ -25,10 +25,10 @@
import com.google.gerrit.server.args4j.ChangeIdHandler;
import com.google.gerrit.server.args4j.ObjectIdHandler;
import com.google.gerrit.server.args4j.PatchSetIdHandler;
-import com.google.gerrit.server.args4j.ProjectControlHandler;
+import com.google.gerrit.server.args4j.ProjectHandler;
import com.google.gerrit.server.args4j.SocketAddressHandler;
import com.google.gerrit.server.args4j.TimestampHandler;
-import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.util.cli.CmdLineParser;
import com.google.gerrit.util.cli.OptionHandlerUtil;
import com.google.gerrit.util.cli.OptionHandlers;
@@ -51,7 +51,7 @@
registerOptionHandler(Change.Id.class, ChangeIdHandler.class);
registerOptionHandler(ObjectId.class, ObjectIdHandler.class);
registerOptionHandler(PatchSet.Id.class, PatchSetIdHandler.class);
- registerOptionHandler(ProjectControl.class, ProjectControlHandler.class);
+ registerOptionHandler(ProjectState.class, ProjectHandler.class);
registerOptionHandler(SocketAddress.class, SocketAddressHandler.class);
registerOptionHandler(Timestamp.class, TimestampHandler.class);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ReviewersUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ReviewersUtil.java
index ee25d54..2971037 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ReviewersUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ReviewersUtil.java
@@ -111,8 +111,7 @@
private final AccountQueryBuilder accountQueryBuilder;
private final Provider<AccountQueryProcessor> queryProvider;
private final GroupBackend groupBackend;
- private final GroupMembers.Factory groupMembersFactory;
- private final Provider<CurrentUser> currentUser;
+ private final GroupMembers groupMembers;
private final ReviewerRecommender reviewerRecommender;
private final Metrics metrics;
@@ -122,8 +121,7 @@
AccountQueryBuilder accountQueryBuilder,
Provider<AccountQueryProcessor> queryProvider,
GroupBackend groupBackend,
- GroupMembers.Factory groupMembersFactory,
- Provider<CurrentUser> currentUser,
+ GroupMembers groupMembers,
ReviewerRecommender reviewerRecommender,
Metrics metrics) {
Set<FillOptions> fillOptions = EnumSet.of(FillOptions.SECONDARY_EMAILS);
@@ -131,9 +129,8 @@
this.accountLoader = accountLoaderFactory.create(fillOptions);
this.accountQueryBuilder = accountQueryBuilder;
this.queryProvider = queryProvider;
- this.currentUser = currentUser;
this.groupBackend = groupBackend;
- this.groupMembersFactory = groupMembersFactory;
+ this.groupMembers = groupMembers;
this.reviewerRecommender = reviewerRecommender;
this.metrics = metrics;
}
@@ -303,10 +300,7 @@
}
try {
- Set<Account> members =
- groupMembersFactory
- .create(currentUser.get())
- .listAccounts(group.getUUID(), project.getNameKey());
+ Set<Account> members = groupMembers.listAccounts(group.getUUID(), project.getNameKey());
if (members.isEmpty()) {
return result;
@@ -330,9 +324,7 @@
return result;
}
}
- } catch (NoSuchGroupException e) {
- return result;
- } catch (NoSuchProjectException e) {
+ } catch (NoSuchGroupException | NoSuchProjectException e) {
return result;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/StarredChangesUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/StarredChangesUtil.java
index 12bd8ff..7971d30 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/StarredChangesUtil.java
@@ -420,7 +420,7 @@
public static Set<Integer> getStarredPatchSets(Set<String> labels, String label) {
return labels
.stream()
- .filter(l -> l.startsWith(label))
+ .filter(l -> l.startsWith(label + "/"))
.filter(l -> Ints.tryParse(l.substring(label.length() + 1)) != null)
.map(l -> Integer.valueOf(l.substring(label.length() + 1)))
.collect(toSet());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AddSshKey.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AddSshKey.java
index 1c5495f..5680b56 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AddSshKey.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AddSshKey.java
@@ -19,6 +19,7 @@
import com.google.common.io.ByteSource;
import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.common.errors.InvalidSshKeyException;
+import com.google.gerrit.extensions.api.accounts.SshKeyInput;
import com.google.gerrit.extensions.common.SshKeyInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
@@ -28,7 +29,6 @@
import com.google.gerrit.reviewdb.client.AccountSshKey;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.account.AddSshKey.Input;
import com.google.gerrit.server.mail.send.AddKeySender;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
@@ -45,13 +45,9 @@
import org.slf4j.LoggerFactory;
@Singleton
-public class AddSshKey implements RestModifyView<AccountResource, Input> {
+public class AddSshKey implements RestModifyView<AccountResource, SshKeyInput> {
private static final Logger log = LoggerFactory.getLogger(AddSshKey.class);
- public static class Input {
- public RawInput raw;
- }
-
private final Provider<CurrentUser> self;
private final PermissionBackend permissionBackend;
private final VersionedAuthorizedKeys.Accessor authorizedKeys;
@@ -73,7 +69,7 @@
}
@Override
- public Response<SshKeyInfo> apply(AccountResource rsrc, Input input)
+ public Response<SshKeyInfo> apply(AccountResource rsrc, SshKeyInput input)
throws AuthException, BadRequestException, OrmException, IOException, ConfigInvalidException,
PermissionBackendException {
if (self.get() != rsrc.getUser()) {
@@ -82,10 +78,10 @@
return apply(rsrc.getUser(), input);
}
- public Response<SshKeyInfo> apply(IdentifiedUser user, Input input)
+ public Response<SshKeyInfo> apply(IdentifiedUser user, SshKeyInput input)
throws BadRequestException, IOException, ConfigInvalidException {
if (input == null) {
- input = new Input();
+ input = new SshKeyInput();
}
if (input.raw == null) {
throw new BadRequestException("SSH public key missing");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteActive.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteActive.java
index 43669c0..4b3bf39 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteActive.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteActive.java
@@ -16,12 +16,12 @@
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
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.IdentifiedUser;
-import com.google.gerrit.server.account.DeleteActive.Input;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -32,7 +32,6 @@
@RequiresCapability(GlobalCapability.MODIFY_ACCOUNT)
@Singleton
public class DeleteActive implements RestModifyView<AccountResource, Input> {
- public static class Input {}
private final Provider<IdentifiedUser> self;
private final SetInactiveFlag setInactiveFlag;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteEmail.java
index aec3a14..cccac63 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteEmail.java
@@ -17,6 +17,7 @@
import static java.util.stream.Collectors.toSet;
import com.google.gerrit.extensions.client.AccountFieldName;
+import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -25,7 +26,6 @@
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.account.DeleteEmail.Input;
import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.server.account.externalids.ExternalIds;
import com.google.gerrit.server.permissions.GlobalPermission;
@@ -41,7 +41,6 @@
@Singleton
public class DeleteEmail implements RestModifyView<AccountResource.Email, Input> {
- public static class Input {}
private final Provider<CurrentUser> self;
private final Realm realm;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteSshKey.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteSshKey.java
index f1ecd29..8dec7d9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteSshKey.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteSshKey.java
@@ -14,11 +14,11 @@
package com.google.gerrit.server.account;
+import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.restapi.AuthException;
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.account.DeleteSshKey.Input;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -33,7 +33,6 @@
@Singleton
public class DeleteSshKey implements RestModifyView<AccountResource.SshKey, Input> {
- public static class Input {}
private final Provider<CurrentUser> self;
private final PermissionBackend permissionBackend;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupMembers.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupMembers.java
index 4dc960d..f1f688c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupMembers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupMembers.java
@@ -21,15 +21,14 @@
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.group.InternalGroup;
import com.google.gerrit.server.group.InternalGroupDescription;
import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.gerrit.server.project.NoSuchProjectException;
-import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectState;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
@@ -37,28 +36,22 @@
import java.util.Set;
public class GroupMembers {
- public interface Factory {
- GroupMembers create(CurrentUser currentUser);
- }
private final GroupCache groupCache;
private final GroupControl.Factory groupControlFactory;
private final AccountCache accountCache;
- private final ProjectControl.GenericFactory projectControl;
- private final CurrentUser currentUser;
+ private final ProjectCache projectCache;
@Inject
GroupMembers(
GroupCache groupCache,
GroupControl.Factory groupControlFactory,
AccountCache accountCache,
- ProjectControl.GenericFactory projectControl,
- @Assisted CurrentUser currentUser) {
+ ProjectCache projectCache) {
this.groupCache = groupCache;
this.groupControlFactory = groupControlFactory;
this.accountCache = accountCache;
- this.projectControl = projectControl;
- this.currentUser = currentUser;
+ this.projectCache = projectCache;
}
public Set<Account> listAccounts(AccountGroup.UUID groupUUID, Project.NameKey project)
@@ -88,11 +81,13 @@
return Collections.emptySet();
}
- final Iterable<AccountGroup.UUID> ownerGroups =
- projectControl.controlFor(project, currentUser).getProjectState().getAllOwners();
+ ProjectState projectState = projectCache.checkedGet(project);
+ if (projectState == null) {
+ throw new NoSuchProjectException(project);
+ }
final HashSet<Account> projectOwners = new HashSet<>();
- for (AccountGroup.UUID ownerGroup : ownerGroups) {
+ for (AccountGroup.UUID ownerGroup : projectState.getAllOwners()) {
if (!seen.contains(ownerGroup)) {
projectOwners.addAll(listAccounts(ownerGroup, project, seen));
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/Index.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/Index.java
index ecc6b8c..8436d1d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/Index.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/Index.java
@@ -14,11 +14,11 @@
package com.google.gerrit.server.account;
+import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.restapi.AuthException;
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.account.Index.Input;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -29,7 +29,6 @@
@Singleton
public class Index implements RestModifyView<AccountResource, Input> {
- public static class Input {}
private final AccountCache accountCache;
private final PermissionBackend permissionBackend;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutActive.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutActive.java
index 7ce2ea8..cbfa172 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutActive.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutActive.java
@@ -16,10 +16,10 @@
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.extensions.common.Input;
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.account.PutActive.Input;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -29,7 +29,6 @@
@RequiresCapability(GlobalCapability.MODIFY_ACCOUNT)
@Singleton
public class PutActive implements RestModifyView<AccountResource, Input> {
- public static class Input {}
private final SetInactiveFlag setInactiveFlag;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutHttpPassword.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutHttpPassword.java
index e00f6b3..5005212 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutHttpPassword.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutHttpPassword.java
@@ -17,6 +17,7 @@
import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_USERNAME;
import com.google.common.base.Strings;
+import com.google.gerrit.extensions.common.HttpPasswordInput;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
@@ -24,7 +25,6 @@
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.account.PutHttpPassword.Input;
import com.google.gerrit.server.account.externalids.ExternalId;
import com.google.gerrit.server.account.externalids.ExternalIds;
import com.google.gerrit.server.account.externalids.ExternalIdsUpdate;
@@ -40,12 +40,7 @@
import org.apache.commons.codec.binary.Base64;
import org.eclipse.jgit.errors.ConfigInvalidException;
-public class PutHttpPassword implements RestModifyView<AccountResource, Input> {
- public static class Input {
- public String httpPassword;
- public boolean generate;
- }
-
+public class PutHttpPassword implements RestModifyView<AccountResource, HttpPasswordInput> {
private static final int LEN = 31;
private static final SecureRandom rng;
@@ -75,7 +70,7 @@
}
@Override
- public Response<String> apply(AccountResource rsrc, Input input)
+ public Response<String> apply(AccountResource rsrc, HttpPasswordInput input)
throws AuthException, ResourceNotFoundException, ResourceConflictException, OrmException,
IOException, ConfigInvalidException, PermissionBackendException {
if (self.get() != rsrc.getUser()) {
@@ -83,7 +78,7 @@
}
if (input == null) {
- input = new Input();
+ input = new HttpPasswordInput();
}
input.httpPassword = Strings.emptyToNull(input.httpPassword);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutName.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutName.java
index 7537230..0ac9d1d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutName.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutName.java
@@ -16,8 +16,8 @@
import com.google.common.base.Strings;
import com.google.gerrit.extensions.client.AccountFieldName;
+import com.google.gerrit.extensions.common.NameInput;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.extensions.restapi.DefaultInput;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
@@ -25,7 +25,6 @@
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.account.PutName.Input;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -37,11 +36,7 @@
import org.eclipse.jgit.errors.ConfigInvalidException;
@Singleton
-public class PutName implements RestModifyView<AccountResource, Input> {
- public static class Input {
- @DefaultInput public String name;
- }
-
+public class PutName implements RestModifyView<AccountResource, NameInput> {
private final Provider<CurrentUser> self;
private final Realm realm;
private final PermissionBackend permissionBackend;
@@ -60,7 +55,7 @@
}
@Override
- public Response<String> apply(AccountResource rsrc, Input input)
+ public Response<String> apply(AccountResource rsrc, NameInput input)
throws AuthException, MethodNotAllowedException, ResourceNotFoundException, OrmException,
IOException, PermissionBackendException, ConfigInvalidException {
if (self.get() != rsrc.getUser()) {
@@ -69,11 +64,11 @@
return apply(rsrc.getUser(), input);
}
- public Response<String> apply(IdentifiedUser user, Input input)
+ public Response<String> apply(IdentifiedUser user, NameInput input)
throws MethodNotAllowedException, ResourceNotFoundException, IOException,
ConfigInvalidException {
if (input == null) {
- input = new Input();
+ input = new NameInput();
}
if (!realm.allowsEdit(AccountFieldName.FULL_NAME)) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutPreferred.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutPreferred.java
index b3f8fc5..5f9ddee 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutPreferred.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutPreferred.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.account;
+import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
@@ -21,7 +22,6 @@
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.account.PutPreferred.Input;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -35,7 +35,6 @@
@Singleton
public class PutPreferred implements RestModifyView<AccountResource.Email, Input> {
- static class Input {}
private final Provider<CurrentUser> self;
private final PermissionBackend permissionBackend;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutStatus.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutStatus.java
index 1df67c3..35ece15 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutStatus.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutStatus.java
@@ -15,15 +15,14 @@
package com.google.gerrit.server.account;
import com.google.common.base.Strings;
+import com.google.gerrit.extensions.api.accounts.StatusInput;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.extensions.restapi.DefaultInput;
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.reviewdb.client.Account;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.account.PutStatus.Input;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -35,17 +34,7 @@
import org.eclipse.jgit.errors.ConfigInvalidException;
@Singleton
-public class PutStatus implements RestModifyView<AccountResource, Input> {
- public static class Input {
- @DefaultInput String status;
-
- public Input(String status) {
- this.status = status;
- }
-
- public Input() {}
- }
-
+public class PutStatus implements RestModifyView<AccountResource, StatusInput> {
private final Provider<CurrentUser> self;
private final PermissionBackend permissionBackend;
private final AccountsUpdate.Server accountsUpdate;
@@ -61,7 +50,7 @@
}
@Override
- public Response<String> apply(AccountResource rsrc, Input input)
+ public Response<String> apply(AccountResource rsrc, StatusInput input)
throws AuthException, ResourceNotFoundException, OrmException, IOException,
PermissionBackendException, ConfigInvalidException {
if (self.get() != rsrc.getUser()) {
@@ -70,10 +59,10 @@
return apply(rsrc.getUser(), input);
}
- public Response<String> apply(IdentifiedUser user, Input input)
+ public Response<String> apply(IdentifiedUser user, StatusInput input)
throws ResourceNotFoundException, IOException, ConfigInvalidException {
if (input == null) {
- input = new Input();
+ input = new StatusInput();
}
String newStatus = input.status;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutUsername.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutUsername.java
index a73bdd9..2368913 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutUsername.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutUsername.java
@@ -15,15 +15,14 @@
package com.google.gerrit.server.account;
import com.google.gerrit.common.errors.NameAlreadyUsedException;
+import com.google.gerrit.extensions.api.accounts.UsernameInput;
import com.google.gerrit.extensions.client.AccountFieldName;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.extensions.restapi.DefaultInput;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.account.PutUsername.Input;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -35,11 +34,7 @@
import org.eclipse.jgit.errors.ConfigInvalidException;
@Singleton
-public class PutUsername implements RestModifyView<AccountResource, Input> {
- public static class Input {
- @DefaultInput public String username;
- }
-
+public class PutUsername implements RestModifyView<AccountResource, UsernameInput> {
private final Provider<CurrentUser> self;
private final ChangeUserName.Factory changeUserNameFactory;
private final PermissionBackend permissionBackend;
@@ -58,7 +53,7 @@
}
@Override
- public String apply(AccountResource rsrc, Input input)
+ public String apply(AccountResource rsrc, UsernameInput input)
throws AuthException, MethodNotAllowedException, UnprocessableEntityException,
ResourceConflictException, OrmException, IOException, ConfigInvalidException,
PermissionBackendException {
@@ -71,7 +66,7 @@
}
if (input == null) {
- input = new Input();
+ input = new UsernameInput();
}
try {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java
index f8539d9..f36322c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java
@@ -21,6 +21,8 @@
import com.google.gerrit.extensions.api.accounts.AccountApi;
import com.google.gerrit.extensions.api.accounts.EmailInput;
import com.google.gerrit.extensions.api.accounts.GpgKeyApi;
+import com.google.gerrit.extensions.api.accounts.SshKeyInput;
+import com.google.gerrit.extensions.api.accounts.StatusInput;
import com.google.gerrit.extensions.api.changes.StarsInput;
import com.google.gerrit.extensions.client.DiffPreferencesInfo;
import com.google.gerrit.extensions.client.EditPreferencesInfo;
@@ -34,6 +36,7 @@
import com.google.gerrit.extensions.common.EmailInfo;
import com.google.gerrit.extensions.common.GpgKeyInfo;
import com.google.gerrit.extensions.common.GroupInfo;
+import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.common.SshKeyInfo;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.Response;
@@ -219,9 +222,9 @@
public void setActive(boolean active) throws RestApiException {
try {
if (active) {
- putActive.apply(account, new PutActive.Input());
+ putActive.apply(account, new Input());
} else {
- deleteActive.apply(account, new DeleteActive.Input());
+ deleteActive.apply(account, new Input());
}
} catch (Exception e) {
throw asRestApiException("Cannot set active", e);
@@ -404,7 +407,7 @@
@Override
public void setStatus(String status) throws RestApiException {
- PutStatus.Input in = new PutStatus.Input(status);
+ StatusInput in = new StatusInput(status);
try {
putStatus.apply(account, in);
} catch (Exception e) {
@@ -423,7 +426,7 @@
@Override
public SshKeyInfo addSshKey(String key) throws RestApiException {
- AddSshKey.Input in = new AddSshKey.Input();
+ SshKeyInput in = new SshKeyInput();
in.raw = RawInputUtil.create(key);
try {
return addSshKey.apply(account, in).value();
@@ -490,7 +493,7 @@
@Override
public void index() throws RestApiException {
try {
- index.apply(account, new Index.Input());
+ index.apply(account, new Input());
} catch (Exception e) {
throw asRestApiException("Cannot index account", e);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
index 0fba74a..701384b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
@@ -35,12 +35,14 @@
import com.google.gerrit.extensions.api.changes.RevisionApi;
import com.google.gerrit.extensions.api.changes.SubmittedTogetherInfo;
import com.google.gerrit.extensions.api.changes.SubmittedTogetherOption;
+import com.google.gerrit.extensions.api.changes.TopicInput;
import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.common.AccountInfo;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.CommentInfo;
import com.google.gerrit.extensions.common.CommitMessageInput;
import com.google.gerrit.extensions.common.EditInfo;
+import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.common.MergePatchSetInput;
import com.google.gerrit.extensions.common.PureRevertInfo;
import com.google.gerrit.extensions.common.RobotCommentInfo;
@@ -435,7 +437,7 @@
@Override
public void topic(String topic) throws RestApiException {
- PutTopic.Input in = new PutTopic.Input();
+ TopicInput in = new TopicInput();
in.topic = topic;
try {
putTopic.apply(change, in);
@@ -646,7 +648,7 @@
@Override
public void index() throws RestApiException {
try {
- index.apply(change, new Index.Input());
+ index.apply(change, new Input());
} catch (Exception e) {
throw asRestApiException("Cannot index change", e);
}
@@ -658,9 +660,9 @@
// StarredChangesUtil.
try {
if (ignore) {
- this.ignore.apply(change, new Ignore.Input());
+ this.ignore.apply(change, new Input());
} else {
- unignore.apply(change, new Unignore.Input());
+ unignore.apply(change, new Input());
}
} catch (OrmException | IllegalLabelException e) {
throw asRestApiException("Cannot ignore change", e);
@@ -682,9 +684,9 @@
// StarredChangesUtil.
try {
if (reviewed) {
- markAsReviewed.apply(change, new MarkAsReviewed.Input());
+ markAsReviewed.apply(change, new Input());
} else {
- markAsUnreviewed.apply(change, new MarkAsUnreviewed.Input());
+ markAsUnreviewed.apply(change, new Input());
}
} catch (OrmException | IllegalLabelException e) {
throw asRestApiException(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeEditApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeEditApiImpl.java
index d1b57e6..823e771 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeEditApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeEditApiImpl.java
@@ -19,6 +19,7 @@
import com.google.gerrit.extensions.api.changes.ChangeEditApi;
import com.google.gerrit.extensions.api.changes.PublishChangeEditInput;
import com.google.gerrit.extensions.common.EditInfo;
+import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.IdString;
@@ -106,7 +107,7 @@
@Override
public void delete() throws RestApiException {
try {
- deleteChangeEdit.apply(changeResource, new DeleteChangeEdit.Input());
+ deleteChangeEdit.apply(changeResource, new Input());
} catch (Exception e) {
throw asRestApiException("Cannot delete change edit", e);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
index 65bbc47..7ecfce7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
@@ -36,8 +36,10 @@
import com.google.gerrit.extensions.common.ActionInfo;
import com.google.gerrit.extensions.common.CommentInfo;
import com.google.gerrit.extensions.common.CommitInfo;
+import com.google.gerrit.extensions.common.DescriptionInput;
import com.google.gerrit.extensions.common.EditInfo;
import com.google.gerrit.extensions.common.FileInfo;
+import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.common.MergeableInfo;
import com.google.gerrit.extensions.common.RobotCommentInfo;
import com.google.gerrit.extensions.common.TestSubmitRuleInput;
@@ -300,13 +302,13 @@
@Override
public void setReviewed(String path, boolean reviewed) throws RestApiException {
try {
- RestModifyView<FileResource, Reviewed.Input> view;
+ RestModifyView<FileResource, Input> view;
if (reviewed) {
view = putReviewed;
} else {
view = deleteReviewed;
}
- view.apply(files.parse(revision, IdString.fromDecoded(path)), new Reviewed.Input());
+ view.apply(files.parse(revision, IdString.fromDecoded(path)), new Input());
} catch (Exception e) {
throw asRestApiException("Cannot update reviewed flag", e);
}
@@ -565,7 +567,7 @@
@Override
public void description(String description) throws RestApiException {
- PutDescription.Input in = new PutDescription.Input();
+ DescriptionInput in = new DescriptionInput();
in.description = description;
try {
putDescription.apply(revision, in);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/groups/GroupApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/groups/GroupApiImpl.java
index 42213f7..28cc60f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/groups/GroupApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/groups/GroupApiImpl.java
@@ -17,10 +17,14 @@
import static com.google.gerrit.server.api.ApiUtil.asRestApiException;
import com.google.gerrit.extensions.api.groups.GroupApi;
+import com.google.gerrit.extensions.api.groups.OwnerInput;
import com.google.gerrit.extensions.common.AccountInfo;
+import com.google.gerrit.extensions.common.DescriptionInput;
import com.google.gerrit.extensions.common.GroupAuditEventInfo;
import com.google.gerrit.extensions.common.GroupInfo;
import com.google.gerrit.extensions.common.GroupOptionsInfo;
+import com.google.gerrit.extensions.common.Input;
+import com.google.gerrit.extensions.common.NameInput;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.server.group.AddMembers;
import com.google.gerrit.server.group.AddSubgroups;
@@ -138,7 +142,7 @@
@Override
public void name(String name) throws RestApiException {
- PutName.Input in = new PutName.Input();
+ NameInput in = new NameInput();
in.name = name;
try {
putName.apply(rsrc, in);
@@ -158,7 +162,7 @@
@Override
public void owner(String owner) throws RestApiException {
- PutOwner.Input in = new PutOwner.Input();
+ OwnerInput in = new OwnerInput();
in.owner = owner;
try {
putOwner.apply(rsrc, in);
@@ -174,7 +178,7 @@
@Override
public void description(String description) throws RestApiException {
- PutDescription.Input in = new PutDescription.Input();
+ DescriptionInput in = new DescriptionInput();
in.description = description;
try {
putDescription.apply(rsrc, in);
@@ -269,7 +273,7 @@
@Override
public void index() throws RestApiException {
try {
- index.apply(rsrc, new Index.Input());
+ index.apply(rsrc, new Input());
} catch (Exception e) {
throw asRestApiException("Cannot index group", e);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/groups/GroupsImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/groups/GroupsImpl.java
index e1e72ba..f439f7d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/groups/GroupsImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/groups/GroupsImpl.java
@@ -34,6 +34,7 @@
import com.google.gerrit.server.group.QueryGroups;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.project.ProjectResource;
import com.google.gerrit.server.project.ProjectsCollection;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -119,7 +120,8 @@
for (String project : req.getProjects()) {
try {
- list.addProject(projects.parse(tlr, IdString.fromDecoded(project)).getControl());
+ ProjectResource rsrc = projects.parse(tlr, IdString.fromDecoded(project));
+ list.addProject(rsrc.getProjectState());
} catch (Exception e) {
throw asRestApiException("Error looking up project " + project, e);
}
@@ -131,6 +133,10 @@
list.setVisibleToAll(req.getVisibleToAll());
+ if (req.getOwnedBy() != null) {
+ list.setOwnedBy(req.getOwnedBy());
+ }
+
if (req.getUser() != null) {
try {
list.setUser(accounts.parse(req.getUser()).getAccountId());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/plugins/PluginApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/plugins/PluginApiImpl.java
index 2fc2e50..71f7832 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/plugins/PluginApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/plugins/PluginApiImpl.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.api.plugins;
import com.google.gerrit.extensions.api.plugins.PluginApi;
+import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.common.PluginInfo;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.server.plugins.DisablePlugin;
@@ -57,16 +58,16 @@
@Override
public void enable() throws RestApiException {
- enable.apply(resource, new EnablePlugin.Input());
+ enable.apply(resource, new Input());
}
@Override
public void disable() throws RestApiException {
- disable.apply(resource, new DisablePlugin.Input());
+ disable.apply(resource, new Input());
}
@Override
public void reload() throws RestApiException {
- reload.apply(resource, new ReloadPlugin.Input());
+ reload.apply(resource, new Input());
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/BranchApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/BranchApiImpl.java
index 642791a..aee9b3f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/BranchApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/BranchApiImpl.java
@@ -20,6 +20,7 @@
import com.google.gerrit.extensions.api.projects.BranchInfo;
import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.api.projects.ReflogEntryInfo;
+import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.RestApiException;
@@ -98,7 +99,7 @@
@Override
public void delete() throws RestApiException {
try {
- deleteBranch.apply(resource(), new DeleteBranch.Input());
+ deleteBranch.apply(resource(), new Input());
} catch (Exception e) {
throw asRestApiException("Cannot delete branch", e);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/DashboardApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/DashboardApiImpl.java
index 0d4afd6..12c4244 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/DashboardApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/DashboardApiImpl.java
@@ -79,7 +79,8 @@
SetDashboardInput input = new SetDashboardInput();
input.id = id;
try {
- set.apply(DashboardResource.projectDefault(project.getControl()), input);
+ set.apply(
+ DashboardResource.projectDefault(project.getProjectState(), project.getUser()), input);
} catch (Exception e) {
String msg = String.format("Cannot %s default dashboard", id != null ? "set" : "remove");
throw asRestApiException(msg, e);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
index 9fd4d48..fae5bba 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
@@ -33,6 +33,8 @@
import com.google.gerrit.extensions.api.projects.DeleteBranchesInput;
import com.google.gerrit.extensions.api.projects.DeleteTagsInput;
import com.google.gerrit.extensions.api.projects.DescriptionInput;
+import com.google.gerrit.extensions.api.projects.HeadInput;
+import com.google.gerrit.extensions.api.projects.ParentInput;
import com.google.gerrit.extensions.api.projects.ProjectApi;
import com.google.gerrit.extensions.api.projects.ProjectInput;
import com.google.gerrit.extensions.api.projects.TagApi;
@@ -59,6 +61,8 @@
import com.google.gerrit.server.project.GetAccess;
import com.google.gerrit.server.project.GetConfig;
import com.google.gerrit.server.project.GetDescription;
+import com.google.gerrit.server.project.GetHead;
+import com.google.gerrit.server.project.GetParent;
import com.google.gerrit.server.project.ListBranches;
import com.google.gerrit.server.project.ListChildProjects;
import com.google.gerrit.server.project.ListDashboards;
@@ -69,6 +73,8 @@
import com.google.gerrit.server.project.PutConfig;
import com.google.gerrit.server.project.PutDescription;
import com.google.gerrit.server.project.SetAccess;
+import com.google.gerrit.server.project.SetHead;
+import com.google.gerrit.server.project.SetParent;
import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
import com.google.inject.assistedinject.AssistedInject;
@@ -110,6 +116,10 @@
private final DashboardApiImpl.Factory dashboardApi;
private final CheckAccess checkAccess;
private final Provider<ListDashboards> listDashboards;
+ private final GetHead getHead;
+ private final SetHead setHead;
+ private final GetParent getParent;
+ private final SetParent setParent;
@AssistedInject
ProjectApiImpl(
@@ -139,6 +149,10 @@
DashboardApiImpl.Factory dashboardApi,
CheckAccess checkAccess,
Provider<ListDashboards> listDashboards,
+ GetHead getHead,
+ SetHead setHead,
+ GetParent getParent,
+ SetParent setParent,
@Assisted ProjectResource project) {
this(
user,
@@ -168,6 +182,10 @@
dashboardApi,
checkAccess,
listDashboards,
+ getHead,
+ setHead,
+ getParent,
+ setParent,
null);
}
@@ -199,6 +217,10 @@
DashboardApiImpl.Factory dashboardApi,
CheckAccess checkAccess,
Provider<ListDashboards> listDashboards,
+ GetHead getHead,
+ SetHead setHead,
+ GetParent getParent,
+ SetParent setParent,
@Assisted String name) {
this(
user,
@@ -228,6 +250,10 @@
dashboardApi,
checkAccess,
listDashboards,
+ getHead,
+ setHead,
+ getParent,
+ setParent,
name);
}
@@ -259,6 +285,10 @@
DashboardApiImpl.Factory dashboardApi,
CheckAccess checkAccess,
Provider<ListDashboards> listDashboards,
+ GetHead getHead,
+ SetHead setHead,
+ GetParent getParent,
+ SetParent setParent,
String name) {
this.user = user;
this.permissionBackend = permissionBackend;
@@ -287,6 +317,10 @@
this.dashboardApi = dashboardApi;
this.checkAccess = checkAccess;
this.listDashboards = listDashboards;
+ this.getHead = getHead;
+ this.setHead = setHead;
+ this.getParent = getParent;
+ this.setParent = setParent;
this.name = name;
}
@@ -378,7 +412,11 @@
@Override
public ConfigInfo config(ConfigInput in) throws RestApiException {
- return putConfig.apply(checkExists(), in);
+ try {
+ return putConfig.apply(checkExists(), in);
+ } catch (Exception e) {
+ throw asRestApiException("Cannot list tags", e);
+ }
}
@Override
@@ -524,6 +562,46 @@
};
}
+ @Override
+ public String head() throws RestApiException {
+ try {
+ return getHead.apply(checkExists());
+ } catch (Exception e) {
+ throw asRestApiException("Cannot get HEAD", e);
+ }
+ }
+
+ @Override
+ public void head(String head) throws RestApiException {
+ HeadInput input = new HeadInput();
+ input.ref = head;
+ try {
+ setHead.apply(checkExists(), input);
+ } catch (Exception e) {
+ throw asRestApiException("Cannot set HEAD", e);
+ }
+ }
+
+ @Override
+ public String parent() throws RestApiException {
+ try {
+ return getParent.apply(checkExists());
+ } catch (Exception e) {
+ throw asRestApiException("Cannot get parent", e);
+ }
+ }
+
+ @Override
+ public void parent(String parent) throws RestApiException {
+ try {
+ ParentInput input = new ParentInput();
+ input.parent = parent;
+ setParent.apply(checkExists(), input);
+ } catch (Exception e) {
+ throw asRestApiException("Cannot set parent", e);
+ }
+ }
+
private ProjectResource checkExists() throws ResourceNotFoundException {
if (project == null) {
throw new ResourceNotFoundException(name);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectsImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectsImpl.java
index 702a7e9..9490075 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectsImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectsImpl.java
@@ -22,14 +22,18 @@
import com.google.gerrit.extensions.common.ProjectInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.ListProjects;
import com.google.gerrit.server.project.ListProjects.FilterType;
import com.google.gerrit.server.project.ProjectsCollection;
+import com.google.gerrit.server.project.QueryProjects;
+import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
+import java.util.List;
import java.util.SortedMap;
@Singleton
@@ -37,15 +41,18 @@
private final ProjectsCollection projects;
private final ProjectApiImpl.Factory api;
private final Provider<ListProjects> listProvider;
+ private final Provider<QueryProjects> queryProvider;
@Inject
ProjectsImpl(
ProjectsCollection projects,
ProjectApiImpl.Factory api,
- Provider<ListProjects> listProvider) {
+ Provider<ListProjects> listProvider,
+ Provider<QueryProjects> queryProvider) {
this.projects = projects;
this.api = api;
this.listProvider = listProvider;
+ this.queryProvider = queryProvider;
}
@Override
@@ -124,4 +131,32 @@
return lp.apply();
}
+
+ @Override
+ public QueryRequest query() {
+ return new QueryRequest() {
+ @Override
+ public List<ProjectInfo> get() throws RestApiException {
+ return ProjectsImpl.this.query(this);
+ }
+ };
+ }
+
+ @Override
+ public QueryRequest query(String query) {
+ return query().withQuery(query);
+ }
+
+ private List<ProjectInfo> query(QueryRequest r) throws RestApiException {
+ try {
+ QueryProjects myQueryProjects = queryProvider.get();
+ myQueryProjects.setQuery(r.getQuery());
+ myQueryProjects.setLimit(r.getLimit());
+ myQueryProjects.setStart(r.getStart());
+
+ return myQueryProjects.apply(TopLevelResource.INSTANCE);
+ } catch (OrmException e) {
+ throw new RestApiException("Cannot query projects", e);
+ }
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/TagApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/TagApiImpl.java
index 283d117..9f19c6d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/TagApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/TagApiImpl.java
@@ -19,6 +19,7 @@
import com.google.gerrit.extensions.api.projects.TagApi;
import com.google.gerrit.extensions.api.projects.TagInfo;
import com.google.gerrit.extensions.api.projects.TagInput;
+import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.server.project.CreateTag;
@@ -81,7 +82,7 @@
@Override
public void delete() throws RestApiException {
try {
- deleteTag.apply(resource(), new DeleteTag.Input());
+ deleteTag.apply(resource(), new Input());
} catch (Exception e) {
throw asRestApiException("Cannot delete tag", e);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/args4j/ProjectControlHandler.java b/gerrit-server/src/main/java/com/google/gerrit/server/args4j/ProjectHandler.java
similarity index 83%
rename from gerrit-server/src/main/java/com/google/gerrit/server/args4j/ProjectControlHandler.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/args4j/ProjectHandler.java
index 1823527..8959d97 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/args4j/ProjectControlHandler.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/args4j/ProjectHandler.java
@@ -22,7 +22,8 @@
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.permissions.ProjectPermission;
import com.google.gerrit.server.project.NoSuchProjectException;
-import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectState;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
@@ -36,23 +37,23 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-public class ProjectControlHandler extends OptionHandler<ProjectControl> {
- private static final Logger log = LoggerFactory.getLogger(ProjectControlHandler.class);
+public class ProjectHandler extends OptionHandler<ProjectState> {
+ private static final Logger log = LoggerFactory.getLogger(ProjectHandler.class);
- private final ProjectControl.GenericFactory projectControlFactory;
+ private final ProjectCache projectCache;
private final PermissionBackend permissionBackend;
private final Provider<CurrentUser> user;
@Inject
- public ProjectControlHandler(
- ProjectControl.GenericFactory projectControlFactory,
+ public ProjectHandler(
+ ProjectCache projectCache,
PermissionBackend permissionBackend,
Provider<CurrentUser> user,
@Assisted final CmdLineParser parser,
@Assisted final OptionDef option,
- @Assisted final Setter<ProjectControl> setter) {
+ @Assisted final Setter<ProjectState> setter) {
super(parser, option, setter);
- this.projectControlFactory = projectControlFactory;
+ this.projectCache = projectCache;
this.permissionBackend = permissionBackend;
this.user = user;
}
@@ -77,20 +78,21 @@
String nameWithoutSuffix = ProjectUtil.stripGitSuffix(projectName);
Project.NameKey nameKey = new Project.NameKey(nameWithoutSuffix);
- ProjectControl control;
+ ProjectState state;
try {
- control = projectControlFactory.controlFor(nameKey, user.get());
+ state = projectCache.checkedGet(nameKey);
+ if (state == null) {
+ throw new CmdLineException(owner, String.format("project %s not found", nameWithoutSuffix));
+ }
permissionBackend.user(user).project(nameKey).check(ProjectPermission.ACCESS);
} catch (AuthException e) {
throw new CmdLineException(owner, new NoSuchProjectException(nameKey).getMessage());
- } catch (NoSuchProjectException e) {
- throw new CmdLineException(owner, e.getMessage());
} catch (PermissionBackendException | IOException e) {
log.warn("Cannot load project " + nameWithoutSuffix, e);
throw new CmdLineException(owner, new NoSuchProjectException(nameKey).getMessage());
}
- setter.addValue(control);
+ setter.addValue(state);
return 1;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeEdits.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeEdits.java
index 18d3482..1ca98b7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeEdits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeEdits.java
@@ -17,6 +17,7 @@
import com.google.common.base.Strings;
import com.google.gerrit.extensions.common.DiffWebLinkInfo;
import com.google.gerrit.extensions.common.EditInfo;
+import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AcceptsCreate;
import com.google.gerrit.extensions.restapi.AcceptsDelete;
@@ -159,8 +160,7 @@
}
}
- public static class DeleteFile implements RestModifyView<ChangeResource, DeleteFile.Input> {
- public static class Input {}
+ public static class DeleteFile implements RestModifyView<ChangeResource, Input> {
interface Factory {
DeleteFile create(String path);
@@ -176,7 +176,7 @@
}
@Override
- public Response<?> apply(ChangeResource rsrc, DeleteFile.Input in)
+ public Response<?> apply(ChangeResource rsrc, Input in)
throws IOException, AuthException, ResourceConflictException, OrmException,
PermissionBackendException {
return deleteContent.apply(rsrc, path);
@@ -214,7 +214,8 @@
@Override
public Response<EditInfo> apply(ChangeResource rsrc)
- throws AuthException, IOException, ResourceNotFoundException, OrmException {
+ throws AuthException, IOException, ResourceNotFoundException, OrmException,
+ PermissionBackendException {
Optional<ChangeEdit> edit = editUtil.byChange(rsrc.getNotes(), rsrc.getUser());
if (!edit.isPresent()) {
return Response.none();
@@ -342,9 +343,7 @@
* restoring a file to its previous contents.
*/
@Singleton
- public static class DeleteContent
- implements RestModifyView<ChangeEditResource, DeleteContent.Input> {
- public static class Input {}
+ public static class DeleteContent implements RestModifyView<ChangeEditResource, Input> {
private final ChangeEditModifier editModifier;
private final GitRepositoryManager repositoryManager;
@@ -356,7 +355,7 @@
}
@Override
- public Response<?> apply(ChangeEditResource rsrc, DeleteContent.Input input)
+ public Response<?> apply(ChangeEditResource rsrc, Input input)
throws AuthException, ResourceConflictException, OrmException, IOException,
PermissionBackendException {
return apply(rsrc.getChangeResource(), rsrc.getPath());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
index 8dc53bc..7769dbb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
@@ -83,6 +83,7 @@
import com.google.gerrit.extensions.config.DownloadCommand;
import com.google.gerrit.extensions.config.DownloadScheme;
import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.index.query.QueryResult;
import com.google.gerrit.reviewdb.client.Account;
@@ -116,11 +117,12 @@
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ReviewerStateInternal;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
+import com.google.gerrit.server.permissions.ChangePermission;
import com.google.gerrit.server.permissions.LabelPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
-import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.RemoveReviewerControl;
import com.google.gerrit.server.project.SubmitRuleOptions;
@@ -228,7 +230,6 @@
private final ApprovalsUtil approvalsUtil;
private final RemoveReviewerControl removeReviewerControl;
private final TrackingFooters trackingFooters;
- private final ChangeControl.GenericFactory changeControlFactory;
private boolean lazyLoad = true;
private AccountLoader accountLoader;
private FixInput fix;
@@ -261,7 +262,6 @@
ApprovalsUtil approvalsUtil,
RemoveReviewerControl removeReviewerControl,
TrackingFooters trackingFooters,
- ChangeControl.GenericFactory changeControlFactory,
@Assisted Iterable<ListChangesOption> options) {
this.db = db;
this.userProvider = user;
@@ -287,7 +287,6 @@
this.indexes = indexes;
this.approvalsUtil = approvalsUtil;
this.removeReviewerControl = removeReviewerControl;
- this.changeControlFactory = changeControlFactory;
this.options = Sets.immutableEnumSet(options);
this.trackingFooters = trackingFooters;
}
@@ -347,6 +346,7 @@
| OrmException
| IOException
| PermissionBackendException
+ | NoSuchProjectException
| RuntimeException e) {
if (!has(CHECK)) {
Throwables.throwIfInstanceOf(e, OrmException.class);
@@ -425,6 +425,7 @@
| OrmException
| IOException
| PermissionBackendException
+ | NoSuchProjectException
| RuntimeException e) {
if (has(CHECK)) {
i = checkOnly(cd);
@@ -491,7 +492,7 @@
private ChangeInfo toChangeInfo(ChangeData cd, Optional<PatchSet.Id> limitToPsId)
throws PatchListNotAvailableException, GpgException, OrmException, IOException,
- PermissionBackendException {
+ PermissionBackendException, NoSuchProjectException {
ChangeInfo out = new ChangeInfo();
CurrentUser user = userProvider.get();
@@ -506,11 +507,7 @@
}
}
- PermissionBackend.WithUser withUser = permissionBackend.user(user).database(db);
- PermissionBackend.ForChange perm =
- lazyLoad
- ? withUser.change(cd)
- : withUser.indexedChange(cd, notesFactory.createFromIndexedChange(cd.change()));
+ PermissionBackend.ForChange perm = permissionBackendForChange(user, cd);
Change in = cd.change();
out.project = in.getProject().get();
out.branch = in.getDest().getShortName();
@@ -596,19 +593,15 @@
src = null;
}
- ChangeControl ctl = null;
- if (needMessages || needRevisions) {
- ctl = changeControlFactory.controlFor(db.get(), cd.change(), userProvider.get());
- }
if (needMessages) {
- out.messages = messages(ctl, cd);
+ out.messages = messages(cd);
}
finish(out);
// This block must come after the ChangeInfo is mostly populated, since
// it will be passed to ActionVisitors as-is.
if (needRevisions) {
- out.revisions = revisions(ctl, cd, src, limitToPsId, out);
+ out.revisions = revisions(cd, src, limitToPsId, out);
if (out.revisions != null) {
for (Map.Entry<String, RevisionInfo> entry : out.revisions.entrySet()) {
if (entry.getValue().isCurrent) {
@@ -1103,8 +1096,7 @@
return result;
}
- private Collection<ChangeMessageInfo> messages(ChangeControl ctl, ChangeData cd)
- throws OrmException {
+ private Collection<ChangeMessageInfo> messages(ChangeData cd) throws OrmException {
List<ChangeMessage> messages = cmUtil.byChange(db.get(), cd.notes());
if (messages.isEmpty()) {
return Collections.emptyList();
@@ -1113,26 +1105,24 @@
List<ChangeMessageInfo> result = Lists.newArrayListWithCapacity(messages.size());
for (ChangeMessage message : messages) {
PatchSet.Id patchNum = message.getPatchSetId();
- if (patchNum == null || ctl.isVisible(db.get())) {
- ChangeMessageInfo cmi = new ChangeMessageInfo();
- cmi.id = message.getKey().get();
- cmi.author = accountLoader.get(message.getAuthor());
- cmi.date = message.getWrittenOn();
- cmi.message = message.getMessage();
- cmi.tag = message.getTag();
- cmi._revisionNumber = patchNum != null ? patchNum.get() : null;
- Account.Id realAuthor = message.getRealAuthor();
- if (realAuthor != null) {
- cmi.realAuthor = accountLoader.get(realAuthor);
- }
- result.add(cmi);
+ ChangeMessageInfo cmi = new ChangeMessageInfo();
+ cmi.id = message.getKey().get();
+ cmi.author = accountLoader.get(message.getAuthor());
+ cmi.date = message.getWrittenOn();
+ cmi.message = message.getMessage();
+ cmi.tag = message.getTag();
+ cmi._revisionNumber = patchNum != null ? patchNum.get() : null;
+ Account.Id realAuthor = message.getRealAuthor();
+ if (realAuthor != null) {
+ cmi.realAuthor = accountLoader.get(realAuthor);
}
+ result.add(cmi);
}
return result;
}
private Collection<AccountInfo> removableReviewers(ChangeData cd, ChangeInfo out)
- throws PermissionBackendException, NoSuchChangeException, OrmException {
+ throws PermissionBackendException, NoSuchProjectException, OrmException, IOException {
// Although this is called removableReviewers, this method also determines
// which CCs are removable.
//
@@ -1228,13 +1218,14 @@
}
private Map<String, RevisionInfo> revisions(
- ChangeControl ctl,
ChangeData cd,
Map<PatchSet.Id, PatchSet> map,
Optional<PatchSet.Id> limitToPsId,
ChangeInfo changeInfo)
- throws PatchListNotAvailableException, GpgException, OrmException, IOException {
+ throws PatchListNotAvailableException, GpgException, OrmException, IOException,
+ PermissionBackendException {
Map<String, RevisionInfo> res = new LinkedHashMap<>();
+ Boolean isWorldReadable = null;
try (Repository repo = openRepoIfNecessary(cd.project());
RevWalk rw = newRevWalk(repo)) {
for (PatchSet in : map.values()) {
@@ -1247,8 +1238,13 @@
} else {
want = id.equals(cd.change().currentPatchSetId());
}
- if (want && ctl.isVisible(db.get())) {
- res.put(in.getRevision().get(), toRevisionInfo(cd, in, repo, rw, false, changeInfo));
+ if (want) {
+ if (isWorldReadable == null) {
+ isWorldReadable = isWorldReadable(cd);
+ }
+ res.put(
+ in.getRevision().get(),
+ toRevisionInfo(cd, in, repo, rw, false, changeInfo, isWorldReadable));
}
}
return res;
@@ -1283,11 +1279,12 @@
}
public RevisionInfo getRevisionInfo(ChangeData cd, PatchSet in)
- throws PatchListNotAvailableException, GpgException, OrmException, IOException {
+ throws PatchListNotAvailableException, GpgException, OrmException, IOException,
+ PermissionBackendException {
accountLoader = accountLoaderFactory.create(has(DETAILED_ACCOUNTS));
try (Repository repo = openRepoIfNecessary(cd.project());
RevWalk rw = newRevWalk(repo)) {
- RevisionInfo rev = toRevisionInfo(cd, in, repo, rw, true, null);
+ RevisionInfo rev = toRevisionInfo(cd, in, repo, rw, true, null, isWorldReadable(cd));
accountLoader.fill();
return rev;
}
@@ -1299,7 +1296,8 @@
@Nullable Repository repo,
@Nullable RevWalk rw,
boolean fillCommit,
- @Nullable ChangeInfo changeInfo)
+ @Nullable ChangeInfo changeInfo,
+ boolean isWorldReadable)
throws PatchListNotAvailableException, GpgException, OrmException, IOException {
Change c = cd.change();
RevisionInfo out = new RevisionInfo();
@@ -1308,7 +1306,7 @@
out.ref = in.getRefName();
out.created = in.getCreatedOn();
out.uploader = accountLoader.get(in.getUploader());
- out.fetch = makeFetchMap(cd, in);
+ out.fetch = makeFetchMap(cd, in, isWorldReadable);
out.kind = changeKindCache.getChangeKind(rw, repo != null ? repo.getConfig() : null, cd, in);
out.description = in.getDescription();
@@ -1398,10 +1396,8 @@
return info;
}
- private Map<String, FetchInfo> makeFetchMap(ChangeData cd, PatchSet in) throws OrmException {
+ private Map<String, FetchInfo> makeFetchMap(ChangeData cd, PatchSet in, boolean isWorldReadable) {
Map<String, FetchInfo> r = new LinkedHashMap<>();
-
- ChangeControl ctl = changeControlFactory.controlFor(db.get(), cd.change(), anonymous);
for (DynamicMap.Entry<DownloadScheme> e : downloadSchemes) {
String schemeName = e.getExportName();
DownloadScheme scheme = e.getProvider().get();
@@ -1409,8 +1405,7 @@
|| (scheme.isAuthRequired() && !userProvider.get().isIdentifiedUser())) {
continue;
}
-
- if (!scheme.isAuthSupported() && !ctl.isVisible(db.get())) {
+ if (!scheme.isAuthSupported() && !isWorldReadable) {
continue;
}
@@ -1464,6 +1459,28 @@
label.all.add(approval);
}
+ /**
+ * @return {@link com.google.gerrit.server.permissions.PermissionBackend.ForChange} constructed
+ * from either an index-backed or a database-backed {@link ChangeData} depending on {@code
+ * lazyload}.
+ */
+ private PermissionBackend.ForChange permissionBackendForChange(CurrentUser user, ChangeData cd)
+ throws OrmException {
+ PermissionBackend.WithUser withUser = permissionBackend.user(user).database(db);
+ return lazyLoad
+ ? withUser.change(cd)
+ : withUser.indexedChange(cd, notesFactory.createFromIndexedChange(cd.change()));
+ }
+
+ private boolean isWorldReadable(ChangeData cd) throws OrmException, PermissionBackendException {
+ try {
+ permissionBackendForChange(anonymous, cd).check(ChangePermission.READ);
+ return true;
+ } catch (AuthException ae) {
+ return false;
+ }
+ }
+
@AutoValue
abstract static class LabelWithStatus {
private static LabelWithStatus create(LabelInfo label, SubmitRecord.Label.Status status) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangesCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangesCollection.java
index 805512e..a56612c5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangesCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangesCollection.java
@@ -16,6 +16,7 @@
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AcceptsPost;
+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.RestApiException;
@@ -126,6 +127,11 @@
}
private boolean canRead(ChangeNotes notes) throws PermissionBackendException {
- return permissionBackend.user(user).change(notes).database(db).test(ChangePermission.READ);
+ try {
+ permissionBackend.user(user).change(notes).database(db).check(ChangePermission.READ);
+ return true;
+ } catch (AuthException e) {
+ return false;
+ }
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Check.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Check.java
index 157928b..c07659f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Check.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Check.java
@@ -17,6 +17,7 @@
import com.google.gerrit.extensions.api.changes.FixInput;
import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
@@ -25,8 +26,8 @@
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.permissions.ProjectPermission;
import com.google.gerrit.server.project.NoSuchProjectException;
-import com.google.gerrit.server.project.ProjectControl;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -37,18 +38,12 @@
private final PermissionBackend permissionBackend;
private final Provider<CurrentUser> user;
private final ChangeJson.Factory jsonFactory;
- private final ProjectControl.GenericFactory projectControlFactory;
@Inject
- Check(
- PermissionBackend permissionBackend,
- Provider<CurrentUser> user,
- ChangeJson.Factory json,
- ProjectControl.GenericFactory projectControlFactory) {
+ Check(PermissionBackend permissionBackend, Provider<CurrentUser> user, ChangeJson.Factory json) {
this.permissionBackend = permissionBackend;
this.user = user;
this.jsonFactory = json;
- this.projectControlFactory = projectControlFactory;
}
@Override
@@ -60,9 +55,13 @@
public Response<ChangeInfo> apply(ChangeResource rsrc, FixInput input)
throws RestApiException, OrmException, PermissionBackendException, NoSuchProjectException,
IOException {
- if (!rsrc.isUserOwner()
- && !projectControlFactory.controlFor(rsrc.getProject(), rsrc.getUser()).isOwner()) {
- permissionBackend.user(user).check(GlobalPermission.MAINTAIN_SERVER);
+ PermissionBackend.WithUser perm = permissionBackend.user(user);
+ if (!rsrc.isUserOwner()) {
+ try {
+ perm.project(rsrc.getProject()).check(ProjectPermission.READ_CONFIG);
+ } catch (AuthException e) {
+ perm.check(GlobalPermission.MAINTAIN_SERVER);
+ }
}
return Response.withMustRevalidate(newChangeJson().fix(input).format(rsrc));
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java
index 4f03f37..8b84f2b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java
@@ -49,7 +49,7 @@
import com.google.gerrit.server.notedb.ReviewerStateInternal;
import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.NoSuchProjectException;
-import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.InternalChangeQuery;
@@ -92,7 +92,7 @@
private final PatchSetInserter.Factory patchSetInserterFactory;
private final MergeUtil.Factory mergeUtilFactory;
private final ChangeNotes.Factory changeNotesFactory;
- private final ProjectControl.GenericFactory projectControlFactory;
+ private final ProjectCache projectCache;
private final ApprovalsUtil approvalsUtil;
private final ChangeMessagesUtil changeMessagesUtil;
private final NotifyUtil notifyUtil;
@@ -109,7 +109,7 @@
PatchSetInserter.Factory patchSetInserterFactory,
MergeUtil.Factory mergeUtilFactory,
ChangeNotes.Factory changeNotesFactory,
- ProjectControl.GenericFactory projectControlFactory,
+ ProjectCache projectCache,
ApprovalsUtil approvalsUtil,
ChangeMessagesUtil changeMessagesUtil,
NotifyUtil notifyUtil) {
@@ -123,7 +123,7 @@
this.patchSetInserterFactory = patchSetInserterFactory;
this.mergeUtilFactory = mergeUtilFactory;
this.changeNotesFactory = changeNotesFactory;
- this.projectControlFactory = projectControlFactory;
+ this.projectCache = projectCache;
this.approvalsUtil = approvalsUtil;
this.changeMessagesUtil = changeMessagesUtil;
this.notifyUtil = notifyUtil;
@@ -197,10 +197,11 @@
String commitMessage = ChangeIdUtil.insertId(input.message, computedChangeId).trim() + '\n';
CodeReviewCommit cherryPickCommit;
- ProjectControl projectControl =
- projectControlFactory.controlFor(dest.getParentKey(), identifiedUser);
+ ProjectState projectState = projectCache.checkedGet(dest.getParentKey());
+ if (projectState == null) {
+ throw new NoSuchProjectException(dest.getParentKey());
+ }
try {
- ProjectState projectState = projectControl.getProjectState();
cherryPickCommit =
mergeUtilFactory
.create(projectState)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteAssignee.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteAssignee.java
index d3feb31..8d8d72e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteAssignee.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteAssignee.java
@@ -16,6 +16,7 @@
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.extensions.common.AccountInfo;
+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.reviewdb.client.Account;
@@ -25,7 +26,6 @@
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountLoader;
-import com.google.gerrit.server.change.DeleteAssignee.Input;
import com.google.gerrit.server.extensions.events.AssigneeChanged;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.permissions.ChangePermission;
@@ -45,7 +45,6 @@
@Singleton
public class DeleteAssignee
extends RetryingRestModifyView<ChangeResource, Input, Response<AccountInfo>> {
- public static class Input {}
private final ChangeMessagesUtil cmUtil;
private final Provider<ReviewDb> db;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteChange.java
index af26e8a..69f6178 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteChange.java
@@ -17,13 +17,13 @@
import static com.google.gerrit.extensions.conditions.BooleanCondition.and;
import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.webui.UiAction;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.change.DeleteChange.Input;
import com.google.gerrit.server.permissions.ChangePermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -39,7 +39,6 @@
@Singleton
public class DeleteChange extends RetryingRestModifyView<ChangeResource, Input, Response<?>>
implements UiAction<ChangeResource> {
- public static class Input {}
private final Provider<ReviewDb> db;
private final Provider<DeleteChangeOp> opProvider;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteChangeEdit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteChangeEdit.java
index e2e3920..480aca1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteChangeEdit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteChangeEdit.java
@@ -14,11 +14,11 @@
package com.google.gerrit.server.change;
+import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.restapi.AuthException;
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.change.DeleteChangeEdit.Input;
import com.google.gerrit.server.edit.ChangeEdit;
import com.google.gerrit.server.edit.ChangeEditUtil;
import com.google.gwtorm.server.OrmException;
@@ -29,7 +29,6 @@
@Singleton
public class DeleteChangeEdit implements RestModifyView<ChangeResource, Input> {
- public static class Input {}
private final ChangeEditUtil editUtil;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftComment.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftComment.java
index 68db189..6d82139 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftComment.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftComment.java
@@ -18,6 +18,7 @@
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.extensions.common.CommentInfo;
+import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
@@ -26,7 +27,6 @@
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CommentsUtil;
import com.google.gerrit.server.PatchSetUtil;
-import com.google.gerrit.server.change.DeleteDraftComment.Input;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.update.BatchUpdate;
import com.google.gerrit.server.update.BatchUpdateOp;
@@ -44,7 +44,6 @@
@Singleton
public class DeleteDraftComment
extends RetryingRestModifyView<DraftCommentResource, Input, Response<CommentInfo>> {
- static class Input {}
private final Provider<ReviewDb> db;
private final CommentsUtil commentsUtil;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewerOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewerOp.java
index c743769..91f7720 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewerOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewerOp.java
@@ -39,6 +39,7 @@
import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage;
import com.google.gerrit.server.notedb.NotesMigration;
import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.RemoveReviewerControl;
import com.google.gerrit.server.update.BatchUpdateOp;
@@ -119,7 +120,7 @@
@Override
public boolean updateChange(ChangeContext ctx)
throws AuthException, ResourceNotFoundException, OrmException, PermissionBackendException,
- IOException {
+ IOException, NoSuchProjectException {
Account.Id reviewerId = reviewer.getId();
if (!approvalsUtil.getReviewers(ctx.getDb(), ctx.getNotes()).all().contains(reviewerId)) {
throw new ResourceNotFoundException();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteVote.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteVote.java
index 10164ce..8c6c3cc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteVote.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteVote.java
@@ -41,6 +41,7 @@
import com.google.gerrit.server.mail.send.DeleteVoteSender;
import com.google.gerrit.server.mail.send.ReplyToChangeSender;
import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.project.RemoveReviewerControl;
@@ -162,7 +163,7 @@
@Override
public boolean updateChange(ChangeContext ctx)
throws OrmException, AuthException, ResourceNotFoundException, IOException,
- PermissionBackendException {
+ PermissionBackendException, NoSuchProjectException {
change = ctx.getChange();
PatchSet.Id psId = change.currentPatchSetId();
ps = psUtil.current(db.get(), ctx.getNotes());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java
index 21da0b8..c167e31 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java
@@ -42,6 +42,8 @@
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.patch.PatchListKey;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
+import com.google.gerrit.server.patch.PatchListObjectTooLargeException;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -145,7 +147,8 @@
@Override
public Response<?> apply(RevisionResource resource)
throws AuthException, BadRequestException, ResourceNotFoundException, OrmException,
- RepositoryNotFoundException, IOException, PatchListNotAvailableException {
+ RepositoryNotFoundException, IOException, PatchListNotAvailableException,
+ PermissionBackendException {
checkOptions();
if (reviewed) {
return Response.ok(reviewed(resource));
@@ -240,6 +243,8 @@
try {
return copy(res.files(), res.patchSetId(), resource, userId);
+ } catch (PatchListObjectTooLargeException e) {
+ log.warn("Cannot copy patch review flags: " + e.getMessage());
} catch (IOException | PatchListNotAvailableException e) {
log.warn("Cannot copy patch review flags", e);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDiff.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDiff.java
index c91748c..25902b9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDiff.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDiff.java
@@ -47,6 +47,7 @@
import com.google.gerrit.server.git.LargeObjectException;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.patch.PatchScriptFactory;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.ProjectCache;
@@ -122,7 +123,7 @@
@Override
public Response<DiffInfo> apply(FileResource resource)
throws ResourceConflictException, ResourceNotFoundException, OrmException, AuthException,
- InvalidChangeOperationException, IOException {
+ InvalidChangeOperationException, IOException, PermissionBackendException {
DiffPreferencesInfo prefs = new DiffPreferencesInfo();
if (whitespace != null) {
prefs.ignoreWhitespace = whitespace;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetPureRevert.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetPureRevert.java
index 6002f75..27c5d49 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetPureRevert.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetPureRevert.java
@@ -26,7 +26,6 @@
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MergeUtil;
import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -53,7 +52,6 @@
private final ChangeNotes.Factory notesFactory;
private final Provider<ReviewDb> dbProvider;
private final PatchSetUtil psUtil;
- private final ChangeControl.GenericFactory changeControlFactory;
@Option(
name = "--claimed-original",
@@ -70,15 +68,13 @@
ProjectCache projectCache,
ChangeNotes.Factory notesFactory,
Provider<ReviewDb> dbProvider,
- PatchSetUtil psUtil,
- ChangeControl.GenericFactory changeControlFactory) {
+ PatchSetUtil psUtil) {
this.mergeUtilFactory = mergeUtilFactory;
this.repoManager = repoManager;
this.projectCache = projectCache;
this.notesFactory = notesFactory;
this.dbProvider = dbProvider;
this.psUtil = psUtil;
- this.changeControlFactory = changeControlFactory;
}
@Override
@@ -88,10 +84,6 @@
PatchSet currentPatchSet = psUtil.current(dbProvider.get(), rsrc.getNotes());
if (currentPatchSet == null) {
throw new ResourceConflictException("current revision is missing");
- } else if (!changeControlFactory
- .controlFor(rsrc.getNotes(), rsrc.getUser())
- .isVisible(dbProvider.get())) {
- throw new AuthException("current revision not accessible");
}
return getPureRevert(rsrc.getNotes());
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRelated.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRelated.java
index a6583b1..44f65e1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRelated.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRelated.java
@@ -27,6 +27,7 @@
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.change.RelatedChangesSorter.PatchSetData;
import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.InternalChangeQuery;
@@ -64,14 +65,15 @@
@Override
public RelatedInfo apply(RevisionResource rsrc)
- throws RepositoryNotFoundException, IOException, OrmException, NoSuchProjectException {
+ throws RepositoryNotFoundException, IOException, OrmException, NoSuchProjectException,
+ PermissionBackendException {
RelatedInfo relatedInfo = new RelatedInfo();
relatedInfo.changes = getRelated(rsrc);
return relatedInfo;
}
private List<ChangeAndCommit> getRelated(RevisionResource rsrc)
- throws OrmException, IOException, NoSuchProjectException {
+ throws OrmException, IOException, PermissionBackendException {
Set<String> groups = getAllGroups(rsrc.getNotes());
if (groups.isEmpty()) {
return Collections.emptyList();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Ignore.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Ignore.java
index 46dabdf..c2c2d1f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Ignore.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Ignore.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.change;
+import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.Response;
@@ -30,12 +31,9 @@
import org.slf4j.LoggerFactory;
@Singleton
-public class Ignore
- implements RestModifyView<ChangeResource, Ignore.Input>, UiAction<ChangeResource> {
+public class Ignore implements RestModifyView<ChangeResource, Input>, UiAction<ChangeResource> {
private static final Logger log = LoggerFactory.getLogger(Ignore.class);
- public static class Input {}
-
private final StarredChangesUtil stars;
@Inject
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Index.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Index.java
index 7c4d158..85e13cc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Index.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Index.java
@@ -14,11 +14,11 @@
package com.google.gerrit.server.change;
+import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.change.Index.Input;
import com.google.gerrit.server.index.change.ChangeIndexer;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
@@ -34,7 +34,6 @@
@Singleton
public class Index extends RetryingRestModifyView<ChangeResource, Input, Response<?>> {
- public static class Input {}
private final Provider<ReviewDb> db;
private final PermissionBackend permissionBackend;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/MarkAsReviewed.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/MarkAsReviewed.java
index 265b2b0..9e77805 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/MarkAsReviewed.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/MarkAsReviewed.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.change;
+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;
@@ -31,11 +32,9 @@
@Singleton
public class MarkAsReviewed
- implements RestModifyView<ChangeResource, MarkAsReviewed.Input>, UiAction<ChangeResource> {
+ implements RestModifyView<ChangeResource, Input>, UiAction<ChangeResource> {
private static final Logger log = LoggerFactory.getLogger(MarkAsReviewed.class);
- public static class Input {}
-
private final Provider<ReviewDb> dbProvider;
private final ChangeData.Factory changeDataFactory;
private final StarredChangesUtil stars;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/MarkAsUnreviewed.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/MarkAsUnreviewed.java
index 6de84ee..436548b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/MarkAsUnreviewed.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/MarkAsUnreviewed.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.change;
+import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.webui.UiAction;
@@ -30,11 +31,9 @@
@Singleton
public class MarkAsUnreviewed
- implements RestModifyView<ChangeResource, MarkAsUnreviewed.Input>, UiAction<ChangeResource> {
+ implements RestModifyView<ChangeResource, Input>, UiAction<ChangeResource> {
private static final Logger log = LoggerFactory.getLogger(MarkAsUnreviewed.class);
- public static class Input {}
-
private final Provider<ReviewDb> dbProvider;
private final ChangeData.Factory changeDataFactory;
private final StarredChangesUtil stars;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Move.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Move.java
index 2f3855c..27d4eb1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Move.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Move.java
@@ -21,6 +21,7 @@
import com.google.common.base.Strings;
import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.extensions.api.changes.MoveInput;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.restapi.AuthException;
@@ -31,18 +32,24 @@
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Change.Status;
import com.google.gerrit.reviewdb.client.ChangeMessage;
+import com.google.gerrit.reviewdb.client.LabelId;
import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ApprovalsUtil;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gerrit.server.update.BatchUpdate;
import com.google.gerrit.server.update.BatchUpdateOp;
@@ -55,7 +62,8 @@
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
-import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import java.util.ArrayList;
+import java.util.List;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
@@ -71,6 +79,9 @@
private final Provider<InternalChangeQuery> queryProvider;
private final ChangeMessagesUtil cmUtil;
private final PatchSetUtil psUtil;
+ private final ApprovalsUtil approvalsUtil;
+ private final ProjectCache projectCache;
+ private final Provider<CurrentUser> userProvider;
@Inject
Move(
@@ -81,7 +92,10 @@
Provider<InternalChangeQuery> queryProvider,
ChangeMessagesUtil cmUtil,
RetryHelper retryHelper,
- PatchSetUtil psUtil) {
+ PatchSetUtil psUtil,
+ ApprovalsUtil approvalsUtil,
+ ProjectCache projectCache,
+ Provider<CurrentUser> userProvider) {
super(retryHelper);
this.permissionBackend = permissionBackend;
this.dbProvider = dbProvider;
@@ -90,6 +104,9 @@
this.queryProvider = queryProvider;
this.cmUtil = cmUtil;
this.psUtil = psUtil;
+ this.approvalsUtil = approvalsUtil;
+ this.projectCache = projectCache;
+ this.userProvider = userProvider;
}
@Override
@@ -138,7 +155,7 @@
@Override
public boolean updateChange(ChangeContext ctx)
- throws OrmException, ResourceConflictException, RepositoryNotFoundException, IOException {
+ throws OrmException, ResourceConflictException, IOException {
change = ctx.getChange();
if (change.getStatus() != Status.NEW) {
throw new ResourceConflictException("Change is " + ChangeUtil.status(change));
@@ -188,10 +205,13 @@
throw new ResourceConflictException("Patch set is not current");
}
- ChangeUpdate update = ctx.getUpdate(change.currentPatchSetId());
+ PatchSet.Id psId = change.currentPatchSetId();
+ ChangeUpdate update = ctx.getUpdate(psId);
update.setBranch(newDestKey.get());
change.setDest(newDestKey);
+ updateApprovals(ctx, update, psId, projectKey);
+
StringBuilder msgBuf = new StringBuilder();
msgBuf.append("Change destination moved from ");
msgBuf.append(changePrevDest.getShortName());
@@ -207,6 +227,46 @@
return true;
}
+
+ /**
+ * We have a long discussion about how to deal with its votes after moving a change from one
+ * branch to another. In the end, we think only keeping the veto votes is the best way since
+ * it's simple for us and less confusing for our users. See the discussion in the following
+ * proposal: https://gerrit-review.googlesource.com/c/gerrit/+/129171
+ */
+ private void updateApprovals(
+ ChangeContext ctx, ChangeUpdate update, PatchSet.Id psId, Project.NameKey project)
+ throws IOException, OrmException {
+ List<PatchSetApproval> approvals = new ArrayList<>();
+ for (PatchSetApproval psa :
+ approvalsUtil.byPatchSet(
+ ctx.getDb(),
+ ctx.getNotes(),
+ userProvider.get(),
+ psId,
+ ctx.getRevWalk(),
+ ctx.getRepoView().getConfig())) {
+ ProjectState projectState = projectCache.checkedGet(project);
+ LabelType type =
+ projectState.getLabelTypes(ctx.getNotes(), ctx.getUser()).byLabel(psa.getLabelId());
+ // Only keep veto votes, defined as votes where:
+ // 1- the label function allows minimum values to block submission.
+ // 2- the vote holds the minimum value.
+ if (type.isMaxNegative(psa) && type.getFunction().isBlock()) {
+ continue;
+ }
+
+ // Remove votes from NoteDb.
+ update.removeApprovalFor(psa.getAccountId(), psa.getLabel());
+ approvals.add(
+ new PatchSetApproval(
+ new PatchSetApproval.Key(psId, psa.getAccountId(), new LabelId(psa.getLabel())),
+ (short) 0,
+ ctx.getWhen()));
+ }
+ // Remove votes from ReviewDb.
+ ctx.getDb().patchSetApprovals().upsert(approvals);
+ }
}
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
index 58634a5..0022656 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
@@ -233,7 +233,7 @@
input.drafts = DraftHandling.DELETE;
}
if (input.labels != null) {
- checkLabels(revision, labelTypes, input.strictLabels, input.labels);
+ checkLabels(revision, labelTypes, input.labels);
}
if (input.comments != null) {
cleanUpComments(input.comments);
@@ -443,12 +443,9 @@
while (itr.hasNext()) {
Map.Entry<String, Short> ent = itr.next();
LabelType type = labelTypes.byLabel(ent.getKey());
- if (type == null && in.strictLabels) {
+ if (type == null) {
throw new BadRequestException(
String.format("label \"%s\" is not a configured label", ent.getKey()));
- } else if (type == null) {
- itr.remove();
- continue;
}
if (!caller.isInternalUser()) {
@@ -479,8 +476,7 @@
changeResourceFactory.create(rev.getNotes(), reviewer), rev.getPatchSet());
}
- private void checkLabels(
- RevisionResource rsrc, LabelTypes labelTypes, boolean strict, Map<String, Short> labels)
+ private void checkLabels(RevisionResource rsrc, LabelTypes labelTypes, Map<String, Short> labels)
throws BadRequestException, AuthException, PermissionBackendException {
PermissionBackend.ForChange perm = rsrc.permissions();
Iterator<Map.Entry<String, Short>> itr = labels.entrySet().iterator();
@@ -488,12 +484,8 @@
Map.Entry<String, Short> ent = itr.next();
LabelType lt = labelTypes.byLabel(ent.getKey());
if (lt == null) {
- if (strict) {
- throw new BadRequestException(
- String.format("label \"%s\" is not a configured label", ent.getKey()));
- }
- itr.remove();
- continue;
+ throw new BadRequestException(
+ String.format("label \"%s\" is not a configured label", ent.getKey()));
}
if (ent.getValue() == null || ent.getValue() == 0) {
@@ -503,23 +495,16 @@
}
if (lt.getValue(ent.getValue()) == null) {
- if (strict) {
- throw new BadRequestException(
- String.format("label \"%s\": %d is not a valid value", ent.getKey(), ent.getValue()));
- }
- itr.remove();
- continue;
+ throw new BadRequestException(
+ String.format("label \"%s\": %d is not a valid value", ent.getKey(), ent.getValue()));
}
short val = ent.getValue();
try {
perm.check(new LabelPermission.WithValue(lt, val));
} catch (AuthException e) {
- if (strict) {
- throw new AuthException(
- String.format("Applying label \"%s\": %d is restricted", lt.getName(), val));
- }
- ent.setValue(perm.squashThenCheck(lt, val));
+ throw new AuthException(
+ String.format("Applying label \"%s\": %d is restricted", lt.getName(), val));
}
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java
index f642aa4..3664293 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java
@@ -91,7 +91,7 @@
private final PermissionBackend permissionBackend;
private final GroupsCollection groupsCollection;
- private final GroupMembers.Factory groupMembersFactory;
+ private final GroupMembers groupMembers;
private final AccountLoader.Factory accountLoaderFactory;
private final Provider<ReviewDb> dbProvider;
private final ChangeData.Factory changeDataFactory;
@@ -111,7 +111,7 @@
ReviewerResource.Factory reviewerFactory,
PermissionBackend permissionBackend,
GroupsCollection groupsCollection,
- GroupMembers.Factory groupMembersFactory,
+ GroupMembers groupMembers,
AccountLoader.Factory accountLoaderFactory,
Provider<ReviewDb> db,
ChangeData.Factory changeDataFactory,
@@ -130,7 +130,7 @@
this.reviewerFactory = reviewerFactory;
this.permissionBackend = permissionBackend;
this.groupsCollection = groupsCollection;
- this.groupMembersFactory = groupMembersFactory;
+ this.groupMembers = groupMembers;
this.accountLoaderFactory = accountLoaderFactory;
this.dbProvider = db;
this.changeDataFactory = changeDataFactory;
@@ -287,10 +287,7 @@
Set<Account.Id> reviewers = new HashSet<>();
Set<Account> members;
try {
- members =
- groupMembersFactory
- .create(rsrc.getUser())
- .listAccounts(group.getGroupUUID(), rsrc.getProject());
+ members = groupMembers.listAccounts(group.getGroupUUID(), rsrc.getProject());
} catch (NoSuchGroupException e) {
return fail(
reviewer,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDescription.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDescription.java
index 4c9cf23..0d932ec 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDescription.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDescription.java
@@ -16,7 +16,7 @@
import com.google.common.base.Strings;
import com.google.gerrit.common.TimeUtil;
-import com.google.gerrit.extensions.restapi.DefaultInput;
+import com.google.gerrit.extensions.common.DescriptionInput;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.webui.UiAction;
@@ -42,16 +42,12 @@
@Singleton
public class PutDescription
- extends RetryingRestModifyView<RevisionResource, PutDescription.Input, Response<String>>
+ extends RetryingRestModifyView<RevisionResource, DescriptionInput, Response<String>>
implements UiAction<RevisionResource> {
private final Provider<ReviewDb> dbProvider;
private final ChangeMessagesUtil cmUtil;
private final PatchSetUtil psUtil;
- public static class Input {
- @DefaultInput public String description;
- }
-
@Inject
PutDescription(
Provider<ReviewDb> dbProvider,
@@ -66,11 +62,11 @@
@Override
protected Response<String> applyImpl(
- BatchUpdate.Factory updateFactory, RevisionResource rsrc, Input input)
+ BatchUpdate.Factory updateFactory, RevisionResource rsrc, DescriptionInput input)
throws UpdateException, RestApiException, PermissionBackendException {
rsrc.permissions().check(ChangePermission.EDIT_DESCRIPTION);
- Op op = new Op(input != null ? input : new Input(), rsrc.getPatchSet().getId());
+ Op op = new Op(input != null ? input : new DescriptionInput(), rsrc.getPatchSet().getId());
try (BatchUpdate u =
updateFactory.create(
dbProvider.get(), rsrc.getChange().getProject(), rsrc.getUser(), TimeUtil.nowTs())) {
@@ -83,13 +79,13 @@
}
private class Op implements BatchUpdateOp {
- private final Input input;
+ private final DescriptionInput input;
private final PatchSet.Id psId;
private String oldDescription;
private String newDescription;
- Op(Input input, PatchSet.Id psId) {
+ Op(DescriptionInput input, PatchSet.Id psId) {
this.input = input;
this.psId = psId;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutMessage.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutMessage.java
index 5edef0a..aa10af8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutMessage.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutMessage.java
@@ -35,7 +35,6 @@
import com.google.gerrit.server.permissions.ChangePermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
-import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.update.BatchUpdate;
import com.google.gerrit.server.update.RetryHelper;
@@ -73,7 +72,6 @@
private final PatchSetUtil psUtil;
private final NotifyUtil notifyUtil;
private final ProjectCache projectCache;
- private final ChangeControl.GenericFactory changeControlFactory;
@Inject
PutMessage(
@@ -86,8 +84,7 @@
@GerritPersonIdent PersonIdent gerritIdent,
PatchSetUtil psUtil,
NotifyUtil notifyUtil,
- ProjectCache projectCache,
- ChangeControl.GenericFactory changeControlFactory) {
+ ProjectCache projectCache) {
super(retryHelper);
this.repositoryManager = repositoryManager;
this.currentUserProvider = currentUserProvider;
@@ -98,7 +95,6 @@
this.psUtil = psUtil;
this.notifyUtil = notifyUtil;
this.projectCache = projectCache;
- this.changeControlFactory = changeControlFactory;
}
@Override
@@ -109,10 +105,6 @@
PatchSet ps = psUtil.current(db.get(), resource.getNotes());
if (ps == null) {
throw new ResourceConflictException("current revision is missing");
- } else if (!changeControlFactory
- .controlFor(resource.getNotes(), resource.getUser())
- .isVisible(db.get())) {
- throw new AuthException("current revision not accessible");
}
if (input == null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java
index 8b5608b..a461bf0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java
@@ -16,8 +16,8 @@
import com.google.common.base.Strings;
import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.extensions.api.changes.TopicInput;
import com.google.gerrit.extensions.restapi.BadRequestException;
-import com.google.gerrit.extensions.restapi.DefaultInput;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.webui.UiAction;
@@ -26,7 +26,6 @@
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ChangeMessagesUtil;
import com.google.gerrit.server.ChangeUtil;
-import com.google.gerrit.server.change.PutTopic.Input;
import com.google.gerrit.server.extensions.events.TopicEdited;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.permissions.ChangePermission;
@@ -44,16 +43,12 @@
import com.google.inject.Singleton;
@Singleton
-public class PutTopic extends RetryingRestModifyView<ChangeResource, Input, Response<String>>
+public class PutTopic extends RetryingRestModifyView<ChangeResource, TopicInput, Response<String>>
implements UiAction<ChangeResource> {
private final Provider<ReviewDb> dbProvider;
private final ChangeMessagesUtil cmUtil;
private final TopicEdited topicEdited;
- public static class Input {
- @DefaultInput public String topic;
- }
-
@Inject
PutTopic(
Provider<ReviewDb> dbProvider,
@@ -68,7 +63,7 @@
@Override
protected Response<String> applyImpl(
- BatchUpdate.Factory updateFactory, ChangeResource req, Input input)
+ BatchUpdate.Factory updateFactory, ChangeResource req, TopicInput input)
throws UpdateException, RestApiException, PermissionBackendException {
req.permissions().check(ChangePermission.EDIT_TOPIC_NAME);
@@ -79,7 +74,7 @@
String.format("topic length exceeds the limit (%s)", ChangeUtil.TOPIC_MAX_LENGTH));
}
- Op op = new Op(input != null ? input : new Input());
+ Op op = new Op(input != null ? input : new TopicInput());
try (BatchUpdate u =
updateFactory.create(
dbProvider.get(), req.getChange().getProject(), req.getUser(), TimeUtil.nowTs())) {
@@ -90,13 +85,13 @@
}
private class Op implements BatchUpdateOp {
- private final Input input;
+ private final TopicInput input;
private Change change;
private String oldTopicName;
private String newTopicName;
- Op(Input input) {
+ Op(TopicInput input) {
this.input = input;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java
index 7d92973..59ab190 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java
@@ -35,13 +35,12 @@
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ChangeUtil;
-import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.change.RebaseUtil.Base;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.permissions.ChangePermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
-import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.update.BatchUpdate;
import com.google.gerrit.server.update.RetryHelper;
@@ -74,8 +73,7 @@
private final RebaseUtil rebaseUtil;
private final ChangeJson.Factory json;
private final Provider<ReviewDb> dbProvider;
- private final Provider<CurrentUser> userProvider;
- private final ChangeControl.GenericFactory changeControlFactory;
+ private final PermissionBackend permissionBackend;
@Inject
public Rebase(
@@ -85,16 +83,14 @@
RebaseUtil rebaseUtil,
ChangeJson.Factory json,
Provider<ReviewDb> dbProvider,
- Provider<CurrentUser> userProvider,
- ChangeControl.GenericFactory changeControlFactory) {
+ PermissionBackend permissionBackend) {
super(retryHelper);
this.repoManager = repoManager;
this.rebaseFactory = rebaseFactory;
this.rebaseUtil = rebaseUtil;
this.json = json;
this.dbProvider = dbProvider;
- this.userProvider = userProvider;
- this.changeControlFactory = changeControlFactory;
+ this.permissionBackend = permissionBackend;
}
@Override
@@ -132,7 +128,8 @@
private ObjectId findBaseRev(
Repository repo, RevWalk rw, RevisionResource rsrc, RebaseInput input)
- throws RestApiException, OrmException, IOException, NoSuchChangeException {
+ throws RestApiException, OrmException, IOException, NoSuchChangeException, AuthException,
+ PermissionBackendException {
Branch.NameKey destRefKey = rsrc.getChange().getDest();
if (input == null || input.base == null) {
return rebaseUtil.findBaseRevision(rsrc.getPatchSet(), destRefKey, repo, rw);
@@ -150,20 +147,21 @@
return destRef.getObjectId();
}
- @SuppressWarnings("resource")
- ReviewDb db = dbProvider.get();
Base base = rebaseUtil.parseBase(rsrc, str);
if (base == null) {
throw new ResourceConflictException("base revision is missing: " + str);
}
PatchSet.Id baseId = base.patchSet().getId();
- ChangeControl baseCtl = changeControlFactory.controlFor(base.notes(), userProvider.get());
- if (!baseCtl.isVisible(db)) {
- throw new AuthException("base revision not accessible: " + str);
- } else if (change.getId().equals(baseId.getParentKey())) {
+ if (change.getId().equals(baseId.getParentKey())) {
throw new ResourceConflictException("cannot rebase change onto itself");
}
+ permissionBackend
+ .user(rsrc.getUser())
+ .database(dbProvider)
+ .change(base.notes())
+ .check(ChangePermission.READ);
+
Change baseChange = base.notes().getChange();
if (!baseChange.getProject().equals(change.getProject())) {
throw new ResourceConflictException(
@@ -228,18 +226,12 @@
extends RetryingRestModifyView<ChangeResource, RebaseInput, ChangeInfo> {
private final PatchSetUtil psUtil;
private final Rebase rebase;
- private final ChangeControl.GenericFactory changeControlFactory;
@Inject
- CurrentRevision(
- RetryHelper retryHelper,
- PatchSetUtil psUtil,
- Rebase rebase,
- ChangeControl.GenericFactory changeControlFactory) {
+ CurrentRevision(RetryHelper retryHelper, PatchSetUtil psUtil, Rebase rebase) {
super(retryHelper);
this.psUtil = psUtil;
this.rebase = rebase;
- this.changeControlFactory = changeControlFactory;
}
@Override
@@ -250,10 +242,6 @@
PatchSet ps = psUtil.current(rebase.dbProvider.get(), rsrc.getNotes());
if (ps == null) {
throw new ResourceConflictException("current revision is missing");
- } else if (!changeControlFactory
- .controlFor(rsrc.getNotes(), rsrc.getUser())
- .isVisible(rebase.dbProvider.get())) {
- throw new AuthException("current revision not accessible");
}
return rebase.applyImpl(updateFactory, new RevisionResource(rsrc, ps), input);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeEdit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeEdit.java
index 38a695a..2909827 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeEdit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeEdit.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.change;
+import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AcceptsPost;
import com.google.gerrit.extensions.restapi.AuthException;
@@ -68,8 +69,7 @@
}
@Singleton
- public static class Rebase implements RestModifyView<ChangeResource, Rebase.Input> {
- public static class Input {}
+ public static class Rebase implements RestModifyView<ChangeResource, Input> {
private final GitRepositoryManager repositoryManager;
private final ChangeEditModifier editModifier;
@@ -81,7 +81,7 @@
}
@Override
- public Response<?> apply(ChangeResource rsrc, Rebase.Input in)
+ public Response<?> apply(ChangeResource rsrc, Input in)
throws AuthException, ResourceConflictException, IOException, OrmException,
PermissionBackendException {
Project.NameKey project = rsrc.getProject();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebuild.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebuild.java
index 682b45f..a3ed670 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebuild.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebuild.java
@@ -16,6 +16,7 @@
import static java.util.stream.Collectors.joining;
+import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
@@ -23,7 +24,6 @@
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.reviewdb.server.ReviewDbUtil;
import com.google.gerrit.server.CommentsUtil;
-import com.google.gerrit.server.change.Rebuild.Input;
import com.google.gerrit.server.notedb.ChangeBundle;
import com.google.gerrit.server.notedb.ChangeBundleReader;
import com.google.gerrit.server.notedb.ChangeNotes;
@@ -40,7 +40,6 @@
@Singleton
public class Rebuild implements RestModifyView<ChangeResource, Input> {
- public static class Input {}
private final Provider<ReviewDb> db;
private final NotesMigration migration;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/RelatedChangesSorter.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/RelatedChangesSorter.java
index 86d6e81..22ff2b7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/RelatedChangesSorter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/RelatedChangesSorter.java
@@ -24,16 +24,20 @@
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.MultimapBuilder;
+import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.project.NoSuchProjectException;
-import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.permissions.ChangePermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
+import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
import java.util.ArrayDeque;
@@ -55,23 +59,27 @@
@Singleton
class RelatedChangesSorter {
private final GitRepositoryManager repoManager;
- private final ProjectControl.GenericFactory projectControlFactory;
+ private final PermissionBackend permissionBackend;
+ private final Provider<ReviewDb> dbProvider;
@Inject
RelatedChangesSorter(
- GitRepositoryManager repoManager, ProjectControl.GenericFactory projectControlFactory) {
+ GitRepositoryManager repoManager,
+ PermissionBackend permissionBackend,
+ Provider<ReviewDb> dbProvider) {
this.repoManager = repoManager;
- this.projectControlFactory = projectControlFactory;
+ this.permissionBackend = permissionBackend;
+ this.dbProvider = dbProvider;
}
public List<PatchSetData> sort(List<ChangeData> in, PatchSet startPs, CurrentUser user)
- throws OrmException, IOException, NoSuchProjectException {
+ throws OrmException, IOException, PermissionBackendException {
checkArgument(!in.isEmpty(), "Input may not be empty");
// Map of all patch sets, keyed by commit SHA-1.
Map<String, PatchSetData> byId = collectById(in);
PatchSetData start = byId.get(startPs.getRevision().get());
checkArgument(start != null, "%s not found in %s", startPs, in);
- ProjectControl ctl = projectControlFactory.controlFor(start.data().project(), user);
+ PermissionBackend.WithUser perm = permissionBackend.user(user).database(dbProvider);
// Map of patch set -> immediate parent.
ListMultimap<PatchSetData, PatchSetData> parents =
@@ -98,9 +106,9 @@
}
}
- Collection<PatchSetData> ancestors = walkAncestors(ctl, parents, start);
+ Collection<PatchSetData> ancestors = walkAncestors(perm, parents, start);
List<PatchSetData> descendants =
- walkDescendants(ctl, children, start, otherPatchSetsOfStart, ancestors);
+ walkDescendants(perm, children, start, otherPatchSetsOfStart, ancestors);
List<PatchSetData> result = new ArrayList<>(ancestors.size() + descendants.size() - 1);
result.addAll(Lists.reverse(descendants));
result.addAll(ancestors);
@@ -133,14 +141,16 @@
}
private static Collection<PatchSetData> walkAncestors(
- ProjectControl ctl, ListMultimap<PatchSetData, PatchSetData> parents, PatchSetData start)
- throws OrmException {
+ PermissionBackend.WithUser perm,
+ ListMultimap<PatchSetData, PatchSetData> parents,
+ PatchSetData start)
+ throws PermissionBackendException {
LinkedHashSet<PatchSetData> result = new LinkedHashSet<>();
Deque<PatchSetData> pending = new ArrayDeque<>();
pending.add(start);
while (!pending.isEmpty()) {
PatchSetData psd = pending.remove();
- if (result.contains(psd) || !isVisible(psd, ctl)) {
+ if (result.contains(psd) || !isVisible(psd, perm)) {
continue;
}
result.add(psd);
@@ -150,24 +160,25 @@
}
private static List<PatchSetData> walkDescendants(
- ProjectControl ctl,
+ PermissionBackend.WithUser perm,
ListMultimap<PatchSetData, PatchSetData> children,
PatchSetData start,
List<PatchSetData> otherPatchSetsOfStart,
Iterable<PatchSetData> ancestors)
- throws OrmException {
+ throws PermissionBackendException {
Set<Change.Id> alreadyEmittedChanges = new HashSet<>();
addAllChangeIds(alreadyEmittedChanges, ancestors);
// Prefer descendants found by following the original patch set passed in.
List<PatchSetData> result =
- walkDescendentsImpl(ctl, alreadyEmittedChanges, children, ImmutableList.of(start));
+ walkDescendentsImpl(perm, alreadyEmittedChanges, children, ImmutableList.of(start));
addAllChangeIds(alreadyEmittedChanges, result);
// Then, go back and add new indirect descendants found by following any
// other patch sets of start. These show up after all direct descendants,
// because we wouldn't know where in the walk to insert them.
- result.addAll(walkDescendentsImpl(ctl, alreadyEmittedChanges, children, otherPatchSetsOfStart));
+ result.addAll(
+ walkDescendentsImpl(perm, alreadyEmittedChanges, children, otherPatchSetsOfStart));
return result;
}
@@ -179,11 +190,11 @@
}
private static List<PatchSetData> walkDescendentsImpl(
- ProjectControl ctl,
+ PermissionBackend.WithUser perm,
Set<Change.Id> alreadyEmittedChanges,
ListMultimap<PatchSetData, PatchSetData> children,
List<PatchSetData> start)
- throws OrmException {
+ throws PermissionBackendException {
if (start.isEmpty()) {
return ImmutableList.of();
}
@@ -194,7 +205,7 @@
pending.addAll(start);
while (!pending.isEmpty()) {
PatchSetData psd = pending.remove();
- if (seen.contains(psd) || !isVisible(psd, ctl)) {
+ if (seen.contains(psd) || !isVisible(psd, perm)) {
continue;
}
seen.add(psd);
@@ -225,10 +236,14 @@
return result;
}
- private static boolean isVisible(PatchSetData psd, ProjectControl ctl) throws OrmException {
- // Reuse existing project control rather than lazily creating a new one for
- // each ChangeData.
- return ctl.controlFor(psd.data().notes()).isPatchVisible(psd.patchSet(), psd.data());
+ private static boolean isVisible(PatchSetData psd, PermissionBackend.WithUser perm)
+ throws PermissionBackendException {
+ try {
+ perm.change(psd.data()).check(ChangePermission.READ);
+ return true;
+ } catch (AuthException e) {
+ return false;
+ }
}
@AutoValue
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Reviewed.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Reviewed.java
index 0d25d35..c412adf 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Reviewed.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Reviewed.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.change;
+import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
@@ -22,7 +23,6 @@
import com.google.inject.Singleton;
public class Reviewed {
- public static class Input {}
@Singleton
public static class PutReviewed implements RestModifyView<FileResource, Input> {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revisions.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revisions.java
index ef039dd..084bc25 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revisions.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revisions.java
@@ -28,7 +28,9 @@
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.edit.ChangeEdit;
import com.google.gerrit.server.edit.ChangeEditUtil;
-import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.permissions.ChangePermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -46,7 +48,7 @@
private final Provider<ReviewDb> dbProvider;
private final ChangeEditUtil editUtil;
private final PatchSetUtil psUtil;
- private final ChangeControl.GenericFactory changeControlFactory;
+ private final PermissionBackend permissionBackend;
@Inject
Revisions(
@@ -54,12 +56,12 @@
Provider<ReviewDb> dbProvider,
ChangeEditUtil editUtil,
PatchSetUtil psUtil,
- ChangeControl.GenericFactory changeControlFactory) {
+ PermissionBackend permissionBackend) {
this.views = views;
this.dbProvider = dbProvider;
this.editUtil = editUtil;
this.psUtil = psUtil;
- this.changeControlFactory = changeControlFactory;
+ this.permissionBackend = permissionBackend;
}
@Override
@@ -74,7 +76,8 @@
@Override
public RevisionResource parse(ChangeResource change, IdString id)
- throws ResourceNotFoundException, AuthException, OrmException, IOException {
+ throws ResourceNotFoundException, AuthException, OrmException, IOException,
+ PermissionBackendException {
if (id.get().equals("current")) {
PatchSet ps = psUtil.current(dbProvider.get(), change.getNotes());
if (ps != null && visible(change)) {
@@ -100,10 +103,17 @@
}
}
- private boolean visible(ChangeResource change) throws OrmException {
- return changeControlFactory
- .controlFor(change.getNotes(), change.getUser())
- .isVisible(dbProvider.get());
+ private boolean visible(ChangeResource change) throws PermissionBackendException {
+ try {
+ permissionBackend
+ .user(change.getUser())
+ .change(change.getNotes())
+ .database(dbProvider)
+ .check(ChangePermission.READ);
+ return true;
+ } catch (AuthException e) {
+ return false;
+ }
}
private List<RevisionResource> find(ChangeResource change, String id)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
index cab61b3..84ba88e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
@@ -55,7 +55,6 @@
import com.google.gerrit.server.permissions.ChangePermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
-import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.InternalChangeQuery;
@@ -507,20 +506,17 @@
private final Submit submit;
private final ChangeJson.Factory json;
private final PatchSetUtil psUtil;
- private final ChangeControl.GenericFactory changeControlFactory;
@Inject
CurrentRevision(
Provider<ReviewDb> dbProvider,
Submit submit,
ChangeJson.Factory json,
- PatchSetUtil psUtil,
- ChangeControl.GenericFactory changeControlFactory) {
+ PatchSetUtil psUtil) {
this.dbProvider = dbProvider;
this.submit = submit;
this.json = json;
this.psUtil = psUtil;
- this.changeControlFactory = changeControlFactory;
}
@Override
@@ -530,10 +526,6 @@
PatchSet ps = psUtil.current(dbProvider.get(), rsrc.getNotes());
if (ps == null) {
throw new ResourceConflictException("current revision is missing");
- } else if (!changeControlFactory
- .controlFor(rsrc.getNotes(), rsrc.getUser())
- .isVisible(dbProvider.get())) {
- throw new AuthException("current revision not accessible");
}
Output out = submit.apply(new RevisionResource(rsrc, ps), input);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Unignore.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Unignore.java
index 2bad16c..39a82d7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Unignore.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Unignore.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.change;
+import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.webui.UiAction;
@@ -26,12 +27,9 @@
import org.slf4j.LoggerFactory;
@Singleton
-public class Unignore
- implements RestModifyView<ChangeResource, Unignore.Input>, UiAction<ChangeResource> {
+public class Unignore implements RestModifyView<ChangeResource, Input>, UiAction<ChangeResource> {
private static final Logger log = LoggerFactory.getLogger(Unignore.class);
- public static class Input {}
-
private final StarredChangesUtil stars;
@Inject
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/DeleteTask.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/DeleteTask.java
index 29ca20f..d20589a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/DeleteTask.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/DeleteTask.java
@@ -19,16 +19,15 @@
import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import com.google.gerrit.extensions.annotations.RequiresAnyCapability;
+import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
-import com.google.gerrit.server.config.DeleteTask.Input;
import com.google.gerrit.server.git.WorkQueue.Task;
import com.google.inject.Singleton;
@Singleton
@RequiresAnyCapability({KILL_TASK, MAINTAIN_SERVER})
public class DeleteTask implements RestModifyView<TaskResource, Input> {
- public static class Input {}
@Override
public Response<?> apply(TaskResource rsrc, Input input) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/FlushCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/FlushCache.java
index 366dae1..5d2ec36 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/FlushCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/FlushCache.java
@@ -18,11 +18,11 @@
import static com.google.gerrit.common.data.GlobalCapability.MAINTAIN_SERVER;
import com.google.gerrit.extensions.annotations.RequiresAnyCapability;
+import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.restapi.AuthException;
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.config.FlushCache.Input;
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
@@ -33,7 +33,6 @@
@RequiresAnyCapability({FLUSH_CACHES, MAINTAIN_SERVER})
@Singleton
public class FlushCache implements RestModifyView<CacheResource, Input> {
- public static class Input {}
public static final String WEB_SESSIONS = "web_sessions";
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
index 0e4e8b4..f7603f0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -50,6 +50,7 @@
import com.google.gerrit.extensions.events.NewProjectCreatedListener;
import com.google.gerrit.extensions.events.PluginEventListener;
import com.google.gerrit.extensions.events.ProjectDeletedListener;
+import com.google.gerrit.extensions.events.ProjectIndexedListener;
import com.google.gerrit.extensions.events.ReviewerAddedListener;
import com.google.gerrit.extensions.events.ReviewerDeletedListener;
import com.google.gerrit.extensions.events.RevisionCreatedListener;
@@ -91,7 +92,6 @@
import com.google.gerrit.server.account.GroupCacheImpl;
import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.account.GroupIncludeCacheImpl;
-import com.google.gerrit.server.account.GroupMembers;
import com.google.gerrit.server.account.VersionedAuthorizedKeys;
import com.google.gerrit.server.account.externalids.ExternalIdModule;
import com.google.gerrit.server.api.accounts.AccountExternalIdCreator;
@@ -114,6 +114,7 @@
import com.google.gerrit.server.git.EmailMerge;
import com.google.gerrit.server.git.GitModule;
import com.google.gerrit.server.git.GitModules;
+import com.google.gerrit.server.git.MergeSuperSetComputation;
import com.google.gerrit.server.git.MergeUtil;
import com.google.gerrit.server.git.MergedByPushOp;
import com.google.gerrit.server.git.NotesBranchUtil;
@@ -151,7 +152,6 @@
import com.google.gerrit.server.mail.send.RegisterNewEmailSender;
import com.google.gerrit.server.mail.send.ReplacePatchSetSender;
import com.google.gerrit.server.mail.send.SetAssigneeSender;
-import com.google.gerrit.server.mail.send.VelocityRuntimeProvider;
import com.google.gerrit.server.mime.FileTypeRegistry;
import com.google.gerrit.server.mime.MimeUtilFileTypeRegistry;
import com.google.gerrit.server.notedb.NoteDbModule;
@@ -163,6 +163,7 @@
import com.google.gerrit.server.project.CommentLinkProvider;
import com.google.gerrit.server.project.PermissionCollection;
import com.google.gerrit.server.project.ProjectCacheImpl;
+import com.google.gerrit.server.project.ProjectNameLockManager;
import com.google.gerrit.server.project.ProjectNode;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.project.SectionSortCache;
@@ -188,7 +189,6 @@
import com.google.inject.internal.UniqueAnnotations;
import com.google.template.soy.tofu.SoyTofu;
import java.util.List;
-import org.apache.velocity.runtime.RuntimeInstance;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.transport.PostReceiveHook;
import org.eclipse.jgit.transport.PostUploadHook;
@@ -250,7 +250,6 @@
factory(ChangeData.AssistedFactory.class);
factory(ChangeJson.AssistedFactory.class);
factory(CreateChangeSender.Factory.class);
- factory(GroupMembers.Factory.class);
factory(EmailMerge.Factory.class);
factory(MergedSender.Factory.class);
factory(MergeUtil.Factory.class);
@@ -284,7 +283,6 @@
bind(ApprovalsUtil.class);
- bind(RuntimeInstance.class).toProvider(VelocityRuntimeProvider.class);
bind(SoyTofu.class).annotatedWith(MailTemplates.class).toProvider(MailSoyTofuProvider.class);
bind(FromAddressGenerator.class).toProvider(FromAddressGeneratorProvider.class).in(SINGLETON);
bind(Boolean.class)
@@ -332,6 +330,7 @@
DynamicSet.setOf(binder(), AccountIndexedListener.class);
DynamicSet.setOf(binder(), ChangeIndexedListener.class);
DynamicSet.setOf(binder(), GroupIndexedListener.class);
+ DynamicSet.setOf(binder(), ProjectIndexedListener.class);
DynamicSet.setOf(binder(), NewProjectCreatedListener.class);
DynamicSet.setOf(binder(), ProjectDeletedListener.class);
DynamicSet.setOf(binder(), GarbageCollectorListener.class);
@@ -377,6 +376,8 @@
DynamicItem.itemOf(binder(), AccountPatchReviewStore.class);
DynamicSet.setOf(binder(), AssigneeValidationListener.class);
DynamicSet.setOf(binder(), ActionVisitor.class);
+ DynamicItem.itemOf(binder(), MergeSuperSetComputation.class);
+ DynamicItem.itemOf(binder(), ProjectNameLockManager.class);
DynamicMap.mapOf(binder(), MailFilter.class);
bind(MailFilter.class).annotatedWith(Exports.named("ListMailFilter")).to(ListMailFilter.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java
index 5d88ec0..3c9168a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java
@@ -20,7 +20,6 @@
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.RequestCleanup;
import com.google.gerrit.server.project.PerRequestProjectControlCache;
-import com.google.gerrit.server.project.ProjectControl;
import com.google.inject.servlet.RequestScoped;
/** Bindings for {@link RequestScoped} entities. */
@@ -32,6 +31,5 @@
bind(IdentifiedUser.RequestFactory.class).in(SINGLETON);
bind(PerRequestProjectControlCache.class).in(RequestScoped.class);
- bind(ProjectControl.Factory.class).in(SINGLETON);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ScheduleConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ScheduleConfig.java
index 4a87474..c5d60a3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/ScheduleConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ScheduleConfig.java
@@ -16,16 +16,16 @@
import com.google.common.annotations.VisibleForTesting;
import java.text.MessageFormat;
+import java.time.DayOfWeek;
+import java.time.Duration;
+import java.time.LocalTime;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.time.temporal.ChronoUnit;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.lib.Config;
-import org.joda.time.DateTime;
-import org.joda.time.LocalDateTime;
-import org.joda.time.LocalTime;
-import org.joda.time.MutableDateTime;
-import org.joda.time.format.DateTimeFormat;
-import org.joda.time.format.DateTimeFormatter;
-import org.joda.time.format.ISODateTimeFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -49,16 +49,16 @@
}
public ScheduleConfig(Config rc, String section, String subsection) {
- this(rc, section, subsection, DateTime.now());
+ this(rc, section, subsection, ZonedDateTime.now());
}
public ScheduleConfig(
Config rc, String section, String subsection, String keyInterval, String keyStartTime) {
- this(rc, section, subsection, keyInterval, keyStartTime, DateTime.now());
+ this(rc, section, subsection, keyInterval, keyStartTime, ZonedDateTime.now());
}
@VisibleForTesting
- ScheduleConfig(Config rc, String section, String subsection, DateTime now) {
+ ScheduleConfig(Config rc, String section, String subsection, ZonedDateTime now) {
this(rc, section, subsection, KEY_INTERVAL, KEY_STARTTIME, now);
}
@@ -69,7 +69,7 @@
String subsection,
String keyInterval,
String keyStartTime,
- DateTime now) {
+ ZonedDateTime now) {
this.rc = rc;
this.section = section;
this.subsection = subsection;
@@ -122,31 +122,24 @@
String section,
String subsection,
String keyStartTime,
- DateTime now,
+ ZonedDateTime now,
long interval) {
long delay = MISSING_CONFIG;
String start = rc.getString(section, subsection, keyStartTime);
try {
if (start != null) {
- DateTimeFormatter formatter;
- MutableDateTime startTime = now.toMutableDateTime();
+ DateTimeFormatter formatter =
+ DateTimeFormatter.ofPattern("[E ]HH:mm").withLocale(Locale.US);
+ LocalTime firstStartTime = LocalTime.parse(start, formatter);
+ ZonedDateTime startTime = now.with(firstStartTime);
try {
- formatter = ISODateTimeFormat.hourMinute();
- LocalTime firstStartTime = formatter.parseLocalTime(start);
- startTime.hourOfDay().set(firstStartTime.getHourOfDay());
- startTime.minuteOfHour().set(firstStartTime.getMinuteOfHour());
- } catch (IllegalArgumentException e1) {
- formatter = DateTimeFormat.forPattern("E HH:mm").withLocale(Locale.US);
- LocalDateTime firstStartDateTime = formatter.parseLocalDateTime(start);
- startTime.dayOfWeek().set(firstStartDateTime.getDayOfWeek());
- startTime.hourOfDay().set(firstStartDateTime.getHourOfDay());
- startTime.minuteOfHour().set(firstStartDateTime.getMinuteOfHour());
+ DayOfWeek dayOfWeek = formatter.parse(start, DayOfWeek::from);
+ startTime = startTime.with(dayOfWeek);
+ } catch (DateTimeParseException ignored) {
+ // Day of week is an optional parameter.
}
- startTime.secondOfMinute().set(0);
- startTime.millisOfSecond().set(0);
- long s = startTime.getMillis();
- long n = now.getMillis();
- delay = (s - n) % interval;
+ startTime = startTime.truncatedTo(ChronoUnit.MINUTES);
+ delay = Duration.between(now, startTime).toMillis() % interval;
if (delay <= 0) {
delay += interval;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
index afd78dc..2614eaf 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
@@ -56,6 +56,7 @@
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.patch.PatchListEntry;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
+import com.google.gerrit.server.patch.PatchListObjectTooLargeException;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gwtorm.server.OrmException;
@@ -425,6 +426,8 @@
p.insertions = patch.getInsertions();
patchSetAttribute.files.add(p);
}
+ } catch (PatchListObjectTooLargeException e) {
+ log.warn("Cannot get patch list: " + e.getMessage());
} catch (PatchListNotAvailableException e) {
log.warn("Cannot get patch list", e);
}
@@ -498,6 +501,8 @@
p.kind = changeKindCache.getChangeKind(db, change, patchSet);
} catch (IOException | OrmException e) {
log.error("Cannot load patch set data for " + patchSet.getId(), e);
+ } catch (PatchListObjectTooLargeException e) {
+ log.warn(String.format("Cannot get size information for %s: %s", pId, e.getMessage()));
} catch (PatchListNotAvailableException e) {
log.error(String.format("Cannot get size information for %s.", pId), e);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ChangeAbandoned.java b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ChangeAbandoned.java
index f9fc60a..ef69616 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ChangeAbandoned.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ChangeAbandoned.java
@@ -25,6 +25,8 @@
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.GpgException;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
+import com.google.gerrit.server.patch.PatchListObjectTooLargeException;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import java.io.IOException;
@@ -70,7 +72,13 @@
util.logEventListenerError(this, l, e);
}
}
- } catch (PatchListNotAvailableException | GpgException | IOException | OrmException e) {
+ } catch (PatchListObjectTooLargeException e) {
+ log.warn("Couldn't fire event: " + e.getMessage());
+ } catch (PatchListNotAvailableException
+ | GpgException
+ | IOException
+ | OrmException
+ | PermissionBackendException e) {
log.error("Couldn't fire event", e);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ChangeMerged.java b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ChangeMerged.java
index feaa54a..e9ae356 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ChangeMerged.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ChangeMerged.java
@@ -25,6 +25,8 @@
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.GpgException;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
+import com.google.gerrit.server.patch.PatchListObjectTooLargeException;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import java.io.IOException;
@@ -64,7 +66,13 @@
util.logEventListenerError(this, l, e);
}
}
- } catch (PatchListNotAvailableException | GpgException | IOException | OrmException e) {
+ } catch (PatchListObjectTooLargeException e) {
+ log.warn("Couldn't fire event: " + e.getMessage());
+ } catch (PatchListNotAvailableException
+ | GpgException
+ | IOException
+ | OrmException
+ | PermissionBackendException e) {
log.error("Couldn't fire event", e);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ChangeRestored.java b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ChangeRestored.java
index 03a6f1f..c25deab 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ChangeRestored.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ChangeRestored.java
@@ -25,6 +25,8 @@
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.GpgException;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
+import com.google.gerrit.server.patch.PatchListObjectTooLargeException;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import java.io.IOException;
@@ -63,7 +65,13 @@
util.logEventListenerError(this, l, e);
}
}
- } catch (PatchListNotAvailableException | GpgException | IOException | OrmException e) {
+ } catch (PatchListObjectTooLargeException e) {
+ log.warn("Couldn't fire event: " + e.getMessage());
+ } catch (PatchListNotAvailableException
+ | GpgException
+ | IOException
+ | OrmException
+ | PermissionBackendException e) {
log.error("Couldn't fire event", e);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/CommentAdded.java b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/CommentAdded.java
index e76a032..77cd1a8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/CommentAdded.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/CommentAdded.java
@@ -26,6 +26,8 @@
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.GpgException;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
+import com.google.gerrit.server.patch.PatchListObjectTooLargeException;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import java.io.IOException;
@@ -74,7 +76,13 @@
util.logEventListenerError(this, l, e);
}
}
- } catch (PatchListNotAvailableException | GpgException | IOException | OrmException e) {
+ } catch (PatchListObjectTooLargeException e) {
+ log.warn("Couldn't fire event: " + e.getMessage());
+ } catch (PatchListNotAvailableException
+ | GpgException
+ | IOException
+ | OrmException
+ | PermissionBackendException e) {
log.error("Couldn't fire event", e);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/EventUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/EventUtil.java
index be308a9..95d7132 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/EventUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/EventUtil.java
@@ -29,6 +29,7 @@
import com.google.gerrit.server.GpgException;
import com.google.gerrit.server.change.ChangeJson;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -80,12 +81,14 @@
}
public RevisionInfo revisionInfo(Project project, PatchSet ps)
- throws OrmException, PatchListNotAvailableException, GpgException, IOException {
+ throws OrmException, PatchListNotAvailableException, GpgException, IOException,
+ PermissionBackendException {
return revisionInfo(project.getNameKey(), ps);
}
public RevisionInfo revisionInfo(Project.NameKey project, PatchSet ps)
- throws OrmException, PatchListNotAvailableException, GpgException, IOException {
+ throws OrmException, PatchListNotAvailableException, GpgException, IOException,
+ PermissionBackendException {
ChangeData cd = changeDataFactory.create(db.get(), project, ps.getId().getParentKey());
return changeJson.getRevisionInfo(cd, ps);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ReviewerAdded.java b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ReviewerAdded.java
index e4f8572..fc6881d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ReviewerAdded.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ReviewerAdded.java
@@ -26,6 +26,8 @@
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.GpgException;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
+import com.google.gerrit.server.patch.PatchListObjectTooLargeException;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import java.io.IOException;
@@ -67,7 +69,13 @@
util.logEventListenerError(this, l, e);
}
}
- } catch (PatchListNotAvailableException | GpgException | IOException | OrmException e) {
+ } catch (PatchListObjectTooLargeException e) {
+ log.warn("Couldn't fire event: " + e.getMessage());
+ } catch (PatchListNotAvailableException
+ | GpgException
+ | IOException
+ | OrmException
+ | PermissionBackendException e) {
log.error("Couldn't fire event", e);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ReviewerDeleted.java b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ReviewerDeleted.java
index 033efe2..28e07a9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ReviewerDeleted.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ReviewerDeleted.java
@@ -26,6 +26,8 @@
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.GpgException;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
+import com.google.gerrit.server.patch.PatchListObjectTooLargeException;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import java.io.IOException;
@@ -78,7 +80,13 @@
util.logEventListenerError(this, listener, e);
}
}
- } catch (PatchListNotAvailableException | GpgException | IOException | OrmException e) {
+ } catch (PatchListObjectTooLargeException e) {
+ log.warn("Couldn't fire event: " + e.getMessage());
+ } catch (PatchListNotAvailableException
+ | GpgException
+ | IOException
+ | OrmException
+ | PermissionBackendException e) {
log.error("Couldn't fire event", e);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/RevisionCreated.java b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/RevisionCreated.java
index 8a781d0..76779ca 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/RevisionCreated.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/RevisionCreated.java
@@ -25,6 +25,8 @@
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.GpgException;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
+import com.google.gerrit.server.patch.PatchListObjectTooLargeException;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import java.io.IOException;
@@ -64,7 +66,13 @@
util.logEventListenerError(this, l, e);
}
}
- } catch (PatchListNotAvailableException | GpgException | IOException | OrmException e) {
+ } catch (PatchListObjectTooLargeException e) {
+ log.warn("Couldn't fire event: " + e.getMessage());
+ } catch (PatchListNotAvailableException
+ | GpgException
+ | IOException
+ | OrmException
+ | PermissionBackendException e) {
log.error("Couldn't fire event", e);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/VoteDeleted.java b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/VoteDeleted.java
index 71a603c..8944698 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/VoteDeleted.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/VoteDeleted.java
@@ -26,6 +26,8 @@
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.server.GpgException;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
+import com.google.gerrit.server.patch.PatchListObjectTooLargeException;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import java.io.IOException;
@@ -78,7 +80,13 @@
util.logEventListenerError(this, l, e);
}
}
- } catch (PatchListNotAvailableException | GpgException | IOException | OrmException e) {
+ } catch (PatchListObjectTooLargeException e) {
+ log.warn("Couldn't fire event: " + e.getMessage());
+ } catch (PatchListNotAvailableException
+ | GpgException
+ | IOException
+ | OrmException
+ | PermissionBackendException e) {
log.error("Couldn't fire event", e);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommit.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommit.java
index 322d158..4991715 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommit.java
@@ -17,12 +17,15 @@
import static com.google.gerrit.reviewdb.client.RefNames.REFS_REJECT_COMMITS;
import static java.nio.charset.StandardCharsets.UTF_8;
-import com.google.gerrit.common.errors.PermissionDeniedException;
+import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.permissions.ProjectPermission;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -70,6 +73,7 @@
private final Provider<IdentifiedUser> currentUser;
private final GitRepositoryManager repoManager;
private final TimeZone tz;
+ private final PermissionBackend permissionBackend;
private NotesBranchUtil.Factory notesBranchUtilFactory;
@Inject
@@ -77,24 +81,23 @@
Provider<IdentifiedUser> currentUser,
GitRepositoryManager repoManager,
@GerritPersonIdent PersonIdent gerritIdent,
- NotesBranchUtil.Factory notesBranchUtilFactory) {
+ NotesBranchUtil.Factory notesBranchUtilFactory,
+ PermissionBackend permissionBackend) {
this.currentUser = currentUser;
this.repoManager = repoManager;
this.notesBranchUtilFactory = notesBranchUtilFactory;
+ this.permissionBackend = permissionBackend;
this.tz = gerritIdent.getTimeZone();
}
public BanCommitResult ban(
- ProjectControl projectControl, List<ObjectId> commitsToBan, String reason)
- throws PermissionDeniedException, LockFailureException, IOException {
- if (!projectControl.isOwner()) {
- throw new PermissionDeniedException("Not project owner: not permitted to ban commits");
- }
+ Project.NameKey project, CurrentUser user, List<ObjectId> commitsToBan, String reason)
+ throws AuthException, LockFailureException, IOException, PermissionBackendException {
+ permissionBackend.user(user).project(project).check(ProjectPermission.BAN_COMMIT);
final BanCommitResult result = new BanCommitResult();
NoteMap banCommitNotes = NoteMap.newEmptyMap();
// Add a note for each banned commit to notes.
- final Project.NameKey project = projectControl.getProject().getNameKey();
try (Repository repo = repoManager.openRepository(project);
RevWalk revWalk = new RevWalk(repo);
ObjectInserter inserter = repo.newObjectInserter()) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/receive/HookUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/HookUtil.java
similarity index 89%
rename from gerrit-server/src/main/java/com/google/gerrit/server/git/receive/HookUtil.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/git/HookUtil.java
index 90b220a..1762b95 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/receive/HookUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/HookUtil.java
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package com.google.gerrit.server.git.receive;
+package com.google.gerrit.server.git;
import java.io.IOException;
import java.util.Map;
@@ -21,8 +21,8 @@
import org.eclipse.jgit.transport.BaseReceivePack;
import org.eclipse.jgit.transport.ServiceMayNotContinueException;
-/** Static utilities for writing {@link ReceiveCommits}-related hooks. */
-class HookUtil {
+/** Static utilities for writing git protocol hooks. */
+public class HookUtil {
/**
* Scan and advertise all refs in the repo if refs have not already been advertised; otherwise,
* just return the advertised map.
@@ -31,7 +31,7 @@
* @return map of refs that were advertised.
* @throws ServiceMayNotContinueException if a problem occurred.
*/
- static Map<String, Ref> ensureAllRefsAdvertised(BaseReceivePack rp)
+ public static Map<String, Ref> ensureAllRefsAdvertised(BaseReceivePack rp)
throws ServiceMayNotContinueException {
Map<String, Ref> refs = rp.getAdvertisedRefs();
if (refs != null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/LabelNormalizer.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/LabelNormalizer.java
index 6a332cd..73cda7f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/LabelNormalizer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/LabelNormalizer.java
@@ -24,32 +24,26 @@
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelTypes;
import com.google.gerrit.common.data.LabelValue;
-import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gerrit.server.permissions.LabelPermission;
-import com.google.gerrit.server.permissions.PermissionBackend;
-import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
-import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
/**
- * Normalizes votes on labels according to project config and permissions.
+ * Normalizes votes on labels according to project config.
*
* <p>Votes are recorded in the database for a user based on the state of the project at that time:
- * what labels are defined for the project, and what the user is allowed to vote on. Both of those
- * can change between the time a vote is originally made and a later point, for example when a
- * change is submitted. This class normalizes old votes against current project configuration.
+ * what labels are defined for the project. The label definition can change between the time a vote
+ * is originally made and a later point, for example when a change is submitted. This class
+ * normalizes old votes against current project configuration.
*/
@Singleton
public class LabelNormalizer {
@@ -77,33 +71,24 @@
}
}
- private final Provider<ReviewDb> db;
private final IdentifiedUser.GenericFactory userFactory;
- private final PermissionBackend permissionBackend;
private final ProjectCache projectCache;
@Inject
- LabelNormalizer(
- Provider<ReviewDb> db,
- IdentifiedUser.GenericFactory userFactory,
- PermissionBackend permissionBackend,
- ProjectCache projectCache) {
- this.db = db;
+ LabelNormalizer(IdentifiedUser.GenericFactory userFactory, ProjectCache projectCache) {
this.userFactory = userFactory;
- this.permissionBackend = permissionBackend;
this.projectCache = projectCache;
}
/**
* @param notes change containing the given approvals.
* @param approvals list of approvals.
- * @return copies of approvals normalized to the defined ranges for the label type and permissions
- * for the user. Approvals for unknown labels are not included in the output, nor are
- * approvals where the user has no permissions for that label.
+ * @return copies of approvals normalized to the defined ranges for the label type. Approvals for
+ * unknown labels are not included in the output.
* @throws OrmException
*/
public Result normalize(ChangeNotes notes, Collection<PatchSetApproval> approvals)
- throws OrmException, PermissionBackendException, IOException {
+ throws OrmException, IOException {
IdentifiedUser user = userFactory.create(notes.getChange().getOwner());
return normalize(notes, user, approvals);
}
@@ -112,13 +97,12 @@
* @param notes change notes containing the given approvals.
* @param user current user.
* @param approvals list of approvals.
- * @return copies of approvals normalized to the defined ranges for the label type and permissions
- * for the user. Approvals for unknown labels are not included in the output, nor are
- * approvals where the user has no permissions for that label.
+ * @return copies of approvals normalized to the defined ranges for the label type. Approvals for
+ * unknown labels are not included in the output.
*/
public Result normalize(
ChangeNotes notes, CurrentUser user, Collection<PatchSetApproval> approvals)
- throws PermissionBackendException, IOException {
+ throws IOException {
List<PatchSetApproval> unchanged = Lists.newArrayListWithCapacity(approvals.size());
List<PatchSetApproval> updated = Lists.newArrayListWithCapacity(approvals.size());
List<PatchSetApproval> deleted = Lists.newArrayListWithCapacity(approvals.size());
@@ -142,9 +126,7 @@
}
PatchSetApproval copy = copy(psa);
applyTypeFloor(label, copy);
- if (!applyRightFloor(notes, label, copy)) {
- deleted.add(psa);
- } else if (copy.getValue() != psa.getValue()) {
+ if (copy.getValue() != psa.getValue()) {
updated.add(copy);
} else {
unchanged.add(psa);
@@ -157,26 +139,6 @@
return new PatchSetApproval(src.getPatchSetId(), src);
}
- private boolean applyRightFloor(ChangeNotes notes, LabelType lt, PatchSetApproval a)
- throws PermissionBackendException {
- PermissionBackend.ForChange forChange =
- permissionBackend.user(userFactory.create(a.getAccountId())).database(db).change(notes);
- // Check if the user is allowed to vote on the label at all
- try {
- forChange.check(new LabelPermission(lt.getName()));
- } catch (AuthException e) {
- return false;
- }
- // Squash vote to nearest allowed value
- try {
- forChange.check(new LabelPermission.WithValue(lt.getName(), a.getValue()));
- return true;
- } catch (AuthException e) {
- a.setValue(forChange.squashThenCheck(lt, a.getValue()));
- return true;
- }
- }
-
private void applyTypeFloor(LabelType lt, PatchSetApproval a) {
LabelValue atMin = lt.getMin();
if (atMin != null && a.getValue() < atMin.getValue()) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java
index 50f4975..23f2526 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java
@@ -34,8 +34,6 @@
import java.util.EnumSet;
import java.util.SortedSet;
import java.util.TreeSet;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ConfigConstants;
@@ -111,8 +109,6 @@
}
private final Path basePath;
- private final Lock namesUpdateLock;
- private volatile SortedSet<Project.NameKey> names = new TreeSet<>();
@Inject
LocalDiskRepositoryManager(SitePaths site, @GerritServerConfig Config cfg) {
@@ -120,8 +116,6 @@
if (basePath == null) {
throw new IllegalStateException("gerrit.basePath must be configured");
}
-
- namesUpdateLock = new ReentrantLock(true /* fair */);
}
/**
@@ -144,32 +138,7 @@
if (isUnreasonableName(name)) {
throw new RepositoryNotFoundException("Invalid name: " + name);
}
- File gitDir = path.resolve(name.get()).toFile();
- if (!names.contains(name)) {
- // The this.names list does not hold the project-name but it can still exist
- // on disk; for instance when the project has been created directly on the
- // file-system through replication.
- //
- if (!name.get().endsWith(Constants.DOT_GIT_EXT)) {
- if (FileKey.resolve(gitDir, FS.DETECTED) != null) {
- onCreateProject(name);
- } else {
- throw new RepositoryNotFoundException(gitDir);
- }
- } else {
- final File directory = gitDir;
- if (FileKey.isGitRepository(new File(directory, Constants.DOT_GIT), FS.DETECTED)) {
- onCreateProject(name);
- } else if (FileKey.isGitRepository(
- new File(directory.getParentFile(), directory.getName() + Constants.DOT_GIT_EXT),
- FS.DETECTED)) {
- onCreateProject(name);
- } else {
- throw new RepositoryNotFoundException(gitDir);
- }
- }
- }
- final FileKey loc = FileKey.lenient(gitDir, FS.DETECTED);
+ FileKey loc = FileKey.lenient(path.resolve(name.get()).toFile(), FS.DETECTED);
try {
return RepositoryCache.open(loc);
} catch (IOException e1) {
@@ -189,26 +158,24 @@
}
File dir = FileKey.resolve(path.resolve(name.get()).toFile(), FS.DETECTED);
- FileKey loc;
if (dir != null) {
// Already exists on disk, use the repository we found.
//
Project.NameKey onDiskName = getProjectName(path, dir.getCanonicalFile().toPath());
- onCreateProject(onDiskName);
- loc = FileKey.exact(dir, FS.DETECTED);
-
- if (!names.contains(name)) {
+ if (!onDiskName.equals(name)) {
throw new RepositoryCaseMismatchException(name);
}
- } else {
- // It doesn't exist under any of the standard permutations
- // of the repository name, so prefer the standard bare name.
- //
- String n = name.get() + Constants.DOT_GIT_EXT;
- loc = FileKey.exact(path.resolve(n).toFile(), FS.DETECTED);
+
+ throw new IllegalStateException("Repository already exists: " + name);
}
+ // It doesn't exist under any of the standard permutations
+ // of the repository name, so prefer the standard bare name.
+ //
+ String n = name.get() + Constants.DOT_GIT_EXT;
+ FileKey loc = FileKey.exact(path.resolve(n).toFile(), FS.DETECTED);
+
try {
Repository db = RepositoryCache.open(loc, false);
db.create(true /* bare */);
@@ -231,8 +198,6 @@
"Failed to create ref log for %s in repository %s", RefNames.REFS_CONFIG, name));
}
- onCreateProject(name);
-
return db;
} catch (IOException e1) {
final RepositoryNotFoundException e2;
@@ -242,17 +207,6 @@
}
}
- private void onCreateProject(Project.NameKey newProjectName) {
- namesUpdateLock.lock();
- try {
- SortedSet<Project.NameKey> n = new TreeSet<>(names);
- n.add(newProjectName);
- names = Collections.unmodifiableSortedSet(n);
- } finally {
- namesUpdateLock.unlock();
- }
- }
-
private boolean isUnreasonableName(Project.NameKey nameKey) {
final String name = nameKey.get();
@@ -281,21 +235,9 @@
@Override
public SortedSet<Project.NameKey> list() {
- // The results of this method are cached by ProjectCacheImpl. Control only
- // enters here if the cache was flushed by the administrator to force
- // scanning the filesystem.
- // Don't rely on the cached names collection but update it to contain
- // the set of found project names
ProjectVisitor visitor = new ProjectVisitor(basePath);
scanProjects(visitor);
-
- namesUpdateLock.lock();
- try {
- names = Collections.unmodifiableSortedSet(visitor.found);
- } finally {
- namesUpdateLock.unlock();
- }
- return names;
+ return Collections.unmodifiableSortedSet(visitor.found);
}
protected void scanProjects(ProjectVisitor visitor) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/LocalMergeSuperSetComputation.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/LocalMergeSuperSetComputation.java
new file mode 100644
index 0000000..e681145
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/LocalMergeSuperSetComputation.java
@@ -0,0 +1,260 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableListMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.gerrit.common.data.SubmitTypeRecord;
+import com.google.gerrit.extensions.client.SubmitType;
+import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.git.MergeOpRepoManager.OpenRepo;
+import com.google.gerrit.server.permissions.ChangePermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.project.NoSuchProjectException;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.InternalChangeQuery;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevSort;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Default implementation of MergeSuperSet that does the computation of the merge super set
+ * sequentially on the local Gerrit instance.
+ */
+public class LocalMergeSuperSetComputation implements MergeSuperSetComputation {
+ private static final Logger log = LoggerFactory.getLogger(LocalMergeSuperSetComputation.class);
+
+ public static class Module extends AbstractModule {
+ @Override
+ protected void configure() {
+ DynamicItem.bind(binder(), MergeSuperSetComputation.class)
+ .to(LocalMergeSuperSetComputation.class);
+ }
+ }
+
+ @AutoValue
+ abstract static class QueryKey {
+ private static QueryKey create(Branch.NameKey branch, Iterable<String> hashes) {
+ return new AutoValue_LocalMergeSuperSetComputation_QueryKey(
+ branch, ImmutableSet.copyOf(hashes));
+ }
+
+ abstract Branch.NameKey branch();
+
+ abstract ImmutableSet<String> hashes();
+ }
+
+ private final PermissionBackend permissionBackend;
+ private final Provider<InternalChangeQuery> queryProvider;
+ private final Map<QueryKey, List<ChangeData>> queryCache;
+ private final Map<Branch.NameKey, Optional<RevCommit>> heads;
+
+ @Inject
+ LocalMergeSuperSetComputation(
+ PermissionBackend permissionBackend, Provider<InternalChangeQuery> queryProvider) {
+ this.permissionBackend = permissionBackend;
+ this.queryProvider = queryProvider;
+ this.queryCache = new HashMap<>();
+ this.heads = new HashMap<>();
+ }
+
+ @Override
+ public ChangeSet completeWithoutTopic(
+ ReviewDb db, MergeOpRepoManager orm, ChangeSet changeSet, CurrentUser user)
+ throws OrmException, IOException, PermissionBackendException {
+ Collection<ChangeData> visibleChanges = new ArrayList<>();
+ Collection<ChangeData> nonVisibleChanges = new ArrayList<>();
+
+ // For each target branch we run a separate rev walk to find open changes
+ // reachable from changes already in the merge super set.
+ ImmutableListMultimap<Branch.NameKey, ChangeData> bc =
+ byBranch(Iterables.concat(changeSet.changes(), changeSet.nonVisibleChanges()));
+ for (Branch.NameKey b : bc.keySet()) {
+ OpenRepo or = getRepo(orm, b.getParentKey());
+ List<RevCommit> visibleCommits = new ArrayList<>();
+ List<RevCommit> nonVisibleCommits = new ArrayList<>();
+ for (ChangeData cd : bc.get(b)) {
+ boolean visible = isVisible(db, changeSet, cd, user);
+
+ if (submitType(cd) == SubmitType.CHERRY_PICK) {
+ if (visible) {
+ visibleChanges.add(cd);
+ } else {
+ nonVisibleChanges.add(cd);
+ }
+
+ continue;
+ }
+
+ // Get the underlying git commit object
+ String objIdStr = cd.currentPatchSet().getRevision().get();
+ RevCommit commit = or.rw.parseCommit(ObjectId.fromString(objIdStr));
+
+ // Always include the input, even if merged. This allows
+ // SubmitStrategyOp to correct the situation later, assuming it gets
+ // returned by byCommitsOnBranchNotMerged below.
+ if (visible) {
+ visibleCommits.add(commit);
+ } else {
+ nonVisibleCommits.add(commit);
+ }
+ }
+
+ Set<String> visibleHashes =
+ walkChangesByHashes(visibleCommits, Collections.emptySet(), or, b);
+ Iterables.addAll(visibleChanges, byCommitsOnBranchNotMerged(or, db, b, visibleHashes));
+
+ Set<String> nonVisibleHashes = walkChangesByHashes(nonVisibleCommits, visibleHashes, or, b);
+ Iterables.addAll(nonVisibleChanges, byCommitsOnBranchNotMerged(or, db, b, nonVisibleHashes));
+ }
+
+ return new ChangeSet(visibleChanges, nonVisibleChanges);
+ }
+
+ private static ImmutableListMultimap<Branch.NameKey, ChangeData> byBranch(
+ Iterable<ChangeData> changes) throws OrmException {
+ ImmutableListMultimap.Builder<Branch.NameKey, ChangeData> builder =
+ ImmutableListMultimap.builder();
+ for (ChangeData cd : changes) {
+ builder.put(cd.change().getDest(), cd);
+ }
+ return builder.build();
+ }
+
+ private OpenRepo getRepo(MergeOpRepoManager orm, Project.NameKey project) throws IOException {
+ try {
+ OpenRepo or = orm.getRepo(project);
+ checkState(or.rw.hasRevSort(RevSort.TOPO));
+ return or;
+ } catch (NoSuchProjectException e) {
+ throw new IOException(e);
+ }
+ }
+
+ private boolean isVisible(ReviewDb db, ChangeSet changeSet, ChangeData cd, CurrentUser user)
+ throws PermissionBackendException {
+ boolean visible = changeSet.ids().contains(cd.getId());
+ if (visible
+ && !permissionBackend.user(user).change(cd).database(db).test(ChangePermission.READ)) {
+ // We thought the change was visible, but it isn't.
+ // This can happen if the ACL changes during the
+ // completeChangeSet computation, for example.
+ visible = false;
+ }
+ return visible;
+ }
+
+ private SubmitType submitType(ChangeData cd) throws OrmException {
+ SubmitTypeRecord str = cd.submitTypeRecord();
+ if (!str.isOk()) {
+ logErrorAndThrow("Failed to get submit type for " + cd.getId() + ": " + str.errorMessage);
+ }
+ return str.type;
+ }
+
+ private List<ChangeData> byCommitsOnBranchNotMerged(
+ OpenRepo or, ReviewDb db, Branch.NameKey branch, Set<String> hashes)
+ throws OrmException, IOException {
+ if (hashes.isEmpty()) {
+ return ImmutableList.of();
+ }
+ QueryKey k = QueryKey.create(branch, hashes);
+ List<ChangeData> cached = queryCache.get(k);
+ if (cached != null) {
+ return cached;
+ }
+
+ List<ChangeData> result = new ArrayList<>();
+ Iterable<ChangeData> destChanges =
+ MergeSuperSet.query(queryProvider.get())
+ .byCommitsOnBranchNotMerged(or.repo, db, branch, hashes);
+ for (ChangeData chd : destChanges) {
+ result.add(chd);
+ }
+ queryCache.put(k, result);
+ return result;
+ }
+
+ private Set<String> walkChangesByHashes(
+ Collection<RevCommit> sourceCommits, Set<String> ignoreHashes, OpenRepo or, Branch.NameKey b)
+ throws IOException {
+ Set<String> destHashes = new HashSet<>();
+ or.rw.reset();
+ markHeadUninteresting(or, b);
+ for (RevCommit c : sourceCommits) {
+ String name = c.name();
+ if (ignoreHashes.contains(name)) {
+ continue;
+ }
+ destHashes.add(name);
+ or.rw.markStart(c);
+ }
+ for (RevCommit c : or.rw) {
+ String name = c.name();
+ if (ignoreHashes.contains(name)) {
+ continue;
+ }
+ destHashes.add(name);
+ }
+
+ return destHashes;
+ }
+
+ private void markHeadUninteresting(OpenRepo or, Branch.NameKey b) throws IOException {
+ Optional<RevCommit> head = heads.get(b);
+ if (head == null) {
+ Ref ref = or.repo.getRefDatabase().exactRef(b.get());
+ head = ref != null ? Optional.of(or.rw.parseCommit(ref.getObjectId())) : Optional.empty();
+ heads.put(b, head);
+ }
+ if (head.isPresent()) {
+ or.rw.markUninteresting(head.get());
+ }
+ }
+
+ private void logErrorAndThrow(String msg) throws OrmException {
+ if (log.isErrorEnabled()) {
+ log.error(msg);
+ }
+ throw new OrmException(msg);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeSuperSet.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeSuperSet.java
index 4e0c3ae..b473d20 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeSuperSet.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeSuperSet.java
@@ -17,31 +17,18 @@
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
-import com.google.auto.value.AutoValue;
import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
-import com.google.gerrit.common.data.SubmitTypeRecord;
-import com.google.gerrit.extensions.client.SubmitType;
-import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.change.Submit;
import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.git.MergeOpRepoManager.OpenRepo;
import com.google.gerrit.server.index.change.ChangeField;
import com.google.gerrit.server.permissions.ChangePermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
-import com.google.gerrit.server.project.ChangeControl;
-import com.google.gerrit.server.project.NoSuchProjectException;
-import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.project.SubmitRuleEvaluator;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gwtorm.server.OrmException;
@@ -49,21 +36,10 @@
import com.google.inject.Provider;
import java.io.IOException;
import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
-import java.util.Map;
-import java.util.Optional;
import java.util.Set;
import org.eclipse.jgit.lib.Config;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevSort;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
/**
* Calculates the minimal superset of changes required to be merged.
@@ -76,38 +52,36 @@
* included.
*/
public class MergeSuperSet {
- private static final Logger log = LoggerFactory.getLogger(MergeSuperSet.class);
- public static void reloadChanges(ChangeSet cs) throws OrmException {
- // Clear exactly the fields requested by query() below.
- for (ChangeData cd : cs.changes()) {
+ public static void reloadChanges(ChangeSet changeSet) throws OrmException {
+ // Clear exactly the fields requested by query(InternalChangeQuery) below.
+ for (ChangeData cd : changeSet.changes()) {
cd.reloadChange();
cd.setPatchSets(null);
cd.setMergeable(null);
}
}
- @AutoValue
- abstract static class QueryKey {
- private static QueryKey create(Branch.NameKey branch, Iterable<String> hashes) {
- return new AutoValue_MergeSuperSet_QueryKey(branch, ImmutableSet.copyOf(hashes));
- }
-
- abstract Branch.NameKey branch();
-
- abstract ImmutableSet<String> hashes();
+ public static InternalChangeQuery query(InternalChangeQuery q) {
+ // Request fields required for completing the ChangeSet and converting to
+ // ChangeInfo without having to touch the database or opening the repository
+ // more than necessary. This provides reasonable performance when loading
+ // the change screen; callers that care about reading the latest value of
+ // these fields should clear them explicitly using reloadChanges().
+ Set<String> fields =
+ ImmutableSet.of(
+ ChangeField.CHANGE.getName(),
+ ChangeField.PATCH_SET.getName(),
+ ChangeField.MERGEABLE.getName());
+ return q.setRequestedFields(fields);
}
private final ChangeData.Factory changeDataFactory;
private final Provider<InternalChangeQuery> queryProvider;
private final Provider<MergeOpRepoManager> repoManagerProvider;
+ private final DynamicItem<MergeSuperSetComputation> mergeSuperSetComputation;
private final PermissionBackend permissionBackend;
private final Config cfg;
- private final Map<QueryKey, List<ChangeData>> queryCache;
- private final Map<Branch.NameKey, Optional<RevCommit>> heads;
- private final SubmitRuleEvaluator.Factory submitRuleEvaluatorFactory;
- private final ChangeControl.GenericFactory changeControlFactory;
- private final ProjectCache projectCache;
private MergeOpRepoManager orm;
private boolean closeOrm;
@@ -118,20 +92,14 @@
ChangeData.Factory changeDataFactory,
Provider<InternalChangeQuery> queryProvider,
Provider<MergeOpRepoManager> repoManagerProvider,
- PermissionBackend permissionBackend,
- SubmitRuleEvaluator.Factory submitRuleEvaluatorFactory,
- ChangeControl.GenericFactory changeControlFactory,
- ProjectCache projectCache) {
+ DynamicItem<MergeSuperSetComputation> mergeSuperSetComputation,
+ PermissionBackend permissionBackend) {
this.cfg = cfg;
this.changeDataFactory = changeDataFactory;
this.queryProvider = queryProvider;
this.repoManagerProvider = repoManagerProvider;
+ this.mergeSuperSetComputation = mergeSuperSetComputation;
this.permissionBackend = permissionBackend;
- this.submitRuleEvaluatorFactory = submitRuleEvaluatorFactory;
- this.changeControlFactory = changeControlFactory;
- this.projectCache = projectCache;
- queryCache = new HashMap<>();
- heads = new HashMap<>();
}
public MergeSuperSet setMergeOpRepoManager(MergeOpRepoManager orm) {
@@ -144,14 +112,19 @@
public ChangeSet completeChangeSet(ReviewDb db, Change change, CurrentUser user)
throws IOException, OrmException, PermissionBackendException {
try {
+ if (orm == null) {
+ orm = repoManagerProvider.get();
+ closeOrm = true;
+ }
+
ChangeData cd = changeDataFactory.create(db, change.getProject(), change.getId());
- ChangeSet cs =
+ ChangeSet changeSet =
new ChangeSet(
cd, permissionBackend.user(user).change(cd).database(db).test(ChangePermission.READ));
if (Submit.wholeTopicEnabled(cfg)) {
- return completeChangeSetIncludingTopics(db, cs, user);
+ return completeChangeSetIncludingTopics(db, changeSet, user);
}
- return completeChangeSetWithoutTopic(db, cs, user);
+ return mergeSuperSetComputation.get().completeWithoutTopic(db, orm, changeSet, user);
} finally {
if (closeOrm && orm != null) {
orm.close();
@@ -160,193 +133,13 @@
}
}
- private SubmitType submitType(CurrentUser user, ChangeData cd, PatchSet ps, boolean visible)
- throws OrmException, IOException {
- // Submit type prolog rules mean that the submit type can depend on the
- // submitting user and the content of the change.
- //
- // If the current user can see the change, run that evaluation to get a
- // preview of what would happen on submit. If the current user can't see
- // the change, instead of guessing who would do the submitting, rely on the
- // project configuration and ignore the prolog rule. If the prolog rule
- // doesn't match that, we may pick the wrong submit type and produce a
- // misleading (but still nonzero) count of the non visible changes that
- // would be submitted together with the visible ones.
- if (!visible) {
- return projectCache.checkedGet(cd.project()).getProject().getSubmitType();
- }
-
- SubmitTypeRecord str =
- ps == cd.currentPatchSet()
- ? cd.submitTypeRecord()
- : submitRuleEvaluatorFactory.create(user, cd).setPatchSet(ps).getSubmitType();
- if (!str.isOk()) {
- logErrorAndThrow("Failed to get submit type for " + cd.getId() + ": " + str.errorMessage);
- }
- return str.type;
- }
-
- private static ImmutableListMultimap<Branch.NameKey, ChangeData> byBranch(
- Iterable<ChangeData> changes) throws OrmException {
- ImmutableListMultimap.Builder<Branch.NameKey, ChangeData> builder =
- ImmutableListMultimap.builder();
- for (ChangeData cd : changes) {
- builder.put(cd.change().getDest(), cd);
- }
- return builder.build();
- }
-
- private Set<String> walkChangesByHashes(
- Collection<RevCommit> sourceCommits, Set<String> ignoreHashes, OpenRepo or, Branch.NameKey b)
- throws IOException {
- Set<String> destHashes = new HashSet<>();
- or.rw.reset();
- markHeadUninteresting(or, b);
- for (RevCommit c : sourceCommits) {
- String name = c.name();
- if (ignoreHashes.contains(name)) {
- continue;
- }
- destHashes.add(name);
- or.rw.markStart(c);
- }
- for (RevCommit c : or.rw) {
- String name = c.name();
- if (ignoreHashes.contains(name)) {
- continue;
- }
- destHashes.add(name);
- }
-
- return destHashes;
- }
-
- private ChangeSet completeChangeSetWithoutTopic(ReviewDb db, ChangeSet changes, CurrentUser user)
- throws IOException, OrmException, PermissionBackendException {
- Collection<ChangeData> visibleChanges = new ArrayList<>();
- Collection<ChangeData> nonVisibleChanges = new ArrayList<>();
-
- // For each target branch we run a separate rev walk to find open changes
- // reachable from changes already in the merge super set.
- ImmutableListMultimap<Branch.NameKey, ChangeData> bc =
- byBranch(Iterables.concat(changes.changes(), changes.nonVisibleChanges()));
- for (Branch.NameKey b : bc.keySet()) {
- OpenRepo or = getRepo(b.getParentKey());
- List<RevCommit> visibleCommits = new ArrayList<>();
- List<RevCommit> nonVisibleCommits = new ArrayList<>();
- for (ChangeData cd : bc.get(b)) {
- boolean visible = changes.ids().contains(cd.getId());
- if (visible && !canRead(db, user, cd)) {
- // We thought the change was visible, but it isn't.
- // This can happen if the ACL changes during the
- // completeChangeSet computation, for example.
- visible = false;
- }
-
- // Pick a revision to use for traversal. If any of the patch sets
- // is visible, we use the most recent one. Otherwise, use the current
- // patch set.
- PatchSet ps = cd.currentPatchSet();
- boolean visiblePatchSet = visible;
- ChangeControl ctl = changeControlFactory.controlFor(cd.notes(), user);
- if (!ctl.isPatchVisible(ps, cd)) {
- Iterable<PatchSet> visiblePatchSets = ctl.getVisiblePatchSets(cd.patchSets(), db);
- if (Iterables.isEmpty(visiblePatchSets)) {
- visiblePatchSet = false;
- } else {
- ps = Iterables.getLast(visiblePatchSets);
- }
- }
-
- if (submitType(user, cd, ps, visiblePatchSet) == SubmitType.CHERRY_PICK) {
- if (visible) {
- visibleChanges.add(cd);
- } else {
- nonVisibleChanges.add(cd);
- }
-
- continue;
- }
-
- // Get the underlying git commit object
- String objIdStr = ps.getRevision().get();
- RevCommit commit = or.rw.parseCommit(ObjectId.fromString(objIdStr));
-
- // Always include the input, even if merged. This allows
- // SubmitStrategyOp to correct the situation later, assuming it gets
- // returned by byCommitsOnBranchNotMerged below.
- if (visible) {
- visibleCommits.add(commit);
- } else {
- nonVisibleCommits.add(commit);
- }
- }
-
- Set<String> visibleHashes =
- walkChangesByHashes(visibleCommits, Collections.emptySet(), or, b);
- Iterables.addAll(visibleChanges, byCommitsOnBranchNotMerged(or, db, b, visibleHashes));
-
- Set<String> nonVisibleHashes = walkChangesByHashes(nonVisibleCommits, visibleHashes, or, b);
- Iterables.addAll(nonVisibleChanges, byCommitsOnBranchNotMerged(or, db, b, nonVisibleHashes));
- }
-
- return new ChangeSet(visibleChanges, nonVisibleChanges);
- }
-
- private OpenRepo getRepo(Project.NameKey project) throws IOException {
- if (orm == null) {
- orm = repoManagerProvider.get();
- closeOrm = true;
- }
- try {
- OpenRepo or = orm.getRepo(project);
- checkState(or.rw.hasRevSort(RevSort.TOPO));
- return or;
- } catch (NoSuchProjectException e) {
- throw new IOException(e);
- }
- }
-
- private void markHeadUninteresting(OpenRepo or, Branch.NameKey b) throws IOException {
- Optional<RevCommit> head = heads.get(b);
- if (head == null) {
- Ref ref = or.repo.getRefDatabase().exactRef(b.get());
- head = ref != null ? Optional.of(or.rw.parseCommit(ref.getObjectId())) : Optional.empty();
- heads.put(b, head);
- }
- if (head.isPresent()) {
- or.rw.markUninteresting(head.get());
- }
- }
-
- private List<ChangeData> byCommitsOnBranchNotMerged(
- OpenRepo or, ReviewDb db, Branch.NameKey branch, Set<String> hashes)
- throws OrmException, IOException {
- if (hashes.isEmpty()) {
- return ImmutableList.of();
- }
- QueryKey k = QueryKey.create(branch, hashes);
- List<ChangeData> cached = queryCache.get(k);
- if (cached != null) {
- return cached;
- }
-
- List<ChangeData> result = new ArrayList<>();
- Iterable<ChangeData> destChanges =
- query().byCommitsOnBranchNotMerged(or.repo, db, branch, hashes);
- for (ChangeData chd : destChanges) {
- result.add(chd);
- }
- queryCache.put(k, result);
- return result;
- }
-
/**
- * Completes {@code cs} with any additional changes from its topics
+ * Completes {@code changeSet} with any additional changes from its topics
*
* <p>{@link #completeChangeSetIncludingTopics} calls this repeatedly, alternating with {@link
- * #completeChangeSetWithoutTopic}, to discover what additional changes should be submitted with a
- * change until the set stops growing.
+ * MergeSuperSetComputation#completeWithoutTopic(ReviewDb, MergeOpRepoManager, ChangeSet,
+ * CurrentUser)}, to discover what additional changes should be submitted with a change until the
+ * set stops growing.
*
* <p>{@code topicsSeen} and {@code visibleTopicsSeen} keep track of topics already explored to
* avoid wasted work.
@@ -355,7 +148,7 @@
*/
private ChangeSet topicClosure(
ReviewDb db,
- ChangeSet cs,
+ ChangeSet changeSet,
CurrentUser user,
Set<String> topicsSeen,
Set<String> visibleTopicsSeen)
@@ -363,13 +156,13 @@
List<ChangeData> visibleChanges = new ArrayList<>();
List<ChangeData> nonVisibleChanges = new ArrayList<>();
- for (ChangeData cd : cs.changes()) {
+ for (ChangeData cd : changeSet.changes()) {
visibleChanges.add(cd);
String topic = cd.change().getTopic();
if (Strings.isNullOrEmpty(topic) || visibleTopicsSeen.contains(topic)) {
continue;
}
- for (ChangeData topicCd : query().byTopicOpen(topic)) {
+ for (ChangeData topicCd : byTopicOpen(topic)) {
if (canRead(db, user, topicCd)) {
visibleChanges.add(topicCd);
} else {
@@ -379,13 +172,13 @@
topicsSeen.add(topic);
visibleTopicsSeen.add(topic);
}
- for (ChangeData cd : cs.nonVisibleChanges()) {
+ for (ChangeData cd : changeSet.nonVisibleChanges()) {
nonVisibleChanges.add(cd);
String topic = cd.change().getTopic();
if (Strings.isNullOrEmpty(topic) || topicsSeen.contains(topic)) {
continue;
}
- for (ChangeData topicCd : query().byTopicOpen(topic)) {
+ for (ChangeData topicCd : byTopicOpen(topic)) {
nonVisibleChanges.add(topicCd);
}
topicsSeen.add(topic);
@@ -394,47 +187,27 @@
}
private ChangeSet completeChangeSetIncludingTopics(
- ReviewDb db, ChangeSet changes, CurrentUser user)
+ ReviewDb db, ChangeSet changeSet, CurrentUser user)
throws IOException, OrmException, PermissionBackendException {
Set<String> topicsSeen = new HashSet<>();
Set<String> visibleTopicsSeen = new HashSet<>();
int oldSeen;
int seen = 0;
+ changeSet = topicClosure(db, changeSet, user, topicsSeen, visibleTopicsSeen);
+ seen = topicsSeen.size() + visibleTopicsSeen.size();
+
do {
oldSeen = seen;
-
- changes = completeChangeSetWithoutTopic(db, changes, user);
- changes = topicClosure(db, changes, user, topicsSeen, visibleTopicsSeen);
-
+ changeSet = mergeSuperSetComputation.get().completeWithoutTopic(db, orm, changeSet, user);
+ changeSet = topicClosure(db, changeSet, user, topicsSeen, visibleTopicsSeen);
seen = topicsSeen.size() + visibleTopicsSeen.size();
} while (seen != oldSeen);
- return changes;
+ return changeSet;
}
- private InternalChangeQuery query() {
- // Request fields required for completing the ChangeSet and converting to
- // ChangeInfo without having to touch the database or opening the repository
- // more than necessary. This provides reasonable performance when loading
- // the change screen; callers that care about reading the latest value of
- // these fields should clear them explicitly using reloadChanges().
- Set<String> fields =
- ImmutableSet.of(
- ChangeField.CHANGE.getName(),
- ChangeField.PATCH_SET.getName(),
- ChangeField.MERGEABLE.getName());
- return queryProvider.get().setRequestedFields(fields);
- }
-
- private void logError(String msg) {
- if (log.isErrorEnabled()) {
- log.error(msg);
- }
- }
-
- private void logErrorAndThrow(String msg) throws OrmException {
- logError(msg);
- throw new OrmException(msg);
+ private List<ChangeData> byTopicOpen(String topic) throws OrmException {
+ return query(queryProvider.get()).byTopicOpen(topic);
}
private boolean canRead(ReviewDb db, CurrentUser user, ChangeData cd)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeSuperSetComputation.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeSuperSetComputation.java
new file mode 100644
index 0000000..63405ba
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeSuperSetComputation.java
@@ -0,0 +1,53 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git;
+
+import com.google.gerrit.extensions.annotations.ExtensionPoint;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gwtorm.server.OrmException;
+import java.io.IOException;
+
+/**
+ * Interface to compute the merge super set to detect changes that should be submitted together.
+ *
+ * <p>E.g. to speed up performance implementations could decide to do the computation in batches in
+ * parallel on different server nodes.
+ */
+@ExtensionPoint
+public interface MergeSuperSetComputation {
+
+ /**
+ * Compute the set of changes that should be submitted together. As input a set of changes is
+ * provided for which it is known that they should be submitted together. This method should
+ * complete the set by including open predecessor changes that need to be submitted as well. To
+ * decide whether open predecessor changes should be included the method must take the submit type
+ * into account (e.g. for changes with submit type "Cherry-Pick" open predecessor changes must not
+ * be included).
+ *
+ * <p>This method is invoked iteratively while new changes to be submitted together are discovered
+ * by expanding the topics of the changes. This method must not do any topic expansion on its own.
+ *
+ * @param db {@link ReviewDb} instance
+ * @param orm {@link MergeOpRepoManager} that should be used to access repositories
+ * @param changeSet A set of changes for which it is known that they should be submitted together
+ * @param user The user for which the visibility checks should be performed
+ * @return the completed set of changes that should be submitted together
+ */
+ ChangeSet completeWithoutTopic(
+ ReviewDb db, MergeOpRepoManager orm, ChangeSet changeSet, CurrentUser user)
+ throws OrmException, IOException, PermissionBackendException;
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
index 5ee9c45..bb811cd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
@@ -19,11 +19,9 @@
import com.google.common.base.CharMatcher;
import com.google.common.base.Joiner;
-import com.google.common.base.MoreObjects;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
@@ -33,6 +31,7 @@
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.common.data.GroupReference;
+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.Permission;
@@ -67,6 +66,7 @@
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.Optional;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
@@ -156,9 +156,6 @@
private static final String KEY_VALUE = "value";
private static final String KEY_CAN_OVERRIDE = "canOverride";
private static final String KEY_BRANCH = "branch";
- private static final ImmutableSet<String> LABEL_FUNCTIONS =
- ImmutableSet.of(
- "MaxWithBlock", "AnyWithBlock", "MaxNoBlock", "NoBlock", "NoOp", "PatchSetLock");
private static final String REVIEWER = "reviewer";
private static final String KEY_ENABLE_REVIEWER_BY_EMAIL = "enableByEmail";
@@ -868,19 +865,20 @@
continue;
}
- String functionName =
- MoreObjects.firstNonNull(rc.getString(LABEL, name, KEY_FUNCTION), "MaxWithBlock");
- if (LABEL_FUNCTIONS.contains(functionName)) {
- label.setFunctionName(functionName);
- } else {
+ String functionName = rc.getString(LABEL, name, KEY_FUNCTION);
+ Optional<LabelFunction> function =
+ functionName != null
+ ? LabelFunction.parse(functionName)
+ : Optional.of(LabelFunction.MAX_WITH_BLOCK);
+ if (!function.isPresent()) {
error(
new ValidationError(
PROJECT_CONFIG,
String.format(
"Invalid %s for label \"%s\". Valid names are: %s",
- KEY_FUNCTION, name, Joiner.on(", ").join(LABEL_FUNCTIONS))));
- label.setFunctionName(null);
+ KEY_FUNCTION, name, Joiner.on(", ").join(LabelFunction.ALL.keySet()))));
}
+ label.setFunction(function.orElse(null));
if (!values.isEmpty()) {
short dv = (short) rc.getInt(LABEL, name, KEY_DEFAULT_VALUE, 0);
@@ -1367,7 +1365,7 @@
String name = e.getKey();
LabelType label = e.getValue();
toUnset.remove(name);
- rc.setString(LABEL, name, KEY_FUNCTION, label.getFunctionName());
+ rc.setString(LABEL, name, KEY_FUNCTION, label.getFunction().getFunctionName());
rc.setInt(LABEL, name, KEY_DEFAULT_VALUE, label.getDefaultValue());
setBooleanConfigKey(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java
index 2dd5f2a..ed86a92 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/VisibleRefFilter.java
@@ -38,7 +38,6 @@
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.permissions.ProjectPermission;
import com.google.gerrit.server.permissions.RefPermission;
-import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gwtorm.server.OrmException;
@@ -80,7 +79,6 @@
private final PermissionBackend.ForProject perm;
private final ProjectState projectState;
private final Repository git;
- private ProjectControl projectCtl;
private boolean showMetadata = true;
private String userEditPrefix;
private Map<Change.Id, Branch.NameKey> visibleChanges;
@@ -141,7 +139,6 @@
Map<String, Ref> result = new HashMap<>();
List<Ref> deferredTags = new ArrayList<>();
- projectCtl = projectState.controlFor(user.get());
for (Ref ref : refs.values()) {
String name = ref.getName();
Change.Id changeId;
@@ -263,10 +260,20 @@
if (visibleChanges == null) {
visible(id);
}
- if (id != null) {
- return (userEditPrefix != null && name.startsWith(userEditPrefix) && visible(id))
- || (visibleChanges.containsKey(id)
- && projectCtl.controlForRef(visibleChanges.get(id)).isEditVisible());
+ if (id == null) {
+ return false;
+ }
+ if (userEditPrefix != null && name.startsWith(userEditPrefix) && visible(id)) {
+ return true;
+ }
+ if (visibleChanges.containsKey(id)) {
+ try {
+ // Default to READ_PRIVATE_CHANGES as there is no special permission for reading edits.
+ perm.ref(visibleChanges.get(id).get()).check(RefPermission.READ_PRIVATE_CHANGES);
+ return true;
+ } catch (PermissionBackendException | AuthException e) {
+ return false;
+ }
}
return false;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/receive/AllRefsWatcher.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/receive/AllRefsWatcher.java
index 4afaacd..c092c43 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/receive/AllRefsWatcher.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/receive/AllRefsWatcher.java
@@ -16,6 +16,7 @@
import static com.google.common.base.Preconditions.checkState;
+import com.google.gerrit.server.git.HookUtil;
import java.util.Map;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.transport.AdvertiseRefsHook;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java
index 22834f3..6eba282 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/receive/AsyncReceiveCommits.java
@@ -33,7 +33,6 @@
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.permissions.ProjectPermission;
import com.google.gerrit.server.project.ContributorAgreementsChecker;
-import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gerrit.server.util.MagicBranch;
@@ -73,7 +72,8 @@
public interface Factory {
AsyncReceiveCommits create(
- ProjectControl projectControl,
+ ProjectState projectState,
+ IdentifiedUser user,
Repository repository,
@Nullable MessageSender messageSender,
SetMultimap<ReviewerStateInternal, Account.Id> extraReviewers);
@@ -106,7 +106,7 @@
private Worker(Collection<ReceiveCommand> commands) {
this.commands = commands;
- rc = factory.create(projectControl, rp, allRefsWatcher, extraReviewers);
+ rc = factory.create(projectState, user, rp, allRefsWatcher, extraReviewers);
rc.init();
rc.setMessageSender(messageSender);
progress = new MultiProgressMonitor(new MessageSenderOutputStream(), "Processing changes");
@@ -165,13 +165,15 @@
}
private final ReceiveCommits.Factory factory;
+ private final PermissionBackend.ForProject perm;
private final ReceivePack rp;
private final ExecutorService executor;
private final RequestScopePropagator scopePropagator;
private final ReceiveConfig receiveConfig;
private final ContributorAgreementsChecker contributorAgreements;
private final long timeoutMillis;
- private final ProjectControl projectControl;
+ private final ProjectState projectState;
+ private final IdentifiedUser user;
private final Repository repo;
private final MessageSender messageSender;
private final SetMultimap<ReviewerStateInternal, Account.Id> extraReviewers;
@@ -190,7 +192,8 @@
Provider<LazyPostReceiveHookChain> lazyPostReceive,
ContributorAgreementsChecker contributorAgreements,
@Named(TIMEOUT_NAME) long timeoutMillis,
- @Assisted ProjectControl projectControl,
+ @Assisted ProjectState projectState,
+ @Assisted IdentifiedUser user,
@Assisted Repository repo,
@Assisted @Nullable MessageSender messageSender,
@Assisted SetMultimap<ReviewerStateInternal, Account.Id> extraReviewers)
@@ -201,22 +204,21 @@
this.receiveConfig = receiveConfig;
this.contributorAgreements = contributorAgreements;
this.timeoutMillis = timeoutMillis;
- this.projectControl = projectControl;
+ this.projectState = projectState;
+ this.user = user;
this.repo = repo;
this.messageSender = messageSender;
this.extraReviewers = extraReviewers;
- IdentifiedUser user = projectControl.getUser().asIdentifiedUser();
- ProjectState state = projectControl.getProjectState();
- Project.NameKey projectName = projectControl.getProject().getNameKey();
+ Project.NameKey projectName = projectState.getNameKey();
rp = new ReceivePack(repo);
rp.setAllowCreates(true);
rp.setAllowDeletes(true);
rp.setAllowNonFastForwards(true);
rp.setRefLogIdent(user.newRefLogIdent());
rp.setTimeout(transferConfig.getTimeout());
- rp.setMaxObjectSizeLimit(transferConfig.getEffectiveMaxObjectSizeLimit(state));
- rp.setCheckReceivedObjects(state.getConfig().getCheckReceivedObjects());
+ rp.setMaxObjectSizeLimit(transferConfig.getEffectiveMaxObjectSizeLimit(projectState));
+ rp.setCheckReceivedObjects(projectState.getConfig().getCheckReceivedObjects());
rp.setRefFilter(new ReceiveRefFilter());
rp.setAllowPushOptions(true);
rp.setPreReceiveHook(this);
@@ -224,8 +226,9 @@
// If the user lacks READ permission, some references may be filtered and hidden from view.
// Check objects mentioned inside the incoming pack file are reachable from visible refs.
+ this.perm = permissionBackend.user(user).project(projectName);
try {
- permissionBackend.user(user).project(projectName).check(ProjectPermission.READ);
+ this.perm.check(ProjectPermission.READ);
} catch (AuthException e) {
rp.setCheckReferencedObjectsAreReachable(receiveConfig.checkReferencedObjectsAreReachable);
}
@@ -233,28 +236,28 @@
List<AdvertiseRefsHook> advHooks = new ArrayList<>(4);
allRefsWatcher = new AllRefsWatcher();
advHooks.add(allRefsWatcher);
- advHooks.add(refFilterFactory.create(state, repo).setShowMetadata(false));
+ advHooks.add(refFilterFactory.create(projectState, repo).setShowMetadata(false));
advHooks.add(new ReceiveCommitsAdvertiseRefsHook(queryProvider, projectName));
advHooks.add(new HackPushNegotiateHook());
rp.setAdvertiseRefsHook(AdvertiseRefsHookChain.newChain(advHooks));
}
/** Determine if the user can upload commits. */
- public Capable canUpload() throws IOException {
- Capable result = projectControl.canPushToAtLeastOneRef();
- if (result != Capable.OK) {
- return result;
+ public Capable canUpload() throws IOException, PermissionBackendException {
+ try {
+ perm.check(ProjectPermission.PUSH_AT_LEAST_ONE_REF);
+ } catch (AuthException e) {
+ return new Capable("Upload denied for project '" + projectState.getName() + "'");
}
try {
- contributorAgreements.check(
- projectControl.getProject().getNameKey(), projectControl.getUser());
+ contributorAgreements.check(projectState.getNameKey(), user);
} catch (AuthException e) {
return new Capable(e.getMessage());
}
if (receiveConfig.checkMagicRefs) {
- return MagicBranch.checkMagicBranchRefs(repo, projectControl.getProject());
+ return MagicBranch.checkMagicBranchRefs(repo, projectState.getProject());
}
return Capable.OK;
}
@@ -269,7 +272,7 @@
log.warn(
String.format(
"Error in ReceiveCommits while processing changes for project %s",
- projectControl.getProject().getName()),
+ projectState.getName()),
e);
rp.sendError("internal error while processing changes");
// ReceiveCommits has tried its best to catch errors, so anything at this
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/receive/ReceiveCommits.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
index f88dc81..a0e1402 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/receive/ReceiveCommits.java
@@ -128,12 +128,12 @@
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.permissions.ProjectPermission;
import com.google.gerrit.server.permissions.RefPermission;
import com.google.gerrit.server.project.CreateRefControl;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.InternalChangeQuery;
@@ -229,7 +229,8 @@
interface Factory {
ReceiveCommits create(
- ProjectControl projectControl,
+ ProjectState projectState,
+ IdentifiedUser user,
ReceivePack receivePack,
AllRefsWatcher allRefsWatcher,
SetMultimap<ReviewerStateInternal, Account.Id> extraReviewers);
@@ -301,7 +302,6 @@
private final CommitValidators.Factory commitValidatorsFactory;
private final DynamicMap<ProjectConfigEntry> pluginConfigEntries;
private final DynamicSet<ReceivePackInitializer> initializers;
- private final IdentifiedUser user;
private final MergedByPushOp.Factory mergedByPushOpFactory;
private final NotesMigration notesMigration;
private final PatchSetInfoFactory patchSetInfoFactory;
@@ -326,7 +326,8 @@
// Assisted injected fields.
private final AllRefsWatcher allRefsWatcher;
private final ImmutableSetMultimap<ReviewerStateInternal, Account.Id> extraReviewers;
- private final ProjectControl projectControl;
+ private final ProjectState projectState;
+ private final IdentifiedUser user;
private final ReceivePack rp;
// Immutable fields derived from constructor arguments.
@@ -407,7 +408,8 @@
TagCache tagCache,
CreateRefControl createRefControl,
DynamicItem<ChangeReportFormatter> changeFormatterProvider,
- @Assisted ProjectControl projectControl,
+ @Assisted ProjectState projectState,
+ @Assisted IdentifiedUser user,
@Assisted ReceivePack rp,
@Assisted AllRefsWatcher allRefsWatcher,
@Assisted SetMultimap<ReviewerStateInternal, Account.Id> extraReviewers)
@@ -420,7 +422,6 @@
this.changeInserterFactory = changeInserterFactory;
this.commitValidatorsFactory = commitValidatorsFactory;
this.changeFormatter = changeFormatterProvider.get();
- this.user = projectControl.getUser().asIdentifiedUser();
this.db = db;
this.editUtil = editUtil;
this.hashtagsFactory = hashtagsFactory;
@@ -451,13 +452,14 @@
// Assisted injected fields.
this.allRefsWatcher = allRefsWatcher;
this.extraReviewers = ImmutableSetMultimap.copyOf(extraReviewers);
- this.projectControl = projectControl;
+ this.projectState = projectState;
+ this.user = user;
this.rp = rp;
// Immutable fields derived from constructor arguments.
repo = rp.getRepository();
- project = projectControl.getProject();
- labelTypes = projectControl.getProjectState().getLabelTypes();
+ project = projectState.getProject();
+ labelTypes = projectState.getLabelTypes();
permissions = permissionBackend.user(user).project(project.getNameKey());
receiveId = RequestId.forProject(project.getNameKey());
rejectCommits = BanCommit.loadRejectCommitsMap(rp.getRepository(), rp.getRevWalk());
@@ -475,8 +477,7 @@
newChanges = Collections.emptyList();
// Other settings populated during processing.
- newChangeForAllNotInTarget =
- projectControl.getProjectState().isCreateNewChangeForAllNotInTarget();
+ newChangeForAllNotInTarget = projectState.isCreateNewChangeForAllNotInTarget();
// Handles for outputting back over the wire to the end user.
messageSender = new ReceivePackMessageSender();
@@ -484,7 +485,7 @@
void init() {
for (ReceivePackInitializer i : initializers) {
- i.init(projectControl.getProject().getNameKey(), rp);
+ i.init(projectState.getNameKey(), rp);
}
}
@@ -819,8 +820,7 @@
continue;
}
- if (projectControl.getProjectState().isAllUsers()
- && RefNames.REFS_USERS_SELF.equals(cmd.getRefName())) {
+ if (projectState.isAllUsers() && RefNames.REFS_USERS_SELF.equals(cmd.getRefName())) {
String newName = RefNames.refsUsers(user.getAccountId());
logDebug("Swapping out command for {} to {}", RefNames.REFS_USERS_SELF, newName);
final ReceiveCommand orgCmd = cmd;
@@ -871,8 +871,14 @@
if (isConfig(cmd)) {
logDebug("Processing {} command", cmd.getRefName());
- if (!projectControl.isOwner()) {
- reject(cmd, "not project owner");
+ try {
+ permissions.check(ProjectPermission.WRITE_CONFIG);
+ } catch (AuthException e) {
+ reject(
+ cmd,
+ String.format(
+ "must be either project owner or have %s permission",
+ ProjectPermission.WRITE_CONFIG.describeForException()));
continue;
}
@@ -927,16 +933,14 @@
ProjectConfigEntry configEntry = e.getProvider().get();
String value = pluginCfg.getString(e.getExportName());
String oldValue =
- projectControl
- .getProjectState()
+ projectState
.getConfig()
.getPluginConfig(e.getPluginName())
.getString(e.getExportName());
if (configEntry.getType() == ProjectConfigEntryType.ARRAY) {
oldValue =
Arrays.stream(
- projectControl
- .getProjectState()
+ projectState
.getConfig()
.getPluginConfig(e.getPluginName())
.getStringList(e.getExportName()))
@@ -944,7 +948,7 @@
}
if ((value == null ? oldValue != null : !value.equals(oldValue))
- && !configEntry.isEditable(projectControl.getProjectState())) {
+ && !configEntry.isEditable(projectState)) {
reject(
cmd,
String.format(
@@ -1451,7 +1455,7 @@
reject(cmd, "see help");
return;
}
- if (projectControl.getProjectState().isAllUsers() && RefNames.REFS_USERS_SELF.equals(ref)) {
+ if (projectState.isAllUsers() && RefNames.REFS_USERS_SELF.equals(ref)) {
logDebug("Handling {}", RefNames.REFS_USERS_SELF);
ref = RefNames.refsUsers(user.getAccountId());
}
@@ -1470,7 +1474,7 @@
magicBranch.dest = new Branch.NameKey(project.getNameKey(), ref);
magicBranch.perm = permissions.ref(ref);
- if (!projectControl.getProject().getState().permitsWrite()) {
+ if (!projectState.getProject().getState().permitsWrite()) {
reject(cmd, "project state does not permit write");
return;
}
@@ -2492,7 +2496,7 @@
replaceOp =
replaceOpFactory
.create(
- projectControl,
+ projectState,
notes.getChange().getDest(),
checkMergedInto,
priorPatchSet,
@@ -2572,7 +2576,11 @@
}
if (isConfig(cmd)) {
logDebug("Reloading project in cache");
- projectCache.evict(project);
+ try {
+ projectCache.evict(project);
+ } catch (IOException e) {
+ log.warn("Cannot evict from project cache, name key: " + project.getName(), e);
+ }
ProjectState ps = projectCache.get(project.getNameKey());
try {
logDebug("Updating project description");
@@ -2790,8 +2798,7 @@
// TODO(dborowitz): Combine this BatchUpdate with the main one in
// insertChangesAndPatchSets.
try (BatchUpdate bu =
- batchUpdateFactory.create(
- db, projectControl.getProject().getNameKey(), user, TimeUtil.nowTs());
+ batchUpdateFactory.create(db, projectState.getNameKey(), user, TimeUtil.nowTs());
ObjectInserter ins = repo.newObjectInserter();
ObjectReader reader = ins.newReader();
RevWalk rw = new RevWalk(reader)) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/receive/ReceiveCommitsAdvertiseRefsHook.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/receive/ReceiveCommitsAdvertiseRefsHook.java
index 3645392..a90668a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/receive/ReceiveCommitsAdvertiseRefsHook.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/receive/ReceiveCommitsAdvertiseRefsHook.java
@@ -22,6 +22,7 @@
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.git.HookUtil;
import com.google.gerrit.server.index.change.ChangeField;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.InternalChangeQuery;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/receive/ReplaceOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/receive/ReplaceOp.java
index 4455aed..9220bc9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/receive/ReplaceOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/receive/ReplaceOp.java
@@ -54,7 +54,7 @@
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.update.BatchUpdateOp;
import com.google.gerrit.server.update.ChangeContext;
@@ -84,7 +84,7 @@
public class ReplaceOp implements BatchUpdateOp {
public interface Factory {
ReplaceOp create(
- ProjectControl projectControl,
+ ProjectState projectState,
Branch.NameKey dest,
boolean checkMergedInto,
@Assisted("priorPatchSetId") PatchSet.Id priorPatchSetId,
@@ -117,7 +117,7 @@
private final ReplacePatchSetSender.Factory replacePatchSetFactory;
private final ProjectCache projectCache;
- private final ProjectControl projectControl;
+ private final ProjectState projectState;
private final Branch.NameKey dest;
private final boolean checkMergedInto;
private final PatchSet.Id priorPatchSetId;
@@ -159,7 +159,7 @@
ReplacePatchSetSender.Factory replacePatchSetFactory,
ProjectCache projectCache,
@SendEmailExecutor ExecutorService sendEmailExecutor,
- @Assisted ProjectControl projectControl,
+ @Assisted ProjectState projectState,
@Assisted Branch.NameKey dest,
@Assisted boolean checkMergedInto,
@Assisted("priorPatchSetId") PatchSet.Id priorPatchSetId,
@@ -186,7 +186,7 @@
this.projectCache = projectCache;
this.sendEmailExecutor = sendEmailExecutor;
- this.projectControl = projectControl;
+ this.projectState = projectState;
this.dest = dest;
this.checkMergedInto = checkMergedInto;
this.priorPatchSetId = priorPatchSetId;
@@ -205,7 +205,7 @@
ctx.getRevWalk().parseBody(commit);
changeKind =
changeKindCache.getChangeKind(
- projectControl.getProject().getNameKey(),
+ projectState.getNameKey(),
ctx.getRevWalk(),
ctx.getRepoView().getConfig(),
priorCommitId,
@@ -299,7 +299,7 @@
approvalsUtil.addApprovalsForNewPatchSet(
ctx.getDb(),
update,
- projectControl.getProjectState().getLabelTypes(),
+ projectState.getLabelTypes(),
newPatchSet,
ctx.getUser(),
approvals);
@@ -314,7 +314,7 @@
approvalsUtil.addReviewers(
ctx.getDb(),
update,
- projectControl.getProjectState().getLabelTypes(),
+ projectState.getLabelTypes(),
change,
newPatchSet,
info,
@@ -406,7 +406,7 @@
continue;
}
- LabelType lt = projectControl.getProjectState().getLabelTypes().byLabel(a.getLabelId());
+ LabelType lt = projectState.getLabelTypes().byLabel(a.getLabelId());
if (lt != null) {
current.put(lt.getName(), a);
}
@@ -496,8 +496,7 @@
public void run() {
try {
ReplacePatchSetSender cm =
- replacePatchSetFactory.create(
- projectControl.getProject().getNameKey(), notes.getChangeId());
+ replacePatchSetFactory.create(projectState.getNameKey(), notes.getChangeId());
cm.setFrom(ctx.getAccount().getId());
cm.setPatchSet(newPatchSet, info);
cm.setChangeMessage(msg.getMessage(), ctx.getWhen());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java
index 2a22c1c..79c0cdb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java
@@ -50,7 +50,6 @@
import com.google.gerrit.server.git.TagCache;
import com.google.gerrit.server.git.validators.OnSubmitValidators;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
-import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.query.change.InternalChangeQuery;
@@ -107,7 +106,6 @@
final AccountCache accountCache;
final ApprovalsUtil approvalsUtil;
- final ChangeControl.GenericFactory changeControlFactory;
final ChangeMerged changeMerged;
final ChangeMessagesUtil cmUtil;
final EmailMerge.Factory mergedSenderFactory;
@@ -146,7 +144,6 @@
Arguments(
AccountCache accountCache,
ApprovalsUtil approvalsUtil,
- ChangeControl.GenericFactory changeControlFactory,
ChangeMerged changeMerged,
ChangeMessagesUtil cmUtil,
EmailMerge.Factory mergedSenderFactory,
@@ -178,7 +175,6 @@
@Assisted boolean dryrun) {
this.accountCache = accountCache;
this.approvalsUtil = approvalsUtil;
- this.changeControlFactory = changeControlFactory;
this.changeMerged = changeMerged;
this.mergedSenderFactory = mergedSenderFactory;
this.repoManager = repoManager;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyOp.java
index 152d398..9a362d4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyOp.java
@@ -45,7 +45,6 @@
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.git.SubmoduleException;
import com.google.gerrit.server.notedb.ChangeUpdate;
-import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.update.BatchUpdateOp;
import com.google.gerrit.server.update.ChangeContext;
@@ -330,7 +329,7 @@
}
private void setApproval(ChangeContext ctx, IdentifiedUser user)
- throws OrmException, IOException, PermissionBackendException {
+ throws OrmException, IOException {
Change.Id id = ctx.getChange().getId();
List<SubmitRecord> records = args.commitStatus.getSubmitRecords(id);
PatchSet.Id oldPsId = toMerge.getPatchsetId();
@@ -352,7 +351,7 @@
}
private LabelNormalizer.Result approve(ChangeContext ctx, ChangeUpdate update)
- throws OrmException, IOException, PermissionBackendException {
+ throws OrmException, IOException {
PatchSet.Id psId = update.getPatchSetId();
Map<PatchSetApproval.Key, PatchSetApproval> byKey = new HashMap<>();
for (PatchSetApproval psa :
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/AddMembers.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/AddMembers.java
index b6bcb3b..24b3f36 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/AddMembers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/AddMembers.java
@@ -204,8 +204,7 @@
return result;
}
- static class PutMember implements RestModifyView<GroupResource, PutMember.Input> {
- static class Input {}
+ static class PutMember implements RestModifyView<GroupResource, Input> {
private final AddMembers put;
private final String id;
@@ -216,7 +215,7 @@
}
@Override
- public AccountInfo apply(GroupResource resource, PutMember.Input input)
+ public AccountInfo apply(GroupResource resource, Input input)
throws AuthException, MethodNotAllowedException, ResourceNotFoundException, OrmException,
IOException, ConfigInvalidException {
AddMembers.Input in = new AddMembers.Input();
@@ -234,7 +233,7 @@
}
@Singleton
- static class UpdateMember implements RestModifyView<MemberResource, PutMember.Input> {
+ static class UpdateMember implements RestModifyView<MemberResource, Input> {
private final GetMember get;
@Inject
@@ -243,7 +242,7 @@
}
@Override
- public AccountInfo apply(MemberResource resource, PutMember.Input input) throws OrmException {
+ public AccountInfo apply(MemberResource resource, Input input) throws OrmException {
// Do nothing, the user is already a member.
return get.apply(resource);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/AddSubgroups.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/AddSubgroups.java
index 2ce168f..f60a8ce 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/AddSubgroups.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/AddSubgroups.java
@@ -114,8 +114,7 @@
return result;
}
- static class PutSubgroup implements RestModifyView<GroupResource, PutSubgroup.Input> {
- static class Input {}
+ static class PutSubgroup implements RestModifyView<GroupResource, Input> {
private final AddSubgroups addSubgroups;
private final String id;
@@ -144,7 +143,7 @@
}
@Singleton
- static class UpdateSubgroup implements RestModifyView<SubgroupResource, PutSubgroup.Input> {
+ static class UpdateSubgroup implements RestModifyView<SubgroupResource, Input> {
private final Provider<GetSubgroup> get;
@Inject
@@ -153,7 +152,7 @@
}
@Override
- public GroupInfo apply(SubgroupResource resource, PutSubgroup.Input input) throws OrmException {
+ public GroupInfo apply(SubgroupResource resource, Input input) throws OrmException {
// Do nothing, the group is already included.
return get.get().apply(resource);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteMembers.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteMembers.java
index 1069e1c..64de014 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteMembers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteMembers.java
@@ -82,8 +82,7 @@
}
@Singleton
- static class DeleteMember implements RestModifyView<MemberResource, DeleteMember.Input> {
- static class Input {}
+ static class DeleteMember implements RestModifyView<MemberResource, Input> {
private final Provider<DeleteMembers> delete;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteSubgroups.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteSubgroups.java
index 14df51b..43c7d59 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteSubgroups.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteSubgroups.java
@@ -82,8 +82,7 @@
}
@Singleton
- static class DeleteSubgroup implements RestModifyView<SubgroupResource, DeleteSubgroup.Input> {
- static class Input {}
+ static class DeleteSubgroup implements RestModifyView<SubgroupResource, Input> {
private final Provider<DeleteSubgroups> delete;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/Index.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/Index.java
index b61f954..b2845fa 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/Index.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/Index.java
@@ -14,13 +14,13 @@
package com.google.gerrit.server.group;
+import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.server.account.GroupCache;
-import com.google.gerrit.server.group.Index.Input;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.IOException;
@@ -28,7 +28,6 @@
@Singleton
public class Index implements RestModifyView<GroupResource, Input> {
- public static class Input {}
private final GroupCache groupCache;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java
index 910468f..6fbfd67 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java
@@ -28,6 +28,7 @@
import com.google.gerrit.extensions.client.ListGroupsOption;
import com.google.gerrit.extensions.common.GroupInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.extensions.restapi.Url;
@@ -41,7 +42,6 @@
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupControl;
-import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.ProjectState;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -56,6 +56,7 @@
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
+import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import org.kohsuke.args4j.Option;
@@ -67,7 +68,7 @@
protected final GroupCache groupCache;
- private final List<ProjectControl> projects = new ArrayList<>();
+ private final List<ProjectState> projects = new ArrayList<>();
private final Set<AccountGroup.UUID> groupsToInspect = new HashSet<>();
private final GroupControl.Factory groupControlFactory;
private final GroupControl.GenericFactory genericGroupControlFactory;
@@ -77,6 +78,7 @@
private final GroupJson json;
private final GroupBackend groupBackend;
private final Groups groups;
+ private final GroupsCollection groupsCollection;
private final Provider<ReviewDb> db;
private EnumSet<ListGroupsOption> options = EnumSet.noneOf(ListGroupsOption.class);
@@ -88,13 +90,14 @@
private String matchSubstring;
private String matchRegex;
private String suggest;
+ private String ownedBy;
@Option(
name = "--project",
aliases = {"-p"},
usage = "projects for which the groups should be listed"
)
- public void addProject(ProjectControl project) {
+ public void addProject(ProjectState project) {
projects.add(project);
}
@@ -209,6 +212,11 @@
options.addAll(ListGroupsOption.fromBits(Integer.parseInt(hex, 16)));
}
+ @Option(name = "--owned-by", usage = "list groups owned by the given group uuid")
+ public void setOwnedBy(String ownedBy) {
+ this.ownedBy = ownedBy;
+ }
+
@Inject
protected ListGroups(
final GroupCache groupCache,
@@ -217,6 +225,7 @@
final Provider<IdentifiedUser> identifiedUser,
final IdentifiedUser.GenericFactory userFactory,
final GetGroups accountGetGroups,
+ final GroupsCollection groupsCollection,
GroupJson json,
GroupBackend groupBackend,
Groups groups,
@@ -230,6 +239,7 @@
this.json = json;
this.groupBackend = groupBackend;
this.groups = groups;
+ this.groupsCollection = groupsCollection;
this.db = db;
}
@@ -241,13 +251,13 @@
return user;
}
- public List<ProjectControl> getProjects() {
+ public List<ProjectState> getProjects() {
return projects;
}
@Override
public SortedMap<String, GroupInfo> apply(TopLevelResource resource)
- throws OrmException, BadRequestException {
+ throws OrmException, RestApiException {
SortedMap<String, GroupInfo> output = new TreeMap<>();
for (GroupInfo info : get()) {
output.put(MoreObjects.firstNonNull(info.name, "Group " + Url.decode(info.id)), info);
@@ -256,7 +266,7 @@
return output;
}
- public List<GroupInfo> get() throws OrmException, BadRequestException {
+ public List<GroupInfo> get() throws OrmException, RestApiException {
if (!Strings.isNullOrEmpty(suggest)) {
return suggestGroups();
}
@@ -265,6 +275,10 @@
throw new BadRequestException("Specify one of m/r");
}
+ if (ownedBy != null) {
+ return getGroupsOwnedBy(ownedBy);
+ }
+
if (owned) {
return getGroupsOwnedBy(user != null ? userFactory.create(user) : identifiedUser.get());
}
@@ -298,7 +312,6 @@
if (!projects.isEmpty()) {
return projects
.stream()
- .map(ProjectControl::getProjectState)
.map(ProjectState::getAllGroups)
.flatMap(Collection::stream)
.map(GroupReference::getUUID)
@@ -318,9 +331,7 @@
List<GroupReference> groupRefs =
Lists.newArrayList(
Iterables.limit(
- groupBackend.suggest(
- suggest,
- projects.stream().findFirst().map(pc -> pc.getProjectState()).orElse(null)),
+ groupBackend.suggest(suggest, projects.stream().findFirst().orElse(null)),
limit <= 0 ? 10 : Math.min(limit, 10)));
List<GroupInfo> groupInfos = Lists.newArrayListWithCapacity(groupRefs.size());
@@ -349,6 +360,9 @@
if (owned) {
return true;
}
+ if (ownedBy != null) {
+ return true;
+ }
if (start != 0) {
return true;
}
@@ -364,14 +378,15 @@
return false;
}
- private List<GroupInfo> getGroupsOwnedBy(IdentifiedUser user) throws OrmException {
+ private List<GroupInfo> filterGroupsOwnedBy(Predicate<GroupDescription.Internal> filter)
+ throws OrmException {
Pattern pattern = getRegexPattern();
Stream<GroupDescription.Internal> foundGroups =
groups
.getAll(db.get())
.map(GroupDescriptions::forAccountGroup)
.filter(group -> !isNotRelevant(pattern, group))
- .filter(group -> isOwner(user, group))
+ .filter(filter)
.sorted(GROUP_COMPARATOR)
.skip(start);
if (limit > 0) {
@@ -385,6 +400,15 @@
return groupInfos;
}
+ private List<GroupInfo> getGroupsOwnedBy(String id) throws OrmException, RestApiException {
+ String uuid = groupsCollection.parse(id).getGroupUUID().get();
+ return filterGroupsOwnedBy(group -> group.getOwnerGroupUUID().get().equals(uuid));
+ }
+
+ private List<GroupInfo> getGroupsOwnedBy(IdentifiedUser user) throws OrmException {
+ return filterGroupsOwnedBy(group -> isOwner(user, group));
+ }
+
private boolean isOwner(CurrentUser user, GroupDescription.Internal group) {
try {
return genericGroupControlFactory.controlFor(user, group.getGroupUUID()).isOwner();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/PutDescription.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/PutDescription.java
index 3d6feea..757ad31 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/PutDescription.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/PutDescription.java
@@ -17,15 +17,14 @@
import com.google.common.base.Strings;
import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.common.errors.NoSuchGroupException;
+import com.google.gerrit.extensions.common.DescriptionInput;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.extensions.restapi.DefaultInput;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
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.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.group.PutDescription.Input;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -34,11 +33,7 @@
import java.util.Objects;
@Singleton
-public class PutDescription implements RestModifyView<GroupResource, Input> {
- public static class Input {
- @DefaultInput public String description;
- }
-
+public class PutDescription implements RestModifyView<GroupResource, DescriptionInput> {
private final Provider<ReviewDb> db;
private final Provider<GroupsUpdate> groupsUpdateProvider;
@@ -50,11 +45,11 @@
}
@Override
- public Response<String> apply(GroupResource resource, Input input)
+ public Response<String> apply(GroupResource resource, DescriptionInput input)
throws AuthException, MethodNotAllowedException, ResourceNotFoundException, OrmException,
IOException {
if (input == null) {
- input = new Input(); // Delete would set description to null.
+ input = new DescriptionInput(); // Delete would set description to null.
}
GroupDescription.Internal internalGroup =
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/PutName.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/PutName.java
index 75a7eb5..3e7fd41 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/PutName.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/PutName.java
@@ -18,16 +18,15 @@
import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.common.errors.NameAlreadyUsedException;
import com.google.gerrit.common.errors.NoSuchGroupException;
+import com.google.gerrit.extensions.common.NameInput;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
-import com.google.gerrit.extensions.restapi.DefaultInput;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.group.PutName.Input;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -35,11 +34,7 @@
import java.io.IOException;
@Singleton
-public class PutName implements RestModifyView<GroupResource, Input> {
- public static class Input {
- @DefaultInput public String name;
- }
-
+public class PutName implements RestModifyView<GroupResource, NameInput> {
private final Provider<ReviewDb> db;
private final Provider<GroupsUpdate> groupsUpdateProvider;
@@ -50,7 +45,7 @@
}
@Override
- public String apply(GroupResource rsrc, Input input)
+ public String apply(GroupResource rsrc, NameInput input)
throws MethodNotAllowedException, AuthException, BadRequestException,
ResourceConflictException, ResourceNotFoundException, OrmException, IOException {
GroupDescription.Internal internalGroup =
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/PutOwner.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/PutOwner.java
index 20e1dbe..6efd6b1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/PutOwner.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/PutOwner.java
@@ -17,17 +17,16 @@
import com.google.common.base.Strings;
import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.common.errors.NoSuchGroupException;
+import com.google.gerrit.extensions.api.groups.OwnerInput;
import com.google.gerrit.extensions.common.GroupInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
-import com.google.gerrit.extensions.restapi.DefaultInput;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.group.PutOwner.Input;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -35,11 +34,7 @@
import java.io.IOException;
@Singleton
-public class PutOwner implements RestModifyView<GroupResource, Input> {
- public static class Input {
- @DefaultInput public String owner;
- }
-
+public class PutOwner implements RestModifyView<GroupResource, OwnerInput> {
private final GroupsCollection groupsCollection;
private final Provider<GroupsUpdate> groupsUpdateProvider;
private final Provider<ReviewDb> db;
@@ -58,7 +53,7 @@
}
@Override
- public GroupInfo apply(GroupResource resource, Input input)
+ public GroupInfo apply(GroupResource resource, OwnerInput input)
throws ResourceNotFoundException, MethodNotAllowedException, AuthException,
BadRequestException, UnprocessableEntityException, OrmException, IOException {
GroupDescription.Internal internalGroup =
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/QueryGroups.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/QueryGroups.java
index c5fd2cb..0a89b2a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/QueryGroups.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/QueryGroups.java
@@ -24,8 +24,6 @@
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.index.query.QueryParseException;
import com.google.gerrit.index.query.QueryResult;
-import com.google.gerrit.server.index.group.GroupIndex;
-import com.google.gerrit.server.index.group.GroupIndexCollection;
import com.google.gerrit.server.query.group.GroupQueryBuilder;
import com.google.gerrit.server.query.group.GroupQueryProcessor;
import com.google.gwtorm.server.OrmException;
@@ -36,7 +34,6 @@
import org.kohsuke.args4j.Option;
public class QueryGroups implements RestReadView<TopLevelResource> {
- private final GroupIndexCollection indexes;
private final GroupQueryBuilder queryBuilder;
private final GroupQueryProcessor queryProcessor;
private final GroupJson json;
@@ -90,11 +87,7 @@
@Inject
protected QueryGroups(
- GroupIndexCollection indexes,
- GroupQueryBuilder queryBuilder,
- GroupQueryProcessor queryProcessor,
- GroupJson json) {
- this.indexes = indexes;
+ GroupQueryBuilder queryBuilder, GroupQueryProcessor queryProcessor, GroupJson json) {
this.queryBuilder = queryBuilder;
this.queryProcessor = queryProcessor;
this.json = json;
@@ -111,11 +104,6 @@
throw new MethodNotAllowedException("query disabled");
}
- GroupIndex searchIndex = indexes.getSearchIndex();
- if (searchIndex == null) {
- throw new MethodNotAllowedException("no group index");
- }
-
if (start != 0) {
queryProcessor.setStart(start);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/DummyIndexModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/DummyIndexModule.java
index 481726b..85d6a7c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/DummyIndexModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/DummyIndexModule.java
@@ -23,6 +23,8 @@
import com.google.gerrit.server.index.change.ChangeIndex;
import com.google.gerrit.server.index.change.DummyChangeIndex;
import com.google.gerrit.server.index.group.GroupIndex;
+import com.google.gerrit.server.index.project.ProjectIndex;
+import com.google.gerrit.server.project.ProjectData;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.inject.AbstractModule;
@@ -48,6 +50,13 @@
}
}
+ private static class DummyProjectIndexFactory implements ProjectIndex.Factory {
+ @Override
+ public ProjectIndex create(Schema<ProjectData> schema) {
+ throw new UnsupportedOperationException();
+ }
+ }
+
@Override
protected void configure() {
install(new IndexModule(1));
@@ -56,5 +65,6 @@
bind(AccountIndex.Factory.class).toInstance(new DummyAccountIndexFactory());
bind(ChangeIndex.Factory.class).toInstance(new DummyChangeIndexFactory());
bind(GroupIndex.Factory.class).toInstance(new DummyGroupIndexFactory());
+ bind(ProjectIndex.Factory.class).toInstance(new DummyProjectIndexFactory());
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java
index 6854a87..8c9a964 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java
@@ -45,6 +45,12 @@
import com.google.gerrit.server.index.group.GroupIndexer;
import com.google.gerrit.server.index.group.GroupIndexerImpl;
import com.google.gerrit.server.index.group.GroupSchemaDefinitions;
+import com.google.gerrit.server.index.project.ProjectIndexCollection;
+import com.google.gerrit.server.index.project.ProjectIndexDefinition;
+import com.google.gerrit.server.index.project.ProjectIndexRewriter;
+import com.google.gerrit.server.index.project.ProjectIndexer;
+import com.google.gerrit.server.index.project.ProjectIndexerImpl;
+import com.google.gerrit.server.index.project.ProjectSchemaDefinitions;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Provides;
@@ -70,7 +76,8 @@
ImmutableList.<SchemaDefinitions<?>>of(
AccountSchemaDefinitions.INSTANCE,
ChangeSchemaDefinitions.INSTANCE,
- GroupSchemaDefinitions.INSTANCE);
+ GroupSchemaDefinitions.INSTANCE,
+ ProjectSchemaDefinitions.INSTANCE);
/** Type of secondary index. */
public static IndexType getIndexType(Injector injector) {
@@ -112,14 +119,22 @@
listener().to(GroupIndexCollection.class);
factory(GroupIndexerImpl.Factory.class);
+ bind(ProjectIndexRewriter.class);
+ bind(ProjectIndexCollection.class);
+ listener().to(ProjectIndexCollection.class);
+ factory(ProjectIndexerImpl.Factory.class);
+
DynamicSet.setOf(binder(), OnlineUpgradeListener.class);
}
@Provides
Collection<IndexDefinition<?, ?, ?>> getIndexDefinitions(
- AccountIndexDefinition accounts, ChangeIndexDefinition changes, GroupIndexDefinition groups) {
+ AccountIndexDefinition accounts,
+ ChangeIndexDefinition changes,
+ GroupIndexDefinition groups,
+ ProjectIndexDefinition projects) {
Collection<IndexDefinition<?, ?, ?>> result =
- ImmutableList.<IndexDefinition<?, ?, ?>>of(accounts, groups, changes);
+ ImmutableList.<IndexDefinition<?, ?, ?>>of(accounts, groups, changes, projects);
Set<String> expected =
FluentIterable.from(ALL_SCHEMA_DEFS).transform(SchemaDefinitions::getName).toSet();
Set<String> actual = FluentIterable.from(result).transform(IndexDefinition::getName).toSet();
@@ -156,6 +171,13 @@
@Provides
@Singleton
+ ProjectIndexer getProjectIndexer(
+ ProjectIndexerImpl.Factory factory, ProjectIndexCollection indexes) {
+ return factory.create(indexes);
+ }
+
+ @Provides
+ @Singleton
@IndexExecutor(INTERACTIVE)
ListeningExecutorService getInteractiveIndexExecutor(
@GerritServerConfig Config config, WorkQueue workQueue) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexUtils.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexUtils.java
index ea9900b..b37ed61 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexUtils.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexUtils.java
@@ -26,6 +26,7 @@
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.index.account.AccountField;
import com.google.gerrit.server.index.group.GroupField;
+import com.google.gerrit.server.index.project.ProjectField;
import com.google.gerrit.server.query.change.SingleGroupUser;
import java.io.IOException;
import java.util.Set;
@@ -94,6 +95,13 @@
return user.toString();
}
+ public static Set<String> projectFields(QueryOptions opts) {
+ Set<String> fs = opts.fields();
+ return fs.contains(ProjectField.NAME.getName())
+ ? fs
+ : Sets.union(fs, ImmutableSet.of(ProjectField.NAME.getName()));
+ }
+
private IndexUtils() {
// hide default constructor
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/project/AllProjectsIndexer.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/project/AllProjectsIndexer.java
new file mode 100644
index 0000000..a53434e
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/project/AllProjectsIndexer.java
@@ -0,0 +1,112 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.index.project;
+
+import static com.google.gerrit.server.git.QueueProvider.QueueType.BATCH;
+
+import com.google.common.base.Stopwatch;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.gerrit.index.SiteIndexer;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.index.IndexExecutor;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectData;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.lib.TextProgressMonitor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Singleton
+public class AllProjectsIndexer extends SiteIndexer<Project.NameKey, ProjectData, ProjectIndex> {
+
+ private static final Logger log = LoggerFactory.getLogger(AllProjectsIndexer.class);
+
+ private final ListeningExecutorService executor;
+ private final ProjectCache projectCache;
+
+ @Inject
+ AllProjectsIndexer(
+ @IndexExecutor(BATCH) ListeningExecutorService executor, ProjectCache projectCache) {
+ this.executor = executor;
+ this.projectCache = projectCache;
+ }
+
+ @Override
+ public SiteIndexer.Result indexAll(final ProjectIndex index) {
+ ProgressMonitor progress = new TextProgressMonitor(new PrintWriter(progressOut));
+ progress.start(2);
+ List<Project.NameKey> names = collectProjects(progress);
+ return reindexProjects(index, names, progress);
+ }
+
+ private SiteIndexer.Result reindexProjects(
+ ProjectIndex index, List<Project.NameKey> names, ProgressMonitor progress) {
+ progress.beginTask("Reindexing projects", names.size());
+ List<ListenableFuture<?>> futures = new ArrayList<>(names.size());
+ AtomicBoolean ok = new AtomicBoolean(true);
+ AtomicInteger done = new AtomicInteger();
+ AtomicInteger failed = new AtomicInteger();
+ Stopwatch sw = Stopwatch.createStarted();
+ for (Project.NameKey name : names) {
+ String desc = "project " + name;
+ ListenableFuture<?> future =
+ executor.submit(
+ () -> {
+ try {
+ projectCache.evict(name);
+ index.replace(projectCache.get(name).toProjectData());
+ verboseWriter.println("Reindexed " + desc);
+ done.incrementAndGet();
+ } catch (Exception e) {
+ failed.incrementAndGet();
+ throw e;
+ }
+ return null;
+ });
+ addErrorListener(future, desc, progress, ok);
+ futures.add(future);
+ }
+
+ try {
+ Futures.successfulAsList(futures).get();
+ } catch (ExecutionException | InterruptedException e) {
+ log.error("Error waiting on project futures", e);
+ return new SiteIndexer.Result(sw, false, 0, 0);
+ }
+
+ progress.endTask();
+ return new SiteIndexer.Result(sw, ok.get(), done.get(), failed.get());
+ }
+
+ private List<Project.NameKey> collectProjects(ProgressMonitor progress) {
+ progress.beginTask("Collecting projects", ProgressMonitor.UNKNOWN);
+ List<Project.NameKey> names = new ArrayList<>();
+ for (Project.NameKey nameKey : projectCache.all()) {
+ names.add(nameKey);
+ }
+ progress.endTask();
+ return names;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/project/IndexedProjectQuery.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/project/IndexedProjectQuery.java
new file mode 100644
index 0000000..41bff05
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/project/IndexedProjectQuery.java
@@ -0,0 +1,34 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.index.project;
+
+import com.google.gerrit.index.Index;
+import com.google.gerrit.index.IndexedQuery;
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.query.DataSource;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.project.ProjectData;
+
+public class IndexedProjectQuery extends IndexedQuery<Project.NameKey, ProjectData>
+ implements DataSource<ProjectData> {
+
+ public IndexedProjectQuery(
+ Index<Project.NameKey, ProjectData> index, Predicate<ProjectData> pred, QueryOptions opts)
+ throws QueryParseException {
+ super(index, pred, opts.convertForBackend());
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectField.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectField.java
new file mode 100644
index 0000000..c4f8e9e
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectField.java
@@ -0,0 +1,44 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.index.project;
+
+import static com.google.gerrit.index.FieldDef.exact;
+import static com.google.gerrit.index.FieldDef.fullText;
+import static com.google.gerrit.index.FieldDef.prefix;
+
+import com.google.common.collect.Iterables;
+import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.SchemaUtil;
+import com.google.gerrit.server.project.ProjectData;
+
+/** Index schema for projects. */
+public class ProjectField {
+
+ public static final FieldDef<ProjectData, String> NAME =
+ exact("name").stored().build(p -> p.getProject().getName());
+
+ public static final FieldDef<ProjectData, String> DESCRIPTION =
+ fullText("description").build(p -> p.getProject().getDescription());
+
+ public static final FieldDef<ProjectData, String> PARENT_NAME =
+ exact("parent_name").build(p -> p.getProject().getParentName());
+
+ public static final FieldDef<ProjectData, Iterable<String>> NAME_PART =
+ prefix("name_part").buildRepeatable(p -> SchemaUtil.getNameParts(p.getProject().getName()));
+
+ public static final FieldDef<ProjectData, Iterable<String>> ANCESTOR_NAME =
+ exact("ancestor_name")
+ .buildRepeatable(p -> Iterables.transform(p.getAncestors(), n -> n.get()));
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectIndex.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectIndex.java
new file mode 100644
index 0000000..5fbdf04
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectIndex.java
@@ -0,0 +1,33 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.index.project;
+
+import com.google.gerrit.index.Index;
+import com.google.gerrit.index.IndexDefinition;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.project.ProjectData;
+import com.google.gerrit.server.query.project.ProjectPredicates;
+
+public interface ProjectIndex extends Index<Project.NameKey, ProjectData> {
+
+ public interface Factory
+ extends IndexDefinition.IndexFactory<Project.NameKey, ProjectData, ProjectIndex> {}
+
+ @Override
+ default Predicate<ProjectData> keyPredicate(Project.NameKey nameKey) {
+ return ProjectPredicates.name(nameKey);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectIndexCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectIndexCollection.java
new file mode 100644
index 0000000..eeebfa1
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectIndexCollection.java
@@ -0,0 +1,29 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.index.project;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.gerrit.index.IndexCollection;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.project.ProjectData;
+import com.google.inject.Singleton;
+
+@Singleton
+public class ProjectIndexCollection
+ extends IndexCollection<Project.NameKey, ProjectData, ProjectIndex> {
+
+ @VisibleForTesting
+ public ProjectIndexCollection() {}
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectIndexDefinition.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectIndexDefinition.java
new file mode 100644
index 0000000..301f209
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectIndexDefinition.java
@@ -0,0 +1,33 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.index.project;
+
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.index.IndexDefinition;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.project.ProjectData;
+import com.google.inject.Inject;
+
+public class ProjectIndexDefinition
+ extends IndexDefinition<Project.NameKey, ProjectData, ProjectIndex> {
+
+ @Inject
+ ProjectIndexDefinition(
+ ProjectIndexCollection indexCollection,
+ ProjectIndex.Factory indexFactory,
+ @Nullable AllProjectsIndexer allProjectsIndexer) {
+ super(ProjectSchemaDefinitions.INSTANCE, indexCollection, indexFactory, allProjectsIndexer);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectIndexRewriter.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectIndexRewriter.java
new file mode 100644
index 0000000..41d8820
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectIndexRewriter.java
@@ -0,0 +1,43 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.index.project;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.gerrit.index.IndexRewriter;
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
+import com.google.gerrit.server.project.ProjectData;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+@Singleton
+public class ProjectIndexRewriter implements IndexRewriter<ProjectData> {
+ private final ProjectIndexCollection indexes;
+
+ @Inject
+ ProjectIndexRewriter(ProjectIndexCollection indexes) {
+ this.indexes = indexes;
+ }
+
+ @Override
+ public Predicate<ProjectData> rewrite(Predicate<ProjectData> in, QueryOptions opts)
+ throws QueryParseException {
+ ProjectIndex index = indexes.getSearchIndex();
+ checkNotNull(index, "no active search index configured for projects");
+ return new IndexedProjectQuery(index, in, opts);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectIndexer.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectIndexer.java
new file mode 100644
index 0000000..e8a8183
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectIndexer.java
@@ -0,0 +1,28 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.index.project;
+
+import com.google.gerrit.reviewdb.client.Project;
+import java.io.IOException;
+
+public interface ProjectIndexer {
+
+ /**
+ * Synchronously index a project.
+ *
+ * @param nameKey name key of project to index.
+ */
+ void index(Project.NameKey nameKey) throws IOException;
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectIndexerImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectIndexerImpl.java
new file mode 100644
index 0000000..2a51f32
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectIndexerImpl.java
@@ -0,0 +1,86 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.index.project;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.common.Nullable;
+import com.google.gerrit.extensions.events.ProjectIndexedListener;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.index.Index;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectData;
+import com.google.inject.assistedinject.Assisted;
+import com.google.inject.assistedinject.AssistedInject;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+
+public class ProjectIndexerImpl implements ProjectIndexer {
+ public interface Factory {
+ ProjectIndexerImpl create(ProjectIndexCollection indexes);
+
+ ProjectIndexerImpl create(@Nullable ProjectIndex index);
+ }
+
+ private final ProjectCache projectCache;
+ private final DynamicSet<ProjectIndexedListener> indexedListener;
+ private final ProjectIndexCollection indexes;
+ private final ProjectIndex index;
+
+ @AssistedInject
+ ProjectIndexerImpl(
+ ProjectCache projectCache,
+ DynamicSet<ProjectIndexedListener> indexedListener,
+ @Assisted ProjectIndexCollection indexes) {
+ this.projectCache = projectCache;
+ this.indexedListener = indexedListener;
+ this.indexes = indexes;
+ this.index = null;
+ }
+
+ @AssistedInject
+ ProjectIndexerImpl(
+ ProjectCache projectCache,
+ DynamicSet<ProjectIndexedListener> indexedListener,
+ @Assisted ProjectIndex index) {
+ this.projectCache = projectCache;
+ this.indexedListener = indexedListener;
+ this.indexes = null;
+ this.index = index;
+ }
+
+ @Override
+ public void index(Project.NameKey nameKey) throws IOException {
+ for (Index<?, ProjectData> i : getWriteIndexes()) {
+ i.replace(projectCache.get(nameKey).toProjectData());
+ }
+ fireProjectIndexedEvent(nameKey.get());
+ }
+
+ private void fireProjectIndexedEvent(String name) {
+ for (ProjectIndexedListener listener : indexedListener) {
+ listener.onProjectIndexed(name);
+ }
+ }
+
+ private Collection<ProjectIndex> getWriteIndexes() {
+ if (indexes != null) {
+ return indexes.getWriteIndexes();
+ }
+
+ return index != null ? Collections.singleton(index) : ImmutableSet.of();
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectSchemaDefinitions.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectSchemaDefinitions.java
new file mode 100644
index 0000000..ccece02
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/project/ProjectSchemaDefinitions.java
@@ -0,0 +1,38 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.index.project;
+
+import static com.google.gerrit.index.SchemaUtil.schema;
+
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.SchemaDefinitions;
+import com.google.gerrit.server.project.ProjectData;
+
+public class ProjectSchemaDefinitions extends SchemaDefinitions<ProjectData> {
+
+ static final Schema<ProjectData> V1 =
+ schema(
+ ProjectField.NAME,
+ ProjectField.DESCRIPTION,
+ ProjectField.PARENT_NAME,
+ ProjectField.NAME_PART,
+ ProjectField.ANCESTOR_NAME);
+
+ public static final ProjectSchemaDefinitions INSTANCE = new ProjectSchemaDefinitions();
+
+ private ProjectSchemaDefinitions() {
+ super("projects", ProjectData.class);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/MailMessage.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/MailMessage.java
index 68b3c23..0d20464 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/MailMessage.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/MailMessage.java
@@ -18,7 +18,7 @@
import com.google.common.collect.ImmutableList;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.server.mail.Address;
-import org.joda.time.DateTime;
+import java.time.Instant;
/**
* A simplified representation of an RFC 2045-2047 mime email message used for representing received
@@ -40,7 +40,7 @@
public abstract ImmutableList<Address> cc();
// Metadata
- public abstract DateTime dateReceived();
+ public abstract Instant dateReceived();
public abstract ImmutableList<String> additionalHeaders();
// Content
@@ -84,7 +84,7 @@
return this;
}
- public abstract Builder dateReceived(DateTime val);
+ public abstract Builder dateReceived(Instant instant);
public abstract ImmutableList.Builder<String> additionalHeadersBuilder();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/RawMailParser.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/RawMailParser.java
index d2f91ed..57fe21f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/RawMailParser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/receive/RawMailParser.java
@@ -33,7 +33,6 @@
import org.apache.james.mime4j.dom.TextBody;
import org.apache.james.mime4j.dom.address.Mailbox;
import org.apache.james.mime4j.message.DefaultMessageBuilder;
-import org.joda.time.DateTime;
/** Parses raw email content received through POP3 or IMAP into an internal {@link MailMessage}. */
public class RawMailParser {
@@ -66,7 +65,9 @@
if (mimeMessage.getSubject() != null) {
messageBuilder.subject(mimeMessage.getSubject());
}
- messageBuilder.dateReceived(new DateTime(mimeMessage.getDate()));
+ if (mimeMessage.getDate() != null) {
+ messageBuilder.dateReceived(mimeMessage.getDate().toInstant());
+ }
// Add From, To and Cc
if (mimeMessage.getFrom() != null && mimeMessage.getFrom().size() > 0) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/ChangeEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/ChangeEmail.java
index 53e7d22..e862c38 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/ChangeEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/ChangeEmail.java
@@ -23,7 +23,6 @@
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetInfo;
@@ -37,6 +36,7 @@
import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListEntry;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
+import com.google.gerrit.server.patch.PatchListObjectTooLargeException;
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
import com.google.gerrit.server.permissions.ChangePermission;
import com.google.gerrit.server.permissions.GlobalPermission;
@@ -115,11 +115,6 @@
patchSetInfo = psi;
}
- @Deprecated
- public void setChangeMessage(ChangeMessage cm) {
- setChangeMessage(cm.getMessage(), cm.getWrittenOn());
- }
-
public void setChangeMessage(String cm, Timestamp t) {
changeMessage = cm;
timestamp = t;
@@ -221,7 +216,7 @@
}
}
- private void setChangeSubjectHeader() throws EmailException {
+ private void setChangeSubjectHeader() {
setHeader("Subject", textTemplate("ChangeSubject"));
}
@@ -236,8 +231,14 @@
return null;
}
- public String getChangeMessageThreadId() throws EmailException {
- return velocify("<gerrit.${change.createdOn.time}.$change.key.get()@$email.gerritHost>");
+ public String getChangeMessageThreadId() {
+ return "<gerrit."
+ + change.getCreatedOn().getTime()
+ + "."
+ + change.getKey().get()
+ + "@"
+ + this.getGerritHost()
+ + ">";
}
/** Format the sender's "cover letter", {@link #getCoverLetter()}. */
@@ -445,17 +446,6 @@
}
@Override
- protected void setupVelocityContext() {
- super.setupVelocityContext();
- velocityContext.put("change", change);
- velocityContext.put("changeId", change.getKey());
- velocityContext.put("coverLetter", getCoverLetter());
- velocityContext.put("fromName", getNameFor(fromId));
- velocityContext.put("patchSet", patchSet);
- velocityContext.put("patchSetInfo", patchSetInfo);
- }
-
- @Override
protected void setupSoyContext() {
super.setupSoyContext();
@@ -493,7 +483,10 @@
patchSetData.put("refName", patchSet.getRefName());
soyContext.put("patchSet", patchSetData);
- // TODO(wyatta): patchSetInfo
+ Map<String, Object> patchSetInfoData = new HashMap<>();
+ patchSetInfoData.put("authorName", patchSetInfo.getAuthor().getName());
+ patchSetInfoData.put("authorEmail", patchSetInfo.getAuthor().getEmail());
+ soyContext.put("patchSetInfo", patchSetInfoData);
footers.add("Gerrit-MessageType: " + messageClass);
footers.add("Gerrit-Change-Id: " + change.getKey().get());
@@ -539,6 +532,9 @@
// Currently these always have a null oldId in the PatchList.
return "[Octopus merge; cannot be formatted as a diff.]\n";
}
+ } catch (PatchListObjectTooLargeException e) {
+ log.warn("Cannot format patch " + e.getMessage());
+ return "";
} catch (PatchListNotAvailableException e) {
log.error("Cannot format patch", e);
return "";
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/CommentSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/CommentSender.java
index 5b7d3b7..8055273 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/CommentSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/CommentSender.java
@@ -35,6 +35,7 @@
import com.google.gerrit.server.patch.PatchFile;
import com.google.gerrit.server.patch.PatchList;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
+import com.google.gerrit.server.patch.PatchListObjectTooLargeException;
import com.google.gerrit.server.util.LabelVote;
import com.google.gwtorm.client.KeyUtil;
import com.google.gwtorm.server.OrmException;
@@ -189,38 +190,6 @@
}
}
- /** No longer used outside Velocity. Remove this method when VTL support is removed. */
- @Deprecated
- public boolean hasInlineComments() {
- return !inlineComments.isEmpty();
- }
-
- /** No longer used outside Velocity. Remove this method when VTL support is removed. */
- @Deprecated
- public String getInlineComments() {
- return getInlineComments(1);
- }
-
- /** No longer used outside Velocity. Remove this method when VTL support is removed. */
- @Deprecated
- public String getInlineComments(int lines) {
- try (Repository repo = getRepository()) {
- StringBuilder cmts = new StringBuilder();
- for (FileCommentGroup group : getGroupedInlineComments(repo)) {
- String link = group.getLink();
- if (link != null) {
- cmts.append(link).append('\n');
- }
- cmts.append(group.getTitle()).append(":\n\n");
- for (Comment c : group.comments) {
- appendComment(cmts, lines, group.fileData, c);
- }
- cmts.append("\n\n");
- }
- return cmts.toString();
- }
- }
-
/**
* @return a list of FileCommentGroup objects representing the inline comments grouped by the
* file.
@@ -232,6 +201,8 @@
if (repo != null) {
try {
patchList = getPatchList();
+ } catch (PatchListObjectTooLargeException e) {
+ log.warn("Failed to get patch list: " + e.getMessage());
} catch (PatchListNotAvailableException e) {
log.error("Failed to get patch list", e);
}
@@ -272,39 +243,6 @@
return groups;
}
- /** No longer used except for Velocity. Remove this method when VTL support is removed. */
- @Deprecated
- private void appendComment(
- StringBuilder out, int contextLines, PatchFile currentFileData, Comment comment) {
- if (comment instanceof RobotComment) {
- RobotComment robotComment = (RobotComment) comment;
- out.append("Robot Comment from ")
- .append(robotComment.robotId)
- .append(" (run ID ")
- .append(robotComment.robotRunId)
- .append("):\n");
- }
- if (comment.range != null) {
- appendRangedComment(out, currentFileData, comment);
- } else {
- appendLineComment(out, contextLines, currentFileData, comment);
- }
- }
-
- /** No longer used except for Velocity. Remove this method when VTL support is removed. */
- @Deprecated
- private void appendRangedComment(StringBuilder out, PatchFile fileData, Comment comment) {
- String prefix = getCommentLinePrefix(comment);
- String emptyPrefix = Strings.padStart(": ", prefix.length(), ' ');
- boolean firstLine = true;
- for (String line : getLinesByRange(comment.range, fileData, comment.side)) {
- out.append(firstLine ? prefix : emptyPrefix).append(line).append('\n');
- firstLine = false;
- }
- appendQuotedParent(out, comment);
- out.append(comment.message.trim()).append('\n');
- }
-
private String getCommentLinePrefix(Comment comment) {
int lineNbr = comment.range == null ? comment.lineNbr : comment.range.startLine;
StringBuilder sb = new StringBuilder();
@@ -336,56 +274,6 @@
return lines;
}
- /** No longer used except for Velocity. Remove this method when VTL support is removed. */
- @Deprecated
- private void appendLineComment(
- StringBuilder out, int contextLines, PatchFile currentFileData, Comment comment) {
- short side = comment.side;
- int lineNbr = comment.lineNbr;
-
- // Initialize maxLines to the known line number.
- int maxLines = lineNbr;
-
- try {
- maxLines = currentFileData.getLineCount(side);
- } catch (IOException err) {
- // The file could not be read, leave the max as is.
- log.warn(String.format("Failed to read file %s on side %d", comment.key.filename, side), err);
- } catch (NoSuchEntityException err) {
- // The file could not be read, leave the max as is.
- log.warn(String.format("Side %d of file %s didn't exist", side, comment.key.filename), err);
- }
-
- int startLine = Math.max(1, lineNbr - contextLines + 1);
- int stopLine = Math.min(maxLines, lineNbr + contextLines);
-
- for (int line = startLine; line <= lineNbr; ++line) {
- appendFileLine(out, currentFileData, side, line);
- }
- appendQuotedParent(out, comment);
- out.append(comment.message.trim()).append('\n');
-
- for (int line = lineNbr + 1; line < stopLine; ++line) {
- appendFileLine(out, currentFileData, side, line);
- }
- }
-
- /** No longer used except for Velocity. Remove this method when VTL support is removed. */
- @Deprecated
- private void appendFileLine(StringBuilder cmts, PatchFile fileData, short side, int line) {
- String lineStr = getLine(fileData, side, line);
- cmts.append("Line ").append(line).append(": ").append(lineStr).append("\n");
- }
-
- /** No longer used except for Velocity. Remove this method when VTL support is removed. */
- @Deprecated
- private void appendQuotedParent(StringBuilder out, Comment child) {
- Optional<Comment> parent = getParent(child);
- if (parent.isPresent()) {
- out.append("> ").append(getShortenedCommentMessage(parent.get())).append('\n');
- }
- }
-
/**
* Get the parent comment of a given comment.
*
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/CreateChangeSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/CreateChangeSender.java
index 6d15d6f..8956f10 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/CreateChangeSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/CreateChangeSender.java
@@ -14,17 +14,20 @@
package com.google.gerrit.server.mail.send;
-import com.google.common.collect.Iterables;
import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.extensions.api.changes.RecipientType;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.WatchConfig.NotifyType;
import com.google.gerrit.server.mail.send.ProjectWatch.Watchers;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.RefPermission;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
+import java.util.stream.StreamSupport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -36,11 +39,20 @@
CreateChangeSender create(Project.NameKey project, Change.Id id);
}
+ private final IdentifiedUser.GenericFactory identifiedUserFactory;
+ private final PermissionBackend permissionBackend;
+
@Inject
public CreateChangeSender(
- EmailArguments ea, @Assisted Project.NameKey project, @Assisted Change.Id id)
+ EmailArguments ea,
+ IdentifiedUser.GenericFactory identifiedUserFactory,
+ PermissionBackend permissionBackend,
+ @Assisted Project.NameKey project,
+ @Assisted Change.Id id)
throws OrmException {
super(ea, newChangeData(ea, project, id));
+ this.identifiedUserFactory = identifiedUserFactory;
+ this.permissionBackend = permissionBackend;
}
@Override
@@ -48,16 +60,13 @@
super.init();
try {
- // Try to mark interested owners with TO and CC or BCC line.
+ // Upgrade watching owners from CC and BCC to TO.
Watchers matching =
getWatchers(NotifyType.NEW_CHANGES, !change.isWorkInProgress() && !change.isPrivate());
- for (Account.Id user :
- Iterables.concat(matching.to.accounts, matching.cc.accounts, matching.bcc.accounts)) {
- if (isOwnerOfProjectOrBranch(user)) {
- add(RecipientType.TO, user);
- }
- }
-
+ // TODO(hiesel): Remove special handling for owners
+ StreamSupport.stream(matching.all().accounts.spliterator(), false)
+ .filter(acc -> isOwnerOfProjectOrBranch(acc))
+ .forEach(acc -> add(RecipientType.TO, acc));
// Add everyone else. Owners added above will not be duplicated.
add(RecipientType.TO, matching.to);
add(RecipientType.CC, matching.cc);
@@ -72,11 +81,10 @@
includeWatchers(NotifyType.NEW_PATCHSETS, !change.isWorkInProgress() && !change.isPrivate());
}
- private boolean isOwnerOfProjectOrBranch(Account.Id user) {
- return projectState != null
- && projectState
- .controlFor(args.identifiedUserFactory.create(user))
- .controlForRef(change.getDest())
- .isOwner();
+ private boolean isOwnerOfProjectOrBranch(Account.Id userId) {
+ return permissionBackend
+ .user(identifiedUserFactory.create(userId))
+ .ref(change.getDest())
+ .testOrFalse(RefPermission.WRITE_CONFIG);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/EmailArguments.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/EmailArguments.java
index 869d7d1..411a98c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/EmailArguments.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/EmailArguments.java
@@ -46,7 +46,6 @@
import com.google.inject.Provider;
import com.google.template.soy.tofu.SoyTofu;
import java.util.List;
-import org.apache.velocity.runtime.RuntimeInstance;
import org.eclipse.jgit.lib.PersonIdent;
public class EmailArguments {
@@ -75,7 +74,6 @@
final ChangeQueryBuilder queryBuilder;
final Provider<ReviewDb> db;
final ChangeData.Factory changeDataFactory;
- final RuntimeInstance velocityRuntime;
final SoyTofu soyTofu;
final EmailSettings settings;
final DynamicSet<OutgoingEmailValidationListener> outgoingEmailValidationListeners;
@@ -106,7 +104,6 @@
ChangeQueryBuilder queryBuilder,
Provider<ReviewDb> db,
ChangeData.Factory changeDataFactory,
- RuntimeInstance velocityRuntime,
@MailTemplates SoyTofu soyTofu,
EmailSettings settings,
@SshAdvertisedAddresses List<String> sshAddresses,
@@ -136,7 +133,6 @@
this.queryBuilder = queryBuilder;
this.db = db;
this.changeDataFactory = changeDataFactory;
- this.velocityRuntime = velocityRuntime;
this.soyTofu = soyTofu;
this.settings = settings;
this.sshAddresses = sshAddresses;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/NotificationEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/NotificationEmail.java
index bceac72..c192dfa 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/NotificationEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/NotificationEmail.java
@@ -45,18 +45,16 @@
setListIdHeader();
}
- private void setListIdHeader() throws EmailException {
+ private void setListIdHeader() {
// Set a reasonable list id so that filters can be used to sort messages
- setVHeader("List-Id", "<$email.listId.replace('@', '.')>");
+ setHeader(
+ "List-Id",
+ "<gerrit-" + branch.getParentKey().get().replace('/', '-') + "." + getGerritHost() + ">");
if (getSettingsUrl() != null) {
- setVHeader("List-Unsubscribe", "<$email.settingsUrl>");
+ setHeader("List-Unsubscribe", "<" + getSettingsUrl() + ">");
}
}
- public String getListId() throws EmailException {
- return velocify("gerrit-$projectName.replace('/', '-')@$email.gerritHost");
- }
-
/** Include users and groups that want notification of events. */
protected void includeWatchers(NotifyType type) {
includeWatchers(type, true);
@@ -103,13 +101,6 @@
}
@Override
- protected void setupVelocityContext() {
- super.setupVelocityContext();
- velocityContext.put("projectName", branch.getParentKey().get());
- velocityContext.put("branch", branch);
- }
-
- @Override
protected void setupSoyContext() {
super.setupSoyContext();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/OutgoingEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/OutgoingEmail.java
index e569adf..ec67d9d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/OutgoingEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/OutgoingEmail.java
@@ -17,7 +17,6 @@
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.gerrit.extensions.client.GeneralPreferencesInfo.EmailStrategy.CC_ON_OWN_COMMENTS;
import static com.google.gerrit.extensions.client.GeneralPreferencesInfo.EmailStrategy.DISABLED;
-import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ListMultimap;
@@ -36,12 +35,8 @@
import com.google.gerrit.server.validators.ValidationException;
import com.google.gwtorm.server.OrmException;
import com.google.template.soy.data.SanitizedContent;
-import java.io.StringReader;
-import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URL;
-import java.nio.file.Files;
-import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
@@ -53,12 +48,6 @@
import java.util.Map;
import java.util.Set;
import java.util.StringJoiner;
-import org.apache.commons.lang.StringUtils;
-import org.apache.velocity.Template;
-import org.apache.velocity.VelocityContext;
-import org.apache.velocity.context.InternalContextAdapterImpl;
-import org.apache.velocity.runtime.RuntimeInstance;
-import org.apache.velocity.runtime.parser.node.SimpleNode;
import org.eclipse.jgit.util.SystemReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -78,7 +67,6 @@
private StringBuilder textBody;
private StringBuilder htmlBody;
private ListMultimap<RecipientType, Account.Id> accountsToNotify = ImmutableListMultimap.of();
- protected VelocityContext velocityContext;
protected Map<String, Object> soyContext;
protected Map<String, Object> soyContextEmailData;
protected List<String> footers;
@@ -237,7 +225,6 @@
* @throws EmailException if an error occurred.
*/
protected void init() throws EmailException {
- setupVelocityContext();
setupSoyContext();
smtpFromAddress = args.fromAddressGenerator.from(fromId);
@@ -309,11 +296,6 @@
return args.urlProvider.get();
}
- /** Set a header in the outgoing message using a template. */
- protected void setVHeader(String name, String value) throws EmailException {
- setHeader(name, velocify(value));
- }
-
/** Set a header in the outgoing message. */
protected void setHeader(String name, String value) {
headers.put(name, new EmailHeader.String(value));
@@ -537,14 +519,6 @@
return new Address(a.getFullName(), e);
}
- protected void setupVelocityContext() {
- velocityContext = new VelocityContext();
-
- velocityContext.put("email", this);
- velocityContext.put("messageClass", messageClass);
- velocityContext.put("StringUtils", StringUtils.class);
- }
-
protected void setupSoyContext() {
soyContext = new HashMap<>();
footers = new ArrayList<>();
@@ -559,41 +533,6 @@
soyContext.put("email", soyContextEmailData);
}
- protected String velocify(String template) throws EmailException {
- try {
- RuntimeInstance runtime = args.velocityRuntime;
- String templateName = "OutgoingEmail";
- SimpleNode tree = runtime.parse(new StringReader(template), templateName);
- InternalContextAdapterImpl ica = new InternalContextAdapterImpl(velocityContext);
- ica.pushCurrentTemplateName(templateName);
- try {
- tree.init(ica, runtime);
- StringWriter w = new StringWriter();
- tree.render(ica, w);
- return w.toString();
- } finally {
- ica.popCurrentTemplateName();
- }
- } catch (Exception e) {
- throw new EmailException("Cannot format velocity template: " + template, e);
- }
- }
-
- protected String velocifyFile(String name) throws EmailException {
- try {
- RuntimeInstance runtime = args.velocityRuntime;
- if (runtime.getLoaderNameForResource(name) == null) {
- name = "com/google/gerrit/server/mail/" + name;
- }
- Template template = runtime.getTemplate(name, UTF_8.name());
- StringWriter w = new StringWriter();
- template.merge(velocityContext, w);
- return w.toString();
- } catch (Exception e) {
- throw new EmailException("Cannot format velocity template " + name, e);
- }
- }
-
private String soyTemplate(String name, SanitizedContent.ContentKind kind) {
return args.soyTofu
.newRenderer("com.google.gerrit.server.mail.template." + name)
@@ -602,7 +541,7 @@
.render();
}
- protected String soyTextTemplate(String name) {
+ protected String textTemplate(String name) {
return soyTemplate(name, SanitizedContent.ContentKind.TEXT);
}
@@ -610,19 +549,6 @@
return soyTemplate(name, SanitizedContent.ContentKind.HTML);
}
- /**
- * Evaluate the named template according to the following priority: 1) Velocity file override,
- * OR... 2) Soy file override, OR... 3) Soy resource.
- */
- protected String textTemplate(String name) throws EmailException {
- String velocityName = name + ".vm";
- Path filePath = args.site.mail_dir.resolve(velocityName);
- if (Files.isRegularFile(filePath)) {
- return velocifyFile(velocityName);
- }
- return soyTextTemplate(name);
- }
-
public String joinStrings(Iterable<Object> in, String joiner) {
return joinStrings(in.iterator(), joiner);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/ProjectWatch.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/ProjectWatch.java
index e1b6e36..a914a73 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/ProjectWatch.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/ProjectWatch.java
@@ -119,12 +119,25 @@
static class List {
protected final Set<Account.Id> accounts = new HashSet<>();
protected final Set<Address> emails = new HashSet<>();
+
+ private static List union(List... others) {
+ List union = new List();
+ for (List other : others) {
+ union.accounts.addAll(other.accounts);
+ union.emails.addAll(other.emails);
+ }
+ return union;
+ }
}
protected final List to = new List();
protected final List cc = new List();
protected final List bcc = new List();
+ List all() {
+ return List.union(to, cc, bcc);
+ }
+
List list(NotifyConfig.Header header) {
switch (header) {
case TO:
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/VelocityRuntimeProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/VelocityRuntimeProvider.java
deleted file mode 100644
index 524bbed..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/VelocityRuntimeProvider.java
+++ /dev/null
@@ -1,118 +0,0 @@
-// Copyright (C) 2011 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.mail.send;
-
-import com.google.gerrit.server.config.SitePaths;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
-import com.google.inject.ProvisionException;
-import com.google.inject.Singleton;
-import java.nio.file.Files;
-import java.util.Properties;
-import org.apache.velocity.runtime.RuntimeConstants;
-import org.apache.velocity.runtime.RuntimeInstance;
-import org.apache.velocity.runtime.RuntimeServices;
-import org.apache.velocity.runtime.log.LogChute;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/** Configures Velocity template engine for sending email. */
-@Singleton
-public class VelocityRuntimeProvider implements Provider<RuntimeInstance> {
- private final SitePaths site;
-
- @Inject
- VelocityRuntimeProvider(SitePaths site) {
- this.site = site;
- }
-
- @Override
- public RuntimeInstance get() {
- String rl = "resource.loader";
- String pkg = "org.apache.velocity.runtime.resource.loader";
-
- Properties p = new Properties();
- p.setProperty(RuntimeConstants.VM_PERM_INLINE_LOCAL, "true");
- p.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS, Slf4jLogChute.class.getName());
- p.setProperty(RuntimeConstants.RUNTIME_REFERENCES_STRICT, "true");
- p.setProperty("runtime.log.logsystem.log4j.category", "velocity");
-
- if (Files.isDirectory(site.mail_dir)) {
- p.setProperty(rl, "file, class");
- p.setProperty("file." + rl + ".class", pkg + ".FileResourceLoader");
- p.setProperty("file." + rl + ".path", site.mail_dir.toAbsolutePath().toString());
- p.setProperty("class." + rl + ".class", pkg + ".ClasspathResourceLoader");
- } else {
- p.setProperty(rl, "class");
- p.setProperty("class." + rl + ".class", pkg + ".ClasspathResourceLoader");
- }
-
- RuntimeInstance ri = new RuntimeInstance();
- try {
- ri.init(p);
- } catch (Exception err) {
- throw new ProvisionException("Cannot configure Velocity templates", err);
- }
- return ri;
- }
-
- /** Connects Velocity to sfl4j. */
- public static class Slf4jLogChute implements LogChute {
- // Logger should be named 'velocity' for consistency with log4j config
- private static final Logger log = LoggerFactory.getLogger("velocity");
-
- @Override
- public void init(RuntimeServices rs) {}
-
- @Override
- public boolean isLevelEnabled(int level) {
- switch (level) {
- default:
- case DEBUG_ID:
- return log.isDebugEnabled();
- case INFO_ID:
- return log.isInfoEnabled();
- case WARN_ID:
- return log.isWarnEnabled();
- case ERROR_ID:
- return log.isErrorEnabled();
- }
- }
-
- @Override
- public void log(int level, String message) {
- log(level, message, null);
- }
-
- @Override
- public void log(int level, String msg, Throwable err) {
- switch (level) {
- default:
- case DEBUG_ID:
- log.debug(msg, err);
- break;
- case INFO_ID:
- log.info(msg, err);
- break;
- case WARN_ID:
- log.warn(msg, err);
- break;
- case ERROR_ID:
- log.error(msg, err);
- break;
- }
- }
- }
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
index 7777400..8900a15 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
@@ -103,7 +103,7 @@
try {
PatchList pl = fileCache.get(key, fileLoaderFactory.create(key, project));
if (pl instanceof LargeObjectTombstone) {
- throw new PatchListNotAvailableException(
+ throw new PatchListObjectTooLargeException(
"Error computing " + key + ". Previous attempt failed with LargeObjectException");
}
if (key.getAlgorithm() == PatchListKey.Algorithm.OPTIMIZED_DIFF) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListObjectTooLargeException.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListObjectTooLargeException.java
new file mode 100644
index 0000000..54e0e6c
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListObjectTooLargeException.java
@@ -0,0 +1,27 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.patch;
+
+/**
+ * Exception thrown when the PatchList could not be computed because previous attempts failed with
+ * {@code LargeObjectException}. This is not thrown on the first computation.
+ */
+public class PatchListObjectTooLargeException extends PatchListNotAvailableException {
+ private static final long serialVersionUID = 1L;
+
+ public PatchListObjectTooLargeException(String message) {
+ super(message);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptFactory.java
index 384d4fd..fe158f8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptFactory.java
@@ -37,7 +37,9 @@
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.LargeObjectException;
import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.permissions.ChangePermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gwtorm.server.OrmException;
@@ -90,7 +92,7 @@
private final DiffPreferencesInfo diffPrefs;
private final ChangeEditUtil editReader;
private final Provider<CurrentUser> userProvider;
- private final ChangeControl.GenericFactory changeControlFactory;
+ private final PermissionBackend permissionBackend;
private Optional<ChangeEdit> edit;
private final Change.Id changeId;
@@ -113,7 +115,7 @@
CommentsUtil commentsUtil,
ChangeEditUtil editReader,
Provider<CurrentUser> userProvider,
- ChangeControl.GenericFactory changeControlFactory,
+ PermissionBackend permissionBackend,
@Assisted ChangeNotes notes,
@Assisted String fileName,
@Assisted("patchSetA") @Nullable PatchSet.Id patchSetA,
@@ -128,7 +130,7 @@
this.commentsUtil = commentsUtil;
this.editReader = editReader;
this.userProvider = userProvider;
- this.changeControlFactory = changeControlFactory;
+ this.permissionBackend = permissionBackend;
this.fileName = fileName;
this.psa = patchSetA;
@@ -149,7 +151,7 @@
CommentsUtil commentsUtil,
ChangeEditUtil editReader,
Provider<CurrentUser> userProvider,
- ChangeControl.GenericFactory changeControlFactory,
+ PermissionBackend permissionBackend,
@Assisted ChangeNotes notes,
@Assisted String fileName,
@Assisted int parentNum,
@@ -164,7 +166,7 @@
this.commentsUtil = commentsUtil;
this.editReader = editReader;
this.userProvider = userProvider;
- this.changeControlFactory = changeControlFactory;
+ this.permissionBackend = permissionBackend;
this.fileName = fileName;
this.psa = null;
@@ -187,7 +189,7 @@
@Override
public PatchScript call()
throws OrmException, LargeObjectException, AuthException, InvalidChangeOperationException,
- IOException {
+ IOException, PermissionBackendException {
if (parentNum < 0) {
validatePatchSetId(psa);
}
@@ -195,10 +197,16 @@
PatchSet psEntityA = psa != null ? psUtil.get(db, notes, psa) : null;
PatchSet psEntityB = psb.get() == 0 ? new PatchSet(psb) : psUtil.get(db, notes, psb);
-
- ChangeControl ctl = changeControlFactory.controlFor(notes, userProvider.get());
- if ((psEntityA != null && !ctl.isVisible(db)) || (psEntityB != null && !ctl.isVisible(db))) {
- throw new NoSuchChangeException(changeId);
+ if (psEntityA != null || psEntityB != null) {
+ try {
+ permissionBackend
+ .user(userProvider)
+ .change(notes)
+ .database(db)
+ .check(ChangePermission.READ);
+ } catch (AuthException e) {
+ throw new NoSuchChangeException(changeId);
+ }
}
try (Repository git = repoManager.openRepository(notes.getProjectName())) {
@@ -212,8 +220,7 @@
final PatchScriptBuilder b = newBuilder(list, git);
final PatchListEntry content = list.get(fileName);
- loadCommentsAndHistory(
- ctl, content.getChangeType(), content.getOldName(), content.getNewName());
+ loadCommentsAndHistory(content.getChangeType(), content.getOldName(), content.getNewName());
return b.toPatchScript(content, comments, history);
} catch (PatchListNotAvailableException e) {
@@ -285,8 +292,7 @@
}
}
- private void loadCommentsAndHistory(
- ChangeControl ctl, ChangeType changeType, String oldName, String newName)
+ private void loadCommentsAndHistory(ChangeType changeType, String oldName, String newName)
throws OrmException {
Map<Patch.Key, Patch> byKey = new HashMap<>();
@@ -298,9 +304,6 @@
//
history = new ArrayList<>();
for (PatchSet ps : psUtil.byChange(db, notes)) {
- if (!ctl.isVisible(db)) {
- continue;
- }
String name = fileName;
if (psa != null) {
switch (changeType) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/permissions/PermissionBackend.java b/gerrit-server/src/main/java/com/google/gerrit/server/permissions/PermissionBackend.java
index 6db9357..c87e8e4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/permissions/PermissionBackend.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/permissions/PermissionBackend.java
@@ -429,65 +429,5 @@
.map((v) -> new LabelPermission.WithValue(label, v))
.collect(toSet());
}
-
- /**
- * Squash a label value to the nearest allowed value.
- *
- * <p>For multi-valued labels like Code-Review with values -2..+2 a user may try to use +2, but
- * only have permission for the -1..+1 range. The caller should have already tried:
- *
- * <pre>
- * check(new LabelPermission.WithValue("Code-Review", 2));
- * </pre>
- *
- * and caught {@link AuthException}. {@code squashThenCheck} will use {@link #test(LabelType)}
- * to determine potential values of Code-Review the user can use, and select the nearest value
- * along the same sign, e.g. -1 for -2 and +1 for +2.
- *
- * @param label definition of the label to test values of.
- * @param val previously denied value the user attempted.
- * @return nearest allowed value, or {@code 0} if no value was allowed.
- * @throws PermissionBackendException backend cannot run test or check.
- */
- public short squashThenCheck(LabelType label, short val) throws PermissionBackendException {
- short s = squashByTest(label, val);
- if (s == 0 || s == val) {
- return 0;
- }
- try {
- check(new LabelPermission.WithValue(label, s));
- return s;
- } catch (AuthException e) {
- return 0;
- }
- }
-
- /**
- * Squash a label value to the nearest allowed value using only test methods.
- *
- * <p>Tests all possible values and selects the closet available to {@code val} while matching
- * the sign of {@code val}. Unlike {@code #squashThenCheck(LabelType, short)} this method only
- * uses {@code test} methods and should not be used in contexts like a review handler without
- * checking the resulting score.
- *
- * @param label definition of the label to test values of.
- * @param val previously denied value the user attempted.
- * @return nearest likely allowed value, or {@code 0} if no value was identified.
- * @throws PermissionBackendException backend cannot run test.
- */
- public short squashByTest(LabelType label, short val) throws PermissionBackendException {
- return nearest(test(label), val);
- }
-
- private static short nearest(Iterable<LabelPermission.WithValue> possible, short wanted) {
- short s = 0;
- for (LabelPermission.WithValue v : possible) {
- if ((wanted < 0 && v.value() < 0 && wanted <= v.value() && v.value() < s)
- || (wanted > 0 && v.value() > 0 && wanted >= v.value() && v.value() > s)) {
- s = v.value();
- }
- }
- return s;
- }
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/permissions/ProjectPermission.java b/gerrit-server/src/main/java/com/google/gerrit/server/permissions/ProjectPermission.java
index d0abf9a..6627f76 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/permissions/ProjectPermission.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/permissions/ProjectPermission.java
@@ -76,7 +76,22 @@
RUN_RECEIVE_PACK,
/** Can run upload pack. */
- RUN_UPLOAD_PACK;
+ RUN_UPLOAD_PACK,
+
+ /** Allow read access to refs/meta/config. */
+ READ_CONFIG,
+
+ /** Allow write access to refs/meta/config. */
+ WRITE_CONFIG,
+
+ /** Allow banning commits from Gerrit preventing pushes of these commits. */
+ BAN_COMMIT,
+
+ /** Allow accessing the project's reflog. */
+ READ_REFLOG,
+
+ /** Can push to at least one reference within the repository. */
+ PUSH_AT_LEAST_ONE_REF;
private final String name;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/permissions/RefPermission.java b/gerrit-server/src/main/java/com/google/gerrit/server/permissions/RefPermission.java
index 8b5d8fb..607162e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/permissions/RefPermission.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/permissions/RefPermission.java
@@ -24,6 +24,7 @@
DELETE(Permission.DELETE),
UPDATE(Permission.PUSH),
FORCE_UPDATE,
+ SET_HEAD,
FORGE_AUTHOR(Permission.FORGE_AUTHOR),
FORGE_COMMITTER(Permission.FORGE_COMMITTER),
@@ -41,7 +42,19 @@
* according to the submit strategy, which may include cherry-pick or rebase. By creating changes
* for each commit, automatic server side rebase, and post-update review are enabled.
*/
- UPDATE_BY_SUBMIT;
+ UPDATE_BY_SUBMIT,
+
+ /**
+ * Can read all private changes on the ref. Typically granted to CI systems if they should run on
+ * private changes.
+ */
+ READ_PRIVATE_CHANGES(Permission.VIEW_PRIVATE_CHANGES),
+
+ /** Read access to ref's config section in {@code project.config}. */
+ READ_CONFIG,
+
+ /** Write access to ref's config section in {@code project.config}. */
+ WRITE_CONFIG;
private final String name;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/DisablePlugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/DisablePlugin.java
index a2da580..266350f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/DisablePlugin.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/DisablePlugin.java
@@ -15,32 +15,41 @@
package com.google.gerrit.server.plugins;
import com.google.common.collect.ImmutableSet;
-import com.google.gerrit.common.data.GlobalCapability;
-import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.common.PluginInfo;
-import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
+import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
-import com.google.gerrit.server.plugins.DisablePlugin.Input;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.permissions.GlobalPermission;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.inject.Inject;
+import com.google.inject.Provider;
import com.google.inject.Singleton;
-@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
@Singleton
public class DisablePlugin implements RestModifyView<PluginResource, Input> {
- public static class Input {}
private final PluginLoader loader;
+ private final Provider<IdentifiedUser> user;
+ private final PermissionBackend permissionBackend;
@Inject
- DisablePlugin(PluginLoader loader) {
+ DisablePlugin(
+ PluginLoader loader, Provider<IdentifiedUser> user, PermissionBackend permissionBackend) {
this.loader = loader;
+ this.user = user;
+ this.permissionBackend = permissionBackend;
}
@Override
- public PluginInfo apply(PluginResource resource, Input input) throws MethodNotAllowedException {
- if (!loader.isRemoteAdminEnabled()) {
- throw new MethodNotAllowedException("remote plugin administration is disabled");
+ public PluginInfo apply(PluginResource resource, Input input) throws RestApiException {
+ try {
+ permissionBackend.user(user).check(GlobalPermission.ADMINISTRATE_SERVER);
+ } catch (PermissionBackendException e) {
+ throw new RestApiException("Could not check permission", e);
}
+ loader.checkRemoteAdminEnabled();
String name = resource.getName();
loader.disablePlugins(ImmutableSet.of(name));
return ListPlugins.toPluginInfo(loader.get(name));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/EnablePlugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/EnablePlugin.java
index f29e36b..569bc39 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/EnablePlugin.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/EnablePlugin.java
@@ -17,11 +17,11 @@
import com.google.common.collect.ImmutableSet;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.common.PluginInfo;
-import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
-import com.google.gerrit.server.plugins.EnablePlugin.Input;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.PrintWriter;
@@ -30,7 +30,6 @@
@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
@Singleton
public class EnablePlugin implements RestModifyView<PluginResource, Input> {
- public static class Input {}
private final PluginLoader loader;
@@ -40,11 +39,8 @@
}
@Override
- public PluginInfo apply(PluginResource resource, Input input)
- throws ResourceConflictException, MethodNotAllowedException {
- if (!loader.isRemoteAdminEnabled()) {
- throw new MethodNotAllowedException("remote plugin administration is disabled");
- }
+ public PluginInfo apply(PluginResource resource, Input input) throws RestApiException {
+ loader.checkRemoteAdminEnabled();
String name = resource.getName();
try {
loader.enablePlugins(ImmutableSet.of(name));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/InstallPlugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/InstallPlugin.java
index 531e9ac..ee9099e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/InstallPlugin.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/InstallPlugin.java
@@ -19,8 +19,8 @@
import com.google.gerrit.extensions.common.InstallPluginInput;
import com.google.gerrit.extensions.common.PluginInfo;
import com.google.gerrit.extensions.restapi.BadRequestException;
-import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.inject.Inject;
@@ -56,10 +56,8 @@
@Override
public Response<PluginInfo> apply(TopLevelResource resource, InstallPluginInput input)
- throws BadRequestException, MethodNotAllowedException, IOException {
- if (!loader.isRemoteAdminEnabled()) {
- throw new MethodNotAllowedException("remote installation is disabled");
- }
+ throws RestApiException, IOException {
+ loader.checkRemoteAdminEnabled();
try {
try (InputStream in = openStream(input)) {
String pluginName = loader.installPluginFromStream(name, in);
@@ -104,7 +102,7 @@
@Override
public Response<PluginInfo> apply(PluginResource resource, InstallPluginInput input)
- throws BadRequestException, MethodNotAllowedException, IOException {
+ throws RestApiException, IOException {
return install.get().setName(resource.getName()).apply(TopLevelResource.INSTANCE, input);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java
index d972087..954ea29 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java
@@ -27,6 +27,7 @@
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;
import com.google.gerrit.extensions.events.LifecycleListener;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.systemstatus.ServerInformation;
import com.google.gerrit.server.PluginUser;
import com.google.gerrit.server.cache.PersistentCacheFactory;
@@ -138,6 +139,12 @@
return remoteAdmin;
}
+ public void checkRemoteAdminEnabled() throws MethodNotAllowedException {
+ if (!remoteAdmin) {
+ throw new MethodNotAllowedException("remote plugin administration is disabled");
+ }
+ }
+
public Plugin get(String name) {
Plugin p = running.get(name);
if (p != null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginsCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginsCollection.java
index 768aa86..9dbc956 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginsCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginsCollection.java
@@ -17,8 +17,8 @@
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AcceptsCreate;
import com.google.gerrit.extensions.restapi.IdString;
-import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestCollection;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
@@ -67,11 +67,8 @@
}
@Override
- public InstallPlugin create(TopLevelResource parent, IdString id)
- throws ResourceNotFoundException, MethodNotAllowedException {
- if (!loader.isRemoteAdminEnabled()) {
- throw new MethodNotAllowedException("remote installation is disabled");
- }
+ public InstallPlugin create(TopLevelResource parent, IdString id) throws RestApiException {
+ loader.checkRemoteAdminEnabled();
return install.get().setName(id.get()).setCreated(true);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ReloadPlugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ReloadPlugin.java
index 7b464bb..1134f50 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ReloadPlugin.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ReloadPlugin.java
@@ -17,10 +17,10 @@
import com.google.common.collect.ImmutableList;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.common.PluginInfo;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestModifyView;
-import com.google.gerrit.server.plugins.ReloadPlugin.Input;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.PrintWriter;
@@ -29,7 +29,6 @@
@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
@Singleton
public class ReloadPlugin implements RestModifyView<PluginResource, Input> {
- public static class Input {}
private final PluginLoader loader;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/BanCommit.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/BanCommit.java
index 278b2af..c3623c6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/BanCommit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/BanCommit.java
@@ -15,13 +15,12 @@
package com.google.gerrit.server.project;
import com.google.common.collect.Lists;
-import com.google.gerrit.common.errors.PermissionDeniedException;
-import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.api.projects.BanCommitInput;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.server.git.BanCommitResult;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.BanCommit.BanResultInfo;
-import com.google.gerrit.server.project.BanCommit.Input;
import com.google.gerrit.server.update.BatchUpdate;
import com.google.gerrit.server.update.RetryHelper;
import com.google.gerrit.server.update.RetryingRestModifyView;
@@ -34,22 +33,8 @@
import org.eclipse.jgit.lib.ObjectId;
@Singleton
-public class BanCommit extends RetryingRestModifyView<ProjectResource, Input, BanResultInfo> {
- public static class Input {
- public List<String> commits;
- public String reason;
-
- public static Input fromCommits(String firstCommit, String... moreCommits) {
- return fromCommits(Lists.asList(firstCommit, moreCommits));
- }
-
- public static Input fromCommits(List<String> commits) {
- Input in = new Input();
- in.commits = commits;
- return in;
- }
- }
-
+public class BanCommit
+ extends RetryingRestModifyView<ProjectResource, BanCommitInput, BanResultInfo> {
private final com.google.gerrit.server.git.BanCommit banCommit;
@Inject
@@ -60,8 +45,8 @@
@Override
protected BanResultInfo applyImpl(
- BatchUpdate.Factory updateFactory, ProjectResource rsrc, Input input)
- throws RestApiException, UpdateException, IOException {
+ BatchUpdate.Factory updateFactory, ProjectResource rsrc, BanCommitInput input)
+ throws RestApiException, UpdateException, IOException, PermissionBackendException {
BanResultInfo r = new BanResultInfo();
if (input != null && input.commits != null && !input.commits.isEmpty()) {
List<ObjectId> commitsToBan = new ArrayList<>(input.commits.size());
@@ -73,14 +58,11 @@
}
}
- try {
- BanCommitResult result = banCommit.ban(rsrc.getControl(), commitsToBan, input.reason);
- r.newlyBanned = transformCommits(result.getNewlyBannedCommits());
- r.alreadyBanned = transformCommits(result.getAlreadyBannedCommits());
- r.ignored = transformCommits(result.getIgnoredObjectIds());
- } catch (PermissionDeniedException e) {
- throw new AuthException(e.getMessage());
- }
+ BanCommitResult result =
+ banCommit.ban(rsrc.getNameKey(), rsrc.getUser(), commitsToBan, input.reason);
+ r.newlyBanned = transformCommits(result.getNewlyBannedCommits());
+ r.alreadyBanned = transformCommits(result.getAlreadyBannedCommits());
+ r.ignored = transformCommits(result.getIgnoredObjectIds());
}
return r;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/BranchResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/BranchResource.java
index 2e81af3..622b1dd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/BranchResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/BranchResource.java
@@ -16,6 +16,7 @@
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.server.CurrentUser;
import com.google.inject.TypeLiteral;
import org.eclipse.jgit.lib.Ref;
@@ -26,8 +27,8 @@
private final String refName;
private final String revision;
- public BranchResource(ProjectControl control, Ref ref) {
- super(control);
+ public BranchResource(ProjectState projectState, CurrentUser user, Ref ref) {
+ super(projectState, user);
this.refName = ref.getName();
this.revision = ref.getObjectId() != null ? ref.getObjectId().name() : null;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/BranchesCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/BranchesCollection.java
index a40eabb..52072d8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/BranchesCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/BranchesCollection.java
@@ -85,7 +85,7 @@
.project(project)
.ref(ref.isSymbolic() ? ref.getTarget().getName() : ref.getName())
.check(RefPermission.READ);
- return new BranchResource(parent.getControl(), ref);
+ return new BranchResource(parent.getProjectState(), parent.getUser(), ref);
} catch (AuthException notAllowed) {
throw new ResourceNotFoundException(id);
} catch (RepositoryNotFoundException noRepo) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
index 2203f77..63dd9a9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
@@ -14,20 +14,18 @@
package com.google.gerrit.server.project;
-import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.gerrit.server.permissions.LabelPermission.ForUser.ON_BEHALF_OF;
-import static java.util.stream.Collectors.toList;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.gerrit.common.Nullable;
+import com.google.gerrit.common.data.LabelFunction;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.PermissionRange;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetApproval;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -46,65 +44,15 @@
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
-import java.io.IOException;
import java.util.Collection;
import java.util.EnumSet;
import java.util.Map;
import java.util.Set;
-import java.util.function.Predicate;
/** Access control management for a user accessing a single change. */
-public class ChangeControl {
+class ChangeControl {
@Singleton
- public static class GenericFactory {
- private final ProjectControl.GenericFactory projectControl;
- private final ChangeNotes.Factory notesFactory;
-
- @Inject
- GenericFactory(ProjectControl.GenericFactory p, ChangeNotes.Factory n) {
- projectControl = p;
- notesFactory = n;
- }
-
- public ChangeControl controlFor(
- ReviewDb db, Project.NameKey project, Change.Id changeId, CurrentUser user)
- throws OrmException {
- return controlFor(notesFactory.create(db, project, changeId), user);
- }
-
- public ChangeControl controlFor(ReviewDb db, Change change, CurrentUser user)
- throws OrmException {
- final Project.NameKey projectKey = change.getProject();
- try {
- return projectControl.controlFor(projectKey, user).controlFor(db, change);
- } catch (NoSuchProjectException e) {
- throw new NoSuchChangeException(change.getId(), e);
- } catch (IOException e) {
- // TODO: propagate this exception
- throw new NoSuchChangeException(change.getId(), e);
- }
- }
-
- public ChangeControl controlFor(ChangeNotes notes, CurrentUser user)
- throws NoSuchChangeException {
- try {
- return projectControl.controlFor(notes.getProjectName(), user).controlFor(notes);
- } catch (NoSuchProjectException | IOException e) {
- throw new NoSuchChangeException(notes.getChangeId(), e);
- }
- }
-
- public ChangeControl validateFor(Change.Id changeId, CurrentUser user) throws OrmException {
- return validateFor(notesFactory.createChecked(changeId), user);
- }
-
- public ChangeControl validateFor(ChangeNotes notes, CurrentUser user) throws OrmException {
- return controlFor(notes, user);
- }
- }
-
- @Singleton
- public static class Factory {
+ static class Factory {
private final ChangeData.Factory changeDataFactory;
private final ChangeNotes.Factory notesFactory;
private final ApprovalsUtil approvalsUtil;
@@ -152,7 +100,7 @@
this.patchSetUtil = patchSetUtil;
}
- public ChangeControl forUser(CurrentUser who) {
+ ChangeControl forUser(CurrentUser who) {
if (getUser().equals(who)) {
return this;
}
@@ -160,40 +108,27 @@
changeDataFactory, approvalsUtil, getRefControl().forUser(who), notes, patchSetUtil);
}
- public RefControl getRefControl() {
+ private RefControl getRefControl() {
return refControl;
}
- public CurrentUser getUser() {
+ private CurrentUser getUser() {
return getRefControl().getUser();
}
- public ProjectControl getProjectControl() {
+ private ProjectControl getProjectControl() {
return getRefControl().getProjectControl();
}
- public Project getProject() {
- return getProjectControl().getProject();
- }
-
- public Change.Id getId() {
- return notes.getChangeId();
- }
-
- public Change getChange() {
+ private Change getChange() {
return notes.getChange();
}
- public ChangeNotes getNotes() {
+ private ChangeNotes getNotes() {
return notes;
}
/** Can this user see this change? */
- public boolean isVisible(ReviewDb db) throws OrmException {
- return isVisible(db, null);
- }
-
- /** Can this user see this change? */
private boolean isVisible(ReviewDb db, @Nullable ChangeData cd) throws OrmException {
if (getChange().isPrivate() && !isPrivateVisible(db, cd)) {
return false;
@@ -202,36 +137,10 @@
}
/** Can the user see this change? Does not account for draft status */
- public boolean isRefVisible() {
+ private boolean isRefVisible() {
return getRefControl().isVisible();
}
- /** Can this user see the given patchset? */
- public boolean isPatchVisible(PatchSet ps, ChangeData cd) throws OrmException {
- // TODO(hiesel) These don't need to be migrated, just remove after support for drafts is removed
- checkArgument(
- cd.getId().equals(ps.getId().getParentKey()), "%s not for change %s", ps, cd.getId());
- return isVisible(cd.db());
- }
-
- /**
- * @return patches for the change visible to the current user.
- * @throws OrmException an error occurred reading the database.
- */
- public Collection<PatchSet> getVisiblePatchSets(Collection<PatchSet> patchSets, ReviewDb db)
- throws OrmException {
- // TODO(hiesel) These don't need to be migrated, just remove after support for drafts is removed
- Predicate<? super PatchSet> predicate =
- ps -> {
- try {
- return isVisible(db);
- } catch (OrmException e) {
- return false;
- }
- };
- return patchSets.stream().filter(predicate).collect(toList());
- }
-
/** Can this user abandon this change? */
private boolean canAbandon(ReviewDb db) throws OrmException {
return (isOwner() // owner (aka creator) of the change can abandon
@@ -243,7 +152,7 @@
}
/** Can this user delete this change? */
- public boolean canDelete(Change.Status status) {
+ private boolean canDelete(Change.Status status) {
switch (status) {
case NEW:
case ABANDONED:
@@ -285,7 +194,7 @@
}
/** Is the current patch set locked against state changes? */
- boolean isPatchSetLocked(ReviewDb db) throws OrmException {
+ private boolean isPatchSetLocked(ReviewDb db) throws OrmException {
if (getChange().getStatus() == Change.Status.MERGED) {
return false;
}
@@ -300,7 +209,7 @@
.byLabel(ap.getLabel());
if (type != null
&& ap.getValue() == 1
- && type.getFunctionName().equalsIgnoreCase("PatchSetLock")) {
+ && type.getFunction() == LabelFunction.PATCH_SET_LOCK) {
return true;
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChildProjectsCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChildProjectsCollection.java
index 0cd7d19..e008d66 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChildProjectsCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChildProjectsCollection.java
@@ -53,7 +53,7 @@
public ChildProjectResource parse(ProjectResource parent, IdString id)
throws ResourceNotFoundException, IOException, PermissionBackendException {
ProjectResource p = projectsCollection.parse(TopLevelResource.INSTANCE, id);
- for (ProjectState pp : p.getControl().getProjectState().parents()) {
+ for (ProjectState pp : p.getProjectState().parents()) {
if (parent.getNameKey().equals(pp.getProject().getNameKey())) {
return new ChildProjectResource(parent, p.getProjectState());
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfoImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfoImpl.java
index eb0dde4..db0787c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfoImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfoImpl.java
@@ -25,6 +25,7 @@
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.extensions.webui.UiAction;
import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.config.PluginConfig;
import com.google.gerrit.server.config.PluginConfigFactory;
@@ -39,15 +40,15 @@
public class ConfigInfoImpl extends ConfigInfo {
public ConfigInfoImpl(
boolean serverEnableSignedPush,
- ProjectControl control,
+ ProjectState projectState,
+ CurrentUser user,
TransferConfig config,
DynamicMap<ProjectConfigEntry> pluginConfigEntries,
PluginConfigFactory cfgFactory,
AllProjectsName allProjects,
UiActions uiActions,
DynamicMap<RestView<ProjectResource>> views) {
- ProjectState projectState = control.getProjectState();
- Project p = control.getProject();
+ Project p = projectState.getProject();
this.description = Strings.emptyToNull(p.getDescription());
InheritedBooleanInfo useContributorAgreements = new InheritedBooleanInfo();
@@ -130,11 +131,10 @@
this.commentlinks.put(cl.name, cl);
}
- pluginConfig =
- getPluginConfig(control.getProjectState(), pluginConfigEntries, cfgFactory, allProjects);
+ pluginConfig = getPluginConfig(projectState, pluginConfigEntries, cfgFactory, allProjects);
actions = new TreeMap<>();
- for (UiAction.Description d : uiActions.from(views, new ProjectResource(control))) {
+ for (UiAction.Description d : uiActions.from(views, new ProjectResource(projectState, user))) {
actions.put(d.getId(), new ActionInfo(d));
}
this.theme = projectState.getTheme();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateAccessChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateAccessChange.java
index 326d395..31b48cd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateAccessChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateAccessChange.java
@@ -38,6 +38,7 @@
import com.google.gerrit.server.git.ProjectConfig;
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.permissions.RefPermission;
import com.google.gerrit.server.update.BatchUpdate;
import com.google.gerrit.server.update.UpdateException;
@@ -89,25 +90,23 @@
throws PermissionBackendException, PermissionDeniedException, IOException,
ConfigInvalidException, OrmException, InvalidNameException, UpdateException,
RestApiException {
- MetaDataUpdate.User metaDataUpdateUser = metaDataUpdateFactory.get();
- List<AccessSection> removals = setAccess.getAccessSections(input.remove);
- List<AccessSection> additions = setAccess.getAccessSections(input.add);
-
- PermissionBackend.ForRef metaRef =
- permissionBackend.user(rsrc.getUser()).project(rsrc.getNameKey()).ref(RefNames.REFS_CONFIG);
- try {
- metaRef.check(RefPermission.READ);
- } catch (AuthException denied) {
+ PermissionBackend.ForProject forProject =
+ permissionBackend.user(rsrc.getUser()).project(rsrc.getNameKey());
+ if (!check(forProject, ProjectPermission.READ_CONFIG)) {
throw new PermissionDeniedException(RefNames.REFS_CONFIG + " not visible");
}
- if (!rsrc.getControl().isOwner()) {
+ if (!check(forProject, ProjectPermission.WRITE_CONFIG)) {
try {
- metaRef.check(RefPermission.CREATE_CHANGE);
+ forProject.ref(RefNames.REFS_CONFIG).check(RefPermission.CREATE_CHANGE);
} catch (AuthException denied) {
throw new PermissionDeniedException("cannot create change for " + RefNames.REFS_CONFIG);
}
}
+ MetaDataUpdate.User metaDataUpdateUser = metaDataUpdateFactory.get();
+ List<AccessSection> removals = setAccess.getAccessSections(input.remove);
+ List<AccessSection> additions = setAccess.getAccessSections(input.add);
+
Project.NameKey newParentProjectName =
input.parent == null ? null : new Project.NameKey(input.parent);
@@ -159,4 +158,14 @@
.setValidate(false)
.setUpdateRef(false);
}
+
+ private boolean check(PermissionBackend.ForProject perm, ProjectPermission p)
+ throws PermissionBackendException {
+ try {
+ perm.check(p);
+ return true;
+ } catch (AuthException denied) {
+ return false;
+ }
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java
index 9b355f1..70cb266 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java
@@ -31,6 +31,7 @@
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.common.ProjectInfo;
import com.google.gerrit.extensions.events.NewProjectCreatedListener;
+import com.google.gerrit.extensions.registration.DynamicItem;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -65,6 +66,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.concurrent.locks.Lock;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.CommitBuilder;
@@ -103,6 +105,7 @@
private final Provider<IdentifiedUser> identifiedUser;
private final Provider<PutConfig> putConfig;
private final AllProjectsName allProjects;
+ private final DynamicItem<ProjectNameLockManager> lockManager;
private final String name;
@Inject
@@ -123,6 +126,7 @@
Provider<IdentifiedUser> identifiedUser,
Provider<PutConfig> putConfig,
AllProjectsName allProjects,
+ DynamicItem<ProjectNameLockManager> lockManager,
@Assisted String name) {
this.projectsCollection = projectsCollection;
this.groupsCollection = groupsCollection;
@@ -140,6 +144,7 @@
this.identifiedUser = identifiedUser;
this.putConfig = putConfig;
this.allProjects = allProjects;
+ this.lockManager = lockManager;
this.name = name;
}
@@ -192,22 +197,27 @@
throw new BadRequestException(e.getMessage());
}
- for (ProjectCreationValidationListener l : projectCreationValidationListeners) {
- try {
- l.validateNewProject(args);
- } catch (ValidationException e) {
- throw new ResourceConflictException(e.getMessage(), e);
+ Lock nameLock = lockManager.get().getLock(args.getProject());
+ nameLock.lock();
+ try {
+ for (ProjectCreationValidationListener l : projectCreationValidationListeners) {
+ try {
+ l.validateNewProject(args);
+ } catch (ValidationException e) {
+ throw new ResourceConflictException(e.getMessage(), e);
+ }
}
- }
- ProjectState projectState = createProject(args);
- if (input.pluginConfigValues != null) {
- ConfigInput in = new ConfigInput();
- in.pluginConfigValues = input.pluginConfigValues;
- putConfig.get().apply(projectState, in);
+ ProjectState projectState = createProject(args);
+ if (input.pluginConfigValues != null) {
+ ConfigInput in = new ConfigInput();
+ in.pluginConfigValues = input.pluginConfigValues;
+ putConfig.get().apply(projectState, in);
+ }
+ return Response.created(json.format(projectState));
+ } finally {
+ nameLock.unlock();
}
-
- return Response.created(json.format(projectState));
}
private ProjectState createProject(CreateProjectArgs args)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateTag.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateTag.java
index 61548c4..8e706a2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateTag.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateTag.java
@@ -66,6 +66,7 @@
private final TagCache tagCache;
private final GitReferenceUpdated referenceUpdated;
private final WebLinks links;
+ private final ProjectControl.GenericFactory projectControlFactory;
private String ref;
@Inject
@@ -76,6 +77,7 @@
TagCache tagCache,
GitReferenceUpdated referenceUpdated,
WebLinks webLinks,
+ ProjectControl.GenericFactory projectControlFactory,
@Assisted String ref) {
this.permissionBackend = permissionBackend;
this.identifiedUser = identifiedUser;
@@ -83,12 +85,13 @@
this.tagCache = tagCache;
this.referenceUpdated = referenceUpdated;
this.links = webLinks;
+ this.projectControlFactory = projectControlFactory;
this.ref = ref;
}
@Override
public TagInfo apply(ProjectResource resource, TagInput input)
- throws RestApiException, IOException, PermissionBackendException {
+ throws RestApiException, IOException, PermissionBackendException, NoSuchProjectException {
if (input == null) {
input = new TagInput();
}
@@ -101,7 +104,11 @@
ref = RefUtil.normalizeTagRef(ref);
- RefControl refControl = resource.getControl().controlForRef(ref);
+ // TODO(hiesel): Remove dependency on RefControl
+ RefControl refControl =
+ projectControlFactory
+ .controlFor(resource.getNameKey(), resource.getUser())
+ .controlForRef(ref);
PermissionBackend.ForRef perm =
permissionBackend.user(identifiedUser).project(resource.getNameKey()).ref(ref);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/DashboardResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/DashboardResource.java
index a3fd09e..87b6fdf 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/DashboardResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/DashboardResource.java
@@ -16,6 +16,7 @@
import com.google.gerrit.extensions.restapi.RestResource;
import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.server.CurrentUser;
import com.google.inject.TypeLiteral;
import org.eclipse.jgit.lib.Config;
@@ -23,31 +24,38 @@
public static final TypeLiteral<RestView<DashboardResource>> DASHBOARD_KIND =
new TypeLiteral<RestView<DashboardResource>>() {};
- public static DashboardResource projectDefault(ProjectControl ctl) {
- return new DashboardResource(ctl, null, null, null, true);
+ public static DashboardResource projectDefault(ProjectState projectState, CurrentUser user) {
+ return new DashboardResource(projectState, user, null, null, null, true);
}
- private final ProjectControl control;
+ private final ProjectState projectState;
+ private final CurrentUser user;
private final String refName;
private final String pathName;
private final Config config;
private final boolean projectDefault;
public DashboardResource(
- ProjectControl control,
+ ProjectState projectState,
+ CurrentUser user,
String refName,
String pathName,
Config config,
boolean projectDefault) {
- this.control = control;
+ this.projectState = projectState;
+ this.user = user;
this.refName = refName;
this.pathName = pathName;
this.config = config;
this.projectDefault = projectDefault;
}
- public ProjectControl getControl() {
- return control;
+ public ProjectState getProjectState() {
+ return projectState;
+ }
+
+ public CurrentUser getUser() {
+ return user;
}
public String getRefName() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/DashboardsCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/DashboardsCollection.java
index d43a066..d5c591f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/DashboardsCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/DashboardsCollection.java
@@ -106,9 +106,8 @@
public DashboardResource parse(ProjectResource parent, IdString id)
throws ResourceNotFoundException, IOException, ConfigInvalidException,
PermissionBackendException {
- ProjectControl myCtl = parent.getControl();
if (isDefaultDashboard(id)) {
- return DashboardResource.projectDefault(myCtl);
+ return DashboardResource.projectDefault(parent.getProjectState(), parent.getUser());
}
DashboardInfo info;
@@ -118,10 +117,9 @@
throw new ResourceNotFoundException(id);
}
- CurrentUser user = myCtl.getUser();
- for (ProjectState ps : myCtl.getProjectState().tree()) {
+ for (ProjectState ps : parent.getProjectState().tree()) {
try {
- return parse(ps.controlFor(user), info, myCtl);
+ return parse(ps, parent.getProjectState(), parent.getUser(), info);
} catch (AmbiguousObjectException | ConfigInvalidException | IncorrectObjectTypeException e) {
throw new ResourceNotFoundException(id);
} catch (ResourceNotFoundException e) {
@@ -138,16 +136,13 @@
return ref;
}
- private DashboardResource parse(ProjectControl ctl, DashboardInfo info, ProjectControl myCtl)
+ private DashboardResource parse(
+ ProjectState parent, ProjectState current, CurrentUser user, DashboardInfo info)
throws ResourceNotFoundException, IOException, AmbiguousObjectException,
IncorrectObjectTypeException, ConfigInvalidException, PermissionBackendException {
String ref = normalizeDashboardRef(info.ref);
try {
- permissionBackend
- .user(ctl.getUser())
- .project(ctl.getProject().getNameKey())
- .ref(ref)
- .check(RefPermission.READ);
+ permissionBackend.user(user).project(parent.getNameKey()).ref(ref).check(RefPermission.READ);
} catch (AuthException e) {
// Don't leak the project's existence
throw new ResourceNotFoundException(info.id);
@@ -156,13 +151,13 @@
throw new ResourceNotFoundException(info.id);
}
- try (Repository git = gitManager.openRepository(ctl.getProject().getNameKey())) {
+ try (Repository git = gitManager.openRepository(parent.getNameKey())) {
ObjectId objId = git.resolve(ref + ":" + info.path);
if (objId == null) {
throw new ResourceNotFoundException(info.id);
}
BlobBasedConfig cfg = new BlobBasedConfig(null, git, objId);
- return new DashboardResource(myCtl, ref, info.path, cfg, false);
+ return new DashboardResource(current, user, ref, info.path, cfg, false);
} catch (RepositoryNotFoundException e) {
throw new ResourceNotFoundException(info.id);
}
@@ -211,8 +206,8 @@
DashboardInfo info = newDashboardInfo(refName, path);
info.project = project;
info.definingProject = definingProject.getName();
- String query = config.getString("dashboard", null, "title");
- info.title = replace(project, query == null ? info.path : query);
+ String title = config.getString("dashboard", null, "title");
+ info.title = replace(project, title == null ? info.path : title);
info.description = replace(project, config.getString("dashboard", null, "description"));
info.foreach = config.getString("dashboard", null, "foreach");
@@ -238,8 +233,8 @@
return info;
}
- private static String replace(String project, String query) {
- return query.replace("${project}", project);
+ private static String replace(String project, String input) {
+ return input == null ? input : input.replace("${project}", project);
}
private static String defaultOf(Project proj) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/DefaultPermissionBackend.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/DefaultPermissionBackend.java
index ef5e41d..f577436 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/DefaultPermissionBackend.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/DefaultPermissionBackend.java
@@ -75,7 +75,7 @@
if (state != null) {
return state.controlFor(user).asForProject().database(db);
}
- return FailedPermissionBackend.project("not found");
+ return FailedPermissionBackend.project("not found", new NoSuchProjectException(project));
} catch (IOException e) {
return FailedPermissionBackend.project("unavailable", e);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/DefaultPermissionBackendModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/DefaultPermissionBackendModule.java
index 8fa049d..bdfc67f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/DefaultPermissionBackendModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/DefaultPermissionBackendModule.java
@@ -32,7 +32,6 @@
// TODO(sop) Hide ProjectControl, RefControl, ChangeControl related bindings.
bind(ProjectControl.GenericFactory.class);
factory(ProjectControl.AssistedFactory.class);
- bind(ChangeControl.GenericFactory.class);
bind(ChangeControl.Factory.class);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/DefaultProjectNameLockManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/DefaultProjectNameLockManager.java
new file mode 100644
index 0000000..8c9bec7
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/DefaultProjectNameLockManager.java
@@ -0,0 +1,60 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.project;
+
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.gerrit.extensions.registration.DynamicItem;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.inject.AbstractModule;
+import com.google.inject.Singleton;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+@Singleton
+public class DefaultProjectNameLockManager implements ProjectNameLockManager {
+
+ public static class Module extends AbstractModule {
+ @Override
+ protected void configure() {
+ DynamicItem.bind(binder(), ProjectNameLockManager.class)
+ .to(DefaultProjectNameLockManager.class);
+ }
+ }
+
+ LoadingCache<Project.NameKey, Lock> lockCache =
+ CacheBuilder.newBuilder()
+ .maximumSize(1024)
+ .expireAfterAccess(5, TimeUnit.MINUTES)
+ .build(
+ new CacheLoader<Project.NameKey, Lock>() {
+ @Override
+ public Lock load(Project.NameKey key) throws Exception {
+ return new ReentrantLock();
+ }
+ });
+
+ @Override
+ public Lock getLock(Project.NameKey name) {
+ try {
+ return lockCache.get(name);
+ } catch (ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java
index 8cd44d1..7c7d6af 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java
@@ -16,6 +16,7 @@
import static org.eclipse.jgit.lib.Constants.R_HEADS;
+import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.Response;
import com.google.gerrit.extensions.restapi.RestApiException;
@@ -24,7 +25,6 @@
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.permissions.RefPermission;
-import com.google.gerrit.server.project.DeleteBranch.Input;
import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -34,7 +34,6 @@
@Singleton
public class DeleteBranch implements RestModifyView<BranchResource, Input> {
- public static class Input {}
private final Provider<InternalChangeQuery> queryProvider;
private final DeleteRef.Factory deleteRefFactory;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteTag.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteTag.java
index a05fa2e..234f1d5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteTag.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteTag.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.project;
+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;
@@ -28,8 +29,7 @@
import java.io.IOException;
@Singleton
-public class DeleteTag implements RestModifyView<TagResource, DeleteTag.Input> {
- public static class Input {}
+public class DeleteTag implements RestModifyView<TagResource, Input> {
private final PermissionBackend permissionBackend;
private final Provider<CurrentUser> user;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetAccess.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetAccess.java
index 07c52f9..6b05ca1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetAccess.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetAccess.java
@@ -18,6 +18,7 @@
import static com.google.gerrit.server.permissions.ProjectPermission.CREATE_REF;
import static com.google.gerrit.server.permissions.RefPermission.CREATE_CHANGE;
import static com.google.gerrit.server.permissions.RefPermission.READ;
+import static com.google.gerrit.server.permissions.RefPermission.WRITE_CONFIG;
import static java.util.stream.Collectors.toMap;
import com.google.common.collect.ImmutableBiMap;
@@ -49,6 +50,7 @@
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.permissions.ProjectPermission;
import com.google.gerrit.server.permissions.RefPermission;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -90,7 +92,6 @@
private final ProjectJson projectJson;
private final ProjectCache projectCache;
private final MetaDataUpdate.Server metaDataUpdateFactory;
- private final ProjectControl.GenericFactory projectControlFactory;
private final GroupBackend groupBackend;
private final GroupJson groupJson;
@@ -103,7 +104,6 @@
ProjectCache projectCache,
MetaDataUpdate.Server metaDataUpdateFactory,
ProjectJson projectJson,
- ProjectControl.GenericFactory projectControlFactory,
GroupBackend groupBackend,
GroupJson groupJson) {
this.user = self;
@@ -112,7 +112,6 @@
this.allProjectsName = allProjectsName;
this.projectJson = projectJson;
this.projectCache = projectCache;
- this.projectControlFactory = projectControlFactory;
this.metaDataUpdateFactory = metaDataUpdateFactory;
this.groupBackend = groupBackend;
this.groupJson = groupJson;
@@ -121,11 +120,11 @@
public ProjectAccessInfo apply(Project.NameKey nameKey)
throws ResourceNotFoundException, ResourceConflictException, IOException,
PermissionBackendException, OrmException {
- try {
- return apply(new ProjectResource(projectControlFactory.controlFor(nameKey, user.get())));
- } catch (NoSuchProjectException e) {
+ ProjectState state = projectCache.checkedGet(nameKey);
+ if (state == null) {
throw new ResourceNotFoundException(nameKey.get());
}
+ return apply(new ProjectResource(state, user.get()));
}
@Override
@@ -138,7 +137,7 @@
Project.NameKey projectName = rsrc.getNameKey();
ProjectAccessInfo info = new ProjectAccessInfo();
- ProjectControl pc = createProjectControl(projectName);
+ ProjectState projectState = projectCache.checkedGet(projectName);
PermissionBackend.ForProject perm = permissionBackend.user(user).project(projectName);
ProjectConfig config;
@@ -149,12 +148,12 @@
md.setMessage("Update group names\n");
config.commit(md);
projectCache.evict(config.getProject());
- pc = createProjectControl(projectName);
+ projectState = projectCache.checkedGet(projectName);
perm = permissionBackend.user(user).project(projectName);
} else if (config.getRevision() != null
- && !config.getRevision().equals(pc.getProjectState().getConfig().getRevision())) {
+ && !config.getRevision().equals(projectState.getConfig().getRevision())) {
projectCache.evict(config.getProject());
- pc = createProjectControl(projectName);
+ projectState = projectCache.checkedGet(projectName);
perm = permissionBackend.user(user).project(projectName);
}
} catch (ConfigInvalidException e) {
@@ -166,25 +165,26 @@
info.local = new HashMap<>();
info.ownerOf = new HashSet<>();
Map<AccountGroup.UUID, GroupInfo> visibleGroups = new HashMap<>();
- boolean checkReadConfig = check(perm, RefNames.REFS_CONFIG, READ);
+ boolean canReadConfig = check(perm, ProjectPermission.READ_CONFIG);
+ boolean canWriteConfig = check(perm, ProjectPermission.WRITE_CONFIG);
for (AccessSection section : config.getAccessSections()) {
String name = section.getName();
if (AccessSection.GLOBAL_CAPABILITIES.equals(name)) {
- if (pc.isOwner()) {
+ if (canWriteConfig) {
info.local.put(name, createAccessSection(visibleGroups, section));
info.ownerOf.add(name);
- } else if (checkReadConfig) {
+ } else if (canReadConfig) {
info.local.put(section.getName(), createAccessSection(visibleGroups, section));
}
} else if (RefConfigSection.isValid(name)) {
- if (pc.controlForRef(name).isOwner()) {
+ if (check(perm, name, WRITE_CONFIG)) {
info.local.put(name, createAccessSection(visibleGroups, section));
info.ownerOf.add(name);
- } else if (checkReadConfig) {
+ } else if (canReadConfig) {
info.local.put(name, createAccessSection(visibleGroups, section));
} else if (check(perm, name, READ)) {
@@ -232,7 +232,7 @@
info.revision = config.getRevision().name();
}
- ProjectState parent = Iterables.getFirst(pc.getProjectState().parents(), null);
+ ProjectState parent = Iterables.getFirst(projectState.parents(), null);
if (parent != null) {
info.inheritsFrom = projectJson.format(parent.getProject());
}
@@ -242,13 +242,13 @@
info.ownerOf.add(AccessSection.GLOBAL_CAPABILITIES);
}
- info.isOwner = toBoolean(pc.isOwner());
+ info.isOwner = toBoolean(canWriteConfig);
info.canUpload =
toBoolean(
- pc.isOwner()
- || (checkReadConfig && perm.ref(RefNames.REFS_CONFIG).testOrFalse(CREATE_CHANGE)));
+ canWriteConfig
+ || (canReadConfig && perm.ref(RefNames.REFS_CONFIG).testOrFalse(CREATE_CHANGE)));
info.canAdd = toBoolean(perm.testOrFalse(CREATE_REF));
- info.configVisible = checkReadConfig || pc.isOwner();
+ info.configVisible = canReadConfig || canWriteConfig;
info.groups =
visibleGroups
@@ -291,6 +291,16 @@
}
}
+ private static boolean check(PermissionBackend.ForProject ctx, ProjectPermission perm)
+ throws PermissionBackendException {
+ try {
+ ctx.check(perm);
+ return true;
+ } catch (AuthException denied) {
+ return false;
+ }
+ }
+
private AccessSectionInfo createAccessSection(
Map<AccountGroup.UUID, GroupInfo> groups, AccessSection section) throws OrmException {
AccessSectionInfo accessSectionInfo = new AccessSectionInfo();
@@ -316,15 +326,6 @@
return accessSectionInfo;
}
- private ProjectControl createProjectControl(Project.NameKey projectName)
- throws IOException, ResourceNotFoundException {
- try {
- return projectControlFactory.controlFor(projectName, user.get());
- } catch (NoSuchProjectException e) {
- throw new ResourceNotFoundException(projectName.get());
- }
- }
-
private static Boolean toBoolean(boolean value) {
return value ? true : null;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetConfig.java
index b1ba281..c2f816e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetConfig.java
@@ -59,7 +59,8 @@
public ConfigInfo apply(ProjectResource resource) {
return new ConfigInfoImpl(
serverEnableSignedPush,
- resource.getControl(),
+ resource.getProjectState(),
+ resource.getUser(),
config,
pluginConfigEntries,
cfgFactory,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetDashboard.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetDashboard.java
index cdf23bb..d4d9a54 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetDashboard.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetDashboard.java
@@ -28,6 +28,7 @@
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.Url;
+import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.inject.Inject;
import java.io.IOException;
@@ -52,64 +53,63 @@
}
@Override
- public DashboardInfo apply(DashboardResource resource)
+ public DashboardInfo apply(DashboardResource rsrc)
throws RestApiException, IOException, PermissionBackendException {
- if (inherited && !resource.isProjectDefault()) {
+ if (inherited && !rsrc.isProjectDefault()) {
throw new BadRequestException("inherited flag can only be used with default");
}
- String project = resource.getControl().getProject().getName();
- if (resource.isProjectDefault()) {
+ if (rsrc.isProjectDefault()) {
// The default is not resolved to a definition yet.
try {
- resource = defaultOf(resource.getControl());
+ rsrc = defaultOf(rsrc.getProjectState(), rsrc.getUser());
} catch (ConfigInvalidException e) {
throw new ResourceConflictException(e.getMessage());
}
}
return DashboardsCollection.parse(
- resource.getControl().getProject(),
- resource.getRefName().substring(REFS_DASHBOARDS.length()),
- resource.getPathName(),
- resource.getConfig(),
- project,
+ rsrc.getProjectState().getProject(),
+ rsrc.getRefName().substring(REFS_DASHBOARDS.length()),
+ rsrc.getPathName(),
+ rsrc.getConfig(),
+ rsrc.getProjectState().getName(),
true);
}
- private DashboardResource defaultOf(ProjectControl ctl)
+ private DashboardResource defaultOf(ProjectState projectState, CurrentUser user)
throws ResourceNotFoundException, IOException, ConfigInvalidException,
PermissionBackendException {
- String id = ctl.getProject().getLocalDefaultDashboard();
+ String id = projectState.getProject().getLocalDefaultDashboard();
if (Strings.isNullOrEmpty(id)) {
- id = ctl.getProject().getDefaultDashboard();
+ id = projectState.getProject().getDefaultDashboard();
}
if (isDefaultDashboard(id)) {
throw new ResourceNotFoundException();
} else if (!Strings.isNullOrEmpty(id)) {
- return parse(ctl, id);
+ return parse(projectState, user, id);
} else if (!inherited) {
throw new ResourceNotFoundException();
}
- for (ProjectState ps : ctl.getProjectState().tree()) {
+ for (ProjectState ps : projectState.tree()) {
id = ps.getProject().getDefaultDashboard();
if (isDefaultDashboard(id)) {
throw new ResourceNotFoundException();
} else if (!Strings.isNullOrEmpty(id)) {
- ctl = ps.controlFor(ctl.getUser());
- return parse(ctl, id);
+ return parse(projectState, user, id);
}
}
throw new ResourceNotFoundException();
}
- private DashboardResource parse(ProjectControl ctl, String id)
+ private DashboardResource parse(ProjectState projectState, CurrentUser user, String id)
throws ResourceNotFoundException, IOException, ConfigInvalidException,
PermissionBackendException {
List<String> p = Lists.newArrayList(Splitter.on(':').limit(2).split(id));
String ref = Url.encode(p.get(0));
String path = Url.encode(p.get(1));
- return dashboards.parse(new ProjectResource(ctl), IdString.fromUrl(ref + ':' + path));
+ return dashboards.parse(
+ new ProjectResource(projectState, user), IdString.fromUrl(ref + ':' + path));
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetHead.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetHead.java
index 31dc7bf..daaf4ef 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetHead.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetHead.java
@@ -20,6 +20,7 @@
import com.google.gerrit.server.git.GitRepositoryManager;
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.permissions.RefPermission;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -72,10 +73,14 @@
}
throw new AuthException("not allowed to see HEAD");
} catch (MissingObjectException | IncorrectObjectTypeException e) {
- if (rsrc.getControl().isOwner()) {
- return head.getObjectId().name();
+ try {
+ permissionBackend
+ .user(rsrc.getUser())
+ .project(rsrc.getNameKey())
+ .check(ProjectPermission.WRITE_CONFIG);
+ } catch (AuthException ae) {
+ throw new AuthException("not allowed to see HEAD");
}
- throw new AuthException("not allowed to see HEAD");
}
}
throw new ResourceNotFoundException(Constants.HEAD);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetParent.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetParent.java
index 8f0b6f0..df87575 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetParent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetParent.java
@@ -21,7 +21,7 @@
import com.google.inject.Singleton;
@Singleton
-class GetParent implements RestReadView<ProjectResource> {
+public class GetParent implements RestReadView<ProjectResource> {
private final AllProjectsName allProjectsName;
@Inject
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetReflog.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetReflog.java
index 44d6a4f..9643e09 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetReflog.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetReflog.java
@@ -16,7 +16,6 @@
import com.google.common.collect.Lists;
import com.google.gerrit.extensions.api.projects.ReflogEntryInfo;
-import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException;
@@ -24,6 +23,9 @@
import com.google.gerrit.server.CommonConverters;
import com.google.gerrit.server.args4j.TimestampHandler;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.permissions.ProjectPermission;
import com.google.inject.Inject;
import java.io.IOException;
import java.sql.Timestamp;
@@ -40,6 +42,7 @@
private static final Logger log = LoggerFactory.getLogger(GetReflog.class);
private final GitRepositoryManager repoManager;
+ private final PermissionBackend permissionBackend;
@Option(
name = "--limit",
@@ -83,15 +86,18 @@
private Timestamp to;
@Inject
- public GetReflog(GitRepositoryManager repoManager) {
+ public GetReflog(GitRepositoryManager repoManager, PermissionBackend permissionBackend) {
this.repoManager = repoManager;
+ this.permissionBackend = permissionBackend;
}
@Override
- public List<ReflogEntryInfo> apply(BranchResource rsrc) throws RestApiException, IOException {
- if (!rsrc.getControl().isOwner()) {
- throw new AuthException("not project owner");
- }
+ public List<ReflogEntryInfo> apply(BranchResource rsrc)
+ throws RestApiException, IOException, PermissionBackendException {
+ permissionBackend
+ .user(rsrc.getUser())
+ .project(rsrc.getNameKey())
+ .check(ProjectPermission.READ_REFLOG);
try (Repository repo = repoManager.openRepository(rsrc.getNameKey())) {
ReflogReader r;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java
index b2edc6b..645058f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListBranches.java
@@ -179,7 +179,6 @@
}
}
- ProjectControl pctl = rsrc.getControl();
PermissionBackend.ForProject perm = permissionBackend.user(user).project(rsrc.getNameKey());
List<BranchInfo> branches = new ArrayList<>(refs.size());
for (Ref ref : refs) {
@@ -207,7 +206,9 @@
}
if (perm.ref(ref.getName()).test(RefPermission.READ)) {
- branches.add(createBranchInfo(perm.ref(ref.getName()), ref, pctl, targets));
+ branches.add(
+ createBranchInfo(
+ perm.ref(ref.getName()), ref, rsrc.getProjectState(), rsrc.getUser(), targets));
}
}
Collections.sort(branches, new BranchComparator());
@@ -234,14 +235,18 @@
}
private BranchInfo createBranchInfo(
- PermissionBackend.ForRef perm, Ref ref, ProjectControl pctl, Set<String> targets) {
+ PermissionBackend.ForRef perm,
+ Ref ref,
+ ProjectState projectState,
+ CurrentUser user,
+ Set<String> targets) {
BranchInfo info = new BranchInfo();
info.ref = ref.getName();
info.revision = ref.getObjectId() != null ? ref.getObjectId().name() : null;
info.canDelete =
!targets.contains(ref.getName()) && perm.testOrFalse(RefPermission.DELETE) ? true : null;
- BranchResource rsrc = new BranchResource(pctl, ref);
+ BranchResource rsrc = new BranchResource(projectState, user, ref);
for (UiAction.Description d : uiActions.from(branchViews, rsrc)) {
if (info.actions == null) {
info.actions = new TreeMap<>();
@@ -249,7 +254,7 @@
info.actions.put(d.getId(), new ActionInfo(d));
}
- List<WebLinkInfo> links = webLinks.getBranchLinks(pctl.getProject().getName(), ref.getName());
+ List<WebLinkInfo> links = webLinks.getBranchLinks(projectState.getName(), ref.getName());
info.webLinks = links.isEmpty() ? null : links;
return info;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java
index dc3610c..afb796e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java
@@ -355,17 +355,15 @@
continue;
}
- final ProjectControl pctl = e.controlFor(currentUser);
if (groupUuid != null
- && !pctl.getProjectState()
- .getLocalGroups()
+ && !e.getLocalGroups()
.contains(GroupReference.forGroup(groupsCollection.parseId(groupUuid.get())))) {
continue;
}
ProjectInfo info = new ProjectInfo();
if (showTree && !format.isJson()) {
- treeMap.put(projectName, projectNodeFactory.create(pctl.getProject(), true));
+ treeMap.put(projectName, projectNodeFactory.create(e.getProject(), true));
continue;
}
@@ -396,8 +394,17 @@
if (!type.matches(git)) {
continue;
}
-
- List<Ref> refs = getBranchRefs(projectName, pctl);
+ boolean canReadAllRefs;
+ try {
+ permissionBackend
+ .user(currentUser)
+ .project(e.getNameKey())
+ .check(ProjectPermission.READ);
+ canReadAllRefs = true;
+ } catch (AuthException ae) {
+ canReadAllRefs = false;
+ }
+ List<Ref> refs = getBranchRefs(projectName, canReadAllRefs);
if (!hasValidRef(refs)) {
continue;
}
@@ -592,13 +599,13 @@
stdout.flush();
}
- private List<Ref> getBranchRefs(Project.NameKey projectName, ProjectControl projectControl) {
+ private List<Ref> getBranchRefs(Project.NameKey projectName, boolean canReadAllRefs) {
Ref[] result = new Ref[showBranch.size()];
try (Repository git = repoManager.openRepository(projectName)) {
PermissionBackend.ForProject perm = permissionBackend.user(currentUser).project(projectName);
for (int i = 0; i < showBranch.size(); i++) {
Ref ref = git.findRef(showBranch.get(i));
- if (all && projectControl.isOwner()) {
+ if (all && canReadAllRefs) {
result[i] = ref;
} else if (ref != null && ref.getObjectId() != null) {
try {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/PerRequestProjectControlCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/PerRequestProjectControlCache.java
index 0f71ac8..b68446f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/PerRequestProjectControlCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/PerRequestProjectControlCache.java
@@ -18,6 +18,7 @@
import com.google.gerrit.server.CurrentUser;
import com.google.inject.Inject;
import com.google.inject.servlet.RequestScoped;
+import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@@ -48,7 +49,7 @@
return ctl;
}
- public void evict(Project project) {
+ public void evict(Project project) throws IOException {
projectCache.evict(project);
controls.remove(project.getNameKey());
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCache.java
index 65c7315..63052bd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCache.java
@@ -45,17 +45,27 @@
*/
ProjectState checkedGet(Project.NameKey projectName) throws IOException;
- /** Invalidate the cached information about the given project. */
- void evict(Project p);
+ /**
+ * Invalidate the cached information about the given project, and triggers reindexing for it
+ *
+ * @param p project that is being evicted
+ * @throws IOException thrown if the reindexing fails
+ */
+ void evict(Project p) throws IOException;
- /** Invalidate the cached information about the given project. */
- void evict(Project.NameKey p);
+ /**
+ * Invalidate the cached information about the given project, and triggers reindexing for it
+ *
+ * @param p the NameKey of the project that is being evicted
+ * @throws IOException thrown if the reindexing fails
+ */
+ void evict(Project.NameKey p) throws IOException;
/**
* Remove information about the given project from the cache. It will no longer be returned from
* {@link #all()}.
*/
- void remove(Project p);
+ void remove(Project p) throws IOException;
/** @return sorted iteration of projects. */
Iterable<Project.NameKey> all();
@@ -75,5 +85,5 @@
Iterable<Project.NameKey> byName(String prefix);
/** Notify the cache that a new project was constructed. */
- void onCreateProject(Project.NameKey newProjectName);
+ void onCreateProject(Project.NameKey newProjectName) throws IOException;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheImpl.java
index 6ee143c..2b31ce3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheImpl.java
@@ -28,8 +28,10 @@
import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.index.project.ProjectIndexer;
import com.google.inject.Inject;
import com.google.inject.Module;
+import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
import com.google.inject.internal.UniqueAnnotations;
@@ -82,6 +84,7 @@
private final LoadingCache<ListKey, SortedSet<Project.NameKey>> list;
private final Lock listLock;
private final ProjectCacheClock clock;
+ private final Provider<ProjectIndexer> indexer;
@Inject
ProjectCacheImpl(
@@ -89,13 +92,15 @@
final AllUsersName allUsersName,
@Named(CACHE_NAME) LoadingCache<String, ProjectState> byName,
@Named(CACHE_LIST) LoadingCache<ListKey, SortedSet<Project.NameKey>> list,
- ProjectCacheClock clock) {
+ ProjectCacheClock clock,
+ Provider<ProjectIndexer> indexer) {
this.allProjectsName = allProjectsName;
this.allUsersName = allUsersName;
this.byName = byName;
this.list = list;
this.listLock = new ReentrantLock(true /* fair */);
this.clock = clock;
+ this.indexer = indexer;
}
@Override
@@ -151,22 +156,20 @@
}
@Override
- public void evict(Project p) {
- if (p != null) {
- byName.invalidate(p.getNameKey().get());
- }
+ public void evict(Project p) throws IOException {
+ evict(p.getNameKey());
}
- /** Invalidate the cached information about the given project. */
@Override
- public void evict(Project.NameKey p) {
+ public void evict(Project.NameKey p) throws IOException {
if (p != null) {
byName.invalidate(p.get());
}
+ indexer.get().index(p);
}
@Override
- public void remove(Project p) {
+ public void remove(Project p) throws IOException {
listLock.lock();
try {
SortedSet<Project.NameKey> n = Sets.newTreeSet(list.get(ListKey.ALL));
@@ -181,7 +184,7 @@
}
@Override
- public void onCreateProject(Project.NameKey newProjectName) {
+ public void onCreateProject(Project.NameKey newProjectName) throws IOException {
listLock.lock();
try {
SortedSet<Project.NameKey> n = Sets.newTreeSet(list.get(ListKey.ALL));
@@ -192,6 +195,7 @@
} finally {
listLock.unlock();
}
+ indexer.get().index(newProjectName);
}
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
index 7a7418c..0e62b3f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
@@ -20,7 +20,6 @@
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.gerrit.common.data.AccessSection;
-import com.google.gerrit.common.data.Capable;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRule;
import com.google.gerrit.extensions.restapi.AuthException;
@@ -50,7 +49,6 @@
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
-import com.google.inject.Provider;
import com.google.inject.Singleton;
import com.google.inject.assistedinject.Assisted;
import java.io.IOException;
@@ -71,10 +69,10 @@
import org.slf4j.LoggerFactory;
/** Access control management for a user accessing a project's data. */
-public class ProjectControl {
+class ProjectControl {
private static final Logger log = LoggerFactory.getLogger(ProjectControl.class);
- public static class GenericFactory {
+ static class GenericFactory {
private final ProjectCache projectCache;
@Inject
@@ -82,7 +80,7 @@
projectCache = pc;
}
- public ProjectControl controlFor(Project.NameKey nameKey, CurrentUser user)
+ ProjectControl controlFor(Project.NameKey nameKey, CurrentUser user)
throws NoSuchProjectException, IOException {
final ProjectState p = projectCache.checkedGet(nameKey);
if (p == null) {
@@ -92,20 +90,7 @@
}
}
- public static class Factory {
- private final Provider<PerRequestProjectControlCache> userCache;
-
- @Inject
- Factory(Provider<PerRequestProjectControlCache> uc) {
- userCache = uc;
- }
-
- public ProjectControl controlFor(Project.NameKey nameKey) throws NoSuchProjectException {
- return userCache.get().get(nameKey);
- }
- }
-
- public interface AssistedFactory {
+ interface AssistedFactory {
ProjectControl create(CurrentUser who, ProjectState ps);
}
@@ -155,27 +140,27 @@
state = ps;
}
- public ProjectControl forUser(CurrentUser who) {
+ ProjectControl forUser(CurrentUser who) {
ProjectControl r = state.controlFor(who);
// Not per-user, and reusing saves lookup time.
r.allSections = allSections;
return r;
}
- public ChangeControl controlFor(ReviewDb db, Change change) throws OrmException {
+ ChangeControl controlFor(ReviewDb db, Change change) throws OrmException {
return changeControlFactory.create(
controlForRef(change.getDest()), db, change.getProject(), change.getId());
}
- public ChangeControl controlFor(ChangeNotes notes) {
+ ChangeControl controlFor(ChangeNotes notes) {
return changeControlFactory.create(controlForRef(notes.getChange().getDest()), notes);
}
- public RefControl controlForRef(Branch.NameKey ref) {
+ RefControl controlForRef(Branch.NameKey ref) {
return controlForRef(ref.get());
}
- public RefControl controlForRef(String refName) {
+ RefControl controlForRef(String refName) {
if (refControls == null) {
refControls = new HashMap<>();
}
@@ -188,20 +173,20 @@
return ctl;
}
- public CurrentUser getUser() {
+ CurrentUser getUser() {
return user;
}
- public ProjectState getProjectState() {
+ ProjectState getProjectState() {
return state;
}
- public Project getProject() {
+ Project getProject() {
return state.getProject();
}
/** Is this user a project owner? */
- public boolean isOwner() {
+ boolean isOwner() {
return (isDeclaredOwner() && !controlForRef("refs/*").isBlocked(Permission.OWNER)) || isAdmin();
}
@@ -209,13 +194,10 @@
* @return {@code Capable.OK} if the user can upload to at least one reference. Does not check
* Contributor Agreements.
*/
- public Capable canPushToAtLeastOneRef() {
- if (!canPerformOnAnyRef(Permission.PUSH)
- && !canPerformOnAnyRef(Permission.CREATE_TAG)
- && !isOwner()) {
- return new Capable("Upload denied for project '" + state.getName() + "'");
- }
- return Capable.OK;
+ boolean canPushToAtLeastOneRef() {
+ return canPerformOnAnyRef(Permission.PUSH)
+ || canPerformOnAnyRef(Permission.CREATE_TAG)
+ || isOwner();
}
/** Can the user run upload pack? */
@@ -390,6 +372,10 @@
}
}
+ boolean canRead() {
+ return !isHidden() && allRefsAreVisible(Collections.emptySet());
+ }
+
ForProject asForProject() {
return new ForProjectImpl();
}
@@ -470,6 +456,15 @@
return canRunReceivePack();
case RUN_UPLOAD_PACK:
return canRunUploadPack();
+
+ case PUSH_AT_LEAST_ONE_REF:
+ return canPushToAtLeastOneRef();
+
+ case BAN_COMMIT:
+ case READ_REFLOG:
+ case READ_CONFIG:
+ case WRITE_CONFIG:
+ return isOwner();
}
throw new PermissionBackendException(perm + " unsupported");
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectData.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectData.java
new file mode 100644
index 0000000..407529d
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectData.java
@@ -0,0 +1,36 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.project;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.reviewdb.client.Project;
+
+public class ProjectData {
+ private final Project project;
+ private final ImmutableList<Project.NameKey> ancestors;
+
+ public ProjectData(Project project, Iterable<Project.NameKey> ancestors) {
+ this.project = project;
+ this.ancestors = ImmutableList.copyOf(ancestors);
+ }
+
+ public Project getProject() {
+ return project;
+ }
+
+ public ImmutableList<Project.NameKey> getAncestors() {
+ return ancestors;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectNameLockManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectNameLockManager.java
new file mode 100644
index 0000000..4666c32
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectNameLockManager.java
@@ -0,0 +1,22 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.project;
+
+import com.google.gerrit.reviewdb.client.Project;
+import java.util.concurrent.locks.Lock;
+
+public interface ProjectNameLockManager {
+ public Lock getLock(Project.NameKey name);
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectResource.java
index a91ba62..22b7bd9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectResource.java
@@ -24,33 +24,32 @@
public static final TypeLiteral<RestView<ProjectResource>> PROJECT_KIND =
new TypeLiteral<RestView<ProjectResource>>() {};
- private final ProjectControl control;
+ private final ProjectState projectState;
+ private final CurrentUser user;
- public ProjectResource(ProjectControl control) {
- this.control = control;
+ public ProjectResource(ProjectState projectState, CurrentUser user) {
+ this.projectState = projectState;
+ this.user = user;
}
ProjectResource(ProjectResource rsrc) {
- this.control = rsrc.getControl();
+ this.projectState = rsrc.getProjectState();
+ this.user = rsrc.getUser();
}
public String getName() {
- return control.getProject().getName();
+ return projectState.getName();
}
public Project.NameKey getNameKey() {
- return control.getProject().getNameKey();
+ return projectState.getNameKey();
}
public ProjectState getProjectState() {
- return control.getProjectState();
+ return projectState;
}
public CurrentUser getUser() {
- return getControl().getUser();
- }
-
- public ProjectControl getControl() {
- return control;
+ return user;
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
index 3015164..bd6386c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
@@ -553,6 +553,10 @@
}
}
+ public ProjectData toProjectData() {
+ return new ProjectData(getProject(), parents().transform(s -> s.getProject().getNameKey()));
+ }
+
private String readFile(Path p) throws IOException {
return Files.exists(p) ? new String(Files.readAllBytes(p), UTF_8) : null;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectsCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectsCollection.java
index e0741f0..8d7b156 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectsCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectsCollection.java
@@ -14,11 +14,14 @@
package com.google.gerrit.server.project;
+import com.google.common.collect.ListMultimap;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AcceptsCreate;
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.NeedsParams;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestCollection;
import com.google.gerrit.extensions.restapi.RestView;
@@ -38,32 +41,48 @@
@Singleton
public class ProjectsCollection
- implements RestCollection<TopLevelResource, ProjectResource>, AcceptsCreate<TopLevelResource> {
+ implements RestCollection<TopLevelResource, ProjectResource>,
+ AcceptsCreate<TopLevelResource>,
+ NeedsParams {
private final DynamicMap<RestView<ProjectResource>> views;
private final Provider<ListProjects> list;
- private final ProjectControl.GenericFactory controlFactory;
+ private final Provider<QueryProjects> queryProjects;
+ private final ProjectCache projectCache;
private final PermissionBackend permissionBackend;
private final Provider<CurrentUser> user;
private final CreateProject.Factory createProjectFactory;
+ private boolean hasQuery;
+
@Inject
ProjectsCollection(
DynamicMap<RestView<ProjectResource>> views,
Provider<ListProjects> list,
- ProjectControl.GenericFactory controlFactory,
+ Provider<QueryProjects> queryProjects,
+ ProjectCache projectCache,
PermissionBackend permissionBackend,
CreateProject.Factory factory,
Provider<CurrentUser> user) {
this.views = views;
this.list = list;
- this.controlFactory = controlFactory;
+ this.queryProjects = queryProjects;
+ this.projectCache = projectCache;
this.permissionBackend = permissionBackend;
this.user = user;
this.createProjectFactory = factory;
}
@Override
+ public void setParams(ListMultimap<String, String> params) throws BadRequestException {
+ // The --query option is defined in QueryProjects
+ this.hasQuery = params.containsKey("query");
+ }
+
+ @Override
public RestView<TopLevelResource> list() {
+ if (hasQuery) {
+ return queryProjects.get();
+ }
return list.get().setFormat(OutputFormat.JSON);
}
@@ -120,10 +139,8 @@
}
Project.NameKey nameKey = new Project.NameKey(id);
- ProjectControl ctl;
- try {
- ctl = controlFactory.controlFor(nameKey, user.get());
- } catch (NoSuchProjectException e) {
+ ProjectState state = projectCache.checkedGet(nameKey);
+ if (state == null) {
return null;
}
@@ -134,7 +151,7 @@
return null; // Pretend like not found on access denied.
}
}
- return new ProjectResource(ctl);
+ return new ProjectResource(state, user.get());
}
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java
index c4a7eb4..9dd8b43 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java
@@ -22,7 +22,6 @@
import com.google.gerrit.extensions.api.projects.ConfigValue;
import com.google.gerrit.extensions.api.projects.ProjectConfigEntryType;
import com.google.gerrit.extensions.registration.DynamicMap;
-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;
@@ -40,6 +39,9 @@
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.git.TransferConfig;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.permissions.ProjectPermission;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -68,6 +70,7 @@
private final UiActions uiActions;
private final DynamicMap<RestView<ProjectResource>> views;
private final Provider<CurrentUser> user;
+ private final PermissionBackend permissionBackend;
@Inject
PutConfig(
@@ -81,7 +84,8 @@
AllProjectsName allProjects,
UiActions uiActions,
DynamicMap<RestView<ProjectResource>> views,
- Provider<CurrentUser> user) {
+ Provider<CurrentUser> user,
+ PermissionBackend permissionBackend) {
this.serverEnableSignedPush = serverEnableSignedPush;
this.metaDataUpdateFactory = metaDataUpdateFactory;
this.projectCache = projectCache;
@@ -93,13 +97,13 @@
this.uiActions = uiActions;
this.views = views;
this.user = user;
+ this.permissionBackend = permissionBackend;
}
@Override
- public ConfigInfo apply(ProjectResource rsrc, ConfigInput input) throws RestApiException {
- if (!rsrc.getControl().isOwner()) {
- throw new AuthException("restricted to project owner");
- }
+ public ConfigInfo apply(ProjectResource rsrc, ConfigInput input)
+ throws RestApiException, PermissionBackendException {
+ permissionBackend.user(user).project(rsrc.getNameKey()).check(ProjectPermission.WRITE_CONFIG);
return apply(rsrc.getProjectState(), input);
}
@@ -192,7 +196,8 @@
ProjectState state = projectStateFactory.create(projectConfig);
return new ConfigInfoImpl(
serverEnableSignedPush,
- state.controlFor(user.get()),
+ state,
+ user.get(),
config,
pluginConfigEntries,
cfgFactory,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutDescription.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutDescription.java
index 78230bd..a2808fc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutDescription.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutDescription.java
@@ -26,6 +26,9 @@
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.permissions.ProjectPermission;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.IOException;
@@ -36,25 +39,31 @@
public class PutDescription implements RestModifyView<ProjectResource, DescriptionInput> {
private final ProjectCache cache;
private final MetaDataUpdate.Server updateFactory;
+ private final PermissionBackend permissionBackend;
@Inject
- PutDescription(ProjectCache cache, MetaDataUpdate.Server updateFactory) {
+ PutDescription(
+ ProjectCache cache,
+ MetaDataUpdate.Server updateFactory,
+ PermissionBackend permissionBackend) {
this.cache = cache;
this.updateFactory = updateFactory;
+ this.permissionBackend = permissionBackend;
}
@Override
public Response<String> apply(ProjectResource resource, DescriptionInput input)
- throws AuthException, ResourceConflictException, ResourceNotFoundException, IOException {
+ throws AuthException, ResourceConflictException, ResourceNotFoundException, IOException,
+ PermissionBackendException {
if (input == null) {
input = new DescriptionInput(); // Delete would set description to null.
}
- ProjectControl ctl = resource.getControl();
- IdentifiedUser user = ctl.getUser().asIdentifiedUser();
- if (!ctl.isOwner()) {
- throw new AuthException("not project owner");
- }
+ IdentifiedUser user = resource.getUser().asIdentifiedUser();
+ permissionBackend
+ .user(user)
+ .project(resource.getNameKey())
+ .check(ProjectPermission.WRITE_CONFIG);
try (MetaDataUpdate md = updateFactory.create(resource.getNameKey())) {
ProjectConfig config = ProjectConfig.read(md);
@@ -70,7 +79,7 @@
md.setAuthor(user);
md.setMessage(msg);
config.commit(md);
- cache.evict(ctl.getProject());
+ cache.evict(resource.getProjectState().getProject());
md.getRepository().setGitwebDescription(project.getDescription());
return Strings.isNullOrEmpty(project.getDescription())
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/QueryProjects.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/QueryProjects.java
new file mode 100644
index 0000000..998bdb2
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/QueryProjects.java
@@ -0,0 +1,120 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.project;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import com.google.gerrit.extensions.common.ProjectInfo;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.gerrit.index.query.QueryParseException;
+import com.google.gerrit.index.query.QueryResult;
+import com.google.gerrit.server.index.project.ProjectIndex;
+import com.google.gerrit.server.index.project.ProjectIndexCollection;
+import com.google.gerrit.server.query.project.ProjectQueryBuilder;
+import com.google.gerrit.server.query.project.ProjectQueryProcessor;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import java.util.ArrayList;
+import java.util.List;
+import org.kohsuke.args4j.Option;
+
+public class QueryProjects implements RestReadView<TopLevelResource> {
+ private final ProjectIndexCollection indexes;
+ private final ProjectQueryBuilder queryBuilder;
+ private final ProjectQueryProcessor queryProcessor;
+ private final ProjectJson json;
+
+ private String query;
+ private int limit;
+ private int start;
+
+ @Option(
+ name = "--query",
+ aliases = {"-q"},
+ usage = "project query"
+ )
+ public void setQuery(String query) {
+ this.query = query;
+ }
+
+ @Option(
+ name = "--limit",
+ aliases = {"-n"},
+ metaVar = "CNT",
+ usage = "maximum number of projects to list"
+ )
+ public void setLimit(int limit) {
+ this.limit = limit;
+ }
+
+ @Option(
+ name = "--start",
+ aliases = {"-S"},
+ metaVar = "CNT",
+ usage = "number of projects to skip"
+ )
+ public void setStart(int start) {
+ this.start = start;
+ }
+
+ @Inject
+ protected QueryProjects(
+ ProjectIndexCollection indexes,
+ ProjectQueryBuilder queryBuilder,
+ ProjectQueryProcessor queryProcessor,
+ ProjectJson json) {
+ this.indexes = indexes;
+ this.queryBuilder = queryBuilder;
+ this.queryProcessor = queryProcessor;
+ this.json = json;
+ }
+
+ @Override
+ public List<ProjectInfo> apply(TopLevelResource resource)
+ throws BadRequestException, MethodNotAllowedException, OrmException {
+ if (Strings.isNullOrEmpty(query)) {
+ throw new BadRequestException("missing query field");
+ }
+
+ ProjectIndex searchIndex = indexes.getSearchIndex();
+ if (searchIndex == null) {
+ throw new MethodNotAllowedException("no project index");
+ }
+
+ if (start != 0) {
+ queryProcessor.setStart(start);
+ }
+
+ if (limit != 0) {
+ queryProcessor.setUserProvidedLimit(limit);
+ }
+
+ try {
+ QueryResult<ProjectData> result = queryProcessor.query(queryBuilder.parse(query));
+ List<ProjectData> pds = result.entities();
+
+ ArrayList<ProjectInfo> projectInfos = Lists.newArrayListWithCapacity(pds.size());
+ for (ProjectData pd : pds) {
+ projectInfos.add(json.format(pd.getProject()));
+ }
+ return projectInfos;
+ } catch (QueryParseException e) {
+ throw new BadRequestException(e.getMessage());
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
index a92c0b7..9a4fe96 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
@@ -44,7 +44,7 @@
import java.util.Set;
/** Manages access control for Git references (aka branches, tags). */
-public class RefControl {
+class RefControl {
private final ProjectControl projectControl;
private final String refName;
@@ -66,19 +66,19 @@
this.effective = new HashMap<>();
}
- public String getRefName() {
+ String getRefName() {
return refName;
}
- public ProjectControl getProjectControl() {
+ ProjectControl getProjectControl() {
return projectControl;
}
- public CurrentUser getUser() {
+ CurrentUser getUser() {
return projectControl.getUser();
}
- public RefControl forUser(CurrentUser who) {
+ RefControl forUser(CurrentUser who) {
ProjectControl newCtl = projectControl.forUser(who);
if (relevant.isUserSpecific()) {
return newCtl.controlForRef(getRefName());
@@ -87,7 +87,7 @@
}
/** Is this user a ref owner? */
- public boolean isOwner() {
+ boolean isOwner() {
if (owner == null) {
if (canPerform(Permission.OWNER)) {
owner = true;
@@ -109,11 +109,6 @@
return isVisible;
}
- /** Can this user see other users change edits? */
- public boolean isEditVisible() {
- return canViewPrivateChanges();
- }
-
private boolean canUpload() {
return projectControl.controlForRef("refs/for/" + getRefName()).canPerform(Permission.PUSH)
&& isProjectStatePermittingWrite();
@@ -275,11 +270,6 @@
return canPerform(Permission.ABANDON);
}
- /** @return true if this user can remove a reviewer for a change. */
- boolean canRemoveReviewer() {
- return canPerform(Permission.REMOVE_REVIEWER);
- }
-
/** @return true if this user can view private changes. */
boolean canViewPrivateChanges() {
return canPerform(Permission.VIEW_PRIVATE_CHANGES);
@@ -395,7 +385,7 @@
}
/** True if the user is blocked from using this permission. */
- public boolean isBlocked(String permissionName) {
+ boolean isBlocked(String permissionName) {
return !doCanPerform(permissionName, false, true);
}
@@ -561,6 +551,8 @@
return canUpdate();
case FORCE_UPDATE:
return canForceUpdate();
+ case SET_HEAD:
+ return projectControl.isOwner();
case FORGE_AUTHOR:
return canForgeAuthor();
@@ -577,6 +569,16 @@
case UPDATE_BY_SUBMIT:
return projectControl.controlForRef("refs/for/" + getRefName()).canSubmit(true);
+ case READ_PRIVATE_CHANGES:
+ return canViewPrivateChanges();
+
+ case READ_CONFIG:
+ return projectControl
+ .controlForRef(RefNames.REFS_CONFIG)
+ .canPerform(RefPermission.READ.name());
+ case WRITE_CONFIG:
+ return isOwner();
+
case SKIP_VALIDATION:
return canForgeAuthor()
&& canForgeCommitter()
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefResource.java
index 124439f..ac2735d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefResource.java
@@ -14,10 +14,12 @@
package com.google.gerrit.server.project;
+import com.google.gerrit.server.CurrentUser;
+
public abstract class RefResource extends ProjectResource {
- public RefResource(ProjectControl control) {
- super(control);
+ public RefResource(ProjectState projectState, CurrentUser user) {
+ super(projectState, user);
}
/** @return the ref's name */
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/RemoveReviewerControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/RemoveReviewerControl.java
index ded2bf8..8f980ee 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/RemoveReviewerControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/RemoveReviewerControl.java
@@ -29,27 +29,28 @@
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
+import java.io.IOException;
@Singleton
public class RemoveReviewerControl {
private final PermissionBackend permissionBackend;
private final Provider<ReviewDb> dbProvider;
- private final ChangeControl.GenericFactory changeControlFactory;
+ private final ProjectControl.GenericFactory projectControlFactory;
@Inject
RemoveReviewerControl(
PermissionBackend permissionBackend,
Provider<ReviewDb> dbProvider,
- ChangeControl.GenericFactory changeControlFactory) {
+ ProjectControl.GenericFactory projectControlFactory) {
this.permissionBackend = permissionBackend;
this.dbProvider = dbProvider;
- this.changeControlFactory = changeControlFactory;
+ this.projectControlFactory = projectControlFactory;
}
/** @throws AuthException if this user is not allowed to remove this approval. */
public void checkRemoveReviewer(
ChangeNotes notes, CurrentUser currentUser, PatchSetApproval approval)
- throws PermissionBackendException, AuthException, NoSuchChangeException, OrmException {
+ throws PermissionBackendException, AuthException, NoSuchProjectException, IOException {
if (canRemoveReviewerWithoutPermissionCheck(
notes.getChange(), currentUser, approval.getAccountId(), approval.getValue())) {
return;
@@ -65,7 +66,7 @@
/** @return true if the user is allowed to remove this reviewer. */
public boolean testRemoveReviewer(
ChangeData cd, CurrentUser currentUser, Account.Id reviewer, int value)
- throws PermissionBackendException, NoSuchChangeException, OrmException {
+ throws PermissionBackendException, NoSuchProjectException, OrmException, IOException {
if (canRemoveReviewerWithoutPermissionCheck(cd.change(), currentUser, reviewer, value)) {
return true;
}
@@ -78,7 +79,7 @@
private boolean canRemoveReviewerWithoutPermissionCheck(
Change change, CurrentUser currentUser, Account.Id reviewer, int value)
- throws NoSuchChangeException, OrmException {
+ throws NoSuchProjectException, IOException {
if (!change.getStatus().isOpen()) {
return false;
}
@@ -94,11 +95,11 @@
// Users with the remove reviewer permission, the branch owner, project
// owner and site admin can remove anyone
- ChangeControl changeControl =
- changeControlFactory.controlFor(dbProvider.get(), change, currentUser);
- if (changeControl.getRefControl().isOwner() // branch owner
- || changeControl.getProjectControl().isOwner() // project owner
- || changeControl.getProjectControl().isAdmin()) { // project admin
+ // TODO(hiesel): Remove all Control usage
+ ProjectControl ctl = projectControlFactory.controlFor(change.getProject(), currentUser);
+ if (ctl.controlForRef(change.getDest()).isOwner() // branch owner
+ || ctl.isOwner() // project owner
+ || ctl.isAdmin()) { // project admin
return true;
}
return false;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetAccess.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetAccess.java
index e875388..c768315 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetAccess.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetAccess.java
@@ -34,6 +34,7 @@
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.permissions.RefPermission;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -93,9 +94,12 @@
permissionBackend.user(identifiedUser).check(GlobalPermission.ADMINISTRATE_SERVER);
checkedAdmin = true;
}
- } else if (!rsrc.getControl().controlForRef(section.getName()).isOwner()) {
- throw new AuthException(
- "You are not allowed to edit permissions for ref: " + section.getName());
+ } else {
+ permissionBackend
+ .user(identifiedUser)
+ .project(rsrc.getNameKey())
+ .ref(section.getName())
+ .check(RefPermission.WRITE_CONFIG);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetDefaultDashboard.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetDefaultDashboard.java
index 9aa9ae7..0dd5f85 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetDefaultDashboard.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetDefaultDashboard.java
@@ -18,7 +18,6 @@
import com.google.common.base.Strings;
import com.google.gerrit.extensions.api.projects.DashboardInfo;
import com.google.gerrit.extensions.common.SetDashboardInput;
-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;
@@ -29,7 +28,9 @@
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.permissions.ProjectPermission;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.io.IOException;
@@ -42,6 +43,7 @@
private final MetaDataUpdate.Server updateFactory;
private final DashboardsCollection dashboards;
private final Provider<GetDashboard> get;
+ private final PermissionBackend permissionBackend;
@Option(name = "--inherited", usage = "set dashboard inherited by children")
private boolean inherited;
@@ -51,30 +53,35 @@
ProjectCache cache,
MetaDataUpdate.Server updateFactory,
DashboardsCollection dashboards,
- Provider<GetDashboard> get) {
+ Provider<GetDashboard> get,
+ PermissionBackend permissionBackend) {
this.cache = cache;
this.updateFactory = updateFactory;
this.dashboards = dashboards;
this.get = get;
+ this.permissionBackend = permissionBackend;
}
@Override
- public Response<DashboardInfo> apply(DashboardResource resource, SetDashboardInput input)
+ public Response<DashboardInfo> apply(DashboardResource rsrc, SetDashboardInput input)
throws RestApiException, IOException, PermissionBackendException {
if (input == null) {
input = new SetDashboardInput(); // Delete would set input to null.
}
input.id = Strings.emptyToNull(input.id);
- ProjectControl ctl = resource.getControl();
- if (!ctl.isOwner()) {
- throw new AuthException("not project owner");
- }
+ permissionBackend
+ .user(rsrc.getUser())
+ .project(rsrc.getProjectState().getNameKey())
+ .check(ProjectPermission.WRITE_CONFIG);
DashboardResource target = null;
if (input.id != null) {
try {
- target = dashboards.parse(new ProjectResource(ctl), IdString.fromUrl(input.id));
+ target =
+ dashboards.parse(
+ new ProjectResource(rsrc.getProjectState(), rsrc.getUser()),
+ IdString.fromUrl(input.id));
} catch (ResourceNotFoundException e) {
throw new BadRequestException("dashboard " + input.id + " not found");
} catch (ConfigInvalidException e) {
@@ -82,7 +89,7 @@
}
}
- try (MetaDataUpdate md = updateFactory.create(ctl.getProject().getNameKey())) {
+ try (MetaDataUpdate md = updateFactory.create(rsrc.getProjectState().getNameKey())) {
ProjectConfig config = ProjectConfig.read(md);
Project project = config.getProject();
if (inherited) {
@@ -100,10 +107,10 @@
if (!msg.endsWith("\n")) {
msg += "\n";
}
- md.setAuthor(ctl.getUser().asIdentifiedUser());
+ md.setAuthor(rsrc.getUser().asIdentifiedUser());
md.setMessage(msg);
config.commit(md);
- cache.evict(ctl.getProject());
+ cache.evict(rsrc.getProjectState().getProject());
if (target != null) {
DashboardInfo info = get.get().apply(target);
@@ -112,7 +119,7 @@
}
return Response.none();
} catch (RepositoryNotFoundException notFound) {
- throw new ResourceNotFoundException(ctl.getProject().getName());
+ throw new ResourceNotFoundException(rsrc.getProjectState().getProject().getName());
} catch (ConfigInvalidException e) {
throw new ResourceConflictException(
String.format("invalid project.config: %s", e.getMessage()));
@@ -135,7 +142,8 @@
throws RestApiException, IOException, PermissionBackendException {
SetDefaultDashboard set = setDefault.get();
set.inherited = inherited;
- return set.apply(DashboardResource.projectDefault(resource.getControl()), input);
+ return set.apply(
+ DashboardResource.projectDefault(resource.getProjectState(), resource.getUser()), input);
}
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetHead.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetHead.java
index eeb47df..a8d93c1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetHead.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetHead.java
@@ -15,11 +15,11 @@
package com.google.gerrit.server.project;
import com.google.common.base.Strings;
+import com.google.gerrit.extensions.api.projects.HeadInput;
import com.google.gerrit.extensions.events.HeadUpdatedListener;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
-import com.google.gerrit.extensions.restapi.DefaultInput;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
@@ -28,7 +28,9 @@
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.extensions.events.AbstractNoNotifyEvent;
import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.project.SetHead.Input;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gerrit.server.permissions.RefPermission;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@@ -43,39 +45,41 @@
import org.slf4j.LoggerFactory;
@Singleton
-public class SetHead implements RestModifyView<ProjectResource, Input> {
+public class SetHead implements RestModifyView<ProjectResource, HeadInput> {
private static final Logger log = LoggerFactory.getLogger(SetHead.class);
- public static class Input {
- @DefaultInput public String ref;
- }
-
private final GitRepositoryManager repoManager;
private final Provider<IdentifiedUser> identifiedUser;
private final DynamicSet<HeadUpdatedListener> headUpdatedListeners;
+ private final PermissionBackend permissionBackend;
@Inject
SetHead(
GitRepositoryManager repoManager,
Provider<IdentifiedUser> identifiedUser,
- DynamicSet<HeadUpdatedListener> headUpdatedListeners) {
+ DynamicSet<HeadUpdatedListener> headUpdatedListeners,
+ PermissionBackend permissionBackend) {
this.repoManager = repoManager;
this.identifiedUser = identifiedUser;
this.headUpdatedListeners = headUpdatedListeners;
+ this.permissionBackend = permissionBackend;
}
@Override
- public String apply(ProjectResource rsrc, Input input)
+ public String apply(ProjectResource rsrc, HeadInput input)
throws AuthException, ResourceNotFoundException, BadRequestException,
- UnprocessableEntityException, IOException {
- if (!rsrc.getControl().isOwner()) {
- throw new AuthException("restricted to project owner");
- }
+ UnprocessableEntityException, IOException, PermissionBackendException {
if (input == null || Strings.isNullOrEmpty(input.ref)) {
throw new BadRequestException("ref required");
}
String ref = RefNames.fullName(input.ref);
+ permissionBackend
+ .user(rsrc.getUser())
+ .project(rsrc.getNameKey())
+ .ref(ref)
+ .check(RefPermission.SET_HEAD);
+
try (Repository repo = repoManager.openRepository(rsrc.getNameKey())) {
Map<String, Ref> cur = repo.getRefDatabase().exactRef(Constants.HEAD, ref);
if (!cur.containsKey(ref)) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetParent.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetParent.java
index 37cfcdd..370af94 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetParent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetParent.java
@@ -19,8 +19,8 @@
import com.google.common.base.MoreObjects;
import com.google.common.base.Strings;
import com.google.common.collect.Iterables;
+import com.google.gerrit.extensions.api.projects.ParentInput;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.extensions.restapi.DefaultInput;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestModifyView;
@@ -33,7 +33,6 @@
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
-import com.google.gerrit.server.project.SetParent.Input;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.IOException;
@@ -41,12 +40,7 @@
import org.eclipse.jgit.errors.RepositoryNotFoundException;
@Singleton
-public class SetParent implements RestModifyView<ProjectResource, Input> {
- public static class Input {
- @DefaultInput public String parent;
- public String commitMessage;
- }
-
+public class SetParent implements RestModifyView<ProjectResource, ParentInput> {
private final ProjectCache cache;
private final PermissionBackend permissionBackend;
private final MetaDataUpdate.Server updateFactory;
@@ -65,13 +59,13 @@
}
@Override
- public String apply(ProjectResource rsrc, Input input)
+ public String apply(ProjectResource rsrc, ParentInput input)
throws AuthException, ResourceConflictException, ResourceNotFoundException,
UnprocessableEntityException, IOException, PermissionBackendException {
return apply(rsrc, input, true);
}
- public String apply(ProjectResource rsrc, Input input, boolean checkIfAdmin)
+ public String apply(ProjectResource rsrc, ParentInput input, boolean checkIfAdmin)
throws AuthException, ResourceConflictException, ResourceNotFoundException,
UnprocessableEntityException, IOException, PermissionBackendException {
IdentifiedUser user = rsrc.getUser().asIdentifiedUser();
@@ -124,6 +118,10 @@
throw new UnprocessableEntityException("parent project " + newParent + " not found");
}
+ if (parent.getName().equals(project.get())) {
+ throw new ResourceConflictException("cannot set parent to self");
+ }
+
if (Iterables.tryFind(
parent.tree(),
p -> {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/TagResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/TagResource.java
index fe4d68d..08ef669 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/TagResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/TagResource.java
@@ -16,6 +16,7 @@
import com.google.gerrit.extensions.api.projects.TagInfo;
import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.server.CurrentUser;
import com.google.inject.TypeLiteral;
public class TagResource extends RefResource {
@@ -24,8 +25,8 @@
private final TagInfo tagInfo;
- public TagResource(ProjectControl control, TagInfo tagInfo) {
- super(control);
+ public TagResource(ProjectState projectState, CurrentUser user, TagInfo tagInfo) {
+ super(projectState, user);
this.tagInfo = tagInfo;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/TagsCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/TagsCollection.java
index 78670ad..7ee0a8e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/TagsCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/TagsCollection.java
@@ -48,9 +48,9 @@
}
@Override
- public TagResource parse(ProjectResource resource, IdString id)
+ public TagResource parse(ProjectResource rsrc, IdString id)
throws ResourceNotFoundException, IOException {
- return new TagResource(resource.getControl(), list.get().get(resource, id));
+ return new TagResource(rsrc.getProjectState(), rsrc.getUser(), list.get().get(rsrc, id));
}
@Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
index 2a71258..dfcc999 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -72,7 +72,6 @@
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.patch.PatchListKey;
import com.google.gerrit.server.patch.PatchListNotAvailableException;
-import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
@@ -326,7 +325,7 @@
ChangeData cd =
new ChangeData(
null, null, null, null, null, null, null, null, null, null, null, null, null, null,
- null, null, null, null, null, project, id, null, null);
+ null, null, null, null, project, id, null, null);
cd.currentPatchSet = new PatchSet(new PatchSet.Id(id, currentPatchSetId));
return cd;
}
@@ -349,7 +348,6 @@
private final TrackingFooters trackingFooters;
private final GetPureRevert pureRevert;
private final SubmitRuleEvaluator.Factory submitRuleEvaluatorFactory;
- private final ChangeControl.GenericFactory changeControlFactory;
// Required assisted injected fields.
private final ReviewDb db;
@@ -375,7 +373,6 @@
private Collection<Comment> publishedComments;
private Collection<RobotComment> robotComments;
private CurrentUser visibleTo;
- private ChangeControl changeControl;
private List<ChangeMessage> messages;
private Optional<ChangedLines> changedLines;
private SubmitTypeRecord submitTypeRecord;
@@ -420,7 +417,6 @@
TrackingFooters trackingFooters,
GetPureRevert pureRevert,
SubmitRuleEvaluator.Factory submitRuleEvaluatorFactory,
- ChangeControl.GenericFactory changeControlFactory,
@Assisted ReviewDb db,
@Assisted Project.NameKey project,
@Assisted Change.Id id,
@@ -443,7 +439,6 @@
this.trackingFooters = trackingFooters;
this.pureRevert = pureRevert;
this.submitRuleEvaluatorFactory = submitRuleEvaluatorFactory;
- this.changeControlFactory = changeControlFactory;
// May be null in tests when created via createForTest above, in which case lazy-loading will
// intentionally fail with NPE. Still not marked @Nullable in the constructor, to force callers
@@ -554,22 +549,8 @@
return visibleTo == user;
}
- private ChangeControl changeControl() throws OrmException {
- // TODO(hiesel): Remove this method.
- if (changeControl == null) {
- Change c = change();
- try {
- changeControl = changeControlFactory.controlFor(db, c, userFactory.create(c.getOwner()));
- } catch (NoSuchChangeException e) {
- throw new OrmException(e);
- }
- }
- return changeControl;
- }
-
- void cacheVisibleTo(ChangeControl ctl) {
- visibleTo = ctl.getUser();
- changeControl = ctl;
+ void cacheVisibleTo(CurrentUser user) {
+ visibleTo = user;
}
public Change change() throws OrmException {
@@ -980,15 +961,8 @@
return null;
}
PatchSet ps = currentPatchSet();
- try {
- if (ps == null || !changeControl().isVisible(db)) {
- return null;
- }
- } catch (OrmException e) {
- if (e.getCause() instanceof NoSuchChangeException) {
- return null;
- }
- throw e;
+ if (ps == null) {
+ return null;
}
try (Repository repo = repoManager.openRepository(project())) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIsVisibleToPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIsVisibleToPredicate.java
index 7bbb27b..19549d9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIsVisibleToPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeIsVisibleToPredicate.java
@@ -23,28 +23,28 @@
import com.google.gerrit.server.permissions.ChangePermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
-import com.google.gerrit.server.project.ChangeControl;
-import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Provider;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public class ChangeIsVisibleToPredicate extends IsVisibleToPredicate<ChangeData> {
+ private static final Logger logger = LoggerFactory.getLogger(ChangeIsVisibleToPredicate.class);
+
protected final Provider<ReviewDb> db;
protected final ChangeNotes.Factory notesFactory;
- protected final ChangeControl.GenericFactory changeControlFactory;
protected final CurrentUser user;
protected final PermissionBackend permissionBackend;
public ChangeIsVisibleToPredicate(
Provider<ReviewDb> db,
ChangeNotes.Factory notesFactory,
- ChangeControl.GenericFactory changeControlFactory,
CurrentUser user,
PermissionBackend permissionBackend) {
super(ChangeQueryBuilder.FIELD_VISIBLETO, IndexUtils.describe(user));
this.db = db;
this.notesFactory = notesFactory;
- this.changeControlFactory = changeControlFactory;
this.user = user;
this.permissionBackend = permissionBackend;
}
@@ -59,15 +59,7 @@
return false;
}
- ChangeControl changeControl;
ChangeNotes notes = notesFactory.createFromIndexedChange(change);
- try {
- changeControl = changeControlFactory.controlFor(notes, user);
- } catch (NoSuchChangeException e) {
- // Ignored
- return false;
- }
-
boolean visible;
try {
visible =
@@ -77,10 +69,14 @@
.database(db)
.test(ChangePermission.READ);
} catch (PermissionBackendException e) {
+ if (e.getCause() instanceof NoSuchProjectException) {
+ logger.info("No such project: {}", cd.project());
+ return false;
+ }
throw new OrmException("unable to check permissions", e);
}
if (visible) {
- cd.cacheVisibleTo(changeControl);
+ cd.cacheVisibleTo(user);
return true;
}
return false;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index 1f28dbd..ccc11aa 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -70,7 +70,6 @@
import com.google.gerrit.server.notedb.ReviewerStateInternal;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.permissions.PermissionBackend;
-import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.ListChildProjects;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gwtorm.server.OrmException;
@@ -196,7 +195,6 @@
final AllProjectsName allProjectsName;
final AllUsersName allUsersName;
final PermissionBackend permissionBackend;
- final ChangeControl.GenericFactory changeControlGenericFactory;
final ChangeData.Factory changeDataFactory;
final ChangeIndex index;
final ChangeIndexRewriter rewriter;
@@ -233,7 +231,6 @@
IdentifiedUser.GenericFactory userFactory,
Provider<CurrentUser> self,
PermissionBackend permissionBackend,
- ChangeControl.GenericFactory changeControlGenericFactory,
ChangeNotes.Factory notesFactory,
ChangeData.Factory changeDataFactory,
CommentsUtil commentsUtil,
@@ -263,7 +260,6 @@
userFactory,
self,
permissionBackend,
- changeControlGenericFactory,
notesFactory,
changeDataFactory,
commentsUtil,
@@ -295,7 +291,6 @@
IdentifiedUser.GenericFactory userFactory,
Provider<CurrentUser> self,
PermissionBackend permissionBackend,
- ChangeControl.GenericFactory changeControlGenericFactory,
ChangeNotes.Factory notesFactory,
ChangeData.Factory changeDataFactory,
CommentsUtil commentsUtil,
@@ -324,7 +319,6 @@
this.self = self;
this.permissionBackend = permissionBackend;
this.notesFactory = notesFactory;
- this.changeControlGenericFactory = changeControlGenericFactory;
this.changeDataFactory = changeDataFactory;
this.commentsUtil = commentsUtil;
this.accountResolver = accountResolver;
@@ -357,7 +351,6 @@
userFactory,
Providers.of(otherUser),
permissionBackend,
- changeControlGenericFactory,
notesFactory,
changeDataFactory,
commentsUtil,
@@ -483,7 +476,10 @@
new ChangeIdPredicate(parseChangeId(triplet.get().id().get())));
}
if (PAT_LEGACY_ID.matcher(query).matches()) {
- return new LegacyChangeIdPredicate(Change.Id.parse(query));
+ Integer id = Ints.tryParse(query);
+ if (id != null) {
+ return new LegacyChangeIdPredicate(new Change.Id(id));
+ }
} else if (PAT_CHANGE_ID.matcher(query).matches()) {
return new ChangeIdPredicate(parseChangeId(query));
}
@@ -926,8 +922,7 @@
}
public Predicate<ChangeData> visibleto(CurrentUser user) {
- return new ChangeIsVisibleToPredicate(
- args.db, args.notesFactory, args.changeControlGenericFactory, user, args.permissionBackend);
+ return new ChangeIsVisibleToPredicate(args.db, args.notesFactory, user, args.permissionBackend);
}
public Predicate<ChangeData> is_visible() throws QueryParseException {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryProcessor.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryProcessor.java
index eb6cf77..b190cd2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryProcessor.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryProcessor.java
@@ -34,7 +34,6 @@
import com.google.gerrit.server.index.change.IndexedChangeQuery;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.permissions.PermissionBackend;
-import com.google.gerrit.server.project.ChangeControl;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.util.ArrayList;
@@ -61,7 +60,6 @@
private final Provider<ReviewDb> db;
private final Provider<CurrentUser> userProvider;
- private final ChangeControl.GenericFactory changeControlFactory;
private final ChangeNotes.Factory notesFactory;
private final DynamicMap<ChangeAttributeFactory> attributeFactories;
private final PermissionBackend permissionBackend;
@@ -82,7 +80,6 @@
ChangeIndexCollection indexes,
ChangeIndexRewriter rewriter,
Provider<ReviewDb> db,
- ChangeControl.GenericFactory changeControlFactory,
ChangeNotes.Factory notesFactory,
DynamicMap<ChangeAttributeFactory> attributeFactories,
PermissionBackend permissionBackend) {
@@ -96,7 +93,6 @@
() -> limitsFactory.create(userProvider.get()).getQueryLimit());
this.db = db;
this.userProvider = userProvider;
- this.changeControlFactory = changeControlFactory;
this.notesFactory = notesFactory;
this.attributeFactories = attributeFactories;
this.permissionBackend = permissionBackend;
@@ -142,8 +138,7 @@
protected Predicate<ChangeData> enforceVisibility(Predicate<ChangeData> pred) {
return new AndChangeSource(
pred,
- new ChangeIsVisibleToPredicate(
- db, notesFactory, changeControlFactory, userProvider.get(), permissionBackend),
+ new ChangeIsVisibleToPredicate(db, notesFactory, userProvider.get(), permissionBackend),
start);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java
index 1917d6f..785ae38 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java
@@ -79,7 +79,7 @@
for (PatchSetApproval p : object.currentApprovals()) {
if (labelType.matches(p)) {
hasVote = true;
- if (match(object, p.getValue(), p.getAccountId(), labelType)) {
+ if (match(object, p.getValue(), p.getAccountId())) {
return true;
}
}
@@ -105,7 +105,7 @@
return null;
}
- protected boolean match(ChangeData cd, short value, Account.Id approver, LabelType type) {
+ protected boolean match(ChangeData cd, short value, Account.Id approver) {
if (value != expVal) {
return false;
}
@@ -119,11 +119,11 @@
return false;
}
- // Double check the value is still permitted for the user.
+ // Check the user has 'READ' permission.
try {
PermissionBackend.ForChange perm =
permissionBackend.user(reviewer).database(dbProvider).change(cd);
- return perm.test(ChangePermission.READ) && expVal == perm.squashByTest(type, value);
+ return perm.test(ChangePermission.READ);
} catch (PermissionBackendException e) {
return false;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java
index c9ddfb7..f8bd2e3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java
@@ -24,7 +24,6 @@
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.permissions.PermissionBackend;
-import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.util.LabelVote;
import com.google.inject.Provider;
@@ -38,7 +37,6 @@
protected static class Args {
protected final ProjectCache projectCache;
protected final PermissionBackend permissionBackend;
- protected final ChangeControl.GenericFactory ccFactory;
protected final IdentifiedUser.GenericFactory userFactory;
protected final Provider<ReviewDb> dbProvider;
protected final String value;
@@ -48,7 +46,6 @@
protected Args(
ProjectCache projectCache,
PermissionBackend permissionBackend,
- ChangeControl.GenericFactory ccFactory,
IdentifiedUser.GenericFactory userFactory,
Provider<ReviewDb> dbProvider,
String value,
@@ -56,7 +53,6 @@
AccountGroup.UUID group) {
this.projectCache = projectCache;
this.permissionBackend = permissionBackend;
- this.ccFactory = ccFactory;
this.userFactory = userFactory;
this.dbProvider = dbProvider;
this.value = value;
@@ -87,14 +83,7 @@
super(
predicates(
new Args(
- a.projectCache,
- a.permissionBackend,
- a.changeControlGenericFactory,
- a.userFactory,
- a.db,
- value,
- accounts,
- group)));
+ a.projectCache, a.permissionBackend, a.userFactory, a.db, value, accounts, group)));
this.value = value;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OutputStreamQuery.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
index ee9c570..185517a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
@@ -31,7 +31,6 @@
import com.google.gerrit.server.data.QueryStatsAttribute;
import com.google.gerrit.server.events.EventFactory;
import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.SubmitRuleEvaluator;
import com.google.gson.Gson;
import com.google.gwtorm.server.OrmException;
@@ -42,17 +41,19 @@
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.lang.reflect.Field;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.util.io.DisabledOutputStream;
-import org.joda.time.format.DateTimeFormat;
-import org.joda.time.format.DateTimeFormatter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -65,7 +66,10 @@
public class OutputStreamQuery {
private static final Logger log = LoggerFactory.getLogger(OutputStreamQuery.class);
- private static final DateTimeFormatter dtf = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss zzz");
+ private static final DateTimeFormatter dtf =
+ DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss zzz")
+ .withLocale(Locale.US)
+ .withZone(ZoneId.systemDefault());
public enum OutputFormat {
TEXT,
@@ -79,7 +83,6 @@
private final EventFactory eventFactory;
private final TrackingFooters trackingFooters;
private final CurrentUser user;
- private final ChangeControl.GenericFactory changeControlFactory;
private final SubmitRuleEvaluator.Factory submitRuleEvaluatorFactory;
private OutputFormat outputFormat = OutputFormat.TEXT;
@@ -105,7 +108,6 @@
EventFactory eventFactory,
TrackingFooters trackingFooters,
CurrentUser user,
- ChangeControl.GenericFactory changeControlFactory,
SubmitRuleEvaluator.Factory submitRuleEvaluatorFactory) {
this.db = db;
this.repoManager = repoManager;
@@ -114,7 +116,6 @@
this.eventFactory = eventFactory;
this.trackingFooters = trackingFooters;
this.user = user;
- this.changeControlFactory = changeControlFactory;
this.submitRuleEvaluatorFactory = submitRuleEvaluatorFactory;
}
@@ -273,13 +274,12 @@
}
}
- ChangeControl ctl = changeControlFactory.controlFor(db, d.change(), user);
if (includePatchSets) {
eventFactory.addPatchSets(
db,
rw,
c,
- ctl.getVisiblePatchSets(d.patchSets(), db),
+ d.patchSets(),
includeApprovals ? d.approvals().asMap() : null,
includeFiles,
d.change(),
@@ -288,7 +288,7 @@
if (includeCurrentPatchSet) {
PatchSet current = d.currentPatchSet();
- if (current != null && ctl.isVisible(d.db())) {
+ if (current != null) {
c.currentPatchSet = eventFactory.asPatchSetAttribute(db, rw, d.change(), current);
eventFactory.addApprovals(c.currentPatchSet, d.currentApprovals(), labelTypes);
@@ -308,7 +308,7 @@
db,
rw,
c,
- ctl.getVisiblePatchSets(d.patchSets(), db),
+ d.patchSets(),
includeApprovals ? d.approvals().asMap() : null,
includeFiles,
d.change(),
@@ -402,7 +402,7 @@
out.print('\n');
} else if (value instanceof Long && isDateField(field)) {
out.print(' ');
- out.print(dtf.print(((Long) value) * 1000L));
+ out.print(dtf.format(Instant.ofEpochSecond((Long) value)));
out.print('\n');
} else if (isPrimitive(value)) {
out.print(' ');
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ParentProjectPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ParentProjectPredicate.java
index 19c0515..e3ff21a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ParentProjectPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ParentProjectPredicate.java
@@ -58,7 +58,7 @@
List<Predicate<ChangeData>> r = new ArrayList<>();
r.add(new ProjectPredicate(projectState.getName()));
try {
- ProjectResource proj = new ProjectResource(projectState.controlFor(self.get()));
+ ProjectResource proj = new ProjectResource(projectState, self.get());
ListChildProjects children = listChildProjects.get();
children.setRecursive(true);
for (ProjectInfo p : children.apply(proj)) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
index 8ad0e0b..fadc853 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
@@ -34,8 +34,12 @@
import java.util.EnumSet;
import java.util.List;
import org.kohsuke.args4j.Option;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public class QueryChanges implements RestReadView<TopLevelResource> {
+ private static final Logger log = LoggerFactory.getLogger(QueryChanges.class);
+
private final ChangeJson.Factory json;
private final ChangeQueryBuilder qb;
private final ChangeQueryProcessor imp;
@@ -108,6 +112,7 @@
} catch (QueryRequiresAuthException e) {
throw new AuthException("Must be signed-in to use this operator");
} catch (QueryParseException e) {
+ log.debug("Reject change query with 400 Bad Request: " + queries, e);
throw new BadRequestException(e.getMessage(), e);
}
return out.size() == 1 ? out.get(0) : out;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/project/ProjectIsVisibleToPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/project/ProjectIsVisibleToPredicate.java
new file mode 100644
index 0000000..20032ce
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/project/ProjectIsVisibleToPredicate.java
@@ -0,0 +1,48 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.query.project;
+
+import com.google.gerrit.index.query.IsVisibleToPredicate;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.index.IndexUtils;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.ProjectPermission;
+import com.google.gerrit.server.project.ProjectData;
+import com.google.gerrit.server.query.account.AccountQueryBuilder;
+import com.google.gwtorm.server.OrmException;
+
+public class ProjectIsVisibleToPredicate extends IsVisibleToPredicate<ProjectData> {
+ protected final PermissionBackend permissionBackend;
+ protected final CurrentUser user;
+
+ public ProjectIsVisibleToPredicate(PermissionBackend permissionBackend, CurrentUser user) {
+ super(AccountQueryBuilder.FIELD_VISIBLETO, IndexUtils.describe(user));
+ this.permissionBackend = permissionBackend;
+ this.user = user;
+ }
+
+ @Override
+ public boolean match(ProjectData pd) throws OrmException {
+ return permissionBackend
+ .user(user)
+ .project(pd.getProject().getNameKey())
+ .testOrFalse(ProjectPermission.READ);
+ }
+
+ @Override
+ public int getCost() {
+ return 1;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/project/ProjectPredicates.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/project/ProjectPredicates.java
new file mode 100644
index 0000000..379c564
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/project/ProjectPredicates.java
@@ -0,0 +1,45 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.query.project;
+
+import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.query.IndexPredicate;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.index.project.ProjectField;
+import com.google.gerrit.server.project.ProjectData;
+import java.util.Locale;
+
+public class ProjectPredicates {
+ public static Predicate<ProjectData> name(Project.NameKey nameKey) {
+ return new ProjectPredicate(ProjectField.NAME, nameKey.get());
+ }
+
+ public static Predicate<ProjectData> inname(String name) {
+ return new ProjectPredicate(ProjectField.NAME_PART, name.toLowerCase(Locale.US));
+ }
+
+ public static Predicate<ProjectData> description(String description) {
+ return new ProjectPredicate(ProjectField.DESCRIPTION, description);
+ }
+
+ static class ProjectPredicate extends IndexPredicate<ProjectData> {
+ ProjectPredicate(FieldDef<ProjectData, ?> def, String value) {
+ super(def, value);
+ }
+ }
+
+ private ProjectPredicates() {}
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/project/ProjectQueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/project/ProjectQueryBuilder.java
new file mode 100644
index 0000000..e9e9c0f
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/project/ProjectQueryBuilder.java
@@ -0,0 +1,83 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.query.project;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import com.google.common.primitives.Ints;
+import com.google.gerrit.index.query.LimitPredicate;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryBuilder;
+import com.google.gerrit.index.query.QueryParseException;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.project.ProjectData;
+import com.google.inject.Inject;
+import java.util.List;
+
+/** Parses a query string meant to be applied to project objects. */
+public class ProjectQueryBuilder extends QueryBuilder<ProjectData> {
+ public static final String FIELD_LIMIT = "limit";
+
+ private static final QueryBuilder.Definition<ProjectData, ProjectQueryBuilder> mydef =
+ new QueryBuilder.Definition<>(ProjectQueryBuilder.class);
+
+ @Inject
+ ProjectQueryBuilder() {
+ super(mydef);
+ }
+
+ @Operator
+ public Predicate<ProjectData> name(String name) {
+ return ProjectPredicates.name(new Project.NameKey(name));
+ }
+
+ @Operator
+ public Predicate<ProjectData> inname(String namePart) {
+ if (namePart.isEmpty()) {
+ return name(namePart);
+ }
+ return ProjectPredicates.inname(namePart);
+ }
+
+ @Operator
+ public Predicate<ProjectData> description(String description) throws QueryParseException {
+ if (Strings.isNullOrEmpty(description)) {
+ throw error("description operator requires a value");
+ }
+
+ return ProjectPredicates.description(description);
+ }
+
+ @Override
+ protected Predicate<ProjectData> defaultField(String query) throws QueryParseException {
+ // Adapt the capacity of this list when adding more default predicates.
+ List<Predicate<ProjectData>> preds = Lists.newArrayListWithCapacity(3);
+ preds.add(name(query));
+ preds.add(inname(query));
+ if (!Strings.isNullOrEmpty(query)) {
+ preds.add(description(query));
+ }
+ return Predicate.or(preds);
+ }
+
+ @Operator
+ public Predicate<ProjectData> limit(String query) throws QueryParseException {
+ Integer limit = Ints.tryParse(query);
+ if (limit == null) {
+ throw error("Invalid limit: " + query);
+ }
+ return new LimitPredicate<>(FIELD_LIMIT, limit);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/project/ProjectQueryProcessor.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/project/ProjectQueryProcessor.java
new file mode 100644
index 0000000..1e181e5
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/project/ProjectQueryProcessor.java
@@ -0,0 +1,79 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.query.project;
+
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.gerrit.server.query.project.ProjectQueryBuilder.FIELD_LIMIT;
+
+import com.google.gerrit.index.IndexConfig;
+import com.google.gerrit.index.query.AndSource;
+import com.google.gerrit.index.query.IndexPredicate;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryProcessor;
+import com.google.gerrit.metrics.MetricMaker;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.account.AccountLimits;
+import com.google.gerrit.server.index.project.ProjectIndexCollection;
+import com.google.gerrit.server.index.project.ProjectIndexRewriter;
+import com.google.gerrit.server.index.project.ProjectSchemaDefinitions;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.project.ProjectData;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+/**
+ * Query processor for the project index.
+ *
+ * <p>Instances are one-time-use. Other singleton classes should inject a Provider rather than
+ * holding on to a single instance.
+ */
+public class ProjectQueryProcessor extends QueryProcessor<ProjectData> {
+ private final PermissionBackend permissionBackend;
+ private final Provider<CurrentUser> userProvider;
+
+ static {
+ // It is assumed that basic rewrites do not touch visibleto predicates.
+ checkState(
+ !ProjectIsVisibleToPredicate.class.isAssignableFrom(IndexPredicate.class),
+ "ProjectQueryProcessor assumes visibleto is not used by the index rewriter.");
+ }
+
+ @Inject
+ protected ProjectQueryProcessor(
+ Provider<CurrentUser> userProvider,
+ AccountLimits.Factory limitsFactory,
+ MetricMaker metricMaker,
+ IndexConfig indexConfig,
+ ProjectIndexCollection indexes,
+ ProjectIndexRewriter rewriter,
+ PermissionBackend permissionBackend) {
+ super(
+ metricMaker,
+ ProjectSchemaDefinitions.INSTANCE,
+ indexConfig,
+ indexes,
+ rewriter,
+ FIELD_LIMIT,
+ () -> limitsFactory.create(userProvider.get()).getQueryLimit());
+ this.permissionBackend = permissionBackend;
+ this.userProvider = userProvider;
+ }
+
+ @Override
+ protected Predicate<ProjectData> enforceVisibility(Predicate<ProjectData> pred) {
+ return new AndSource<>(
+ pred, new ProjectIsVisibleToPredicate(permissionBackend, userProvider.get()), start);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_151.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_151.java
index 2015c14..7d12e58 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_151.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_151.java
@@ -14,17 +14,17 @@
package com.google.gerrit.server.schema;
-import com.google.common.collect.Streams;
import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit;
-import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit.Key;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.ResultSet;
import com.google.inject.Inject;
import com.google.inject.Provider;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
import java.sql.Timestamp;
-import java.util.Comparator;
+import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@@ -36,21 +36,45 @@
}
@Override
- protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException {
- List<AccountGroup> accountGroups = db.accountGroups().all().toList();
- for (AccountGroup accountGroup : accountGroups) {
- ResultSet<AccountGroupMemberAudit> groupMemberAudits =
- db.accountGroupMembersAudit().byGroup(accountGroup.getId());
- Optional<Timestamp> firstTimeMentioned =
- Streams.stream(groupMemberAudits)
- .map(AccountGroupMemberAudit::getKey)
- .map(Key::getAddedOn)
- .min(Comparator.naturalOrder());
- Timestamp createdOn =
- firstTimeMentioned.orElseGet(() -> AccountGroup.auditCreationInstantTs());
+ protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
+ try (PreparedStatement groupUpdate =
+ prepareStatement(db, "UPDATE account_groups SET created_on = ? WHERE group_id = ?");
+ PreparedStatement addedOnRetrieval =
+ prepareStatement(
+ db,
+ "SELECT added_on FROM account_group_members_audit WHERE group_id = ?"
+ + " ORDER BY added_on ASC")) {
+ List<AccountGroup.Id> accountGroups = getAllGroupIds(db);
+ for (AccountGroup.Id groupId : accountGroups) {
+ Optional<Timestamp> firstTimeMentioned = getFirstTimeMentioned(addedOnRetrieval, groupId);
+ Timestamp createdOn = firstTimeMentioned.orElseGet(AccountGroup::auditCreationInstantTs);
- accountGroup.setCreatedOn(createdOn);
+ groupUpdate.setTimestamp(1, createdOn);
+ groupUpdate.setInt(2, groupId.get());
+ groupUpdate.executeUpdate();
+ }
}
- db.accountGroups().update(accountGroups);
+ }
+
+ private static Optional<Timestamp> getFirstTimeMentioned(
+ PreparedStatement addedOnRetrieval, AccountGroup.Id groupId) throws SQLException {
+ addedOnRetrieval.setInt(1, groupId.get());
+ try (ResultSet resultSet = addedOnRetrieval.executeQuery()) {
+ if (resultSet.first()) {
+ return Optional.of(resultSet.getTimestamp(1));
+ }
+ }
+ return Optional.empty();
+ }
+
+ private static List<AccountGroup.Id> getAllGroupIds(ReviewDb db) throws SQLException {
+ try (Statement stmt = newStatement(db);
+ ResultSet rs = stmt.executeQuery("SELECT group_id FROM account_groups")) {
+ List<AccountGroup.Id> groupIds = new ArrayList<>();
+ while (rs.next()) {
+ groupIds.add(new AccountGroup.Id(rs.getInt(1)));
+ }
+ return groupIds;
+ }
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_87.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_87.java
index 8ab949e..8a3ea08 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_87.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_87.java
@@ -20,11 +20,12 @@
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
+import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
-import java.util.Collections;
import java.util.HashSet;
+import java.util.Optional;
import java.util.Set;
public class Schema_87 extends SchemaVersion {
@@ -35,16 +36,37 @@
@Override
protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
- for (AccountGroup.Id id : scanSystemGroups(db)) {
- AccountGroup group = db.accountGroups().get(id);
- if (group != null && SystemGroupBackend.isSystemGroup(group.getGroupUUID())) {
- db.accountGroups().delete(Collections.singleton(group));
- db.accountGroupNames().deleteKeys(Collections.singleton(group.getNameKey()));
+ try (PreparedStatement uuidRetrieval =
+ prepareStatement(db, "SELECT group_uuid FROM account_groups WHERE group_id = ?");
+ PreparedStatement groupDeletion =
+ prepareStatement(db, "DELETE FROM account_groups WHERE group_id = ?");
+ PreparedStatement groupNameDeletion =
+ prepareStatement(db, "DELETE FROM account_group_names WHERE group_id = ?")) {
+ for (AccountGroup.Id id : scanSystemGroups(db)) {
+ Optional<AccountGroup.UUID> groupUuid = getUuid(uuidRetrieval, id);
+ if (groupUuid.filter(SystemGroupBackend::isSystemGroup).isPresent()) {
+ groupDeletion.setInt(1, id.get());
+ groupDeletion.executeUpdate();
+
+ groupNameDeletion.setInt(1, id.get());
+ groupNameDeletion.executeUpdate();
+ }
}
}
}
- private Set<AccountGroup.Id> scanSystemGroups(ReviewDb db) throws SQLException {
+ private static Optional<AccountGroup.UUID> getUuid(
+ PreparedStatement uuidRetrieval, AccountGroup.Id id) throws SQLException {
+ uuidRetrieval.setInt(1, id.get());
+ try (ResultSet uuidResults = uuidRetrieval.executeQuery()) {
+ if (uuidResults.first()) {
+ Optional.of(new AccountGroup.UUID(uuidResults.getString(1)));
+ }
+ }
+ return Optional.empty();
+ }
+
+ private static Set<AccountGroup.Id> scanSystemGroups(ReviewDb db) throws SQLException {
try (Statement stmt = newStatement(db);
ResultSet rs =
stmt.executeQuery("SELECT group_id FROM account_groups WHERE group_type = 'SYSTEM'")) {
diff --git a/gerrit-server/src/main/java/gerrit/PRED_get_legacy_label_types_1.java b/gerrit-server/src/main/java/gerrit/PRED_get_legacy_label_types_1.java
index 9bfcc61..2c76999 100644
--- a/gerrit-server/src/main/java/gerrit/PRED_get_legacy_label_types_1.java
+++ b/gerrit-server/src/main/java/gerrit/PRED_get_legacy_label_types_1.java
@@ -78,7 +78,7 @@
return new StructureTerm(
symLabelType,
SymbolTerm.intern(type.getName()),
- SymbolTerm.intern(type.getFunctionName()),
+ SymbolTerm.intern(type.getFunction().getFunctionName()),
min != null ? new IntegerTerm(min.getValue()) : NONE,
max != null ? new IntegerTerm(max.getValue()) : NONE);
}
diff --git a/gerrit-server/src/main/prolog/gerrit_common.pl b/gerrit-server/src/main/prolog/gerrit_common.pl
index 4671e0d..8fd0657 100644
--- a/gerrit-server/src/main/prolog/gerrit_common.pl
+++ b/gerrit-server/src/main/prolog/gerrit_common.pl
@@ -279,12 +279,12 @@
!,
max_with_block(Label, Min, Max, S).
max_with_block(Label, Min, Max, reject(Who)) :-
- check_label_range_permission(Label, Min, ok(Who)),
+ commit_label(label(Label, Min), Who),
!
.
max_with_block(Label, Min, Max, ok(Who)) :-
- \+ check_label_range_permission(Label, Min, ok(_)),
- check_label_range_permission(Label, Max, ok(Who)),
+ \+ commit_label(label(Label, Min), _),
+ commit_label(label(Label, Max), Who),
!
.
max_with_block(Label, Min, Max, need(Max)) :-
@@ -306,7 +306,7 @@
%%
any_with_block(Label, Min, reject(Who)) :-
Min < 0,
- check_label_range_permission(Label, Min, ok(Who)),
+ commit_label(label(Label, Min), Who),
!
.
any_with_block(Label, Min, may(_)).
@@ -321,7 +321,7 @@
!,
max_no_block(Label, Max, S).
max_no_block(Label, Max, ok(Who)) :-
- check_label_range_permission(Label, Max, ok(Who)),
+ commit_label(label(Label, Max), Who),
!
.
max_no_block(Label, Max, need(Max)) :-
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Abandoned.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Abandoned.soy
index 50c5fc3..623cfe26 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Abandoned.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Abandoned.soy
@@ -24,7 +24,7 @@
* @param email
* @param fromName
*/
-{template .Abandoned autoescape="strict" kind="text"}
+{template .Abandoned kind="text"}
{$fromName} has abandoned this change.
{if $email.changeUrl} ( {$email.changeUrl} ){/if}{\n}
{\n}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/AbandonedHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/AbandonedHtml.soy
index c7d4699..75d940f 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/AbandonedHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/AbandonedHtml.soy
@@ -21,7 +21,7 @@
* @param email
* @param fromName
*/
-{template .AbandonedHtml autoescape="strict" kind="html"}
+{template .AbandonedHtml}
<p>
{$fromName} <strong>abandoned</strong> this change.
</p>
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/AddKey.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/AddKey.soy
index aa2b27d..af99569 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/AddKey.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/AddKey.soy
@@ -21,7 +21,7 @@
* adding a new SSH or GPG key to an account.
* @param email
*/
-{template .AddKey autoescape="strict" kind="text"}
+{template .AddKey kind="text"}
One or more new {$email.keyType} keys have been added to Gerrit Code Review at
{sp}{$email.gerritHost}:
@@ -68,4 +68,4 @@
This is a send-only email address. Replies to this message will not be read
or answered.
-{/template}
\ No newline at end of file
+{/template}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/AddKeyHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/AddKeyHtml.soy
index 017fd6d..712abc7 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/AddKeyHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/AddKeyHtml.soy
@@ -19,7 +19,7 @@
/**
* @param email
*/
-{template .AddKeyHtml autoescape="strict" kind="html"}
+{template .AddKeyHtml}
<p>
One or more new {$email.keyType} keys have been added to Gerrit Code Review
at {$email.gerritHost}:
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooter.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooter.soy
index 37ac126..f1d201b 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooter.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooter.soy
@@ -21,7 +21,7 @@
* that will be appended to ALL emails related to changes.
* @param email
*/
-{template .ChangeFooter autoescape="strict" kind="text"}
+{template .ChangeFooter kind="text"}
--{sp}
{\n}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooterHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooterHtml.soy
index 00f21db..99263e8 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooterHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooterHtml.soy
@@ -20,7 +20,7 @@
* @param change
* @param email
*/
-{template .ChangeFooterHtml autoescape="strict" kind="html"}
+{template .ChangeFooterHtml}
{if $email.changeUrl or $email.settingsUrl}
<p>
{if $email.changeUrl}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeSubject.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeSubject.soy
index 98de6e7..d8cffc4 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeSubject.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeSubject.soy
@@ -23,6 +23,6 @@
* @param change
* @param shortProjectName
*/
-{template .ChangeSubject autoescape="strict" kind="text"}
+{template .ChangeSubject kind="text"}
Change in {$shortProjectName}[{$branch.shortName}]: {$change.shortSubject}
{/template}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Comment.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Comment.soy
index 7bedc1c..7f3062c 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Comment.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Comment.soy
@@ -25,7 +25,7 @@
* @param fromName
* @param commentFiles
*/
-{template .Comment autoescape="strict" kind="text"}
+{template .Comment kind="text"}
{$fromName} has posted comments on this change.
{if $email.changeUrl} ( {$email.changeUrl} ){/if}{\n}
{\n}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentFooter.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentFooter.soy
index 73fdfba..3998438 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentFooter.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentFooter.soy
@@ -21,5 +21,5 @@
* that will be appended to emails related to a user submitting comments on
* changes.
*/
-{template .CommentFooter autoescape="strict" kind="text"}
+{template .CommentFooter kind="text"}
{/template}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentFooterHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentFooterHtml.soy
index 7bf28e7..033c1b1 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentFooterHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentFooterHtml.soy
@@ -16,5 +16,5 @@
{namespace com.google.gerrit.server.mail.template}
-{template .CommentFooterHtml autoescape="strict" kind="html"}
+{template .CommentFooterHtml}
{/template}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentHtml.soy
index 870ad46..6917736 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentHtml.soy
@@ -24,7 +24,7 @@
* @param patchSet
* @param patchSetCommentBlocks
*/
-{template .CommentHtml autoescape="strict" kind="html"}
+{template .CommentHtml}
{let $commentHeaderStyle kind="css"}
margin-bottom: 4px;
{/let}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteReviewer.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteReviewer.soy
index 888ee4b..fc1d60f 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteReviewer.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteReviewer.soy
@@ -24,7 +24,7 @@
* @param email
* @param fromName
*/
-{template .DeleteReviewer autoescape="strict" kind="text"}
+{template .DeleteReviewer kind="text"}
{$fromName} has removed{sp}
{foreach $reviewerName in $email.reviewerNames}
{if not isFirst($reviewerName)},{sp}{/if}
@@ -41,4 +41,4 @@
{$coverLetter}
{\n}
{/if}
-{/template}
\ No newline at end of file
+{/template}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteReviewerHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteReviewerHtml.soy
index 5faa411..f73e387 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteReviewerHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteReviewerHtml.soy
@@ -20,7 +20,7 @@
* @param email
* @param fromName
*/
-{template .DeleteReviewerHtml autoescape="strict" kind="html"}
+{template .DeleteReviewerHtml}
<p>
{$fromName}{sp}
<strong>
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteVote.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteVote.soy
index b249ded..724e90d 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteVote.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteVote.soy
@@ -23,7 +23,7 @@
* @param coverLetter
* @param fromName
*/
-{template .DeleteVote autoescape="strict" kind="text"}
+{template .DeleteVote kind="text"}
{$fromName} has removed a vote on this change.{\n}
{\n}
Change subject: {$change.subject}{\n}
@@ -34,4 +34,4 @@
{$coverLetter}
{\n}
{/if}
-{/template}
\ No newline at end of file
+{/template}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteVoteHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteVoteHtml.soy
index 3d76ae2..cb8162d 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteVoteHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteVoteHtml.soy
@@ -21,7 +21,7 @@
* @param email
* @param fromName
*/
-{template .DeleteVoteHtml autoescape="strict" kind="html"}
+{template .DeleteVoteHtml}
<p>
{$fromName} <strong>removed a vote</strong> from this change.
</p>
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Footer.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Footer.soy
index 24db2fd..2b146ec 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Footer.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Footer.soy
@@ -22,7 +22,7 @@
* CommentFooter.
* @param footers
*/
-{template .Footer autoescape="strict" kind="text"}
+{template .Footer kind="text"}
{foreach $footer in $footers}
{$footer}{\n}
{/foreach}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/FooterHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/FooterHtml.soy
index 9f9c503..22929d1 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/FooterHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/FooterHtml.soy
@@ -19,7 +19,7 @@
/**
* @param footers
*/
-{template .FooterHtml autoescape="strict" kind="html"}
+{template .FooterHtml}
{\n}
{\n}
{foreach $footer in $footers}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/HeaderHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/HeaderHtml.soy
index fdc3fee..4710d8c 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/HeaderHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/HeaderHtml.soy
@@ -16,5 +16,5 @@
{namespace com.google.gerrit.server.mail.template}
-{template .HeaderHtml autoescape="strict" kind="html"}
+{template .HeaderHtml}
{/template}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Merged.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Merged.soy
index d483264..40924e6 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Merged.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Merged.soy
@@ -24,7 +24,7 @@
* @param email
* @param fromName
*/
-{template .Merged autoescape="strict" kind="text"}
+{template .Merged kind="text"}
{$fromName} has submitted this change and it was merged.
{if $email.changeUrl} ( {$email.changeUrl} ){/if}{\n}
{\n}
@@ -39,4 +39,4 @@
{$email.unifiedDiff}
{\n}
{/if}
-{/template}
\ No newline at end of file
+{/template}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/MergedHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/MergedHtml.soy
index 927601b..b11c5e5 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/MergedHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/MergedHtml.soy
@@ -21,7 +21,7 @@
* @param email
* @param fromName
*/
-{template .MergedHtml autoescape="strict" kind="html"}
+{template .MergedHtml}
<p>
{$fromName} <strong>merged</strong> this change.
</p>
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/NewChange.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/NewChange.soy
index 9f7429f..ca24d19 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/NewChange.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/NewChange.soy
@@ -25,7 +25,7 @@
* @param patchSet
* @param projectName
*/
-{template .NewChange autoescape="strict" kind="text"}
+{template .NewChange kind="text"}
{if $email.reviewerNames}
Hello{sp}
{foreach $reviewerName in $email.reviewerNames}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/NewChangeHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/NewChangeHtml.soy
index 8026666..16b0df4 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/NewChangeHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/NewChangeHtml.soy
@@ -24,7 +24,7 @@
* @param patchSet
* @param projectName
*/
-{template .NewChangeHtml autoescape="strict" kind="html"}
+{template .NewChangeHtml}
<p>
{if $email.reviewerNames}
{$fromName} would like{sp}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Private.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Private.soy
index b26535b..5840223 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Private.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Private.soy
@@ -24,7 +24,7 @@
* Private template to generate "View Change" buttons.
* @param email
*/
-{template .ViewChangeButton autoescape="strict" kind="html"}
+{template .ViewChangeButton}
<a href="{$email.changeUrl}">View Change</a>
{/template}
@@ -32,7 +32,7 @@
* Private template to render PRE block with consistent font-sizing.
* @param content
*/
-{template .Pre autoescape="strict" kind="html"}
+{template .Pre}
{let $preStyle kind="css"}
font-family: monospace,monospace; // Use this to avoid browsers scaling down
// monospace text.
@@ -56,7 +56,7 @@
*
* @param content
*/
-{template .WikiFormat autoescape="strict" kind="html"}
+{template .WikiFormat}
{let $blockquoteStyle kind="css"}
border-left: 1px solid #aaa;
margin: 10px 0;
@@ -90,7 +90,7 @@
/**
* @param diffLines
*/
-{template .UnifiedDiff autoescape="strict" kind="html"}
+{template .UnifiedDiff}
{let $addStyle kind="css"}
color: hsl(120, 100%, 40%);
{/let}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RegisterNewEmail.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RegisterNewEmail.soy
index 2b30ae6..2886cc0 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RegisterNewEmail.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RegisterNewEmail.soy
@@ -21,7 +21,7 @@
* related to registering new email accounts.
* @param email
*/
-{template .RegisterNewEmail autoescape="strict" kind="text"}
+{template .RegisterNewEmail kind="text"}
Welcome to Gerrit Code Review at {$email.gerritHost}.{\n}
{\n}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ReplacePatchSet.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ReplacePatchSet.soy
index e41bdda..124cdf3 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ReplacePatchSet.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ReplacePatchSet.soy
@@ -26,7 +26,7 @@
* @param patchSet
* @param projectName
*/
-{template .ReplacePatchSet autoescape="strict" kind="text"}
+{template .ReplacePatchSet kind="text"}
{if $email.reviewerNames and $fromEmail == $change.ownerEmail}
Hello{sp}
{foreach $reviewerName in $email.reviewerNames}
@@ -60,4 +60,4 @@
{$patchSet.refName}
{\n}
{/if}
-{/template}
\ No newline at end of file
+{/template}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ReplacePatchSetHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ReplacePatchSetHtml.soy
index 05c60a1..e618bef 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ReplacePatchSetHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ReplacePatchSetHtml.soy
@@ -24,7 +24,7 @@
* @param patchSet
* @param projectName
*/
-{template .ReplacePatchSetHtml autoescape="strict" kind="html"}
+{template .ReplacePatchSetHtml}
<p>
{$fromName} <strong>uploaded patch set #{$patchSet.patchSetId}</strong>{sp}
to{sp}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Restored.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Restored.soy
index 14ae0f3..4fc6d8c 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Restored.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Restored.soy
@@ -24,7 +24,7 @@
* @param email
* @param fromName
*/
-{template .Restored autoescape="strict" kind="text"}
+{template .Restored kind="text"}
{$fromName} has restored this change.
{if $email.changeUrl} ( {$email.changeUrl} ){/if}{\n}
{\n}
@@ -36,4 +36,4 @@
{$coverLetter}
{\n}
{/if}
-{/template}
\ No newline at end of file
+{/template}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RestoredHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RestoredHtml.soy
index ea4f615..bb856ac 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RestoredHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RestoredHtml.soy
@@ -20,7 +20,7 @@
* @param email
* @param fromName
*/
-{template .RestoredHtml autoescape="strict" kind="html"}
+{template .RestoredHtml}
<p>
{$fromName} <strong>restored</strong> this change.
</p>
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Reverted.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Reverted.soy
index 7f74df9..09e32ff 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Reverted.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/Reverted.soy
@@ -24,7 +24,7 @@
* @param email
* @param fromName
*/
-{template .Reverted autoescape="strict" kind="text"}
+{template .Reverted kind="text"}
{$fromName} has reverted this change.
{if $email.changeUrl} ( {$email.changeUrl} ){/if}{\n}
{\n}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RevertedHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RevertedHtml.soy
index d6407e7..63ad6f0 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RevertedHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RevertedHtml.soy
@@ -20,7 +20,7 @@
* @param email
* @param fromName
*/
-{template .RevertedHtml autoescape="strict" kind="html"}
+{template .RevertedHtml}
<p>
{$fromName} <strong>reverted</strong> this change.
</p>
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/SetAssignee.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/SetAssignee.soy
index ca4f267..98290e9 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/SetAssignee.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/SetAssignee.soy
@@ -25,7 +25,7 @@
* @param patchSet
* @param projectName
*/
-{template .SetAssignee autoescape="strict" kind="text"}
+{template .SetAssignee kind="text"}
Hello{sp}
{$email.assigneeName},
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/SetAssigneeHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/SetAssigneeHtml.soy
index 31cfbd6..dbd3fae 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/SetAssigneeHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/SetAssigneeHtml.soy
@@ -23,7 +23,7 @@
* @param patchSet
* @param projectName
*/
-{template .SetAssigneeHtml autoescape="strict" kind="html"}
+{template .SetAssigneeHtml}
<p>
{$fromName} has <strong>assigned</strong> a change to{sp}
{$email.assigneeName}.{sp}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/config/ScheduleConfigTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/config/ScheduleConfigTest.java
index e6f36b9..0423a53 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/config/ScheduleConfigTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/config/ScheduleConfigTest.java
@@ -20,15 +20,19 @@
import static java.util.concurrent.TimeUnit.MINUTES;
import static org.junit.Assert.assertEquals;
+import java.time.LocalDateTime;
+import java.time.Month;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.lib.Config;
-import org.joda.time.DateTime;
import org.junit.Test;
public class ScheduleConfigTest {
// Friday June 13, 2014 10:00 UTC
- private static final DateTime NOW = DateTime.parse("2014-06-13T10:00:00-00:00");
+ private static final ZonedDateTime NOW =
+ LocalDateTime.of(2014, Month.JUNE, 13, 10, 0, 0).atOffset(ZoneOffset.UTC).toZonedDateTime();
@Test
public void initialDelay() throws Exception {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/edit/tree/ChangeFileContentModificationSubject.java b/gerrit-server/src/test/java/com/google/gerrit/server/edit/tree/ChangeFileContentModificationSubject.java
index 801b2b0..574c795 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/edit/tree/ChangeFileContentModificationSubject.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/edit/tree/ChangeFileContentModificationSubject.java
@@ -17,10 +17,9 @@
import static com.google.common.truth.Truth.assertAbout;
import com.google.common.io.CharStreams;
-import com.google.common.truth.FailureStrategy;
+import com.google.common.truth.FailureMetadata;
import com.google.common.truth.StringSubject;
import com.google.common.truth.Subject;
-import com.google.common.truth.SubjectFactory;
import com.google.common.truth.Truth;
import com.google.gerrit.extensions.restapi.RawInput;
import java.io.IOException;
@@ -30,26 +29,14 @@
public class ChangeFileContentModificationSubject
extends Subject<ChangeFileContentModificationSubject, ChangeFileContentModification> {
- private static final SubjectFactory<
- ChangeFileContentModificationSubject, ChangeFileContentModification>
- MODIFICATION_SUBJECT_FACTORY =
- new SubjectFactory<
- ChangeFileContentModificationSubject, ChangeFileContentModification>() {
- @Override
- public ChangeFileContentModificationSubject getSubject(
- FailureStrategy failureStrategy, ChangeFileContentModification modification) {
- return new ChangeFileContentModificationSubject(failureStrategy, modification);
- }
- };
-
public static ChangeFileContentModificationSubject assertThat(
ChangeFileContentModification modification) {
- return assertAbout(MODIFICATION_SUBJECT_FACTORY).that(modification);
+ return assertAbout(ChangeFileContentModificationSubject::new).that(modification);
}
private ChangeFileContentModificationSubject(
- FailureStrategy failureStrategy, ChangeFileContentModification modification) {
- super(failureStrategy, modification);
+ FailureMetadata failureMetadata, ChangeFileContentModification modification) {
+ super(failureMetadata, modification);
}
public StringSubject filePath() {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/edit/tree/TreeModificationSubject.java b/gerrit-server/src/test/java/com/google/gerrit/server/edit/tree/TreeModificationSubject.java
index ac4ebb8..59ee2b7 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/edit/tree/TreeModificationSubject.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/edit/tree/TreeModificationSubject.java
@@ -16,26 +16,15 @@
import static com.google.common.truth.Truth.assertAbout;
-import com.google.common.truth.FailureStrategy;
+import com.google.common.truth.FailureMetadata;
import com.google.common.truth.Subject;
-import com.google.common.truth.SubjectFactory;
import com.google.gerrit.truth.ListSubject;
import java.util.List;
public class TreeModificationSubject extends Subject<TreeModificationSubject, TreeModification> {
- private static final SubjectFactory<TreeModificationSubject, TreeModification>
- TREE_MODIFICATION_SUBJECT_FACTORY =
- new SubjectFactory<TreeModificationSubject, TreeModification>() {
- @Override
- public TreeModificationSubject getSubject(
- FailureStrategy failureStrategy, TreeModification treeModification) {
- return new TreeModificationSubject(failureStrategy, treeModification);
- }
- };
-
public static TreeModificationSubject assertThat(TreeModification treeModification) {
- return assertAbout(TREE_MODIFICATION_SUBJECT_FACTORY).that(treeModification);
+ return assertAbout(TreeModificationSubject::new).that(treeModification);
}
public static ListSubject<TreeModificationSubject, TreeModification> assertThatList(
@@ -45,8 +34,8 @@
}
private TreeModificationSubject(
- FailureStrategy failureStrategy, TreeModification treeModification) {
- super(failureStrategy, treeModification);
+ FailureMetadata failureMetadata, TreeModification treeModification) {
+ super(failureMetadata, treeModification);
}
public ChangeFileContentModificationSubject asChangeFileContentModification() {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/git/LabelNormalizerTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/git/LabelNormalizerTest.java
index 75b00fd..5453fad 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/git/LabelNormalizerTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/git/LabelNormalizerTest.java
@@ -150,7 +150,7 @@
}
@Test
- public void normalizeByPermission() throws Exception {
+ public void noNormalizeByPermission() throws Exception {
ProjectConfig pc = loadAllProjects();
allow(pc, forLabel("Code-Review"), -1, 1, REGISTERED_USERS, "refs/heads/*");
allow(pc, forLabel("Verified"), -1, 1, REGISTERED_USERS, "refs/heads/*");
@@ -158,8 +158,7 @@
PatchSetApproval cr = psa(userId, "Code-Review", 2);
PatchSetApproval v = psa(userId, "Verified", 1);
- assertEquals(
- Result.create(list(v), list(copy(cr, 1)), list()), norm.normalize(notes, list(cr, v)));
+ assertEquals(Result.create(list(cr, v), list(), list()), norm.normalize(notes, list(cr, v)));
}
@Test
@@ -177,10 +176,10 @@
}
@Test
- public void emptyPermissionRangeOmitsResult() throws Exception {
+ public void emptyPermissionRangeKeepsResult() throws Exception {
PatchSetApproval cr = psa(userId, "Code-Review", 1);
PatchSetApproval v = psa(userId, "Verified", 1);
- assertEquals(Result.create(list(), list(), list(cr, v)), norm.normalize(notes, list(cr, v)));
+ assertEquals(Result.create(list(cr, v), list(), list()), norm.normalize(notes, list(cr, v)));
}
@Test
@@ -191,7 +190,7 @@
PatchSetApproval cr = psa(userId, "Code-Review", 0);
PatchSetApproval v = psa(userId, "Verified", 0);
- assertEquals(Result.create(list(cr), list(), list(v)), norm.normalize(notes, list(cr, v)));
+ assertEquals(Result.create(list(cr, v), list(), list()), norm.normalize(notes, list(cr, v)));
}
private ProjectConfig loadAllProjects() throws Exception {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/index/change/FakeQueryBuilder.java b/gerrit-server/src/test/java/com/google/gerrit/server/index/change/FakeQueryBuilder.java
index a194336..b525504 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/index/change/FakeQueryBuilder.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/index/change/FakeQueryBuilder.java
@@ -27,8 +27,7 @@
new FakeQueryBuilder.Definition<>(FakeQueryBuilder.class),
new ChangeQueryBuilder.Arguments(
null, null, 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, null, null, indexes, null, null, null, null, null, null, null, null));
}
@Operator
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/AbstractParserTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/AbstractParserTest.java
index 19ad8bb..7309437 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/AbstractParserTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/AbstractParserTest.java
@@ -20,9 +20,9 @@
import com.google.gerrit.reviewdb.client.Comment;
import com.google.gerrit.server.mail.Address;
import java.sql.Timestamp;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
-import org.joda.time.DateTime;
import org.junit.Ignore;
@Ignore
@@ -85,7 +85,7 @@
MailMessage.Builder b = MailMessage.builder();
b.id("id");
b.from(new Address("Foo Bar", "foo@bar.com"));
- b.dateReceived(new DateTime());
+ b.dateReceived(Instant.now());
b.subject("");
return b;
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/MetadataParserTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/MetadataParserTest.java
index 84bae96..dc25939 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/MetadataParserTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/MetadataParserTest.java
@@ -20,8 +20,10 @@
import com.google.gerrit.server.mail.Address;
import com.google.gerrit.server.mail.MetadataName;
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.Month;
+import java.time.ZoneOffset;
import org.junit.Test;
public class MetadataParserTest {
@@ -31,7 +33,7 @@
// email headers of the message.
MailMessage.Builder b = MailMessage.builder();
b.id("");
- b.dateReceived(new DateTime());
+ b.dateReceived(Instant.now());
b.subject("");
b.addAdditionalHeader(toHeaderWithDelimiter(MetadataName.CHANGE_NUMBER) + "123");
@@ -48,8 +50,11 @@
assertThat(meta.changeNumber).isEqualTo(123);
assertThat(meta.patchSet).isEqualTo(1);
assertThat(meta.messageType).isEqualTo("comment");
- assertThat(meta.timestamp.getTime())
- .isEqualTo(new DateTime(2016, 10, 25, 9, 11, 35, 0, DateTimeZone.UTC).getMillis());
+ assertThat(meta.timestamp.toInstant())
+ .isEqualTo(
+ LocalDateTime.of(2016, Month.OCTOBER, 25, 9, 11, 35)
+ .atOffset(ZoneOffset.UTC)
+ .toInstant());
}
@Test
@@ -58,7 +63,7 @@
// the text body of the message.
MailMessage.Builder b = MailMessage.builder();
b.id("");
- b.dateReceived(new DateTime());
+ b.dateReceived(Instant.now());
b.subject("");
StringBuilder stringBuilder = new StringBuilder();
@@ -77,8 +82,11 @@
assertThat(meta.changeNumber).isEqualTo(123);
assertThat(meta.patchSet).isEqualTo(1);
assertThat(meta.messageType).isEqualTo("comment");
- assertThat(meta.timestamp.getTime())
- .isEqualTo(new DateTime(2016, 10, 25, 9, 11, 35, 0, DateTimeZone.UTC).getMillis());
+ assertThat(meta.timestamp.toInstant())
+ .isEqualTo(
+ LocalDateTime.of(2016, Month.OCTOBER, 25, 9, 11, 35)
+ .atOffset(ZoneOffset.UTC)
+ .toInstant());
}
@Test
@@ -87,7 +95,7 @@
// the HTML body of the message.
MailMessage.Builder b = MailMessage.builder();
b.id("");
- b.dateReceived(new DateTime());
+ b.dateReceived(Instant.now());
b.subject("");
StringBuilder stringBuilder = new StringBuilder();
@@ -111,7 +119,10 @@
assertThat(meta.changeNumber).isEqualTo(123);
assertThat(meta.patchSet).isEqualTo(1);
assertThat(meta.messageType).isEqualTo("comment");
- assertThat(meta.timestamp.getTime())
- .isEqualTo(new DateTime(2016, 10, 25, 9, 11, 35, 0, DateTimeZone.UTC).getMillis());
+ assertThat(meta.timestamp.toInstant())
+ .isEqualTo(
+ LocalDateTime.of(2016, Month.OCTOBER, 25, 9, 11, 35)
+ .atOffset(ZoneOffset.UTC)
+ .toInstant());
}
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/RawMailParserTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/RawMailParserTest.java
index 4efa817..001d12d 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/RawMailParserTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/RawMailParserTest.java
@@ -65,7 +65,7 @@
assertThat(have.to()).isEqualTo(want.to());
assertThat(have.from()).isEqualTo(want.from());
assertThat(have.cc()).isEqualTo(want.cc());
- assertThat(have.dateReceived().getMillis()).isEqualTo(want.dateReceived().getMillis());
+ assertThat(have.dateReceived()).isEqualTo(want.dateReceived());
assertThat(have.additionalHeaders()).isEqualTo(want.additionalHeaders());
assertThat(have.subject()).isEqualTo(want.subject());
assertThat(have.textContent()).isEqualTo(want.textContent());
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/AttachmentMessage.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/AttachmentMessage.java
index be8d882..eb4d180 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/AttachmentMessage.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/AttachmentMessage.java
@@ -16,8 +16,9 @@
import com.google.gerrit.server.mail.Address;
import com.google.gerrit.server.mail.receive.MailMessage;
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
+import java.time.LocalDateTime;
+import java.time.Month;
+import java.time.ZoneOffset;
import org.junit.Ignore;
/**
@@ -82,7 +83,10 @@
.htmlContent("<div dir=\"ltr\">Contains unwanted attachment</div>")
.subject("Test Subject")
.addAdditionalHeader("MIME-Version: 1.0")
- .dateReceived(new DateTime(2016, 10, 25, 9, 11, 35, 0, DateTimeZone.UTC));
+ .dateReceived(
+ LocalDateTime.of(2016, Month.OCTOBER, 25, 9, 11, 35)
+ .atOffset(ZoneOffset.UTC)
+ .toInstant());
return expect.build();
}
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/Base64HeaderMessage.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/Base64HeaderMessage.java
index affa3bd..91dc6f1 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/Base64HeaderMessage.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/Base64HeaderMessage.java
@@ -16,8 +16,9 @@
import com.google.gerrit.server.mail.Address;
import com.google.gerrit.server.mail.receive.MailMessage;
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
+import java.time.LocalDateTime;
+import java.time.Month;
+import java.time.ZoneOffset;
import org.junit.Ignore;
/** Tests parsing a Base64 encoded subject. */
@@ -58,7 +59,10 @@
.addTo(new Address("ekempin", "ekempin@google.com"))
.textContent(textContent)
.subject("\uD83D\uDE1B test")
- .dateReceived(new DateTime(2016, 10, 25, 9, 11, 35, 0, DateTimeZone.UTC));
+ .dateReceived(
+ LocalDateTime.of(2016, Month.OCTOBER, 25, 9, 11, 35)
+ .atOffset(ZoneOffset.UTC)
+ .toInstant());
return expect.build();
}
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/HtmlMimeMessage.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/HtmlMimeMessage.java
index 487e9dd..756581f 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/HtmlMimeMessage.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/HtmlMimeMessage.java
@@ -16,8 +16,9 @@
import com.google.gerrit.server.mail.Address;
import com.google.gerrit.server.mail.receive.MailMessage;
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
+import java.time.LocalDateTime;
+import java.time.Month;
+import java.time.ZoneOffset;
import org.junit.Ignore;
/** Tests a message containing mime/alternative (text + html) content. */
@@ -98,7 +99,10 @@
.htmlContent(unencodedHtmlContent)
.subject("Change in gerrit[master]: Implement receiver class structure and bindings")
.addAdditionalHeader("MIME-Version: 1.0")
- .dateReceived(new DateTime(2016, 10, 25, 9, 11, 35, 0, DateTimeZone.UTC));
+ .dateReceived(
+ LocalDateTime.of(2016, Month.OCTOBER, 25, 9, 11, 35)
+ .atOffset(ZoneOffset.UTC)
+ .toInstant());
return expect.build();
}
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/NonUTF8Message.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/NonUTF8Message.java
index 9f2af0d..3fafd4b 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/NonUTF8Message.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/NonUTF8Message.java
@@ -15,8 +15,9 @@
import com.google.gerrit.server.mail.Address;
import com.google.gerrit.server.mail.receive.MailMessage;
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
+import java.time.LocalDateTime;
+import java.time.Month;
+import java.time.ZoneOffset;
import org.junit.Ignore;
/** Tests that non-UTF8 encodings are handled correctly. */
@@ -62,7 +63,10 @@
.addTo(new Address("ekempin", "ekempin@google.com"))
.textContent(textContent)
.subject("\uD83D\uDE1B test")
- .dateReceived(new DateTime(2016, 10, 25, 9, 11, 35, 0, DateTimeZone.UTC));
+ .dateReceived(
+ LocalDateTime.of(2016, Month.OCTOBER, 25, 9, 11, 35)
+ .atOffset(ZoneOffset.UTC)
+ .toInstant());
return expect.build();
}
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/QuotedPrintableHeaderMessage.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/QuotedPrintableHeaderMessage.java
index 2c17859..2dc48b5 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/QuotedPrintableHeaderMessage.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/QuotedPrintableHeaderMessage.java
@@ -16,8 +16,9 @@
import com.google.gerrit.server.mail.Address;
import com.google.gerrit.server.mail.receive.MailMessage;
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
+import java.time.LocalDateTime;
+import java.time.Month;
+import java.time.ZoneOffset;
import org.junit.Ignore;
/** Tests parsing a quoted printable encoded subject */
@@ -59,7 +60,10 @@
.addTo(new Address("ekempin", "ekempin@google.com"))
.textContent(textContent)
.subject("âme vulgaire")
- .dateReceived(new DateTime(2016, 10, 25, 9, 11, 35, 0, DateTimeZone.UTC));
+ .dateReceived(
+ LocalDateTime.of(2016, Month.OCTOBER, 25, 9, 11, 35)
+ .atOffset(ZoneOffset.UTC)
+ .toInstant());
return expect.build();
}
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/SimpleTextMessage.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/SimpleTextMessage.java
index ce833d5..aa5b78a 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/SimpleTextMessage.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/receive/data/SimpleTextMessage.java
@@ -16,8 +16,9 @@
import com.google.gerrit.server.mail.Address;
import com.google.gerrit.server.mail.receive.MailMessage;
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
+import java.time.LocalDateTime;
+import java.time.Month;
+import java.time.ZoneOffset;
import org.junit.Ignore;
/** Tests parsing a simple text message with different headers. */
@@ -124,7 +125,10 @@
.addCc(new Address("Patrick Hiesel", "hiesel@google.com"))
.textContent(textContent)
.subject("Change in gerrit[master]: (Re)enable voting buttons for merged changes")
- .dateReceived(new DateTime(2016, 10, 25, 9, 11, 35, 0, DateTimeZone.UTC))
+ .dateReceived(
+ LocalDateTime.of(2016, Month.OCTOBER, 25, 9, 11, 35)
+ .atOffset(ZoneOffset.UTC)
+ .toInstant())
.addAdditionalHeader(
"Authentication-Results: mx.google.com; dkim=pass header.i=@google.com;")
.addAdditionalHeader(
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeBundleTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeBundleTest.java
index 90e6800..33e1005 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeBundleTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeBundleTest.java
@@ -46,13 +46,14 @@
import com.google.gwtorm.protobuf.CodecFactory;
import com.google.gwtorm.protobuf.ProtobufCodec;
import java.sql.Timestamp;
+import java.time.LocalDate;
+import java.time.Month;
+import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.TimeZone;
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeZone;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -67,6 +68,7 @@
CodecFactory.encoder(PatchSetApproval.class);
private static final ProtobufCodec<PatchLineComment> PATCH_LINE_COMMENT_CODEC =
CodecFactory.encoder(PatchLineComment.class);
+ private static final String TIMEZONE_ID = "US/Eastern";
private String systemTimeZoneProperty;
private TimeZone systemTimeZone;
@@ -76,10 +78,9 @@
@Before
public void setUp() {
- String tz = "US/Eastern";
- systemTimeZoneProperty = System.setProperty("user.timezone", tz);
+ systemTimeZoneProperty = System.setProperty("user.timezone", TIMEZONE_ID);
systemTimeZone = TimeZone.getDefault();
- TimeZone.setDefault(TimeZone.getTimeZone(tz));
+ TimeZone.setDefault(TimeZone.getTimeZone(TIMEZONE_ID));
long maxMs = ChangeRebuilderImpl.MAX_WINDOW_MS;
assertThat(maxMs).isGreaterThan(1000L);
TestTimeUtil.resetWithClockStep(maxMs * 2, MILLISECONDS);
@@ -1517,8 +1518,11 @@
PatchSetApproval a2 = clone(a1);
a2.setGranted(
new Timestamp(
- new DateTime(1900, 1, 1, 0, 0, 0, DateTimeZone.forTimeZone(TimeZone.getDefault()))
- .getMillis()));
+ LocalDate.of(1900, Month.JANUARY, 1)
+ .atStartOfDay()
+ .atZone(ZoneId.of(TIMEZONE_ID))
+ .toInstant()
+ .toEpochMilli()));
// Both are ReviewDb, exact match is required.
ChangeBundle b1 =
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
index f7ce73f..049b86b 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
@@ -35,7 +35,6 @@
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.collect.Lists;
-import com.google.gerrit.common.data.Capable;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.PermissionRange;
import com.google.gerrit.common.data.PermissionRule;
@@ -133,7 +132,7 @@
}
private void assertCanUpload(ProjectControl u) {
- assertThat(u.canPushToAtLeastOneRef()).named("can upload").isEqualTo(Capable.OK);
+ assertThat(u.canPushToAtLeastOneRef()).named("can upload").isTrue();
}
private void assertCreateChange(String ref, ProjectControl u) {
@@ -142,7 +141,7 @@
}
private void assertCannotUpload(ProjectControl u) {
- assertThat(u.canPushToAtLeastOneRef()).named("cannot upload").isNotEqualTo(Capable.OK);
+ assertThat(u.canPushToAtLeastOneRef()).named("cannot upload").isFalse();
}
private void assertCannotCreateChange(String ref, ProjectControl u) {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/project/Util.java b/gerrit-server/src/test/java/com/google/gerrit/server/project/Util.java
index 5a72d5c..6604641 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/project/Util.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/project/Util.java
@@ -17,6 +17,7 @@
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.data.GroupReference;
+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.Permission;
@@ -47,7 +48,7 @@
public static final LabelType patchSetLock() {
LabelType label =
category("Patch-Set-Lock", value(1, "Patch Set Locked"), value(0, "Patch Set Unlocked"));
- label.setFunctionName("PatchSetLock");
+ label.setFunction(LabelFunction.PATCH_SET_LOCK);
return label;
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index 1e722fc..8c83fbc 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -38,6 +38,8 @@
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.extensions.api.GerritApi;
import com.google.gerrit.extensions.api.changes.AddReviewerInput;
+import com.google.gerrit.extensions.api.changes.AssigneeInput;
+import com.google.gerrit.extensions.api.changes.ChangeApi;
import com.google.gerrit.extensions.api.changes.Changes.QueryRequest;
import com.google.gerrit.extensions.api.changes.DraftInput;
import com.google.gerrit.extensions.api.changes.HashtagsInput;
@@ -192,6 +194,17 @@
private String systemTimeZone;
+ // These queries must be kept in sync with PolyGerrit:
+ // polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js
+
+ protected static String DASHBOARD_WORK_IN_PROGRESS_QUERY = "is:open owner:${user} is:wip";
+ protected static String DASHBOARD_OUTGOING_QUERY = "is:open owner:${user} -is:wip -is:ignored";
+ protected static String DASHBOARD_INCOMING_QUERY =
+ "is:open -owner:${user} -is:wip -is:ignored (reviewer:${user} OR assignee:${user})";
+ protected static String DASHBOARD_RECENTLY_CLOSED_QUERY =
+ "is:closed -is:ignored (-is:wip OR owner:self) "
+ + "(owner:${user} OR reviewer:${user} OR assignee:${user})";
+
protected abstract Injector createInjector();
@Before
@@ -1232,7 +1245,7 @@
long thirtyHoursInMs = MILLISECONDS.convert(30, HOURS);
resetTimeWithClockStep(thirtyHoursInMs, MILLISECONDS);
TestRepository<Repo> repo = createProject("repo");
- long startMs = TestTimeUtil.START.getMillis();
+ long startMs = TestTimeUtil.START.toEpochMilli();
Change change1 = insert(repo, newChange(repo), null, new Timestamp(startMs));
Change change2 = insert(repo, newChange(repo), null, new Timestamp(startMs + thirtyHoursInMs));
@@ -1259,7 +1272,7 @@
long thirtyHoursInMs = MILLISECONDS.convert(30, HOURS);
resetTimeWithClockStep(thirtyHoursInMs, MILLISECONDS);
TestRepository<Repo> repo = createProject("repo");
- long startMs = TestTimeUtil.START.getMillis();
+ long startMs = TestTimeUtil.START.toEpochMilli();
Change change1 = insert(repo, newChange(repo), null, new Timestamp(startMs));
Change change2 = insert(repo, newChange(repo), null, new Timestamp(startMs + thirtyHoursInMs));
TestTimeUtil.setClockStep(0, MILLISECONDS);
@@ -1281,7 +1294,7 @@
long thirtyHoursInMs = MILLISECONDS.convert(30, HOURS);
resetTimeWithClockStep(thirtyHoursInMs, MILLISECONDS);
TestRepository<Repo> repo = createProject("repo");
- long startMs = TestTimeUtil.START.getMillis();
+ long startMs = TestTimeUtil.START.toEpochMilli();
Change change1 = insert(repo, newChange(repo), null, new Timestamp(startMs));
Change change2 = insert(repo, newChange(repo), null, new Timestamp(startMs + thirtyHoursInMs));
TestTimeUtil.setClockStep(0, MILLISECONDS);
@@ -2187,6 +2200,307 @@
"revertof:" + changeToRevert._number, new Change.Id(changeThatReverts._number));
}
+ /** Change builder for helping in tests for dashboard sections. */
+ protected class DashboardChangeState {
+ private final Account.Id ownerId;
+ private final List<Account.Id> reviewedBy;
+ private final List<Account.Id> ignoredBy;
+ private boolean wip;
+ private boolean abandoned;
+ @Nullable private Account.Id mergedBy;
+ @Nullable private Account.Id assigneeId;
+
+ @Nullable Change.Id id;
+
+ DashboardChangeState(Account.Id ownerId) {
+ this.ownerId = ownerId;
+ reviewedBy = new ArrayList<>();
+ ignoredBy = new ArrayList<>();
+ }
+
+ DashboardChangeState assignTo(Account.Id assigneeId) {
+ this.assigneeId = assigneeId;
+ return this;
+ }
+
+ DashboardChangeState wip() {
+ wip = true;
+ return this;
+ }
+
+ DashboardChangeState abandon() {
+ abandoned = true;
+ return this;
+ }
+
+ DashboardChangeState mergeBy(Account.Id mergedBy) {
+ this.mergedBy = mergedBy;
+ return this;
+ }
+
+ DashboardChangeState ignoreBy(Account.Id ignorerId) {
+ ignoredBy.add(ignorerId);
+ return this;
+ }
+
+ DashboardChangeState addReviewer(Account.Id reviewerId) {
+ reviewedBy.add(reviewerId);
+ return this;
+ }
+
+ DashboardChangeState create(TestRepository<Repo> repo) throws Exception {
+ requestContext.setContext(newRequestContext(ownerId));
+ Change change = insert(repo, newChange(repo), ownerId);
+ id = change.getId();
+ ChangeApi cApi = gApi.changes().id(change.getChangeId());
+ if (assigneeId != null) {
+ AssigneeInput in = new AssigneeInput();
+ in.assignee = "" + assigneeId;
+ cApi.setAssignee(in);
+ }
+ if (wip) {
+ cApi.setWorkInProgress();
+ }
+ if (abandoned) {
+ cApi.abandon();
+ }
+ for (Account.Id reviewerId : reviewedBy) {
+ cApi.addReviewer("" + reviewerId);
+ }
+ for (Account.Id ignorerId : ignoredBy) {
+ requestContext.setContext(newRequestContext(ignorerId));
+ StarsInput in = new StarsInput(new HashSet<>(Arrays.asList("ignore")));
+ gApi.accounts().self().setStars("" + id, in);
+ }
+ if (mergedBy != null) {
+ requestContext.setContext(newRequestContext(mergedBy));
+ cApi = gApi.changes().id(change.getChangeId());
+ cApi.current().review(ReviewInput.approve());
+ cApi.current().submit();
+ }
+ requestContext.setContext(newRequestContext(user.getAccountId()));
+ return this;
+ }
+ }
+
+ protected List<ChangeInfo> assertDashboardQuery(
+ String viewedUser, String query, DashboardChangeState... expected) throws Exception {
+ Change.Id[] ids = new Change.Id[expected.length];
+ for (int i = 0; i < expected.length; i++) {
+ ids[i] = expected[i].id;
+ }
+ return assertQueryByIds(query.replaceAll("\\$\\{user}", viewedUser), ids);
+ }
+
+ @Test
+ public void dashboardWorkInProgressReviews() throws Exception {
+ TestRepository<Repo> repo = createProject("repo");
+ DashboardChangeState ownedOpenWip =
+ new DashboardChangeState(user.getAccountId()).wip().create(repo);
+
+ // Create changes that should not be returned by query.
+ new DashboardChangeState(user.getAccountId()).wip().abandon().create(repo);
+ new DashboardChangeState(user.getAccountId()).mergeBy(user.getAccountId()).create(repo);
+ new DashboardChangeState(createAccount("other")).wip().create(repo);
+
+ assertDashboardQuery("self", DASHBOARD_WORK_IN_PROGRESS_QUERY, ownedOpenWip);
+ }
+
+ @Test
+ public void dashboardOutgoingReviews() throws Exception {
+ TestRepository<Repo> repo = createProject("repo");
+ Account.Id otherAccountId = createAccount("other");
+ DashboardChangeState ownedOpenReviewable =
+ new DashboardChangeState(user.getAccountId()).create(repo);
+ DashboardChangeState ownedOpenReviewableIgnoredByOther =
+ new DashboardChangeState(user.getAccountId()).ignoreBy(otherAccountId).create(repo);
+
+ // Create changes that should not be returned by any queries in this test.
+ new DashboardChangeState(user.getAccountId()).wip().create(repo);
+ new DashboardChangeState(otherAccountId).create(repo);
+
+ // Viewing one's own dashboard.
+ assertDashboardQuery(
+ "self", DASHBOARD_OUTGOING_QUERY, ownedOpenReviewableIgnoredByOther, ownedOpenReviewable);
+
+ // Viewing another user's dashboard.
+ requestContext.setContext(newRequestContext(otherAccountId));
+ assertDashboardQuery(user.getUserName(), DASHBOARD_OUTGOING_QUERY, ownedOpenReviewable);
+ }
+
+ @Test
+ public void dashboardIncomingReviews() throws Exception {
+ TestRepository<Repo> repo = createProject("repo");
+ Account.Id otherAccountId = createAccount("other");
+ DashboardChangeState reviewingReviewable =
+ new DashboardChangeState(otherAccountId).addReviewer(user.getAccountId()).create(repo);
+ DashboardChangeState reviewingReviewableIgnoredByReviewer =
+ new DashboardChangeState(otherAccountId)
+ .addReviewer(user.getAccountId())
+ .ignoreBy(user.getAccountId())
+ .create(repo);
+ DashboardChangeState assignedReviewable =
+ new DashboardChangeState(otherAccountId).assignTo(user.getAccountId()).create(repo);
+ DashboardChangeState assignedReviewableIgnoredByAssignee =
+ new DashboardChangeState(otherAccountId)
+ .assignTo(user.getAccountId())
+ .ignoreBy(user.getAccountId())
+ .create(repo);
+
+ // Create changes that should not be returned by any queries in this test.
+ new DashboardChangeState(otherAccountId).wip().addReviewer(user.getAccountId()).create(repo);
+ new DashboardChangeState(otherAccountId).wip().assignTo(user.getAccountId()).create(repo);
+ new DashboardChangeState(otherAccountId).addReviewer(otherAccountId).create(repo);
+ new DashboardChangeState(otherAccountId)
+ .addReviewer(user.getAccountId())
+ .mergeBy(user.getAccountId())
+ .create(repo);
+
+ // Viewing one's own dashboard.
+ assertDashboardQuery("self", DASHBOARD_INCOMING_QUERY, assignedReviewable, reviewingReviewable);
+
+ // Viewing another user's dashboard.
+ requestContext.setContext(newRequestContext(otherAccountId));
+ assertDashboardQuery(
+ user.getUserName(),
+ DASHBOARD_INCOMING_QUERY,
+ assignedReviewableIgnoredByAssignee,
+ assignedReviewable,
+ reviewingReviewableIgnoredByReviewer,
+ reviewingReviewable);
+ }
+
+ @Test
+ public void dashboardRecentlyClosedReviews() throws Exception {
+ TestRepository<Repo> repo = createProject("repo");
+ Account.Id otherAccountId = createAccount("other");
+ DashboardChangeState mergedOwned =
+ new DashboardChangeState(user.getAccountId()).mergeBy(user.getAccountId()).create(repo);
+ DashboardChangeState mergedOwnedIgnoredByOther =
+ new DashboardChangeState(user.getAccountId())
+ .ignoreBy(otherAccountId)
+ .mergeBy(user.getAccountId())
+ .create(repo);
+ DashboardChangeState mergedReviewing =
+ new DashboardChangeState(otherAccountId)
+ .addReviewer(user.getAccountId())
+ .mergeBy(user.getAccountId())
+ .create(repo);
+ DashboardChangeState mergedReviewingIgnoredByUser =
+ new DashboardChangeState(otherAccountId)
+ .addReviewer(user.getAccountId())
+ .ignoreBy(user.getAccountId())
+ .mergeBy(user.getAccountId())
+ .create(repo);
+ DashboardChangeState mergedAssigned =
+ new DashboardChangeState(otherAccountId)
+ .assignTo(user.getAccountId())
+ .mergeBy(user.getAccountId())
+ .create(repo);
+ DashboardChangeState mergedAssignedIgnoredByUser =
+ new DashboardChangeState(otherAccountId)
+ .assignTo(user.getAccountId())
+ .ignoreBy(user.getAccountId())
+ .mergeBy(user.getAccountId())
+ .create(repo);
+ DashboardChangeState abandonedOwned =
+ new DashboardChangeState(user.getAccountId()).abandon().create(repo);
+ DashboardChangeState abandonedOwnedIgnoredByOther =
+ new DashboardChangeState(user.getAccountId())
+ .ignoreBy(otherAccountId)
+ .abandon()
+ .create(repo);
+ DashboardChangeState abandonedOwnedWip =
+ new DashboardChangeState(user.getAccountId()).wip().abandon().create(repo);
+ DashboardChangeState abandonedOwnedWipIgnoredByOther =
+ new DashboardChangeState(user.getAccountId())
+ .ignoreBy(otherAccountId)
+ .wip()
+ .abandon()
+ .create(repo);
+ DashboardChangeState abandonedReviewing =
+ new DashboardChangeState(otherAccountId)
+ .addReviewer(user.getAccountId())
+ .abandon()
+ .create(repo);
+ DashboardChangeState abandonedReviewingIgnoredByUser =
+ new DashboardChangeState(otherAccountId)
+ .addReviewer(user.getAccountId())
+ .ignoreBy(user.getAccountId())
+ .abandon()
+ .create(repo);
+ DashboardChangeState abandonedAssigned =
+ new DashboardChangeState(otherAccountId)
+ .assignTo(user.getAccountId())
+ .abandon()
+ .create(repo);
+ DashboardChangeState abandonedAssignedIgnoredByUser =
+ new DashboardChangeState(otherAccountId)
+ .assignTo(user.getAccountId())
+ .ignoreBy(user.getAccountId())
+ .abandon()
+ .create(repo);
+ DashboardChangeState abandonedAssignedWip =
+ new DashboardChangeState(otherAccountId)
+ .assignTo(user.getAccountId())
+ .wip()
+ .abandon()
+ .create(repo);
+ DashboardChangeState abandonedAssignedWipIgnoredByUser =
+ new DashboardChangeState(otherAccountId)
+ .assignTo(user.getAccountId())
+ .ignoreBy(user.getAccountId())
+ .wip()
+ .abandon()
+ .create(repo);
+
+ // Create changes that should not be returned by any queries in this test.
+ new DashboardChangeState(otherAccountId)
+ .addReviewer(user.getAccountId())
+ .wip()
+ .abandon()
+ .create(repo);
+ new DashboardChangeState(otherAccountId)
+ .addReviewer(user.getAccountId())
+ .ignoreBy(user.getAccountId())
+ .wip()
+ .abandon()
+ .create(repo);
+
+ // Viewing one's own dashboard.
+ assertDashboardQuery(
+ "self",
+ DASHBOARD_RECENTLY_CLOSED_QUERY,
+ abandonedAssigned,
+ abandonedReviewing,
+ abandonedOwnedWipIgnoredByOther,
+ abandonedOwnedWip,
+ abandonedOwnedIgnoredByOther,
+ abandonedOwned,
+ mergedAssigned,
+ mergedReviewing,
+ mergedOwnedIgnoredByOther,
+ mergedOwned);
+
+ // Viewing another user's dashboard.
+ requestContext.setContext(newRequestContext(otherAccountId));
+ assertDashboardQuery(
+ user.getUserName(),
+ DASHBOARD_RECENTLY_CLOSED_QUERY,
+ abandonedAssignedWipIgnoredByUser,
+ abandonedAssignedWip,
+ abandonedAssignedIgnoredByUser,
+ abandonedAssigned,
+ abandonedReviewingIgnoredByUser,
+ abandonedReviewing,
+ abandonedOwned,
+ mergedAssignedIgnoredByUser,
+ mergedAssigned,
+ mergedReviewingIgnoredByUser,
+ mergedReviewing,
+ mergedOwned);
+ }
+
protected ChangeInserter newChange(TestRepository<Repo> repo) throws Exception {
return newChange(repo, null, null, null, null, false);
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/project/AbstractQueryProjectsTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/project/AbstractQueryProjectsTest.java
new file mode 100644
index 0000000..8804b96
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/project/AbstractQueryProjectsTest.java
@@ -0,0 +1,372 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.query.project;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.util.stream.Collectors.toList;
+
+import com.google.common.base.CharMatcher;
+import com.google.gerrit.extensions.api.GerritApi;
+import com.google.gerrit.extensions.api.projects.ProjectInput;
+import com.google.gerrit.extensions.api.projects.Projects.QueryRequest;
+import com.google.gerrit.extensions.common.AccountInfo;
+import com.google.gerrit.extensions.common.ProjectInfo;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.lifecycle.LifecycleManager;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.AnonymousUser;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.account.AccountManager;
+import com.google.gerrit.server.account.Accounts;
+import com.google.gerrit.server.account.AccountsUpdate;
+import com.google.gerrit.server.account.AuthRequest;
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.query.account.InternalAccountQuery;
+import com.google.gerrit.server.schema.SchemaCreator;
+import com.google.gerrit.server.util.ManualRequestContext;
+import com.google.gerrit.server.util.OneOffRequestContext;
+import com.google.gerrit.server.util.RequestContext;
+import com.google.gerrit.server.util.ThreadLocalRequestContext;
+import com.google.gerrit.testutil.ConfigSuite;
+import com.google.gerrit.testutil.GerritServerTests;
+import com.google.gerrit.testutil.InMemoryDatabase;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Provider;
+import com.google.inject.util.Providers;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import org.eclipse.jgit.lib.Config;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+@Ignore
+public abstract class AbstractQueryProjectsTest extends GerritServerTests {
+ @ConfigSuite.Default
+ public static Config defaultConfig() {
+ Config cfg = new Config();
+ cfg.setInt("index", null, "maxPages", 10);
+ return cfg;
+ }
+
+ @Inject protected Accounts accounts;
+
+ @Inject protected AccountsUpdate.Server accountsUpdate;
+
+ @Inject protected AccountCache accountCache;
+
+ @Inject protected AccountManager accountManager;
+
+ @Inject protected GerritApi gApi;
+
+ @Inject protected IdentifiedUser.GenericFactory userFactory;
+
+ @Inject private Provider<AnonymousUser> anonymousUser;
+
+ @Inject protected InMemoryDatabase schemaFactory;
+
+ @Inject protected SchemaCreator schemaCreator;
+
+ @Inject protected ThreadLocalRequestContext requestContext;
+
+ @Inject protected OneOffRequestContext oneOffRequestContext;
+
+ @Inject protected InternalAccountQuery internalAccountQuery;
+
+ @Inject protected AllProjectsName allProjects;
+
+ protected LifecycleManager lifecycle;
+ protected Injector injector;
+ protected ReviewDb db;
+ protected AccountInfo currentUserInfo;
+ protected CurrentUser user;
+
+ protected abstract Injector createInjector();
+
+ @Before
+ public void setUpInjector() throws Exception {
+ lifecycle = new LifecycleManager();
+ injector = createInjector();
+ lifecycle.add(injector);
+ injector.injectMembers(this);
+ lifecycle.start();
+ setUpDatabase();
+ }
+
+ protected void setUpDatabase() throws Exception {
+ db = schemaFactory.open();
+ schemaCreator.create(db);
+
+ Account.Id userId = createAccount("user", "User", "user@example.com", true);
+ user = userFactory.create(userId);
+ requestContext.setContext(newRequestContext(userId));
+ currentUserInfo = gApi.accounts().id(userId.get()).get();
+ }
+
+ protected RequestContext newRequestContext(Account.Id requestUserId) {
+ final CurrentUser requestUser = userFactory.create(requestUserId);
+ return new RequestContext() {
+ @Override
+ public CurrentUser getUser() {
+ return requestUser;
+ }
+
+ @Override
+ public Provider<ReviewDb> getReviewDbProvider() {
+ return Providers.of(db);
+ }
+ };
+ }
+
+ protected void setAnonymous() {
+ requestContext.setContext(
+ new RequestContext() {
+ @Override
+ public CurrentUser getUser() {
+ return anonymousUser.get();
+ }
+
+ @Override
+ public Provider<ReviewDb> getReviewDbProvider() {
+ return Providers.of(db);
+ }
+ });
+ }
+
+ @After
+ public void tearDownInjector() {
+ if (lifecycle != null) {
+ lifecycle.stop();
+ }
+ requestContext.setContext(null);
+ if (db != null) {
+ db.close();
+ }
+ InMemoryDatabase.drop(schemaFactory);
+ }
+
+ @Test
+ public void byName() throws Exception {
+ assertQuery("name:project");
+ assertQuery("name:non-existing");
+
+ ProjectInfo project = createProject(name("project"));
+
+ assertQuery("name:" + project.name, project);
+
+ // only exact match
+ ProjectInfo projectWithHyphen = createProject(name("project-with-hyphen"));
+ createProject(name("project-no-match-with-hyphen"));
+ assertQuery("name:" + projectWithHyphen.name, projectWithHyphen);
+ }
+
+ @Test
+ public void byInname() throws Exception {
+ String namePart = getSanitizedMethodName();
+ namePart = CharMatcher.is('_').removeFrom(namePart);
+
+ ProjectInfo project1 = createProject(name("project-" + namePart));
+ ProjectInfo project2 = createProject(name("project-" + namePart + "-2"));
+ ProjectInfo project3 = createProject(name("project-" + namePart + "3"));
+
+ assertQuery("inname:" + namePart, project1, project2, project3);
+ assertQuery("inname:" + namePart.toUpperCase(Locale.US), project1, project2, project3);
+ assertQuery("inname:" + namePart.toLowerCase(Locale.US), project1, project2, project3);
+ }
+
+ @Test
+ public void byDescription() throws Exception {
+ ProjectInfo project1 =
+ createProjectWithDescription(name("project1"), "This is a test project.");
+ ProjectInfo project2 = createProjectWithDescription(name("project2"), "ANOTHER TEST PROJECT.");
+ createProjectWithDescription(name("project3"), "Maintainers of project foo.");
+ assertQuery("description:test", project1, project2);
+
+ assertQuery("description:non-existing");
+
+ exception.expect(BadRequestException.class);
+ exception.expectMessage("description operator requires a value");
+ assertQuery("description:\"\"");
+ }
+
+ @Test
+ public void byDefaultField() throws Exception {
+ ProjectInfo project1 = createProject(name("foo-project"));
+ ProjectInfo project2 = createProject(name("project2"));
+ ProjectInfo project3 =
+ createProjectWithDescription(
+ name("project3"),
+ "decription that contains foo and the UUID of project2: " + project2.id);
+
+ assertQuery("non-existing");
+ assertQuery("foo", project1, project3);
+ assertQuery(project2.id, project2, project3);
+ }
+
+ @Test
+ public void withLimit() throws Exception {
+ ProjectInfo project1 = createProject(name("project1"));
+ ProjectInfo project2 = createProject(name("project2"));
+ ProjectInfo project3 = createProject(name("project3"));
+
+ String query =
+ "name:" + project1.name + " OR name:" + project2.name + " OR name:" + project3.name;
+ List<ProjectInfo> result = assertQuery(query, project1, project2, project3);
+
+ result = assertQuery(newQuery(query).withLimit(2), result.subList(0, 2));
+ }
+
+ @Test
+ public void withStart() throws Exception {
+ ProjectInfo project1 = createProject(name("project1"));
+ ProjectInfo project2 = createProject(name("project2"));
+ ProjectInfo project3 = createProject(name("project3"));
+
+ String query =
+ "name:" + project1.name + " OR name:" + project2.name + " OR name:" + project3.name;
+ List<ProjectInfo> result = assertQuery(query, project1, project2, project3);
+
+ assertQuery(newQuery(query).withStart(1), result.subList(1, 3));
+ }
+
+ @Test
+ public void asAnonymous() throws Exception {
+ ProjectInfo project = createProject(name("project"));
+
+ setAnonymous();
+ assertQuery("name:" + project.name);
+ }
+
+ private Account.Id createAccount(String username, String fullName, String email, boolean active)
+ throws Exception {
+ try (ManualRequestContext ctx = oneOffRequestContext.open()) {
+ Account.Id id = accountManager.authenticate(AuthRequest.forUser(username)).getAccountId();
+ if (email != null) {
+ accountManager.link(id, AuthRequest.forEmail(email));
+ }
+ accountsUpdate
+ .create()
+ .update(
+ id,
+ a -> {
+ a.setFullName(fullName);
+ a.setPreferredEmail(email);
+ a.setActive(active);
+ });
+ return id;
+ }
+ }
+
+ protected ProjectInfo createProject(String name) throws Exception {
+ ProjectInput in = new ProjectInput();
+ in.name = name;
+ return gApi.projects().create(in).get();
+ }
+
+ protected ProjectInfo createProjectWithDescription(String name, String description)
+ throws Exception {
+ ProjectInput in = new ProjectInput();
+ in.name = name;
+ in.description = description;
+ return gApi.projects().create(in).get();
+ }
+
+ protected ProjectInfo getProject(Project.NameKey nameKey) throws Exception {
+ return gApi.projects().name(nameKey.get()).get();
+ }
+
+ protected List<ProjectInfo> assertQuery(Object query, ProjectInfo... projects) throws Exception {
+ return assertQuery(newQuery(query), projects);
+ }
+
+ protected List<ProjectInfo> assertQuery(QueryRequest query, ProjectInfo... projects)
+ throws Exception {
+ return assertQuery(query, Arrays.asList(projects));
+ }
+
+ protected List<ProjectInfo> assertQuery(QueryRequest query, List<ProjectInfo> projects)
+ throws Exception {
+ List<ProjectInfo> result = query.get();
+ Iterable<String> names = names(result);
+ assertThat(names)
+ .named(format(query, result, projects))
+ .containsExactlyElementsIn(names(projects));
+ return result;
+ }
+
+ protected QueryRequest newQuery(Object query) {
+ return gApi.projects().query(query.toString());
+ }
+
+ protected String format(
+ QueryRequest query, List<ProjectInfo> actualProjects, List<ProjectInfo> expectedProjects) {
+ StringBuilder b = new StringBuilder();
+ b.append("query '").append(query.getQuery()).append("' with expected projects ");
+ b.append(format(expectedProjects));
+ b.append(" and result ");
+ b.append(format(actualProjects));
+ return b.toString();
+ }
+
+ protected String format(Iterable<ProjectInfo> projects) {
+ StringBuilder b = new StringBuilder();
+ b.append("[");
+ Iterator<ProjectInfo> it = projects.iterator();
+ while (it.hasNext()) {
+ ProjectInfo p = it.next();
+ b.append("{")
+ .append(p.id)
+ .append(", ")
+ .append("name=")
+ .append(p.name)
+ .append(", ")
+ .append("parent=")
+ .append(p.parent)
+ .append(", ")
+ .append("description=")
+ .append(p.description)
+ .append("}");
+ if (it.hasNext()) {
+ b.append(", ");
+ }
+ }
+ b.append("]");
+ return b.toString();
+ }
+
+ protected static Iterable<String> names(ProjectInfo... projects) {
+ return names(Arrays.asList(projects));
+ }
+
+ protected static Iterable<String> names(List<ProjectInfo> projects) {
+ return projects.stream().map(p -> p.name).collect(toList());
+ }
+
+ protected String name(String name) {
+ if (name == null) {
+ return null;
+ }
+
+ return name + "_" + getSanitizedMethodName();
+ }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/project/LuceneQueryProjectsTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/project/LuceneQueryProjectsTest.java
new file mode 100644
index 0000000..4a09d87
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/project/LuceneQueryProjectsTest.java
@@ -0,0 +1,44 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.query.project;
+
+import com.google.gerrit.server.index.project.ProjectSchemaDefinitions;
+import com.google.gerrit.testutil.ConfigSuite;
+import com.google.gerrit.testutil.InMemoryModule;
+import com.google.gerrit.testutil.IndexVersions;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import java.util.List;
+import java.util.Map;
+import org.eclipse.jgit.lib.Config;
+
+public class LuceneQueryProjectsTest extends AbstractQueryProjectsTest {
+ @ConfigSuite.Configs
+ public static Map<String, Config> againstPreviousIndexVersion() {
+ // the current schema version is already tested by the inherited default config suite
+ List<Integer> schemaVersions =
+ IndexVersions.getWithoutLatest(
+ com.google.gerrit.server.index.project.ProjectSchemaDefinitions.INSTANCE);
+ return IndexVersions.asConfigMap(
+ ProjectSchemaDefinitions.INSTANCE, schemaVersions, "againstIndexVersion", defaultConfig());
+ }
+
+ @Override
+ protected Injector createInjector() {
+ Config luceneConfig = new Config(config);
+ InMemoryModule.setDefaults(luceneConfig);
+ return Guice.createInjector(new InMemoryModule(luceneConfig, notesMigration));
+ }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaCreatorTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaCreatorTest.java
index 7eda3cc..3cd1696 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaCreatorTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaCreatorTest.java
@@ -18,6 +18,7 @@
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
+import com.google.gerrit.common.data.LabelFunction;
import com.google.gerrit.common.data.LabelType;
import com.google.gerrit.common.data.LabelTypes;
import com.google.gerrit.common.data.LabelValue;
@@ -105,7 +106,7 @@
assertThat(codeReview).isNotNull();
assertThat(codeReview.getName()).isEqualTo("Code-Review");
assertThat(codeReview.getDefaultValue()).isEqualTo(0);
- assertThat(codeReview.getFunctionName()).isEqualTo("MaxWithBlock");
+ assertThat(codeReview.getFunction()).isEqualTo(LabelFunction.MAX_WITH_BLOCK);
assertThat(codeReview.isCopyMinScore()).isTrue();
assertValueRange(codeReview, 2, 1, 0, -1, -2);
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/schema/Schema_150_to_151_Test.java b/gerrit-server/src/test/java/com/google/gerrit/server/schema/Schema_150_to_151_Test.java
index dcd1ae5..a06c7a0 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/schema/Schema_150_to_151_Test.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/schema/Schema_150_to_151_Test.java
@@ -15,27 +15,29 @@
package com.google.gerrit.server.schema;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.TruthJUnit.assume;
-import com.google.common.collect.ImmutableList;
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.extensions.api.groups.GroupInput;
import com.google.gerrit.extensions.common.GroupInfo;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountGroup.Id;
-import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.group.CreateGroup;
import com.google.gerrit.testutil.SchemaUpgradeTestEnvironment;
import com.google.gerrit.testutil.TestUpdateUI;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.ResultSet;
+import com.google.gwtorm.jdbc.JdbcSchema;
import com.google.inject.Inject;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
import java.sql.Timestamp;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.Month;
import java.time.ZoneOffset;
+import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -48,11 +50,40 @@
@Inject private Schema_151 schema151;
private ReviewDb db;
+ private Connection connection;
+ private PreparedStatement createdOnRetrieval;
+ private PreparedStatement createdOnUpdate;
+ private PreparedStatement auditEntryDeletion;
@Before
public void setUp() throws Exception {
testEnv.getInjector().injectMembers(this);
db = testEnv.getDb();
+ assume().that(db instanceof JdbcSchema).isTrue();
+
+ connection = ((JdbcSchema) db).getConnection();
+ createdOnRetrieval =
+ connection.prepareStatement("SELECT created_on FROM account_groups WHERE group_id = ?");
+ createdOnUpdate =
+ connection.prepareStatement("UPDATE account_groups SET created_on = ? WHERE group_id = ?");
+ auditEntryDeletion =
+ connection.prepareStatement("DELETE FROM account_group_members_audit WHERE group_id = ?");
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (auditEntryDeletion != null) {
+ auditEntryDeletion.close();
+ }
+ if (createdOnUpdate != null) {
+ createdOnUpdate.close();
+ }
+ if (createdOnRetrieval != null) {
+ createdOnRetrieval.close();
+ }
+ if (connection != null) {
+ connection.close();
+ }
}
@Test
@@ -63,8 +94,8 @@
schema151.migrateData(db, new TestUpdateUI());
- AccountGroup group = db.accountGroups().get(groupId);
- assertThat(group.getCreatedOn()).isAtLeast(testStartTime);
+ Timestamp createdOn = getCreatedOn(groupId);
+ assertThat(createdOn).isAtLeast(testStartTime);
}
@Test
@@ -75,8 +106,8 @@
schema151.migrateData(db, new TestUpdateUI());
- AccountGroup group = db.accountGroups().get(groupId);
- assertThat(group.getCreatedOn()).isEqualTo(AccountGroup.auditCreationInstantTs());
+ Timestamp createdOn = getCreatedOn(groupId);
+ assertThat(createdOn).isEqualTo(AccountGroup.auditCreationInstantTs());
}
private AccountGroup.Id createGroup(String name) throws Exception {
@@ -87,16 +118,26 @@
return new Id(groupInfo.groupId);
}
- private void setCreatedOnToVeryOldTimestamp(Id groupId) throws OrmException {
- AccountGroup group = db.accountGroups().get(groupId);
+ private Timestamp getCreatedOn(Id groupId) throws Exception {
+ createdOnRetrieval.setInt(1, groupId.get());
+ try (ResultSet results = createdOnRetrieval.executeQuery()) {
+ if (results.first()) {
+ return results.getTimestamp(1);
+ }
+ }
+ return null;
+ }
+
+ private void setCreatedOnToVeryOldTimestamp(Id groupId) throws Exception {
+ createdOnUpdate.setInt(1, groupId.get());
Instant instant = LocalDateTime.of(1800, Month.JANUARY, 1, 0, 0).toInstant(ZoneOffset.UTC);
- group.setCreatedOn(Timestamp.from(instant));
- db.accountGroups().update(ImmutableList.of(group));
+ createdOnUpdate.setTimestamp(1, Timestamp.from(instant));
+ createdOnUpdate.setInt(2, groupId.get());
+ createdOnUpdate.executeUpdate();
}
private void removeAuditEntriesFor(AccountGroup.Id groupId) throws Exception {
- ResultSet<AccountGroupMemberAudit> groupMemberAudits =
- db.accountGroupMembersAudit().byGroup(groupId);
- db.accountGroupMembersAudit().delete(groupMemberAudits);
+ auditEntryDeletion.setInt(1, groupId.get());
+ auditEntryDeletion.executeUpdate();
}
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java
index e036495..d542bc3 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java
@@ -47,6 +47,7 @@
import com.google.gerrit.server.config.TrackingFootersProvider;
import com.google.gerrit.server.git.GarbageCollection;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.LocalMergeSuperSetComputation;
import com.google.gerrit.server.git.PerThreadRequestScope;
import com.google.gerrit.server.git.SearchingChangeCacheImpl;
import com.google.gerrit.server.git.SendEmailExecutor;
@@ -57,6 +58,7 @@
import com.google.gerrit.server.index.change.ChangeSchemaDefinitions;
import com.google.gerrit.server.index.group.AllGroupsIndexer;
import com.google.gerrit.server.index.group.GroupSchemaDefinitions;
+import com.google.gerrit.server.index.project.ProjectSchemaDefinitions;
import com.google.gerrit.server.mail.SignedTokenEmailTokenVerifier;
import com.google.gerrit.server.notedb.ChangeBundleReader;
import com.google.gerrit.server.notedb.GwtormChangeBundleReader;
@@ -66,6 +68,7 @@
import com.google.gerrit.server.plugins.PluginRestApiModule;
import com.google.gerrit.server.plugins.ServerInformationImpl;
import com.google.gerrit.server.project.DefaultPermissionBackendModule;
+import com.google.gerrit.server.project.DefaultProjectNameLockManager;
import com.google.gerrit.server.schema.DataSourceType;
import com.google.gerrit.server.schema.InMemoryAccountPatchReviewStore;
import com.google.gerrit.server.schema.NotesMigrationSchemaFactory;
@@ -202,7 +205,7 @@
return CanonicalWebUrlProvider.class;
}
});
- //Replacement of DiffExecutorModule to not use thread pool in the tests
+ // Replacement of DiffExecutorModule to not use thread pool in the tests
install(
new AbstractModule() {
@Override
@@ -220,6 +223,7 @@
install(new SignedTokenEmailTokenVerifier.Module());
install(new GpgModule(cfg));
install(new InMemoryAccountPatchReviewStore.Module());
+ install(new LocalMergeSuperSetComputation.Module());
bind(AllAccountsIndexer.class).toProvider(Providers.of(null));
bind(AllChangesIndexer.class).toProvider(Providers.of(null));
@@ -246,6 +250,7 @@
bind(ServerInformationImpl.class);
bind(ServerInformation.class).to(ServerInformationImpl.class);
install(new PluginRestApiModule());
+ install(new DefaultProjectNameLockManager.Module());
}
@Provides
@@ -292,6 +297,7 @@
putSchemaVersion(singleVersions, AccountSchemaDefinitions.INSTANCE);
putSchemaVersion(singleVersions, ChangeSchemaDefinitions.INSTANCE);
putSchemaVersion(singleVersions, GroupSchemaDefinitions.INSTANCE);
+ putSchemaVersion(singleVersions, ProjectSchemaDefinitions.INSTANCE);
return singleVersions;
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/TestTimeUtil.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/TestTimeUtil.java
index dd44cb9ae..5bbe3b6 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/TestTimeUtil.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/TestTimeUtil.java
@@ -17,18 +17,21 @@
import static com.google.common.base.Preconditions.checkState;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import com.google.gerrit.common.TimeUtil;
import java.sql.Timestamp;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.Month;
+import java.time.ZoneOffset;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
-import org.joda.time.DateTime;
-import org.joda.time.DateTimeUtils;
-import org.joda.time.DateTimeUtils.MillisProvider;
-import org.joda.time.DateTimeZone;
/** Static utility methods for dealing with dates and times in tests. */
public class TestTimeUtil {
- public static final DateTime START =
- new DateTime(2009, 9, 30, 17, 0, 0, DateTimeZone.forOffsetHours(-4));
+ public static final Instant START =
+ LocalDateTime.of(2009, Month.SEPTEMBER, 30, 17, 0, 0)
+ .atOffset(ZoneOffset.ofHours(-4))
+ .toInstant();
private static Long clockStepMs;
private static AtomicLong clockMs;
@@ -43,7 +46,7 @@
*/
public static synchronized void resetWithClockStep(long clockStep, TimeUnit clockStepUnit) {
// Set an arbitrary start point so tests are more repeatable.
- clockMs = new AtomicLong(START.getMillis());
+ clockMs = new AtomicLong(START.toEpochMilli());
setClockStep(clockStep, clockStepUnit);
}
@@ -56,13 +59,7 @@
public static synchronized void setClockStep(long clockStep, TimeUnit clockStepUnit) {
checkState(clockMs != null, "call resetWithClockStep first");
clockStepMs = MILLISECONDS.convert(clockStep, clockStepUnit);
- DateTimeUtils.setCurrentMillisProvider(
- new MillisProvider() {
- @Override
- public long getMillis() {
- return clockMs.getAndAdd(clockStepMs);
- }
- });
+ TimeUtil.setCurrentMillisSupplier(() -> clockMs.getAndAdd(clockStepMs));
}
/**
@@ -89,7 +86,7 @@
/** Reset the clock to use the actual system clock. */
public static synchronized void useSystemTime() {
clockMs = null;
- DateTimeUtils.setCurrentMillisSystem();
+ TimeUtil.resetCurrentMillisSupplier();
}
private TestTimeUtil() {}
diff --git a/gerrit-server/src/test/resources/com/google/gerrit/rules/gerrit_common_test.pl b/gerrit-server/src/test/resources/com/google/gerrit/rules/gerrit_common_test.pl
index c993394..a7df2b9 100644
--- a/gerrit-server/src/test/resources/com/google/gerrit/rules/gerrit_common_test.pl
+++ b/gerrit-server/src/test/resources/com/google/gerrit/rules/gerrit_common_test.pl
@@ -65,7 +65,7 @@
test(default_submit_fails) :-
findall(P, default_submit(P), All),
All = [submit(C, V)],
- C = label('Code-Review', ok(test_user(alice))),
+ C = label('Code-Review', ok(_)),
V = label('Verified', need(1)).
@@ -84,7 +84,7 @@
test(can_submit_not_ready) :-
can_submit(gerrit:default_submit, S),
S = not_ready(submit(C, V)),
- C = label('Code-Review', ok(test_user(alice))),
+ C = label('Code-Review', ok(_)),
V = label('Verified', need(1)).
test(can_submit_only_verified_not_ready) :-
@@ -99,7 +99,7 @@
can_submit(gerrit:default_submit, R),
filter_submit_results(filter_out_v, [R], S),
S = [ok(submit(C))],
- C = label('Code-Review', ok(test_user(alice))).
+ C = label('Code-Review', ok(_)).
test(filter_submit_add_code_review) :-
set_commit_labels([
@@ -119,7 +119,7 @@
can_submit(gerrit:default_submit, R),
arg(1, R, S),
find_label(S, 'Code-Review', L),
- L = label('Code-Review', ok(test_user(alice))).
+ L = label('Code-Review', ok(_)).
test(find_default_verified) :-
can_submit(gerrit:default_submit, R),
@@ -133,7 +133,7 @@
test(remove_default_code_review) :-
can_submit(gerrit:default_submit, R),
arg(1, R, S),
- C = label('Code-Review', ok(test_user(alice))),
+ C = label('Code-Review', ok(_)),
remove_label(S, C, Out),
Out = submit(V),
V = label('Verified', need(1)).
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/AbstractGitCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/AbstractGitCommand.java
index b9a98b9..710b3dc 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/AbstractGitCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/AbstractGitCommand.java
@@ -19,7 +19,6 @@
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.permissions.PermissionBackendException;
-import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.sshd.SshScope.Context;
import com.google.inject.Inject;
@@ -31,7 +30,7 @@
public abstract class AbstractGitCommand extends BaseCommand {
@Argument(index = 0, metaVar = "PROJECT.git", required = true, usage = "project name")
- protected ProjectControl projectControl;
+ protected ProjectState projectState;
@Inject private SshScope sshScope;
@@ -41,12 +40,9 @@
@Inject private SshScope.Context context;
- @Inject private IdentifiedUser user;
-
@Inject private IdentifiedUser.GenericFactory userFactory;
protected Repository repo;
- protected ProjectState state;
protected Project.NameKey projectName;
protected Project project;
@@ -69,7 +65,7 @@
@Override
public Project.NameKey getProjectName() {
- return projectControl.getProjectState().getNameKey();
+ return projectState.getNameKey();
}
});
} finally {
@@ -88,8 +84,7 @@
}
private void service() throws IOException, PermissionBackendException, Failure {
- state = projectControl.getProjectState();
- project = state.getProject();
+ project = projectState.getProject();
projectName = project.getNameKey();
try {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java
index 6923ad1..fa3a0f5 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java
@@ -79,6 +79,8 @@
private ExitCallback exit;
+ @Inject protected CurrentUser user;
+
@Inject private SshScope sshScope;
@Inject private CmdLineParser.Factory cmdLineParserFactory;
@@ -88,7 +90,6 @@
@Inject @CommandExecutor private ScheduledThreadPoolExecutor executor;
@Inject private PermissionBackend permissionBackend;
- @Inject private CurrentUser user;
@Inject private SshScope.Context context;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/ChangeArgumentParser.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/ChangeArgumentParser.java
index 1c55f48..d5fc4547 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/ChangeArgumentParser.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/ChangeArgumentParser.java
@@ -28,7 +28,7 @@
import com.google.gerrit.server.permissions.GlobalPermission;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
-import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.sshd.BaseCommand.UnloggedFailure;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@@ -67,15 +67,15 @@
}
public void addChange(
- String id, Map<Change.Id, ChangeResource> changes, ProjectControl projectControl)
+ String id, Map<Change.Id, ChangeResource> changes, ProjectState projectState)
throws UnloggedFailure, OrmException, PermissionBackendException {
- addChange(id, changes, projectControl, true);
+ addChange(id, changes, projectState, true);
}
public void addChange(
String id,
Map<Change.Id, ChangeResource> changes,
- ProjectControl projectControl,
+ ProjectState projectState,
boolean useIndex)
throws UnloggedFailure, OrmException, PermissionBackendException {
List<ChangeNotes> matched = useIndex ? changeFinder.find(id) : changeFromNotesFactory(id);
@@ -89,7 +89,7 @@
}
for (ChangeNotes notes : matched) {
if (!changes.containsKey(notes.getChangeId())
- && inProject(projectControl, notes.getProjectName())
+ && inProject(projectState, notes.getProjectName())
&& (canMaintainServer
|| permissionBackend
.user(currentUser)
@@ -127,9 +127,9 @@
}
}
- private boolean inProject(ProjectControl projectControl, Project.NameKey project) {
- if (projectControl != null) {
- return projectControl.getProject().getNameKey().equals(project);
+ private boolean inProject(ProjectState projectState, Project.NameKey project) {
+ if (projectState != null) {
+ return projectState.getNameKey().equals(project);
}
// No --project option, so they want every project.
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java
index 0d7fa24..c6e00aa 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java
@@ -14,6 +14,8 @@
package com.google.gerrit.sshd.commands;
+import static java.util.stream.Collectors.toList;
+
import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.common.ProjectInfo;
@@ -24,7 +26,6 @@
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.ListChildProjects;
import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.ProjectResource;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.sshd.CommandMetaData;
@@ -57,21 +58,21 @@
metaVar = "NAME",
usage = "new parent project"
)
- private ProjectControl newParent;
+ private ProjectState newParent;
@Option(
name = "--children-of",
metaVar = "NAME",
usage = "parent project for which the child projects should be reparented"
)
- private ProjectControl oldParent;
+ private ProjectState oldParent;
@Option(
name = "--exclude",
metaVar = "NAME",
usage = "child project of old parent project which should not be reparented"
)
- private List<ProjectControl> excludedChildren = new ArrayList<>();
+ private List<ProjectState> excludedChildren = new ArrayList<>();
@Argument(
index = 0,
@@ -80,7 +81,7 @@
metaVar = "NAME",
usage = "projects to modify"
)
- private List<ProjectControl> children = new ArrayList<>();
+ private List<ProjectState> children = new ArrayList<>();
@Inject private ProjectCache projectCache;
@@ -125,10 +126,8 @@
}
}
- final List<Project.NameKey> childProjects = new ArrayList<>();
- for (ProjectControl pc : children) {
- childProjects.add(pc.getProject().getNameKey());
- }
+ final List<Project.NameKey> childProjects =
+ children.stream().map(ProjectState::getNameKey).collect(toList());
if (oldParent != null) {
try {
childProjects.addAll(getChildrenForReparenting(oldParent));
@@ -174,7 +173,13 @@
err.append("error: ").append(msg).append("\n");
}
- projectCache.evict(nameKey);
+ try {
+ projectCache.evict(nameKey);
+ } catch (IOException e) {
+ final String msg = "Cannot reindex project: " + name;
+ log.error(msg, e);
+ err.append("error: ").append(msg).append("\n");
+ }
}
if (err.length() > 0) {
@@ -190,18 +195,18 @@
* list of child projects does not contain projects that were specified to be excluded from
* reparenting.
*/
- private List<Project.NameKey> getChildrenForReparenting(ProjectControl parent)
+ private List<Project.NameKey> getChildrenForReparenting(ProjectState parent)
throws PermissionBackendException {
final List<Project.NameKey> childProjects = new ArrayList<>();
final List<Project.NameKey> excluded = new ArrayList<>(excludedChildren.size());
- for (ProjectControl excludedChild : excludedChildren) {
+ for (ProjectState excludedChild : excludedChildren) {
excluded.add(excludedChild.getProject().getNameKey());
}
final List<Project.NameKey> automaticallyExcluded = new ArrayList<>(excludedChildren.size());
if (newParentKey != null) {
automaticallyExcluded.addAll(getAllParents(newParentKey));
}
- for (ProjectInfo child : listChildProjects.apply(new ProjectResource(parent))) {
+ for (ProjectInfo child : listChildProjects.apply(new ProjectResource(parent, user))) {
final Project.NameKey childName = new Project.NameKey(child.name);
if (!excluded.contains(childName)) {
if (!automaticallyExcluded.contains(childName)) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BanCommitCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BanCommitCommand.java
index d06f65c..d514e2c 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BanCommitCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BanCommitCommand.java
@@ -18,10 +18,11 @@
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
+import com.google.gerrit.extensions.api.projects.BanCommitInput;
import com.google.gerrit.server.project.BanCommit;
import com.google.gerrit.server.project.BanCommit.BanResultInfo;
-import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.ProjectResource;
+import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.gerrit.sshd.SshCommand;
import com.google.inject.Inject;
@@ -51,7 +52,7 @@
metaVar = "PROJECT",
usage = "name of the project for which the commit should be banned"
)
- private ProjectControl projectControl;
+ private ProjectState projectState;
@Argument(
index = 1,
@@ -67,11 +68,11 @@
@Override
protected void run() throws Failure {
try {
- BanCommit.Input input =
- BanCommit.Input.fromCommits(Lists.transform(commitsToBan, ObjectId::getName));
+ BanCommitInput input =
+ BanCommitInput.fromCommits(Lists.transform(commitsToBan, ObjectId::getName));
input.reason = reason;
- BanResultInfo r = banCommit.apply(new ProjectResource(projectControl), input);
+ BanResultInfo r = banCommit.apply(new ProjectResource(projectState, user), input);
printCommits(r.newlyBanned, "The following commits were banned");
printCommits(r.alreadyBanned, "The following commits were already banned");
printCommits(r.ignored, "The following ids do not represent commits and were ignored");
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateBranchCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateBranchCommand.java
index 5962faa..fd1e189 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateBranchCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateBranchCommand.java
@@ -17,7 +17,7 @@
import com.google.gerrit.extensions.api.GerritApi;
import com.google.gerrit.extensions.api.projects.BranchInput;
import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.gerrit.sshd.SshCommand;
import com.google.inject.Inject;
@@ -28,7 +28,7 @@
public final class CreateBranchCommand extends SshCommand {
@Argument(index = 0, required = true, metaVar = "PROJECT", usage = "name of the project")
- private ProjectControl project;
+ private ProjectState project;
@Argument(index = 1, required = true, metaVar = "NAME", usage = "name of branch to be created")
private String name;
@@ -48,7 +48,7 @@
try {
BranchInput in = new BranchInput();
in.revision = revision;
- gApi.projects().name(project.getProject().getNameKey().get()).branch(name).create(in);
+ gApi.projects().name(project.getName()).branch(name).create(in);
} catch (RestApiException e) {
throw die(e);
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java
index 0df2a80..d6ecb0a 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CreateProjectCommand.java
@@ -28,7 +28,7 @@
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.permissions.PermissionBackendException;
-import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.project.SuggestParentCandidates;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.gerrit.sshd.SshCommand;
@@ -68,7 +68,7 @@
metaVar = "NAME",
usage = "parent project"
)
- private ProjectControl newParent;
+ private ProjectState newParent;
@Option(name = "--permissions-only", usage = "create project for use only as parent")
private boolean permissionsOnly;
@@ -188,7 +188,7 @@
input.owners = Lists.transform(ownerIds, AccountGroup.UUID::get);
}
if (newParent != null) {
- input.parent = newParent.getProject().getName();
+ input.parent = newParent.getName();
}
input.permissionsOnly = permissionsOnly;
input.description = projectDescription;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/GarbageCollectionCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/GarbageCollectionCommand.java
index b0b26fa..25f0e77 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/GarbageCollectionCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/GarbageCollectionCommand.java
@@ -17,6 +17,7 @@
import static com.google.gerrit.common.data.GlobalCapability.MAINTAIN_SERVER;
import static com.google.gerrit.common.data.GlobalCapability.RUN_GC;
import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
+import static java.util.stream.Collectors.toList;
import com.google.common.collect.Lists;
import com.google.gerrit.common.data.GarbageCollectionResult;
@@ -24,7 +25,7 @@
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.git.GarbageCollection;
import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.gerrit.sshd.SshCommand;
import com.google.inject.Inject;
@@ -54,7 +55,7 @@
metaVar = "NAME",
usage = "projects for which the Git garbage collection should be run"
)
- private List<ProjectControl> projects = new ArrayList<>();
+ private List<ProjectState> projects = new ArrayList<>();
@Inject private ProjectCache projectCache;
@@ -80,10 +81,7 @@
if (all) {
projectNames = Lists.newArrayList(projectCache.all());
} else {
- projectNames = Lists.newArrayListWithCapacity(projects.size());
- for (ProjectControl pc : projects) {
- projectNames.add(pc.getProject().getNameKey());
- }
+ projectNames = projects.stream().map(ProjectState::getNameKey).collect(toList());
}
GarbageCollectionResult result =
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/IndexChangesCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/IndexChangesCommand.java
index 821257c..bb33dea 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/IndexChangesCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/IndexChangesCommand.java
@@ -14,6 +14,7 @@
package com.google.gerrit.sshd.commands;
+import com.google.gerrit.extensions.common.Input;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.change.ChangeResource;
import com.google.gerrit.server.change.Index;
@@ -55,7 +56,7 @@
boolean ok = true;
for (ChangeResource rsrc : changes.values()) {
try {
- index.apply(rsrc, new Index.Input());
+ index.apply(rsrc, new Input());
} catch (Exception e) {
ok = false;
writeError(
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/IndexProjectCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/IndexProjectCommand.java
index 476c25b..ba937a2 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/IndexProjectCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/IndexProjectCommand.java
@@ -18,8 +18,8 @@
import com.google.gerrit.extensions.annotations.RequiresAnyCapability;
import com.google.gerrit.server.project.Index;
-import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.ProjectResource;
+import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.gerrit.sshd.SshCommand;
import com.google.inject.Inject;
@@ -40,7 +40,7 @@
metaVar = "PROJECT",
usage = "projects for which the changes should be indexed"
)
- private List<ProjectControl> projects = new ArrayList<>();
+ private List<ProjectState> projects = new ArrayList<>();
@Override
protected void run() throws UnloggedFailure, Failure, Exception {
@@ -50,14 +50,12 @@
projects.stream().forEach(this::index);
}
- private void index(ProjectControl projectControl) {
+ private void index(ProjectState projectState) {
try {
- index.apply(new ProjectResource(projectControl), null);
+ index.apply(new ProjectResource(projectState, user), null);
} catch (Exception e) {
writeError(
- "error",
- String.format(
- "Unable to index %s: %s", projectControl.getProject().getName(), e.getMessage()));
+ "error", String.format("Unable to index %s: %s", projectState.getName(), e.getMessage()));
}
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/LsUserRefs.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/LsUserRefs.java
index 275da7c..e467cc4 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/LsUserRefs.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/LsUserRefs.java
@@ -25,7 +25,7 @@
import com.google.gerrit.server.account.AccountResolver;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.VisibleRefFilter;
-import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.util.ManualRequestContext;
import com.google.gerrit.server.util.OneOffRequestContext;
import com.google.gerrit.sshd.CommandMetaData;
@@ -59,7 +59,7 @@
required = true,
usage = "project for which the refs should be listed"
)
- private ProjectControl projectControl;
+ private ProjectState projectState;
@Option(
name = "--user",
@@ -87,13 +87,13 @@
return;
}
- Project.NameKey projectName = projectControl.getProject().getNameKey();
+ Project.NameKey projectName = projectState.getNameKey();
try (Repository repo = repoManager.openRepository(projectName);
ManualRequestContext ctx = requestContext.openAs(userAccount.getId())) {
try {
Map<String, Ref> refsMap =
refFilterFactory
- .create(projectControl.getProjectState(), repo)
+ .create(projectState, repo)
.filter(repo.getRefDatabase().getRefs(ALL), false);
for (String ref : refsMap.keySet()) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PatchSetParser.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PatchSetParser.java
index c3613b1..9fcd201 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PatchSetParser.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PatchSetParser.java
@@ -24,7 +24,7 @@
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.project.NoSuchChangeException;
-import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.InternalChangeQuery;
import com.google.gerrit.sshd.BaseCommand.UnloggedFailure;
@@ -57,15 +57,15 @@
this.changeFinder = changeFinder;
}
- public PatchSet parsePatchSet(String token, ProjectControl projectControl, String branch)
+ public PatchSet parsePatchSet(String token, ProjectState projectState, String branch)
throws UnloggedFailure, OrmException {
// By commit?
//
if (token.matches("^([0-9a-fA-F]{4," + RevId.LEN + "})$")) {
InternalChangeQuery query = queryProvider.get();
List<ChangeData> cds;
- if (projectControl != null) {
- Project.NameKey p = projectControl.getProject().getNameKey();
+ if (projectState != null) {
+ Project.NameKey p = projectState.getNameKey();
if (branch != null) {
cds = query.byBranchCommit(p.get(), branch, token);
} else {
@@ -77,7 +77,7 @@
List<PatchSet> matches = new ArrayList<>(cds.size());
for (ChangeData cd : cds) {
Change c = cd.change();
- if (!(inProject(c, projectControl) && inBranch(c, branch))) {
+ if (!(inProject(c, projectState) && inBranch(c, branch))) {
continue;
}
for (PatchSet ps : cd.patchSets()) {
@@ -106,19 +106,15 @@
} catch (IllegalArgumentException e) {
throw error("\"" + token + "\" is not a valid patch set");
}
- ChangeNotes notes = getNotes(projectControl, patchSetId.getParentKey());
+ ChangeNotes notes = getNotes(projectState, patchSetId.getParentKey());
PatchSet patchSet = psUtil.get(db.get(), notes, patchSetId);
if (patchSet == null) {
throw error("\"" + token + "\" no such patch set");
}
- if (projectControl != null || branch != null) {
+ if (projectState != null || branch != null) {
Change change = notes.getChange();
- if (!inProject(change, projectControl)) {
- throw error(
- "change "
- + change.getId()
- + " not in project "
- + projectControl.getProject().getName());
+ if (!inProject(change, projectState)) {
+ throw error("change " + change.getId() + " not in project " + projectState.getName());
}
if (!inBranch(change, branch)) {
throw error("change " + change.getId() + " not in branch " + branch);
@@ -130,10 +126,10 @@
throw error("\"" + token + "\" is not a valid patch set");
}
- private ChangeNotes getNotes(@Nullable ProjectControl projectControl, Change.Id changeId)
+ private ChangeNotes getNotes(@Nullable ProjectState projectState, Change.Id changeId)
throws OrmException, UnloggedFailure {
- if (projectControl != null) {
- return notesFactory.create(db.get(), projectControl.getProject().getNameKey(), changeId);
+ if (projectState != null) {
+ return notesFactory.create(db.get(), projectState.getNameKey(), changeId);
}
try {
ChangeNotes notes = changeFinder.findOne(changeId);
@@ -143,12 +139,12 @@
}
}
- private static boolean inProject(Change change, ProjectControl projectControl) {
- if (projectControl == null) {
+ private static boolean inProject(Change change, ProjectState projectState) {
+ if (projectState == null) {
// No --project option, so they want every project.
return true;
}
- return projectControl.getProject().getNameKey().equals(change.getProject());
+ return projectState.getNameKey().equals(change.getProject());
}
private static boolean inBranch(Change change, String branch) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginAdminSshCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginAdminSshCommand.java
new file mode 100644
index 0000000..7e32615
--- /dev/null
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginAdminSshCommand.java
@@ -0,0 +1,36 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.sshd.commands;
+
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.server.plugins.PluginLoader;
+import com.google.gerrit.sshd.SshCommand;
+import com.google.inject.Inject;
+
+@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
+public abstract class PluginAdminSshCommand extends SshCommand {
+ @Inject protected PluginLoader loader;
+
+ abstract void doRun() throws UnloggedFailure;
+
+ @Override
+ protected final void run() throws UnloggedFailure {
+ if (!loader.isRemoteAdminEnabled()) {
+ throw die("remote plugin administration is disabled");
+ }
+ doRun();
+ }
+}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginEnableCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginEnableCommand.java
index d7c8f3a..baaf715 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginEnableCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginEnableCommand.java
@@ -17,29 +17,18 @@
import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
import com.google.common.collect.Sets;
-import com.google.gerrit.common.data.GlobalCapability;
-import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.server.plugins.PluginInstallException;
-import com.google.gerrit.server.plugins.PluginLoader;
import com.google.gerrit.sshd.CommandMetaData;
-import com.google.gerrit.sshd.SshCommand;
-import com.google.inject.Inject;
import java.util.List;
import org.kohsuke.args4j.Argument;
-@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
@CommandMetaData(name = "enable", description = "Enable plugins", runsAt = MASTER_OR_SLAVE)
-final class PluginEnableCommand extends SshCommand {
+final class PluginEnableCommand extends PluginAdminSshCommand {
@Argument(index = 0, metaVar = "NAME", required = true, usage = "plugin(s) to enable")
List<String> names;
- @Inject private PluginLoader loader;
-
@Override
- protected void run() throws UnloggedFailure {
- if (!loader.isRemoteAdminEnabled()) {
- throw die("remote plugin administration is disabled");
- }
+ protected void doRun() throws UnloggedFailure {
if (names != null && !names.isEmpty()) {
try {
loader.enablePlugins(Sets.newHashSet(names));
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginInstallCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginInstallCommand.java
index 820052c..337eadb 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginInstallCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginInstallCommand.java
@@ -17,13 +17,8 @@
import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
import com.google.common.base.Strings;
-import com.google.gerrit.common.data.GlobalCapability;
-import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.server.plugins.PluginInstallException;
-import com.google.gerrit.server.plugins.PluginLoader;
import com.google.gerrit.sshd.CommandMetaData;
-import com.google.gerrit.sshd.SshCommand;
-import com.google.inject.Inject;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@@ -33,9 +28,8 @@
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.Option;
-@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
@CommandMetaData(name = "install", description = "Install/Add a plugin", runsAt = MASTER_OR_SLAVE)
-final class PluginInstallCommand extends SshCommand {
+final class PluginInstallCommand extends PluginAdminSshCommand {
@Option(
name = "--name",
aliases = {"-n"},
@@ -51,14 +45,9 @@
@Argument(index = 0, metaVar = "-|URL", usage = "JAR to load")
private String source;
- @Inject private PluginLoader loader;
-
@SuppressWarnings("resource")
@Override
- protected void run() throws UnloggedFailure {
- if (!loader.isRemoteAdminEnabled()) {
- throw die("remote installation is disabled");
- }
+ protected void doRun() throws UnloggedFailure {
if (Strings.isNullOrEmpty(source)) {
throw die("Argument \"-|URL\" is required");
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginReloadCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginReloadCommand.java
index 0f2c912..86a74d1 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginReloadCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginReloadCommand.java
@@ -16,30 +16,19 @@
import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
-import com.google.gerrit.common.data.GlobalCapability;
-import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.server.plugins.InvalidPluginException;
import com.google.gerrit.server.plugins.PluginInstallException;
-import com.google.gerrit.server.plugins.PluginLoader;
import com.google.gerrit.sshd.CommandMetaData;
-import com.google.gerrit.sshd.SshCommand;
-import com.google.inject.Inject;
import java.util.List;
import org.kohsuke.args4j.Argument;
-@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
@CommandMetaData(name = "reload", description = "Reload/Restart plugins", runsAt = MASTER_OR_SLAVE)
-final class PluginReloadCommand extends SshCommand {
+final class PluginReloadCommand extends PluginAdminSshCommand {
@Argument(index = 0, metaVar = "NAME", usage = "plugins to reload/restart")
private List<String> names;
- @Inject private PluginLoader loader;
-
@Override
- protected void run() throws UnloggedFailure {
- if (!loader.isRemoteAdminEnabled()) {
- throw die("remote plugin administration is disabled");
- }
+ protected void doRun() throws UnloggedFailure {
if (names == null || names.isEmpty()) {
loader.rescan();
} else {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginRemoveCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginRemoveCommand.java
index 8a38739..0119349b 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginRemoveCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/PluginRemoveCommand.java
@@ -17,28 +17,17 @@
import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
import com.google.common.collect.Sets;
-import com.google.gerrit.common.data.GlobalCapability;
-import com.google.gerrit.extensions.annotations.RequiresCapability;
-import com.google.gerrit.server.plugins.PluginLoader;
import com.google.gerrit.sshd.CommandMetaData;
-import com.google.gerrit.sshd.SshCommand;
-import com.google.inject.Inject;
import java.util.List;
import org.kohsuke.args4j.Argument;
-@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
@CommandMetaData(name = "remove", description = "Disable plugins", runsAt = MASTER_OR_SLAVE)
-final class PluginRemoveCommand extends SshCommand {
+final class PluginRemoveCommand extends PluginAdminSshCommand {
@Argument(index = 0, metaVar = "NAME", required = true, usage = "plugin to remove")
List<String> names;
- @Inject private PluginLoader loader;
-
@Override
- protected void run() throws UnloggedFailure {
- if (!loader.isRemoteAdminEnabled()) {
- throw die("remote plugin administration is disabled");
- }
+ protected void doRun() throws UnloggedFailure {
if (names != null && !names.isEmpty()) {
loader.disablePlugins(Sets.newHashSet(names));
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java
index 0f68d61..b199349 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Receive.java
@@ -93,11 +93,15 @@
throw new Failure(1, "fatal: unable to check permissions " + e);
}
- AsyncReceiveCommits arc = factory.create(projectControl, repo, null, reviewers);
+ AsyncReceiveCommits arc = factory.create(projectState, currentUser, repo, null, reviewers);
- Capable r = arc.canUpload();
- if (r != Capable.OK) {
- throw die(r.getMessage());
+ try {
+ Capable r = arc.canUpload();
+ if (r != Capable.OK) {
+ throw die(r.getMessage());
+ }
+ } catch (PermissionBackendException e) {
+ throw die(e.getMessage());
}
ReceivePack rp = arc.getReceivePack();
@@ -110,9 +114,7 @@
// we want to present this error to the user
if (badStream.getCause() instanceof TooLargeObjectInPackException) {
StringBuilder msg = new StringBuilder();
- msg.append("Receive error on project \"")
- .append(projectControl.getProject().getName())
- .append("\"");
+ msg.append("Receive error on project \"").append(projectState.getName()).append("\"");
msg.append(" (user ");
msg.append(currentUser.getAccount().getUserName());
msg.append(" account ");
@@ -127,9 +129,7 @@
// Log what the heck is going on, as detailed as we can.
//
StringBuilder msg = new StringBuilder();
- msg.append("Unpack error on project \"")
- .append(projectControl.getProject().getName())
- .append("\":\n");
+ msg.append("Unpack error on project \"").append(projectState.getName()).append("\":\n");
msg.append(" AdvertiseRefsHook: ").append(rp.getAdvertiseRefsHook());
if (rp.getAdvertiseRefsHook() == AdvertiseRefsHook.DEFAULT) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/RenameGroupCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/RenameGroupCommand.java
index 6ec3a28..74dcc12 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/RenameGroupCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/RenameGroupCommand.java
@@ -14,6 +14,7 @@
package com.google.gerrit.sshd.commands;
+import com.google.gerrit.extensions.common.NameInput;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.TopLevelResource;
@@ -48,7 +49,7 @@
protected void run() throws Failure {
try {
GroupResource rsrc = groups.parse(TopLevelResource.INSTANCE, IdString.fromDecoded(groupName));
- PutName.Input input = new PutName.Input();
+ NameInput input = new NameInput();
input.name = newGroupName;
putName.apply(rsrc, input);
} catch (RestApiException | OrmException | IOException e) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
index 2a82a26..1d764b9 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
@@ -34,7 +34,6 @@
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.util.LabelVote;
import com.google.gerrit.sshd.CommandMetaData;
@@ -81,7 +80,7 @@
)
void addPatchSetId(String token) {
try {
- PatchSet ps = psParser.parsePatchSet(token, projectControl, branch);
+ PatchSet ps = psParser.parsePatchSet(token, projectState, branch);
patchSets.add(ps);
} catch (UnloggedFailure e) {
throw new IllegalArgumentException(e.getMessage(), e);
@@ -95,7 +94,7 @@
aliases = "-p",
usage = "project containing the specified patch set(s)"
)
- private ProjectControl projectControl;
+ private ProjectState projectState;
@Option(name = "--branch", aliases = "-b", usage = "branch containing the specified patch set(s)")
private String branch;
@@ -135,12 +134,6 @@
private boolean json;
@Option(
- name = "--strict-labels",
- usage = "Strictly check if the labels specified can be applied to the given patch set(s)"
- )
- private boolean strictLabels;
-
- @Option(
name = "--tag",
aliases = "-t",
usage = "applies a tag to the given review",
@@ -274,7 +267,6 @@
review.notify = notify;
review.labels = new TreeMap<>();
review.drafts = ReviewInput.DraftHandling.PUBLISH;
- review.strictLabels = strictLabels;
for (ApproveOption ao : optionList) {
Short v = ao.value();
if (v != null) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java
index 033b4c6..16cc49a 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetAccountCommand.java
@@ -22,7 +22,11 @@
import com.google.gerrit.common.errors.EmailException;
import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.api.accounts.EmailInput;
+import com.google.gerrit.extensions.api.accounts.SshKeyInput;
import com.google.gerrit.extensions.common.EmailInfo;
+import com.google.gerrit.extensions.common.HttpPasswordInput;
+import com.google.gerrit.extensions.common.Input;
+import com.google.gerrit.extensions.common.NameInput;
import com.google.gerrit.extensions.common.SshKeyInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
@@ -142,7 +146,6 @@
@Inject private DeleteSshKey deleteSshKey;
- private IdentifiedUser user;
private AccountResource rsrc;
@Override
@@ -178,7 +181,7 @@
throws OrmException, IOException, UnloggedFailure, ConfigInvalidException,
PermissionBackendException {
user = genericUserFactory.create(id);
- rsrc = new AccountResource(user);
+ rsrc = new AccountResource(user.asIdentifiedUser());
try {
for (String email : addEmails) {
addEmail(email);
@@ -193,13 +196,13 @@
}
if (fullName != null) {
- PutName.Input in = new PutName.Input();
+ NameInput in = new NameInput();
in.name = fullName;
putName.apply(rsrc, in);
}
if (httpPassword != null || clearHttpPassword) {
- PutHttpPassword.Input in = new PutHttpPassword.Input();
+ HttpPasswordInput in = new HttpPasswordInput();
in.httpPassword = httpPassword;
putHttpPassword.apply(rsrc, in);
}
@@ -232,7 +235,7 @@
throws RestApiException, OrmException, IOException, ConfigInvalidException,
PermissionBackendException {
for (String sshKey : sshKeys) {
- AddSshKey.Input in = new AddSshKey.Input();
+ SshKeyInput in = new SshKeyInput();
in.raw = RawInputUtil.create(sshKey.getBytes(UTF_8), "plain/text");
addSshKey.apply(rsrc, in);
}
@@ -262,7 +265,7 @@
ConfigInvalidException, PermissionBackendException {
AccountSshKey sshKey =
new AccountSshKey(new AccountSshKey.Id(user.getAccountId(), i.seq), i.sshPublicKey);
- deleteSshKey.apply(new AccountResource.SshKey(user, sshKey), null);
+ deleteSshKey.apply(new AccountResource.SshKey(user.asIdentifiedUser(), sshKey), null);
}
private void addEmail(String email)
@@ -284,10 +287,10 @@
if (email.equals("ALL")) {
List<EmailInfo> emails = getEmails.apply(rsrc);
for (EmailInfo e : emails) {
- deleteEmail.apply(new AccountResource.Email(user, e.email), new DeleteEmail.Input());
+ deleteEmail.apply(new AccountResource.Email(user.asIdentifiedUser(), e.email), new Input());
}
} else {
- deleteEmail.apply(new AccountResource.Email(user, email), new DeleteEmail.Input());
+ deleteEmail.apply(new AccountResource.Email(user.asIdentifiedUser(), email), new Input());
}
}
@@ -296,7 +299,7 @@
ConfigInvalidException {
for (EmailInfo e : getEmails.apply(rsrc)) {
if (e.email.equals(email)) {
- putPreferred.apply(new AccountResource.Email(user, email), null);
+ putPreferred.apply(new AccountResource.Email(user.asIdentifiedUser(), email), null);
return;
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetHeadCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetHeadCommand.java
index ce4116d..ef7ab916 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetHeadCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetHeadCommand.java
@@ -14,11 +14,11 @@
package com.google.gerrit.sshd.commands;
+import com.google.gerrit.extensions.api.projects.HeadInput;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
-import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.ProjectResource;
+import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.project.SetHead;
-import com.google.gerrit.server.project.SetHead.Input;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.gerrit.sshd.SshCommand;
import com.google.inject.Inject;
@@ -29,7 +29,7 @@
public class SetHeadCommand extends SshCommand {
@Argument(index = 0, required = true, metaVar = "NAME", usage = "name of the project")
- private ProjectControl project;
+ private ProjectState project;
@Option(name = "--new-head", required = true, metaVar = "REF", usage = "new HEAD reference")
private String newHead;
@@ -43,10 +43,10 @@
@Override
protected void run() throws Exception {
- Input input = new SetHead.Input();
+ HeadInput input = new HeadInput();
input.ref = newHead;
try {
- setHead.apply(new ProjectResource(project), input);
+ setHead.apply(new ProjectResource(project, user), input);
} catch (UnprocessableEntityException e) {
throw die(e);
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetProjectCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetProjectCommand.java
index c275af8..a963a35 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetProjectCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetProjectCommand.java
@@ -17,11 +17,11 @@
import com.google.common.base.Strings;
import com.google.gerrit.extensions.api.projects.ConfigInput;
import com.google.gerrit.extensions.client.InheritableBoolean;
-import com.google.gerrit.extensions.client.ProjectState;
import com.google.gerrit.extensions.client.SubmitType;
import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.ProjectResource;
+import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.project.PutConfig;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.gerrit.sshd.SshCommand;
@@ -32,7 +32,7 @@
@CommandMetaData(name = "set-project", description = "Change a project's settings")
final class SetProjectCommand extends SshCommand {
@Argument(index = 0, required = true, metaVar = "NAME", usage = "name of the project")
- private ProjectControl projectControl;
+ private ProjectState projectState;
@Option(
name = "--description",
@@ -148,19 +148,19 @@
configInput.useContentMerge = contentMerge;
configInput.useContributorAgreements = contributorAgreements;
configInput.useSignedOffBy = signedOffBy;
- configInput.state = state;
+ configInput.state = state.getProject().getState();
configInput.maxObjectSizeLimit = maxObjectSizeLimit;
// Description is different to other parameters, null won't result in
// keeping the existing description, it would delete it.
if (Strings.emptyToNull(projectDescription) != null) {
configInput.description = projectDescription;
} else {
- configInput.description = projectControl.getProject().getDescription();
+ configInput.description = projectState.getProject().getDescription();
}
try {
- putConfig.apply(new ProjectResource(projectControl), configInput);
- } catch (RestApiException e) {
+ putConfig.apply(new ProjectResource(projectState, user), configInput);
+ } catch (RestApiException | PermissionBackendException e) {
throw die(e);
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetReviewersCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetReviewersCommand.java
index 026f9b7..85cf467 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetReviewersCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetReviewersCommand.java
@@ -24,7 +24,7 @@
import com.google.gerrit.server.change.PostReviewers;
import com.google.gerrit.server.change.ReviewerResource;
import com.google.gerrit.server.permissions.PermissionBackendException;
-import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.sshd.ChangeArgumentParser;
import com.google.gerrit.sshd.CommandMetaData;
import com.google.gerrit.sshd.SshCommand;
@@ -46,7 +46,7 @@
private static final Logger log = LoggerFactory.getLogger(SetReviewersCommand.class);
@Option(name = "--project", aliases = "-p", usage = "project containing the change")
- private ProjectControl projectControl;
+ private ProjectState projectState;
@Option(
name = "--add",
@@ -75,7 +75,7 @@
)
void addChange(String token) {
try {
- changeArgumentParser.addChange(token, changes, projectControl);
+ changeArgumentParser.addChange(token, changes, projectState);
} catch (UnloggedFailure e) {
throw new IllegalArgumentException(e.getMessage(), e);
} catch (OrmException e) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Upload.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Upload.java
index 7049c7f..0d78279 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Upload.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Upload.java
@@ -51,8 +51,8 @@
protected void runImpl() throws IOException, Failure {
try {
permissionBackend
- .user(projectControl.getUser())
- .project(projectControl.getProject().getNameKey())
+ .user(user)
+ .project(projectState.getNameKey())
.check(ProjectPermission.RUN_UPLOAD_PACK);
} catch (AuthException e) {
throw new Failure(1, "fatal: upload-pack not permitted on this server");
@@ -61,7 +61,7 @@
}
final UploadPack up = new UploadPack(repo);
- up.setAdvertiseRefsHook(refFilterFactory.create(projectControl.getProjectState(), repo));
+ up.setAdvertiseRefsHook(refFilterFactory.create(projectState, repo));
up.setPackConfig(config.getPackConfig());
up.setTimeout(config.getTimeout());
up.setPostUploadHook(PostUploadHookChain.newChain(Lists.newArrayList(postUploadHooks)));
@@ -71,7 +71,7 @@
uploadValidatorsFactory.create(project, repo, session.getRemoteAddressAsString()));
up.setPreUploadHook(PreUploadHookChain.newChain(allPreUploadHooks));
for (UploadPackInitializer initializer : uploadPackInitializers) {
- initializer.init(projectControl.getProject().getNameKey(), up);
+ initializer.init(projectState.getNameKey(), up);
}
try {
up.upload(in, out, err);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/UploadArchive.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/UploadArchive.java
index 9a3e6ab..41cc485b 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/UploadArchive.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/UploadArchive.java
@@ -18,7 +18,6 @@
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.extensions.restapi.AuthException;
-import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.change.AllowedFormats;
import com.google.gerrit.server.change.ArchiveFormat;
import com.google.gerrit.server.permissions.PermissionBackend;
@@ -122,7 +121,6 @@
@Inject private PermissionBackend permissionBackend;
@Inject private CommitsCollection commits;
- @Inject private IdentifiedUser user;
@Inject private AllowedFormats allowedFormats;
private Options options = new Options();
@@ -250,7 +248,7 @@
// Check reachability of the specific revision.
try (RevWalk rw = new RevWalk(repo)) {
RevCommit commit = rw.parseCommit(revId);
- return commits.canRead(state, repo, commit);
+ return commits.canRead(projectState, repo, commit);
}
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/plugin/LfsPluginAuthCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/plugin/LfsPluginAuthCommand.java
index b44f0fc..1858f40 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/plugin/LfsPluginAuthCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/plugin/LfsPluginAuthCommand.java
@@ -20,7 +20,6 @@
import com.google.gerrit.sshd.CommandModule;
import com.google.gerrit.sshd.SshCommand;
import com.google.inject.Inject;
-import com.google.inject.Provider;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jgit.lib.Config;
@@ -55,15 +54,13 @@
}
private final DynamicItem<LfsSshPluginAuth> auth;
- private final Provider<CurrentUser> user;
@Argument(index = 0, multiValued = true, metaVar = "PARAMS")
private List<String> args = new ArrayList<>();
@Inject
- LfsPluginAuthCommand(DynamicItem<LfsSshPluginAuth> auth, Provider<CurrentUser> user) {
+ LfsPluginAuthCommand(DynamicItem<LfsSshPluginAuth> auth) {
this.auth = auth;
- this.user = user;
}
@Override
@@ -74,6 +71,6 @@
throw new UnloggedFailure(1, CONFIGURATION_ERROR);
}
- stdout.print(pluginAuth.authenticate(user.get(), args));
+ stdout.print(pluginAuth.authenticate(user, args));
}
}
diff --git a/gerrit-test-util/src/main/java/com/google/gerrit/extensions/client/RangeSubject.java b/gerrit-test-util/src/main/java/com/google/gerrit/extensions/client/RangeSubject.java
index 70f5ec6..40347a7 100644
--- a/gerrit-test-util/src/main/java/com/google/gerrit/extensions/client/RangeSubject.java
+++ b/gerrit-test-util/src/main/java/com/google/gerrit/extensions/client/RangeSubject.java
@@ -16,28 +16,19 @@
import static com.google.common.truth.Truth.assertAbout;
-import com.google.common.truth.FailureStrategy;
+import com.google.common.truth.FailureMetadata;
import com.google.common.truth.IntegerSubject;
import com.google.common.truth.Subject;
-import com.google.common.truth.SubjectFactory;
import com.google.common.truth.Truth;
public class RangeSubject extends Subject<RangeSubject, Comment.Range> {
- private static final SubjectFactory<RangeSubject, Comment.Range> RANGE_SUBJECT_FACTORY =
- new SubjectFactory<RangeSubject, Comment.Range>() {
- @Override
- public RangeSubject getSubject(FailureStrategy failureStrategy, Comment.Range range) {
- return new RangeSubject(failureStrategy, range);
- }
- };
-
public static RangeSubject assertThat(Comment.Range range) {
- return assertAbout(RANGE_SUBJECT_FACTORY).that(range);
+ return assertAbout(RangeSubject::new).that(range);
}
- private RangeSubject(FailureStrategy failureStrategy, Comment.Range range) {
- super(failureStrategy, range);
+ private RangeSubject(FailureMetadata failureMetadata, Comment.Range range) {
+ super(failureMetadata, range);
}
public IntegerSubject startLine() {
diff --git a/gerrit-test-util/src/main/java/com/google/gerrit/extensions/common/CommitInfoSubject.java b/gerrit-test-util/src/main/java/com/google/gerrit/extensions/common/CommitInfoSubject.java
index b2717af..37ae643 100644
--- a/gerrit-test-util/src/main/java/com/google/gerrit/extensions/common/CommitInfoSubject.java
+++ b/gerrit-test-util/src/main/java/com/google/gerrit/extensions/common/CommitInfoSubject.java
@@ -16,30 +16,20 @@
import static com.google.common.truth.Truth.assertAbout;
-import com.google.common.truth.FailureStrategy;
+import com.google.common.truth.FailureMetadata;
import com.google.common.truth.StringSubject;
import com.google.common.truth.Subject;
-import com.google.common.truth.SubjectFactory;
import com.google.common.truth.Truth;
import com.google.gerrit.truth.ListSubject;
public class CommitInfoSubject extends Subject<CommitInfoSubject, CommitInfo> {
- private static final SubjectFactory<CommitInfoSubject, CommitInfo> COMMIT_INFO_SUBJECT_FACTORY =
- new SubjectFactory<CommitInfoSubject, CommitInfo>() {
- @Override
- public CommitInfoSubject getSubject(
- FailureStrategy failureStrategy, CommitInfo commitInfo) {
- return new CommitInfoSubject(failureStrategy, commitInfo);
- }
- };
-
public static CommitInfoSubject assertThat(CommitInfo commitInfo) {
- return assertAbout(COMMIT_INFO_SUBJECT_FACTORY).that(commitInfo);
+ return assertAbout(CommitInfoSubject::new).that(commitInfo);
}
- private CommitInfoSubject(FailureStrategy failureStrategy, CommitInfo commitInfo) {
- super(failureStrategy, commitInfo);
+ private CommitInfoSubject(FailureMetadata failureMetadata, CommitInfo commitInfo) {
+ super(failureMetadata, commitInfo);
}
public StringSubject commit() {
diff --git a/gerrit-test-util/src/main/java/com/google/gerrit/extensions/common/ContentEntrySubject.java b/gerrit-test-util/src/main/java/com/google/gerrit/extensions/common/ContentEntrySubject.java
index 9c9893c..4e10ec4b 100644
--- a/gerrit-test-util/src/main/java/com/google/gerrit/extensions/common/ContentEntrySubject.java
+++ b/gerrit-test-util/src/main/java/com/google/gerrit/extensions/common/ContentEntrySubject.java
@@ -16,31 +16,21 @@
import static com.google.common.truth.Truth.assertAbout;
-import com.google.common.truth.FailureStrategy;
+import com.google.common.truth.FailureMetadata;
import com.google.common.truth.StringSubject;
import com.google.common.truth.Subject;
-import com.google.common.truth.SubjectFactory;
import com.google.common.truth.Truth;
import com.google.gerrit.extensions.common.DiffInfo.ContentEntry;
import com.google.gerrit.truth.ListSubject;
public class ContentEntrySubject extends Subject<ContentEntrySubject, ContentEntry> {
- private static final SubjectFactory<ContentEntrySubject, ContentEntry> DIFF_INFO_SUBJECT_FACTORY =
- new SubjectFactory<ContentEntrySubject, ContentEntry>() {
- @Override
- public ContentEntrySubject getSubject(
- FailureStrategy failureStrategy, ContentEntry contentEntry) {
- return new ContentEntrySubject(failureStrategy, contentEntry);
- }
- };
-
public static ContentEntrySubject assertThat(ContentEntry contentEntry) {
- return assertAbout(DIFF_INFO_SUBJECT_FACTORY).that(contentEntry);
+ return assertAbout(ContentEntrySubject::new).that(contentEntry);
}
- private ContentEntrySubject(FailureStrategy failureStrategy, ContentEntry contentEntry) {
- super(failureStrategy, contentEntry);
+ private ContentEntrySubject(FailureMetadata failureMetadata, ContentEntry contentEntry) {
+ super(failureMetadata, contentEntry);
}
public void isDueToRebase() {
diff --git a/gerrit-test-util/src/main/java/com/google/gerrit/extensions/common/DiffInfoSubject.java b/gerrit-test-util/src/main/java/com/google/gerrit/extensions/common/DiffInfoSubject.java
index 1b1b847..a91c0ba 100644
--- a/gerrit-test-util/src/main/java/com/google/gerrit/extensions/common/DiffInfoSubject.java
+++ b/gerrit-test-util/src/main/java/com/google/gerrit/extensions/common/DiffInfoSubject.java
@@ -17,29 +17,20 @@
import static com.google.common.truth.Truth.assertAbout;
import com.google.common.truth.ComparableSubject;
-import com.google.common.truth.FailureStrategy;
+import com.google.common.truth.FailureMetadata;
import com.google.common.truth.Subject;
-import com.google.common.truth.SubjectFactory;
import com.google.common.truth.Truth;
import com.google.gerrit.extensions.common.DiffInfo.ContentEntry;
import com.google.gerrit.truth.ListSubject;
public class DiffInfoSubject extends Subject<DiffInfoSubject, DiffInfo> {
- private static final SubjectFactory<DiffInfoSubject, DiffInfo> DIFF_INFO_SUBJECT_FACTORY =
- new SubjectFactory<DiffInfoSubject, DiffInfo>() {
- @Override
- public DiffInfoSubject getSubject(FailureStrategy failureStrategy, DiffInfo diffInfo) {
- return new DiffInfoSubject(failureStrategy, diffInfo);
- }
- };
-
public static DiffInfoSubject assertThat(DiffInfo diffInfo) {
- return assertAbout(DIFF_INFO_SUBJECT_FACTORY).that(diffInfo);
+ return assertAbout(DiffInfoSubject::new).that(diffInfo);
}
- private DiffInfoSubject(FailureStrategy failureStrategy, DiffInfo diffInfo) {
- super(failureStrategy, diffInfo);
+ private DiffInfoSubject(FailureMetadata failureMetadata, DiffInfo diffInfo) {
+ super(failureMetadata, diffInfo);
}
public ListSubject<ContentEntrySubject, ContentEntry> content() {
diff --git a/gerrit-test-util/src/main/java/com/google/gerrit/extensions/common/EditInfoSubject.java b/gerrit-test-util/src/main/java/com/google/gerrit/extensions/common/EditInfoSubject.java
index 95b2158..ea5f72e 100644
--- a/gerrit-test-util/src/main/java/com/google/gerrit/extensions/common/EditInfoSubject.java
+++ b/gerrit-test-util/src/main/java/com/google/gerrit/extensions/common/EditInfoSubject.java
@@ -16,26 +16,17 @@
import static com.google.common.truth.Truth.assertAbout;
-import com.google.common.truth.FailureStrategy;
+import com.google.common.truth.FailureMetadata;
import com.google.common.truth.StringSubject;
import com.google.common.truth.Subject;
-import com.google.common.truth.SubjectFactory;
import com.google.common.truth.Truth;
import com.google.gerrit.truth.OptionalSubject;
import java.util.Optional;
public class EditInfoSubject extends Subject<EditInfoSubject, EditInfo> {
- private static final SubjectFactory<EditInfoSubject, EditInfo> EDIT_INFO_SUBJECT_FACTORY =
- new SubjectFactory<EditInfoSubject, EditInfo>() {
- @Override
- public EditInfoSubject getSubject(FailureStrategy failureStrategy, EditInfo editInfo) {
- return new EditInfoSubject(failureStrategy, editInfo);
- }
- };
-
public static EditInfoSubject assertThat(EditInfo editInfo) {
- return assertAbout(EDIT_INFO_SUBJECT_FACTORY).that(editInfo);
+ return assertAbout(EditInfoSubject::new).that(editInfo);
}
public static OptionalSubject<EditInfoSubject, EditInfo> assertThat(
@@ -43,8 +34,8 @@
return OptionalSubject.assertThat(editInfoOptional, EditInfoSubject::assertThat);
}
- private EditInfoSubject(FailureStrategy failureStrategy, EditInfo editInfo) {
- super(failureStrategy, editInfo);
+ private EditInfoSubject(FailureMetadata failureMetadata, EditInfo editInfo) {
+ super(failureMetadata, editInfo);
}
public CommitInfoSubject commit() {
diff --git a/gerrit-test-util/src/main/java/com/google/gerrit/extensions/common/FileInfoSubject.java b/gerrit-test-util/src/main/java/com/google/gerrit/extensions/common/FileInfoSubject.java
index f8cdb34..ac92634 100644
--- a/gerrit-test-util/src/main/java/com/google/gerrit/extensions/common/FileInfoSubject.java
+++ b/gerrit-test-util/src/main/java/com/google/gerrit/extensions/common/FileInfoSubject.java
@@ -17,28 +17,19 @@
import static com.google.common.truth.Truth.assertAbout;
import com.google.common.truth.ComparableSubject;
-import com.google.common.truth.FailureStrategy;
+import com.google.common.truth.FailureMetadata;
import com.google.common.truth.IntegerSubject;
import com.google.common.truth.Subject;
-import com.google.common.truth.SubjectFactory;
import com.google.common.truth.Truth;
public class FileInfoSubject extends Subject<FileInfoSubject, FileInfo> {
- private static final SubjectFactory<FileInfoSubject, FileInfo> FILE_INFO_SUBJECT_FACTORY =
- new SubjectFactory<FileInfoSubject, FileInfo>() {
- @Override
- public FileInfoSubject getSubject(FailureStrategy failureStrategy, FileInfo fileInfo) {
- return new FileInfoSubject(failureStrategy, fileInfo);
- }
- };
-
public static FileInfoSubject assertThat(FileInfo fileInfo) {
- return assertAbout(FILE_INFO_SUBJECT_FACTORY).that(fileInfo);
+ return assertAbout(FileInfoSubject::new).that(fileInfo);
}
- private FileInfoSubject(FailureStrategy failureStrategy, FileInfo fileInfo) {
- super(failureStrategy, fileInfo);
+ private FileInfoSubject(FailureMetadata failureMetadata, FileInfo fileInfo) {
+ super(failureMetadata, fileInfo);
}
public IntegerSubject linesInserted() {
diff --git a/gerrit-test-util/src/main/java/com/google/gerrit/extensions/common/FixReplacementInfoSubject.java b/gerrit-test-util/src/main/java/com/google/gerrit/extensions/common/FixReplacementInfoSubject.java
index f798622..811cc47 100644
--- a/gerrit-test-util/src/main/java/com/google/gerrit/extensions/common/FixReplacementInfoSubject.java
+++ b/gerrit-test-util/src/main/java/com/google/gerrit/extensions/common/FixReplacementInfoSubject.java
@@ -16,33 +16,22 @@
import static com.google.common.truth.Truth.assertAbout;
-import com.google.common.truth.FailureStrategy;
+import com.google.common.truth.FailureMetadata;
import com.google.common.truth.StringSubject;
import com.google.common.truth.Subject;
-import com.google.common.truth.SubjectFactory;
import com.google.common.truth.Truth;
import com.google.gerrit.extensions.client.RangeSubject;
public class FixReplacementInfoSubject
extends Subject<FixReplacementInfoSubject, FixReplacementInfo> {
- private static final SubjectFactory<FixReplacementInfoSubject, FixReplacementInfo>
- FIX_REPLACEMENT_INFO_SUBJECT_FACTORY =
- new SubjectFactory<FixReplacementInfoSubject, FixReplacementInfo>() {
- @Override
- public FixReplacementInfoSubject getSubject(
- FailureStrategy failureStrategy, FixReplacementInfo fixReplacementInfo) {
- return new FixReplacementInfoSubject(failureStrategy, fixReplacementInfo);
- }
- };
-
public static FixReplacementInfoSubject assertThat(FixReplacementInfo fixReplacementInfo) {
- return assertAbout(FIX_REPLACEMENT_INFO_SUBJECT_FACTORY).that(fixReplacementInfo);
+ return assertAbout(FixReplacementInfoSubject::new).that(fixReplacementInfo);
}
private FixReplacementInfoSubject(
- FailureStrategy failureStrategy, FixReplacementInfo fixReplacementInfo) {
- super(failureStrategy, fixReplacementInfo);
+ FailureMetadata failureMetadata, FixReplacementInfo fixReplacementInfo) {
+ super(failureMetadata, fixReplacementInfo);
}
public StringSubject path() {
diff --git a/gerrit-test-util/src/main/java/com/google/gerrit/extensions/common/FixSuggestionInfoSubject.java b/gerrit-test-util/src/main/java/com/google/gerrit/extensions/common/FixSuggestionInfoSubject.java
index 9af4d1f..beaf6c1 100644
--- a/gerrit-test-util/src/main/java/com/google/gerrit/extensions/common/FixSuggestionInfoSubject.java
+++ b/gerrit-test-util/src/main/java/com/google/gerrit/extensions/common/FixSuggestionInfoSubject.java
@@ -16,32 +16,21 @@
import static com.google.common.truth.Truth.assertAbout;
-import com.google.common.truth.FailureStrategy;
+import com.google.common.truth.FailureMetadata;
import com.google.common.truth.StringSubject;
import com.google.common.truth.Subject;
-import com.google.common.truth.SubjectFactory;
import com.google.common.truth.Truth;
import com.google.gerrit.truth.ListSubject;
public class FixSuggestionInfoSubject extends Subject<FixSuggestionInfoSubject, FixSuggestionInfo> {
- private static final SubjectFactory<FixSuggestionInfoSubject, FixSuggestionInfo>
- FIX_SUGGESTION_INFO_SUBJECT_FACTORY =
- new SubjectFactory<FixSuggestionInfoSubject, FixSuggestionInfo>() {
- @Override
- public FixSuggestionInfoSubject getSubject(
- FailureStrategy failureStrategy, FixSuggestionInfo fixSuggestionInfo) {
- return new FixSuggestionInfoSubject(failureStrategy, fixSuggestionInfo);
- }
- };
-
public static FixSuggestionInfoSubject assertThat(FixSuggestionInfo fixSuggestionInfo) {
- return assertAbout(FIX_SUGGESTION_INFO_SUBJECT_FACTORY).that(fixSuggestionInfo);
+ return assertAbout(FixSuggestionInfoSubject::new).that(fixSuggestionInfo);
}
private FixSuggestionInfoSubject(
- FailureStrategy failureStrategy, FixSuggestionInfo fixSuggestionInfo) {
- super(failureStrategy, fixSuggestionInfo);
+ FailureMetadata failureMetadata, FixSuggestionInfo fixSuggestionInfo) {
+ super(failureMetadata, fixSuggestionInfo);
}
public StringSubject fixId() {
diff --git a/gerrit-test-util/src/main/java/com/google/gerrit/extensions/common/GitPersonSubject.java b/gerrit-test-util/src/main/java/com/google/gerrit/extensions/common/GitPersonSubject.java
index 9ef06dc..5f29bc9 100644
--- a/gerrit-test-util/src/main/java/com/google/gerrit/extensions/common/GitPersonSubject.java
+++ b/gerrit-test-util/src/main/java/com/google/gerrit/extensions/common/GitPersonSubject.java
@@ -17,28 +17,19 @@
import static com.google.common.truth.Truth.assertAbout;
import com.google.common.truth.ComparableSubject;
-import com.google.common.truth.FailureStrategy;
+import com.google.common.truth.FailureMetadata;
import com.google.common.truth.Subject;
-import com.google.common.truth.SubjectFactory;
import com.google.common.truth.Truth;
import java.sql.Timestamp;
public class GitPersonSubject extends Subject<GitPersonSubject, GitPerson> {
- private static final SubjectFactory<GitPersonSubject, GitPerson> GIT_PERSON_SUBJECT_FACTORY =
- new SubjectFactory<GitPersonSubject, GitPerson>() {
- @Override
- public GitPersonSubject getSubject(FailureStrategy failureStrategy, GitPerson gitPerson) {
- return new GitPersonSubject(failureStrategy, gitPerson);
- }
- };
-
public static GitPersonSubject assertThat(GitPerson gitPerson) {
- return assertAbout(GIT_PERSON_SUBJECT_FACTORY).that(gitPerson);
+ return assertAbout(GitPersonSubject::new).that(gitPerson);
}
- private GitPersonSubject(FailureStrategy failureStrategy, GitPerson gitPerson) {
- super(failureStrategy, gitPerson);
+ private GitPersonSubject(FailureMetadata failureMetadata, GitPerson gitPerson) {
+ super(failureMetadata, gitPerson);
}
public ComparableSubject<?, Timestamp> creationDate() {
diff --git a/gerrit-test-util/src/main/java/com/google/gerrit/extensions/common/PathSubject.java b/gerrit-test-util/src/main/java/com/google/gerrit/extensions/common/PathSubject.java
index 307c19e..aebeee2 100644
--- a/gerrit-test-util/src/main/java/com/google/gerrit/extensions/common/PathSubject.java
+++ b/gerrit-test-util/src/main/java/com/google/gerrit/extensions/common/PathSubject.java
@@ -16,25 +16,16 @@
import static com.google.common.truth.Truth.assertAbout;
-import com.google.common.truth.FailureStrategy;
+import com.google.common.truth.FailureMetadata;
import com.google.common.truth.Subject;
-import com.google.common.truth.SubjectFactory;
import java.nio.file.Path;
public class PathSubject extends Subject<PathSubject, Path> {
- private static final SubjectFactory<PathSubject, Path> PATH_SUBJECT_FACTORY =
- new SubjectFactory<PathSubject, Path>() {
- @Override
- public PathSubject getSubject(FailureStrategy failureStrategy, Path path) {
- return new PathSubject(failureStrategy, path);
- }
- };
-
- private PathSubject(FailureStrategy failureStrategy, Path path) {
- super(failureStrategy, path);
+ private PathSubject(FailureMetadata failureMetadata, Path path) {
+ super(failureMetadata, path);
}
public static PathSubject assertThat(Path path) {
- return assertAbout(PATH_SUBJECT_FACTORY).that(path);
+ return assertAbout(PathSubject::new).that(path);
}
}
diff --git a/gerrit-test-util/src/main/java/com/google/gerrit/extensions/common/RobotCommentInfoSubject.java b/gerrit-test-util/src/main/java/com/google/gerrit/extensions/common/RobotCommentInfoSubject.java
index afa1b9b..465d447 100644
--- a/gerrit-test-util/src/main/java/com/google/gerrit/extensions/common/RobotCommentInfoSubject.java
+++ b/gerrit-test-util/src/main/java/com/google/gerrit/extensions/common/RobotCommentInfoSubject.java
@@ -16,24 +16,13 @@
import static com.google.common.truth.Truth.assertAbout;
-import com.google.common.truth.FailureStrategy;
+import com.google.common.truth.FailureMetadata;
import com.google.common.truth.Subject;
-import com.google.common.truth.SubjectFactory;
import com.google.gerrit.truth.ListSubject;
import java.util.List;
public class RobotCommentInfoSubject extends Subject<RobotCommentInfoSubject, RobotCommentInfo> {
- private static final SubjectFactory<RobotCommentInfoSubject, RobotCommentInfo>
- ROBOT_COMMENT_INFO_SUBJECT_FACTORY =
- new SubjectFactory<RobotCommentInfoSubject, RobotCommentInfo>() {
- @Override
- public RobotCommentInfoSubject getSubject(
- FailureStrategy failureStrategy, RobotCommentInfo robotCommentInfo) {
- return new RobotCommentInfoSubject(failureStrategy, robotCommentInfo);
- }
- };
-
public static ListSubject<RobotCommentInfoSubject, RobotCommentInfo> assertThatList(
List<RobotCommentInfo> robotCommentInfos) {
return ListSubject.assertThat(robotCommentInfos, RobotCommentInfoSubject::assertThat)
@@ -41,12 +30,12 @@
}
public static RobotCommentInfoSubject assertThat(RobotCommentInfo robotCommentInfo) {
- return assertAbout(ROBOT_COMMENT_INFO_SUBJECT_FACTORY).that(robotCommentInfo);
+ return assertAbout(RobotCommentInfoSubject::new).that(robotCommentInfo);
}
private RobotCommentInfoSubject(
- FailureStrategy failureStrategy, RobotCommentInfo robotCommentInfo) {
- super(failureStrategy, robotCommentInfo);
+ FailureMetadata failureMetadata, RobotCommentInfo robotCommentInfo) {
+ super(failureMetadata, robotCommentInfo);
}
public ListSubject<FixSuggestionInfoSubject, FixSuggestionInfo> fixSuggestions() {
diff --git a/gerrit-test-util/src/main/java/com/google/gerrit/extensions/restapi/BinaryResultSubject.java b/gerrit-test-util/src/main/java/com/google/gerrit/extensions/restapi/BinaryResultSubject.java
index 30ac496..cf9df87 100644
--- a/gerrit-test-util/src/main/java/com/google/gerrit/extensions/restapi/BinaryResultSubject.java
+++ b/gerrit-test-util/src/main/java/com/google/gerrit/extensions/restapi/BinaryResultSubject.java
@@ -16,11 +16,10 @@
import static com.google.common.truth.Truth.assertAbout;
-import com.google.common.truth.FailureStrategy;
+import com.google.common.truth.FailureMetadata;
import com.google.common.truth.PrimitiveByteArraySubject;
import com.google.common.truth.StringSubject;
import com.google.common.truth.Subject;
-import com.google.common.truth.SubjectFactory;
import com.google.common.truth.Truth;
import com.google.gerrit.truth.OptionalSubject;
import java.io.ByteArrayOutputStream;
@@ -29,18 +28,8 @@
public class BinaryResultSubject extends Subject<BinaryResultSubject, BinaryResult> {
- private static final SubjectFactory<BinaryResultSubject, BinaryResult>
- BINARY_RESULT_SUBJECT_FACTORY =
- new SubjectFactory<BinaryResultSubject, BinaryResult>() {
- @Override
- public BinaryResultSubject getSubject(
- FailureStrategy failureStrategy, BinaryResult binaryResult) {
- return new BinaryResultSubject(failureStrategy, binaryResult);
- }
- };
-
public static BinaryResultSubject assertThat(BinaryResult binaryResult) {
- return assertAbout(BINARY_RESULT_SUBJECT_FACTORY).that(binaryResult);
+ return assertAbout(BinaryResultSubject::new).that(binaryResult);
}
public static OptionalSubject<BinaryResultSubject, BinaryResult> assertThat(
@@ -48,8 +37,8 @@
return OptionalSubject.assertThat(binaryResultOptional, BinaryResultSubject::assertThat);
}
- private BinaryResultSubject(FailureStrategy failureStrategy, BinaryResult binaryResult) {
- super(failureStrategy, binaryResult);
+ private BinaryResultSubject(FailureMetadata failureMetadata, BinaryResult binaryResult) {
+ super(failureMetadata, binaryResult);
}
public StringSubject asString() throws IOException {
diff --git a/gerrit-test-util/src/main/java/com/google/gerrit/truth/ListSubject.java b/gerrit-test-util/src/main/java/com/google/gerrit/truth/ListSubject.java
index e7f1074..bcd8dcf 100644
--- a/gerrit-test-util/src/main/java/com/google/gerrit/truth/ListSubject.java
+++ b/gerrit-test-util/src/main/java/com/google/gerrit/truth/ListSubject.java
@@ -17,10 +17,9 @@
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.truth.Truth.assertAbout;
-import com.google.common.truth.FailureStrategy;
+import com.google.common.truth.FailureMetadata;
import com.google.common.truth.IterableSubject;
import com.google.common.truth.Subject;
-import com.google.common.truth.SubjectFactory;
import java.util.List;
import java.util.function.Function;
@@ -38,8 +37,8 @@
}
private ListSubject(
- FailureStrategy failureStrategy, List<E> list, Function<E, S> elementAssertThatFunction) {
- super(failureStrategy, list);
+ FailureMetadata failureMetadata, List<E> list, Function<E, S> elementAssertThatFunction) {
+ super(failureMetadata, list);
this.elementAssertThatFunction = elementAssertThatFunction;
}
@@ -71,7 +70,7 @@
}
private static class ListSubjectFactory<S extends Subject<S, T>, T>
- extends SubjectFactory<IterableSubject, Iterable<?>> {
+ implements Subject.Factory<IterableSubject, Iterable<?>> {
private Function<T, S> elementAssertThatFunction;
@@ -81,10 +80,10 @@
@SuppressWarnings("unchecked")
@Override
- public ListSubject<S, T> getSubject(FailureStrategy failureStrategy, Iterable<?> objects) {
+ public ListSubject<S, T> createSubject(FailureMetadata failureMetadata, Iterable<?> objects) {
// The constructor of ListSubject only accepts lists.
// -> Casting is appropriate.
- return new ListSubject<>(failureStrategy, (List<T>) objects, elementAssertThatFunction);
+ return new ListSubject<>(failureMetadata, (List<T>) objects, elementAssertThatFunction);
}
}
}
diff --git a/gerrit-test-util/src/main/java/com/google/gerrit/truth/OptionalSubject.java b/gerrit-test-util/src/main/java/com/google/gerrit/truth/OptionalSubject.java
index 49e91a8..f24b5da 100644
--- a/gerrit-test-util/src/main/java/com/google/gerrit/truth/OptionalSubject.java
+++ b/gerrit-test-util/src/main/java/com/google/gerrit/truth/OptionalSubject.java
@@ -17,9 +17,8 @@
import static com.google.common.truth.Truth.assertAbout;
import com.google.common.truth.DefaultSubject;
-import com.google.common.truth.FailureStrategy;
+import com.google.common.truth.FailureMetadata;
import com.google.common.truth.Subject;
-import com.google.common.truth.SubjectFactory;
import com.google.common.truth.Truth;
import java.util.Optional;
import java.util.function.Function;
@@ -47,10 +46,10 @@
}
private OptionalSubject(
- FailureStrategy failureStrategy,
+ FailureMetadata failureMetadata,
Optional<T> optional,
Function<? super T, ? extends S> valueAssertThatFunction) {
- super(failureStrategy, optional);
+ super(failureMetadata, optional);
this.valueAssertThatFunction = valueAssertThatFunction;
}
@@ -82,7 +81,7 @@
}
private static class OptionalSubjectFactory<S extends Subject<S, ? super T>, T>
- extends SubjectFactory<OptionalSubject<S, T>, Optional<T>> {
+ implements Subject.Factory<OptionalSubject<S, T>, Optional<T>> {
private Function<? super T, ? extends S> valueAssertThatFunction;
@@ -91,8 +90,9 @@
}
@Override
- public OptionalSubject<S, T> getSubject(FailureStrategy failureStrategy, Optional<T> optional) {
- return new OptionalSubject<>(failureStrategy, optional, valueAssertThatFunction);
+ public OptionalSubject<S, T> createSubject(
+ FailureMetadata failureMetadata, Optional<T> optional) {
+ return new OptionalSubject<>(failureMetadata, optional, valueAssertThatFunction);
}
}
}
diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
index 9a02fcd..45270fa 100644
--- a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
+++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
@@ -50,6 +50,7 @@
import com.google.gerrit.server.events.StreamEventsApiListener;
import com.google.gerrit.server.git.GarbageCollectionModule;
import com.google.gerrit.server.git.GitRepositoryManagerModule;
+import com.google.gerrit.server.git.LocalMergeSuperSetComputation;
import com.google.gerrit.server.git.SearchingChangeCacheImpl;
import com.google.gerrit.server.git.WorkQueue;
import com.google.gerrit.server.git.receive.ReceiveCommitsExecutorModule;
@@ -65,6 +66,7 @@
import com.google.gerrit.server.plugins.PluginModule;
import com.google.gerrit.server.plugins.PluginRestApiModule;
import com.google.gerrit.server.project.DefaultPermissionBackendModule;
+import com.google.gerrit.server.project.DefaultProjectNameLockManager;
import com.google.gerrit.server.schema.DataSourceModule;
import com.google.gerrit.server.schema.DataSourceProvider;
import com.google.gerrit.server.schema.DataSourceType;
@@ -325,6 +327,7 @@
modules.add(cfgInjector.getInstance(MailReceiver.Module.class));
modules.add(new SmtpEmailSender.Module());
modules.add(new SignedTokenEmailTokenVerifier.Module());
+ modules.add(new LocalMergeSuperSetComputation.Module());
// Plugin module needs to be inserted *before* the index module.
// There is the concept of LifecycleModule, in Gerrit's own extension
@@ -368,6 +371,7 @@
modules.add(new ChangeCleanupRunner.Module());
modules.add(new AccountDeactivator.Module());
modules.addAll(LibModuleLoader.loadModules(cfgInjector));
+ modules.add(new DefaultProjectNameLockManager.Module());
return cfgInjector.createChildInjector(modules);
}
diff --git a/gerrit-war/src/main/resources/log4j.properties b/gerrit-war/src/main/resources/log4j.properties
index 8bc9bb2..28c0ee4 100644
--- a/gerrit-war/src/main/resources/log4j.properties
+++ b/gerrit-war/src/main/resources/log4j.properties
@@ -46,9 +46,5 @@
log4j.logger.com.mchange.v2.resourcepool=WARN
log4j.logger.com.mchange.v2.sql=WARN
-# Silence non-critical messages from Velocity
-#
-log4j.logger.velocity=WARN
-
# Silence non-critical messages from apache.http
log4j.logger.org.apache.http=WARN
diff --git a/lib/BUILD b/lib/BUILD
index 91334cb..62e0c2a 100644
--- a/lib/BUILD
+++ b/lib/BUILD
@@ -83,18 +83,6 @@
)
java_library(
- name = "velocity",
- data = ["//lib:LICENSE-Apache2.0"],
- visibility = ["//visibility:public"],
- exports = ["@velocity//jar"],
- runtime_deps = [
- "//lib/commons:collections",
- "//lib/commons:lang",
- "//lib/commons:oro",
- ],
-)
-
-java_library(
name = "jsch",
data = ["//lib:LICENSE-jsch"],
visibility = ["//visibility:public"],
diff --git a/lib/elasticsearch/BUILD b/lib/elasticsearch/BUILD
index c40925e..18c62af 100644
--- a/lib/elasticsearch/BUILD
+++ b/lib/elasticsearch/BUILD
@@ -8,13 +8,13 @@
":compress-lzf",
":hppc",
":jna",
+ ":joda-time",
":jsr166e",
":netty",
":t-digest",
"//lib/jackson:jackson-core",
"//lib/jackson:jackson-dataformat-cbor",
"//lib/jackson:jackson-dataformat-smile",
- "//lib/joda:joda-time",
"//lib/lucene:lucene-codecs",
"//lib/lucene:lucene-highlighter",
"//lib/lucene:lucene-join",
@@ -48,6 +48,19 @@
)
java_library(
+ name = "joda-time",
+ data = ["//lib:LICENSE-Apache2.0"],
+ exports = ["@joda_time//jar"],
+ runtime_deps = ["joda-convert"],
+)
+
+java_library(
+ name = "joda-convert",
+ data = ["//lib:LICENSE-Apache2.0"],
+ exports = ["@joda_convert//jar"],
+)
+
+java_library(
name = "compress-lzf",
data = ["//lib:LICENSE-Apache2.0"],
visibility = ["//lib/elasticsearch:__pkg__"],
diff --git a/lib/guava.bzl b/lib/guava.bzl
index 768b99e..b5df4ea 100644
--- a/lib/guava.bzl
+++ b/lib/guava.bzl
@@ -1,5 +1,5 @@
-GUAVA_VERSION = "22.0"
+GUAVA_VERSION = "23.0"
-GUAVA_BIN_SHA1 = "3564ef3803de51fb0530a8377ec6100b33b0d073"
+GUAVA_BIN_SHA1 = "c947004bb13d18182be60077ade044099e4f26f1"
GUAVA_DOC_URL = "https://google.github.io/guava/releases/" + GUAVA_VERSION + "/api/docs/"
diff --git a/lib/joda/BUILD b/lib/joda/BUILD
deleted file mode 100644
index e1a1924..0000000
--- a/lib/joda/BUILD
+++ /dev/null
@@ -1,13 +0,0 @@
-java_library(
- name = "joda-time",
- data = ["//lib:LICENSE-Apache2.0"],
- visibility = ["//visibility:public"],
- exports = ["@joda_time//jar"],
- runtime_deps = ["joda-convert"],
-)
-
-java_library(
- name = "joda-convert",
- data = ["//lib:LICENSE-Apache2.0"],
- exports = ["@joda_convert//jar"],
-)
diff --git a/plugins/replication b/plugins/replication
index 8bc97a1..26448d2 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit 8bc97a106a46a0f32350396e74769af11ec7b98e
+Subproject commit 26448d2bf3621fcf897df8db091c975b2b228a83
diff --git a/polygerrit-ui/app/elements/admin/gr-project-command/gr-project-command.html b/polygerrit-ui/app/elements/admin/gr-project-command/gr-project-command.html
new file mode 100644
index 0000000..6bf2211
--- /dev/null
+++ b/polygerrit-ui/app/elements/admin/gr-project-command/gr-project-command.html
@@ -0,0 +1,32 @@
+<!--
+Copyright (C) 2017 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../../styles/shared-styles.html">
+<link rel="import" href="../../shared/gr-button/gr-button.html">
+
+<dom-module id="gr-project-command">
+ <template>
+ <style include="shared-styles">
+ :host {
+ display: block;
+ margin-bottom: 2em;
+ }
+ </style>
+ <h3>[[title]]</h3>
+ <gr-button on-tap="_onCommandTap">[[title]]</gr-button>
+ </template>
+ <script src="gr-project-command.js"></script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/admin/gr-project-command/gr-project-command.js b/polygerrit-ui/app/elements/admin/gr-project-command/gr-project-command.js
new file mode 100644
index 0000000..48789b0
--- /dev/null
+++ b/polygerrit-ui/app/elements/admin/gr-project-command/gr-project-command.js
@@ -0,0 +1,34 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+(function() {
+ 'use strict';
+
+ Polymer({
+ is: 'gr-project-command',
+
+ properties: {
+ title: String,
+ },
+
+ /**
+ * Fired when command button is tapped.
+ *
+ * @event command-tap
+ */
+
+ _onCommandTap() {
+ this.dispatchEvent(new CustomEvent('command-tap', {bubbles: true}));
+ },
+ });
+})();
diff --git a/polygerrit-ui/app/elements/admin/gr-project-command/gr-project-command_test.html b/polygerrit-ui/app/elements/admin/gr-project-command/gr-project-command_test.html
new file mode 100644
index 0000000..8fae4f8
--- /dev/null
+++ b/polygerrit-ui/app/elements/admin/gr-project-command/gr-project-command_test.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<!--
+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-project-command</title>
+
+<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
+<link rel="import" href="gr-project-command.html">
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+ <template>
+ <gr-project-command></gr-project-command>
+ </template>
+</test-fixture>
+
+<script>
+ suite('gr-project-command tests', () => {
+ let element;
+
+ setup(() => {
+ element = fixture('basic');
+ });
+
+ test('dispatched command-tap on button tap', done => {
+ element.addEventListener('command-tap', () => {
+ done();
+ });
+ MockInteractions.tap(
+ Polymer.dom(element.root).querySelector('gr-button'));
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/elements/admin/gr-project-commands/gr-project-commands.html b/polygerrit-ui/app/elements/admin/gr-project-commands/gr-project-commands.html
index 6c0908a..f43403a 100644
--- a/polygerrit-ui/app/elements/admin/gr-project-commands/gr-project-commands.html
+++ b/polygerrit-ui/app/elements/admin/gr-project-commands/gr-project-commands.html
@@ -19,10 +19,12 @@
<link rel="import" href="../../../bower_components/iron-input/iron-input.html">
<link rel="import" href="../../../styles/gr-form-styles.html">
<link rel="import" href="../../../styles/shared-styles.html">
+<link rel="import" href="../../plugins/gr-endpoint-decorator/gr-endpoint-decorator.html">
<link rel="import" href="../../shared/gr-confirm-dialog/gr-confirm-dialog.html">
<link rel="import" href="../../shared/gr-overlay/gr-overlay.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../gr-create-change-dialog/gr-create-change-dialog.html">
+<link rel="import" href="../gr-project-command/gr-project-command.html">
<dom-module id="gr-project-commands">
<template>
@@ -47,24 +49,23 @@
<div id="loadedContent" class$="[[_computeLoadingClass(_loading)]]">
<h2 id="options">Command</h2>
<div id="form">
- <fieldset>
- <h3 id="createChange">Create Change</h3>
- <fieldset>
- <gr-button id="createNewChange" on-tap="_createNewChange">
- Create Change
- </gr-button>
- </fieldset>
- <h3 id="runGC" hidden$="[[!_projectConfig.actions.gc.enabled]]">
- Run GC
- </h3>
- <fieldset>
- <gr-button
- on-tap="_handleRunningGC"
- hidden$="[[!_projectConfig.actions.gc.enabled]]">
- Run GC
- </gr-button>
- </fieldset>
- </fieldset>
+ <gr-project-command
+ title="Create Change"
+ on-command-tap="_createNewChange">
+ </gr-project-command>
+
+ <gr-project-command
+ title="Run GC"
+ hidden$="[[!_projectConfig.actions.gc.enabled]]"
+ on-command-tap="_handleRunningGC">
+ </gr-project-command>
+
+ <gr-endpoint-decorator name="project-command">
+ <gr-endpoint-param name="config" value="[[_projectConfig]]">
+ </gr-endpoint-param>
+ <gr-endpoint-param name="projectName" value="[[project]]">
+ </gr-endpoint-param>
+ </gr-endpoint-decorator>
</div>
</div>
</main>
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.html b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.html
index a96b6b0..6c31a5b 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.html
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.html
@@ -19,6 +19,7 @@
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../change-list/gr-change-list/gr-change-list.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
+<link rel="import" href="../gr-user-header/gr-user-header.html">
<dom-module id="gr-dashboard-view">
<template>
@@ -34,6 +35,9 @@
gr-change-list {
width: 100%;
}
+ .hide {
+ display: none;
+ }
@media only screen and (max-width: 50em) {
.loading {
padding: 0 var(--default-horizontal-margin);
@@ -42,6 +46,9 @@
</style>
<div class="loading" hidden$="[[!_loading]]">Loading...</div>
<div hidden$="[[_loading]]" hidden>
+ <gr-user-header
+ user-id="[[params.user]]"
+ class$="[[_computeUserHeaderClass(params.user)]]"></gr-user-header>
<gr-change-list
show-reviewed-state
account="[[account]]"
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 6c5bad3..4d39a90 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
@@ -14,25 +14,39 @@
(function() {
'use strict';
+ // NOTE: These queries are tested in Java. Any changes made to definitions
+ // here require corresponding changes to:
+ // gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
const DEFAULT_SECTIONS = [
{
+ // WIP open changes owned by viewing user. This section is omitted when
+ // viewing other users, so we don't need to filter anything out.
name: 'Work in progress',
query: 'is:open owner:${user} is:wip',
selfOnly: true,
},
{
+ // Non-WIP open changes owned by viewed user. Filter out changes ignored
+ // by the viewing user.
name: 'Outgoing reviews',
- query: 'is:open owner:${user} -is:wip',
+ query: 'is:open owner:${user} -is:wip -is:ignored',
},
{
+ // Non-WIP open changes not owned by the viewed user, that the viewed user
+ // is associated with (as either a reviewer or the assignee). Changes
+ // ignored by the viewing user are filtered out.
name: 'Incoming reviews',
- query: 'is:open ((reviewer:${user} -owner:${user} -is:ignored) OR ' +
- 'assignee:${user}) -is:wip',
+ query: 'is:open -owner:${user} -is:wip -is:ignored ' +
+ '(reviewer:${user} OR assignee:${user})',
},
{
name: 'Recently closed',
- query: 'is:closed (owner:${user} OR reviewer:${user} OR ' +
- 'assignee:${user})',
+ // Closed changes where viewed user is owner, reviewer, or assignee.
+ // Changes ignored by the viewing user are filtered out, and so are WIP
+ // changes not owned by the viewing user (the one instance of
+ // 'owner:self' is intentional and implements this logic).
+ query: 'is:closed -is:ignored (-is:wip OR owner:self) ' +
+ '(owner:${user} OR reviewer:${user} OR assignee:${user})',
suffixForDashboard: '-age:4w limit:10',
},
];
@@ -53,6 +67,8 @@
},
/** @type {{ selectedChangeIndex: number }} */
viewState: Object,
+
+ /** @type {{ user: string }} */
params: {
type: Object,
},
@@ -73,7 +89,7 @@
},
observers: [
- '_userChanged(params.user)',
+ '_paramsChanged(params.*)',
],
behaviors: [
@@ -95,20 +111,28 @@
return 'Dashboard for ' + user;
},
- /**
- * Allows a refresh if menu item is selected again.
- */
- _userChanged(user) {
- if (!user) { return; }
+ _paramsChanged(paramsChangeRecord) {
+ const params = paramsChangeRecord.base;
+
+ if (!params.user && !params.sections) {
+ return;
+ }
+
+ const user = params.user || 'self';
+ const sections = (params.sections || DEFAULT_SECTIONS).filter(
+ section => (user === 'self' || !section.selfOnly));
+ const title = params.title || this._computeTitle(user);
// NOTE: This method may be called before attachment. Fire title-change
// in an async so that attachment to the DOM can take place first.
- this.async(
- () => this.fire('title-change', {title: this._computeTitle(user)}));
+ this.async(() => this.fire('title-change', {title}));
+
+ // Return if params indicate no longer in view.
+ if (!user && sections === DEFAULT_SECTIONS) {
+ return;
+ }
this._loading = true;
- const sections = this._sectionMetadata.filter(
- section => (user === 'self' || !section.selfOnly));
const queries =
sections.map(
section => this._dashboardQueryForSection(section, user));
@@ -136,5 +160,8 @@
return query.replace(/\$\{user\}/g, user);
},
+ _computeUserHeaderClass(userParam) {
+ return userParam === 'self' ? 'hide' : '';
+ },
});
})();
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html
index 2edf26f..40376ad 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view_test.html
@@ -64,17 +64,18 @@
});
test('viewing another user\'s dashboard omits selfOnly sections', () => {
- element._sectionMetadata = [
- {query: '1'},
- {query: '2', selfOnly: true},
- ];
-
- element.params = {user: 'self'};
+ element.params = {
+ sections: [
+ {query: '1'},
+ {query: '2', selfOnly: true},
+ ],
+ user: 'self',
+ };
flushAsynchronousOperations();
assert.isTrue(
getChangesStub.calledWith(null, ['1', '2'], null, element.options));
- element.params = {user: 'user'};
+ element.set('params.user', 'user');
flushAsynchronousOperations();
assert.isTrue(
getChangesStub.calledWith(null, ['1'], null, element.options));
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
index 3ac3a57..ec35d2d 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
@@ -14,12 +14,15 @@
limitations under the License.
-->
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+
<link rel="import" href="../../../behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html">
<link rel="import" href="../../../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html">
<link rel="import" href="../../../behaviors/rest-client-behavior/rest-client-behavior.html">
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
<link rel="import" href="../../diff/gr-diff-preferences/gr-diff-preferences.html">
+<link rel="import" href="../../plugins/gr-endpoint-decorator/gr-endpoint-decorator.html">
<link rel="import" href="../../shared/gr-account-link/gr-account-link.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../../shared/gr-change-star/gr-change-star.html">
@@ -29,17 +32,15 @@
<link rel="import" href="../../shared/gr-linked-text/gr-linked-text.html">
<link rel="import" href="../../shared/gr-overlay/gr-overlay.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
-
<link rel="import" href="../gr-change-actions/gr-change-actions.html">
<link rel="import" href="../gr-change-metadata/gr-change-metadata.html">
<link rel="import" href="../gr-commit-info/gr-commit-info.html">
<link rel="import" href="../gr-download-dialog/gr-download-dialog.html">
-<link rel="import" href="../gr-file-list/gr-file-list.html">
<link rel="import" href="../gr-file-list-header/gr-file-list-header.html">
+<link rel="import" href="../gr-file-list/gr-file-list.html">
<link rel="import" href="../gr-messages-list/gr-messages-list.html">
<link rel="import" href="../gr-related-changes-list/gr-related-changes-list.html">
<link rel="import" href="../gr-reply-dialog/gr-reply-dialog.html">
-<link rel="import" href="../../../styles/shared-styles.html">
<dom-module id="gr-change-view">
<template>
@@ -436,6 +437,7 @@
diff-view-mode="{{viewState.diffMode}}"
patch-num="{{_patchRange.patchNum}}"
base-patch-num="{{_patchRange.basePatchNum}}"
+ files-expanded="[[_filesExpanded]]"
revisions="[[_sortedRevisions]]"
on-open-diff-prefs="_handleOpenDiffPrefs"
on-open-download-dialog="_handleOpenDownloadDialog"
@@ -455,9 +457,12 @@
diff-view-mode="[[viewState.diffMode]]"
edit-loaded="[[_editLoaded]]"
num-files-shown="{{_numFilesShown}}"
+ files-expanded="{{_filesExpanded}}"
file-list-increment="{{_numFilesShown}}"
on-files-shown-changed="_setShownFiles"></gr-file-list>
</section>
+ <gr-endpoint-decorator name="change-view-integration">
+ </gr-endpoint-decorator>
<gr-messages-list id="messageList"
class="hideOnMobileOverlay"
change-num="[[_changeNum]]"
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 676e3de..542c041 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
@@ -148,6 +148,7 @@
// new patches. This is just the initial setting from the change view vs.
// an update coming from the two way data binding.
_patchNum: String,
+ _filesExpanded: String,
_basePatchNum: String,
_relatedChangesLoading: {
type: Boolean,
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-constants.html b/polygerrit-ui/app/elements/change/gr-file-list-constants.html
new file mode 100644
index 0000000..cfc129c
--- /dev/null
+++ b/polygerrit-ui/app/elements/change/gr-file-list-constants.html
@@ -0,0 +1,30 @@
+<!--
+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.
+-->
+<script>
+ (function(window) {
+ 'use strict';
+
+ const GrFileListConstants = window.GrFileListConstants || {};
+
+ GrFileListConstants.FilesExpandedState = {
+ ALL: 'all',
+ NONE: 'none',
+ SOME: 'some',
+ };
+
+ window.GrFileListConstants = GrFileListConstants;
+ })(window);
+</script>
\ No newline at end of file
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.html b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.html
index fd07d4e..ee22da86 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header.html
@@ -19,11 +19,13 @@
<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
<link rel="import" href="../../diff/gr-patch-range-select/gr-patch-range-select.html">
+<link rel="import" href="../../edit/gr-edit-controls/gr-edit-controls.html">
<link rel="import" href="../../shared/gr-editable-label/gr-editable-label.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../../shared/gr-select/gr-select.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
-
+<link rel="import" href="../../shared/gr-icons/gr-icons.html">
+<link rel="import" href="../gr-file-list-constants.html">
<dom-module id="gr-file-list-header">
<template>
@@ -42,7 +44,6 @@
}
.patchInfo-header {
background-color: #fafafa;
- border-bottom: 1px solid #ddd;
border-top: 1px solid #ddd;
display: flex;
min-height: 3.2em;
@@ -73,26 +74,12 @@
.mobile {
display: none;
}
- #diffPrefsContainer,
- .rightControls {
- align-self: flex-end;
- margin: auto 0 auto auto;
- }
- .showOnEdit {
- display: none;
- }
- .editLoaded .hideOnEdit {
- display: none;
- }
- .editLoaded .showOnEdit {
- display: initial;
- }
.patchInfo-header-wrapper .container {
align-items: center;
display: flex;
}
- #modeSelect {
- margin-left: .1em;
+ .downloadContainer {
+ margin-right: 1em;
}
.fileList-header {
align-items: center;
@@ -103,16 +90,37 @@
padding: 0 .25em;
}
.rightControls {
+ align-self: flex-end;
+ margin: auto 0 auto auto;
align-items: center;
display: flex;
flex-wrap: wrap;
font-weight: normal;
justify-content: flex-end;
}
+ #collapseBtn,
+ .expanded #expandBtn,
+ .fileViewActions{
+ display: none;
+ }
+ .expanded #expandBtn {
+ display: none;
+ }
+ gr-button.selected iron-icon {
+ color: var(--color-link);
+ }
+ .expanded #collapseBtn,
+ .openFile .fileViewActions {
+ align-items: center;
+ display: flex;
+ }
+ .fileViewActions > *:not(:last-child) {
+ margin-right: .6em;
+ }
.separator {
background-color: rgba(0, 0, 0, .3);
height: 1.5em;
- margin: 0 .6em;
+ margin: 0 1em;
width: 1px;
}
.separator.transparent {
@@ -124,9 +132,15 @@
.editLoaded .hideOnEdit {
display: none;
}
+ .showOnEdit {
+ display: none;
+ }
.editLoaded .showOnEdit {
display: initial;
}
+ .editLoaded .showOnEdit.flexContainer {
+ display: flex;
+ }
.label {
font-family: var(--font-family-bold);
margin-right: 1em;
@@ -160,12 +174,6 @@
<span class="separator"></span>
<a href$="[[changeUrl]]">Go to latest patch set</a>
</span>
- <span class="container downloadContainer desktop">
- <span class="separator"></span>
- <gr-button link
- class="download"
- on-tap="_handleDownloadTap">Download</gr-button>
- </span>
<span class="container descriptionContainer hideOnEdit">
<span class="separator"></span>
<gr-editable-label
@@ -178,45 +186,63 @@
on-changed="_handleDescriptionChanged"></gr-editable-label>
</span>
</div>
- <span id="diffPrefsContainer"
- class="hideOnEdit"
- hidden$="[[_computePrefsButtonHidden(diffPrefs, loggedIn)]]"
- hidden>
- <gr-button link
- class="prefsButton desktop"
- on-tap="_handlePrefsTap">Diff Preferences</gr-button>
- </span>
- </div>
- </div>
- <div class="fileList-header">
- <div class="rightControls">
- <template is="dom-if"
- if="[[_fileListActionsVisible(shownFileCount, _maxFilesForBulkActions)]]">
- <gr-button
- id="expandBtn"
- link
- on-tap="_expandAllDiffs">Show diffs</gr-button>
- <span class="separator"></span>
- <gr-button
- id="collapseBtn"
- link
- on-tap="_collapseAllDiffs">Hide diffs</gr-button>
- </template>
- <template is="dom-if"
- if="[[!_fileListActionsVisible(shownFileCount, _maxFilesForBulkActions)]]">
- <div class="warning">
- Bulk actions disabled because there are too many files.
+ <div class$="rightControls [[_computeExpandedClass(filesExpanded)]]">
+ <span class="showOnEdit flexContainer">
+ <gr-edit-controls id="editControls" change="[[change]]"></gr-edit-controls>
+ <span class="separator"></span>
+ </span>
+ <span class="downloadContainer desktop">
+ <gr-button link
+ class="download"
+ on-tap="_handleDownloadTap">Download</gr-button>
+ </span>
+ <template is="dom-if"
+ if="[[_fileListActionsVisible(shownFileCount, _maxFilesForBulkActions)]]">
+ <gr-button
+ id="expandBtn"
+ link
+ on-tap="_expandAllDiffs">Expand All</gr-button>
+ <gr-button
+ id="collapseBtn"
+ link
+ on-tap="_collapseAllDiffs">Collapse All</gr-button>
+ </template>
+ <template is="dom-if"
+ if="[[!_fileListActionsVisible(shownFileCount, _maxFilesForBulkActions)]]">
+ <div class="warning">
+ Bulk actions disabled because there are too many files.
+ </div>
+ </template>
+ <div class="fileViewActions">
+ <span class="separator"></span>
+ <span>Diff Views:</span>
+ <gr-button
+ id="sideBySideBtn"
+ link
+ has-tooltip
+ title="Side-by-side diff"
+ class$="[[_computeSelectedClass(diffViewMode, _VIEW_MODES.SIDE_BY_SIDE)]]"
+ on-tap="_handleSideBySideTap"><iron-icon icon="gr-icons:side-by-side"></iron-icon></gr-button>
+ <gr-button
+ id="unifiedBtn"
+ link
+ has-tooltip
+ title="Unified dff"
+ class$="[[_computeSelectedClass(diffViewMode, _VIEW_MODES.UNIFIED)]]"
+ on-tap="_handleUnifiedTap"><iron-icon icon="gr-icons:unified"></iron-icon></gr-button>
+ <span id="diffPrefsContainer"
+ class="hideOnEdit"
+ hidden$="[[_computePrefsButtonHidden(diffPrefs, loggedIn)]]"
+ hidden>
+ <gr-button
+ link
+ has-tooltip
+ title="Diff preferences"
+ class="prefsButton desktop"
+ on-tap="_handlePrefsTap"><iron-icon icon="gr-icons:settings"></iron-icon></gr-button>
+ </span>
</div>
- </template>
- <span class="separator"></span>
- <gr-select
- id="modeSelect"
- bind-value="{{diffViewMode}}">
- <select>
- <option value="SIDE_BY_SIDE">Side By Side</option>
- <option value="UNIFIED_DIFF">Unified</option>
- </select>
- </gr-select>
+ </div>
</div>
</div>
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
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 ace880a..36cfe06 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
@@ -40,6 +40,7 @@
},
patchNum: String,
basePatchNum: String,
+ filesExpanded: String,
revisions: Array,
// Caps the number of files that can be shown and have the 'show diffs' /
// 'hide diffs' buttons still be functional.
@@ -52,6 +53,15 @@
type: Boolean,
computed: '_computeDescriptionReadOnly(loggedIn, change, account)',
},
+ /** @type {?} */
+ _VIEW_MODES: {
+ type: Object,
+ readOnly: true,
+ value: {
+ SIDE_BY_SIDE: 'SIDE_BY_SIDE',
+ UNIFIED: 'UNIFIED_DIFF',
+ },
+ },
},
behaviors: [
@@ -59,13 +69,39 @@
],
_expandAllDiffs() {
+ this._expanded = true;
this.fire('expand-diffs');
},
_collapseAllDiffs() {
+ this._expanded = false;
this.fire('collapse-diffs');
},
+ _computeSelectedClass(diffViewMode, buttonViewMode) {
+ return buttonViewMode === diffViewMode ? 'selected' : '';
+ },
+
+ _computeExpandedClass(filesExpanded) {
+ const classes = [];
+ if (filesExpanded === GrFileListConstants.FilesExpandedState.ALL) {
+ classes.push('expanded');
+ }
+ if (filesExpanded === GrFileListConstants.FilesExpandedState.SOME ||
+ filesExpanded === GrFileListConstants.FilesExpandedState.ALL) {
+ classes.push('openFile');
+ }
+ return classes.join(' ');
+ },
+
+ _handleSideBySideTap() {
+ this.diffViewMode = this._VIEW_MODES.SIDE_BY_SIDE;
+ },
+
+ _handleUnifiedTap() {
+ this.diffViewMode = this._VIEW_MODES.UNIFIED;
+ },
+
_computeDescriptionPlaceholder(readOnly) {
return (readOnly ? 'No' : 'Add') + ' patchset description';
},
diff --git a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.html b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.html
index 4c079ed..3a2fd63 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list-header/gr-file-list-header_test.html
@@ -191,14 +191,51 @@
});
test('diff mode selector is set correctly', () => {
- const select = element.$.modeSelect;
+ const sideBySideBtn = element.$.sideBySideBtn;
+ const unifiedBtn = element.$.unifiedBtn;
element.diffViewMode = 'SIDE_BY_SIDE';
flushAsynchronousOperations();
- assert.equal(select.nativeSelect.value, 'SIDE_BY_SIDE');
-
+ assert.isTrue(sideBySideBtn.classList.contains('selected'));
+ assert.isFalse(unifiedBtn.classList.contains('selected'));
element.diffViewMode = 'UNIFIED_DIFF';
flushAsynchronousOperations();
- assert.equal(select.nativeSelect.value, 'UNIFIED_DIFF');
+ assert.isFalse(sideBySideBtn.classList.contains('selected'));
+ assert.isTrue(unifiedBtn.classList.contains('selected'));
+ });
+
+ test('fileViewActions are properly hidden', () => {
+ const actions = element.$$('.fileViewActions');
+ assert.equal(getComputedStyle(actions).display, 'none');
+ element.filesExpanded = GrFileListConstants.FilesExpandedState.SOME;
+ flushAsynchronousOperations();
+ assert.notEqual(getComputedStyle(actions).display, 'none');
+ element.filesExpanded = GrFileListConstants.FilesExpandedState.ALL;
+ flushAsynchronousOperations();
+ assert.notEqual(getComputedStyle(actions).display, 'none');
+ element.filesExpanded = GrFileListConstants.FilesExpandedState.NONE;
+ flushAsynchronousOperations();
+ assert.equal(getComputedStyle(actions).display, 'none');
+ });
+
+ test('expand/collapse buttons are toggled correctly', () => {
+ element.shownFileCount = 10;
+ flushAsynchronousOperations();
+ const expandBtn = element.$$('#expandBtn');
+ const collapseBtn = element.$$('#collapseBtn');
+ assert.notEqual(getComputedStyle(expandBtn).display, 'none');
+ assert.equal(getComputedStyle(collapseBtn).display, 'none');
+ element.filesExpanded = GrFileListConstants.FilesExpandedState.SOME;
+ flushAsynchronousOperations();
+ assert.notEqual(getComputedStyle(expandBtn).display, 'none');
+ assert.equal(getComputedStyle(collapseBtn).display, 'none');
+ element.filesExpanded = GrFileListConstants.FilesExpandedState.ALL;
+ flushAsynchronousOperations();
+ assert.equal(getComputedStyle(expandBtn).display, 'none');
+ assert.notEqual(getComputedStyle(collapseBtn).display, 'none');
+ element.filesExpanded = GrFileListConstants.FilesExpandedState.NONE;
+ flushAsynchronousOperations();
+ assert.notEqual(getComputedStyle(expandBtn).display, 'none');
+ assert.equal(getComputedStyle(collapseBtn).display, 'none');
});
test('navigateToChange called when range select changes', () => {
@@ -257,6 +294,16 @@
assert.isTrue(isVisible(element.$$('.descriptionContainer')));
assert.isTrue(isVisible(element.$.diffPrefsContainer));
});
+
+ test('edit-controls visibility', () => {
+ element.editLoaded = true;
+ flushAsynchronousOperations();
+ assert.isTrue(isVisible(element.$.editControls.parentElement));
+
+ element.editLoaded = false;
+ flushAsynchronousOperations();
+ assert.isFalse(isVisible(element.$.editControls.parentElement));
+ });
});
});
</script>
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
index 3cccbca..41d3037 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
@@ -18,18 +18,20 @@
<link rel="import" href="../../../behaviors/async-foreach-behavior/async-foreach-behavior.html">
<link rel="import" href="../../../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html">
<link rel="import" href="../../../behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html">
+<link rel="import" href="../../../styles/shared-styles.html">
<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
<link rel="import" href="../../core/gr-reporting/gr-reporting.html">
<link rel="import" href="../../diff/gr-comment-api/gr-comment-api.html">
<link rel="import" href="../../diff/gr-diff/gr-diff.html">
<link rel="import" href="../../diff/gr-diff-cursor/gr-diff-cursor.html">
+<link rel="import" href="../../edit/gr-edit-file-controls/gr-edit-file-controls.html">
<link rel="import" href="../../shared/gr-button/gr-button.html">
<link rel="import" href="../../shared/gr-cursor-manager/gr-cursor-manager.html">
<link rel="import" href="../../shared/gr-linked-text/gr-linked-text.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../../shared/gr-select/gr-select.html">
<link rel="import" href="../../shared/gr-tooltip-content/gr-tooltip-content.html">
-<link rel="import" href="../../../styles/shared-styles.html">
+<link rel="import" href="../gr-file-list-constants.html">
<dom-module id="gr-file-list">
<template>
@@ -50,6 +52,12 @@
:host(.editLoaded) .hideOnEdit {
display: none;
}
+ .showOnEdit {
+ display: none;
+ }
+ :host(.editLoaded) .showOnEdit {
+ display: initial;
+ }
.reviewed,
.status {
align-items: center;
@@ -181,6 +189,10 @@
display: initial;
opacity: 100;
}
+ .editFileControls {
+ margin-left: 1em;
+ width: 4em;
+ }
@media screen and (max-width: 50em) {
.desktop {
display: none;
@@ -296,6 +308,12 @@
<span class="markReviewed" title="Mark as reviewed (shortcut: r)">[[_computeReviewedText(file.isReviewed)]]</span>
</label>
</div>
+ <div class="editFileControls showOnEdit">
+ <gr-edit-file-controls
+ class$="[[_computeClass('', file.__path)]]"
+ file-path="[[file.__path]]"
+ on-edit-tap="_handleEditTap"></gr-edit-file-controls>
+ </div>
</div>
<template is="dom-if"
if="[[_isFileExpanded(file.__path, _expandedFilePaths.*)]]">
@@ -335,6 +353,7 @@
</div>
<!-- Empty div here exists to keep spacing in sync with file rows. -->
<div class="reviewed hideOnEdit" hidden$="[[!_loggedIn]]" hidden></div>
+ <div class="editFileControls showOnEdit"></div>
</div>
<div
class="row totalChanges"
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 b1a321e..8ed6759 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
@@ -61,6 +61,11 @@
type: Boolean,
observer: '_editLoadedChanged',
},
+ filesExpanded: {
+ type: String,
+ value: GrFileListConstants.FilesExpandedState.NONE,
+ notify: true,
+ },
_files: {
type: Array,
observer: '_filesChanged',
@@ -301,6 +306,8 @@
collapseAllDiffs() {
this._showInlineDiffs = false;
this._expandedFilePaths = [];
+ this.filesExpanded = this._computeExpandedFiles(
+ this._expandedFilePaths.length, this._files.length);
this.$.diffCursor.handleDiffUpdate();
},
@@ -426,12 +433,7 @@
_getFiles() {
return this.$.restAPI.getChangeFilesAsSpeciallySortedArray(
- this.changeNum, this.patchRange).then(files => {
- // Append UI-specific properties.
- return files.map(file => {
- return file;
- });
- });
+ this.changeNum, this.patchRange);
},
/**
@@ -815,6 +817,15 @@
detail.path);
},
+ _computeExpandedFiles(expandedCount, totalCount) {
+ if (expandedCount === 0) {
+ return GrFileListConstants.FilesExpandedState.NONE;
+ } else if (expandedCount === totalCount) {
+ return GrFileListConstants.FilesExpandedState.ALL;
+ }
+ return GrFileListConstants.FilesExpandedState.SOME;
+ },
+
/**
* Handle splices to the list of expanded file paths. If there are any new
* entries in the expanded list, then render each diff corresponding in
@@ -825,6 +836,9 @@
_expandedPathsChanged(record) {
if (!record) { return; }
+ this.filesExpanded = this._computeExpandedFiles(
+ this._expandedFilePaths.length, this._files.length);
+
// Find the paths introduced by the new index splices:
const newPaths = record.indexSplices
.map(splice => {
@@ -922,5 +936,10 @@
_computeReviewedText(isReviewed) {
return isReviewed ? 'MARK UNREVIEWED' : 'MARK REVIEWED';
},
+
+ _handleEditTap(e) {
+ const url = Gerrit.Nav.getEditUrlForDiff(this.change, e.detail.path);
+ Gerrit.Nav.navigateToRelativeUrl(url);
+ },
});
})();
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 2e91d80..6933b78 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
@@ -700,7 +700,6 @@
assert.isTrue(element._updateDiffPreferences.called);
});
-
test('expanded attribute not set on path when not expanded', () => {
element._files = [
{__path: '/COMMIT_MSG'},
@@ -791,6 +790,29 @@
element.push('_expandedFilePaths', path);
});
+ test('filesExpanded value updates to correct enum', () => {
+ element._files = [{__path: 'foo.bar'}, {__path: 'baz.bar'}];
+ flushAsynchronousOperations();
+ assert.equal(element.filesExpanded,
+ GrFileListConstants.FilesExpandedState.NONE);
+ element.push('_expandedFilePaths', 'baz.bar');
+ flushAsynchronousOperations();
+ assert.equal(element.filesExpanded,
+ GrFileListConstants.FilesExpandedState.SOME);
+ element.push('_expandedFilePaths', 'foo.bar');
+ flushAsynchronousOperations();
+ assert.equal(element.filesExpanded,
+ GrFileListConstants.FilesExpandedState.ALL);
+ element.collapseAllDiffs();
+ flushAsynchronousOperations();
+ assert.equal(element.filesExpanded,
+ GrFileListConstants.FilesExpandedState.NONE);
+ element.expandAllDiffs();
+ flushAsynchronousOperations();
+ assert.equal(element.filesExpanded,
+ GrFileListConstants.FilesExpandedState.ALL);
+ });
+
suite('_handleFileListTap', () => {
function testForModifier(modifier) {
const e = {preventDefault() {}};
@@ -1237,6 +1259,20 @@
});
});
});
+
+ test('editing actions', () => {
+ element.editLoaded = true;
+ element.change = {_number: '42', project: 'test'};
+ const navStub = sandbox.stub(Gerrit.Nav, 'navigateToRelativeUrl');
+ const editControls =
+ Polymer.dom(element.root).querySelectorAll('.row:not(.header)')
+ .map(row => row.querySelector('gr-edit-file-controls'));
+
+ // Commit message should not have edit controls.
+ assert.isTrue(editControls[0].classList.contains('invisible'));
+ MockInteractions.tap(editControls[1].$.edit);
+ assert.isTrue(navStub.called);
+ });
});
a11ySuite('basic');
</script>
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
index f27c7e9..593b932 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.html
@@ -56,6 +56,13 @@
padding: .5em 1.5em;
width: 100%;
}
+ .actions {
+ display: flex;
+ justify-content: space-between;
+ }
+ .actions gr-button {
+ margin-left: 1em;
+ }
.peopleContainer,
.labelsContainer {
flex-shrink: 0;
@@ -137,9 +144,6 @@
#savingLabel.saving {
display: inline;
}
- #cancelButton {
- float: right;
- }
@media screen and (max-width: 50em) {
:host {
max-height: none;
@@ -261,33 +265,40 @@
Saving comments...
</span>
</section>
- <section>
- <gr-button
- primary
- disabled="[[_computeSendButtonDisabled(knownLatestState, _sendButtonLabel, diffDrafts, draft, _reviewersMutated, _labelsChanged, _includeComments)]]"
- class="action send"
- on-tap="_sendTapHandler">[[_sendButtonLabel]]</gr-button>
- <template is="dom-if" if="[[canBeStarted]]">
+ <section class="actions">
+ <div class="left">
+ <span
+ id="checkingStatusLabel"
+ hidden$="[[!_isState(knownLatestState, 'checking')]]">
+ Checking whether patch [[patchNum]] is latest...
+ </span>
+ <span
+ id="notLatestLabel"
+ hidden$="[[!_isState(knownLatestState, 'not-latest')]]">
+ Patch [[patchNum]] is not latest.
+ <gr-button link on-tap="_reload">Reload</gr-button>
+ </span>
+ </div>
+ <div class="right">
<gr-button
- disabled="[[_isState(knownLatestState, 'not-latest')]]"
- class="action save"
- on-tap="_saveTapHandler">Save</gr-button>
- </template>
- <span
- id="checkingStatusLabel"
- hidden$="[[!_isState(knownLatestState, 'checking')]]">
- Checking whether patch [[patchNum]] is latest...
- </span>
- <span
- id="notLatestLabel"
- hidden$="[[!_isState(knownLatestState, 'not-latest')]]">
- Patch [[patchNum]] is not latest.
- <gr-button link on-tap="_reload">Reload</gr-button>
- </span>
- <gr-button
- id="cancelButton"
- class="action cancel"
- on-tap="_cancelTapHandler">Cancel</gr-button>
+ link
+ id="cancelButton"
+ class="action cancel"
+ on-tap="_cancelTapHandler">Cancel</gr-button>
+ <gr-button
+ link
+ primary
+ disabled="[[_computeSendButtonDisabled(knownLatestState, _sendButtonLabel, diffDrafts, draft, _reviewersMutated, _labelsChanged, _includeComments)]]"
+ class="action send"
+ on-tap="_sendTapHandler">[[_sendButtonLabel]]</gr-button>
+ <template is="dom-if" if="[[canBeStarted]]">
+ <gr-button
+ link
+ disabled="[[_isState(knownLatestState, 'not-latest')]]"
+ class="action save"
+ on-tap="_saveTapHandler">Save</gr-button>
+ </template>
+ </div>
</section>
</div>
<gr-js-api-interface id="jsAPI"></gr-js-api-interface>
diff --git a/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.html b/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.html
index 85db3a1..0fb9df2 100644
--- a/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.html
+++ b/polygerrit-ui/app/elements/core/gr-account-dropdown/gr-account-dropdown.html
@@ -24,11 +24,8 @@
<dom-module id="gr-account-dropdown">
<template>
<style include="shared-styles">
- button {
- background: none;
- border: none;
- font: inherit;
- padding: .3em 0;
+ gr-dropdown {
+ padding: .5em;
}
gr-avatar {
height: 2em;
diff --git a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.html b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.html
index 9f5d39d..5a83a89 100644
--- a/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.html
+++ b/polygerrit-ui/app/elements/core/gr-main-header/gr-main-header.html
@@ -57,6 +57,7 @@
}
ul {
list-style: none;
+ padding-left: 1em;
}
.links > li {
cursor: default;
@@ -89,10 +90,10 @@
max-width: 500px;
}
gr-dropdown {
- padding: 0.5em;
+ padding: 1em .5em;
}
.browse {
- padding: 1em;
+ margin: .5em;
text-decoration: none;
}
.accountContainer:not(.loggedIn):not(.loggedOut) .loginButton,
@@ -104,7 +105,7 @@
.accountContainer {
align-items: center;
display: flex;
- margin: 0 -0.5em 0 0.5em;
+ margin: 0 -.5em 0 .5em;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
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 b03f2e5..8453da9 100644
--- a/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html
+++ b/polygerrit-ui/app/elements/core/gr-navigation/gr-navigation.html
@@ -57,6 +57,7 @@
console.warn('Use of uninitialized routing');
};
+ const EDIT_PATCHNUM = 'edit';
const PARENT_PATCHNUM = 'PARENT';
window.Gerrit.Nav = {
@@ -277,6 +278,7 @@
changeNum,
project,
path,
+ patchNum: EDIT_PATCHNUM,
});
},
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 5b9f4f6..89ca50f 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.js
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
@@ -87,8 +87,7 @@
PLUGIN_LIST_FILTER: '/admin/plugins/q/filter::filter',
PLUGIN_LIST_FILTER_OFFSET: '/admin/plugins/q/filter::filter,:offset',
- QUERY: '/q/:query',
- QUERY_OFFSET: '/q/:query,:offset',
+ QUERY: /^\/q\/([^,]+)(,(\d+))?$/,
// Matches /c/<changeNum>/[<basePatchNum>..][<patchNum>][/].
CHANGE_LEGACY: /^\/c\/(\d+)\/?(((-?\d+|edit)(\.\.(\d+|edit))?))?\/?$/,
@@ -126,6 +125,16 @@
*/
const LINE_ADDRESS_PATTERN = /^([ab]?)(\d+)$/;
+ /**
+ * Pattern to recognize '+' in url-encoded strings for replacement with ' '.
+ */
+ const PLUS_PATTERN = /\+/g;
+
+ /**
+ * Pattern to recognize leading '?' in window.location.search, for stripping.
+ */
+ const QUESTION_PATTERN = /^\?*/;
+
// Polymer makes `app` intrinsically defined on the window by virtue of the
// custom element having the id "app", but it is made explicit here.
const app = document.querySelector('#app');
@@ -178,70 +187,23 @@
page.redirect(url);
},
+ /**
+ * @param {!Object} params
+ * @return {string}
+ */
_generateUrl(params) {
const base = this.getBaseUrl();
let url = '';
+ const Views = Gerrit.Nav.View;
- if (params.view === Gerrit.Nav.View.SEARCH) {
- const operators = [];
- if (params.owner) {
- operators.push('owner:' + this.encodeURL(params.owner, false));
- }
- if (params.project) {
- operators.push('project:' + this.encodeURL(params.project, false));
- }
- if (params.branch) {
- operators.push('branch:' + this.encodeURL(params.branch, false));
- }
- if (params.topic) {
- operators.push('topic:"' + this.encodeURL(params.topic, false) + '"');
- }
- if (params.hashtag) {
- operators.push('hashtag:"' +
- this.encodeURL(params.hashtag.toLowerCase(), false) + '"');
- }
- if (params.statuses) {
- if (params.statuses.length === 1) {
- operators.push(
- 'status:' + this.encodeURL(params.statuses[0], false));
- } else if (params.statuses.length > 1) {
- operators.push(
- '(' +
- params.statuses.map(s => `status:${this.encodeURL(s, false)}`)
- .join(' OR ') +
- ')');
- }
- }
- url = '/q/' + operators.join('+');
- } else if (params.view === Gerrit.Nav.View.CHANGE) {
- let range = this._getPatchRangeExpression(params);
- if (range.length) { range = '/' + range; }
- if (params.project) {
- url = `/c/${params.project}/+/${params.changeNum}${range}`;
- } else {
- url = `/c/${params.changeNum}${range}`;
- }
- } else if (params.view === Gerrit.Nav.View.DASHBOARD) {
- url = `/dashboard/${params.user || 'self'}`;
- } else if (params.view === Gerrit.Nav.View.DIFF) {
- let range = this._getPatchRangeExpression(params);
- if (range.length) { range = '/' + range; }
-
- let suffix = `${range}/${this.encodeURL(params.path, true)}`;
- if (params.lineNum) {
- suffix += '#';
- if (params.leftSide) { suffix += 'b'; }
- suffix += params.lineNum;
- }
-
- if (params.project) {
- url = `/c/${params.project}/+/${params.changeNum}${suffix}`;
- } else {
- url = `/c/${params.changeNum}${suffix}`;
- }
- if (params.edit) {
- url += ',edit';
- }
+ if (params.view === Views.SEARCH) {
+ url = this._generateSearchUrl(params);
+ } else if (params.view === Views.CHANGE) {
+ url = this._generateChangeUrl(params);
+ } else if (params.view === Views.DASHBOARD) {
+ url = this._generateDashboardUrl(params);
+ } else if (params.view === Views.DIFF || params.view === Views.EDIT) {
+ url = this._generateDiffOrEditUrl(params);
} else {
throw new Error('Can\'t generate');
}
@@ -250,6 +212,108 @@
},
/**
+ * @param {!Object} params
+ * @return {string}
+ */
+ _generateSearchUrl(params) {
+ const operators = [];
+ if (params.owner) {
+ operators.push('owner:' + this.encodeURL(params.owner, false));
+ }
+ if (params.project) {
+ operators.push('project:' + this.encodeURL(params.project, false));
+ }
+ if (params.branch) {
+ operators.push('branch:' + this.encodeURL(params.branch, false));
+ }
+ if (params.topic) {
+ operators.push('topic:"' + this.encodeURL(params.topic, false) + '"');
+ }
+ if (params.hashtag) {
+ operators.push('hashtag:"' +
+ this.encodeURL(params.hashtag.toLowerCase(), false) + '"');
+ }
+ if (params.statuses) {
+ if (params.statuses.length === 1) {
+ operators.push(
+ 'status:' + this.encodeURL(params.statuses[0], false));
+ } else if (params.statuses.length > 1) {
+ operators.push(
+ '(' +
+ params.statuses.map(s => `status:${this.encodeURL(s, false)}`)
+ .join(' OR ') +
+ ')');
+ }
+ }
+ return '/q/' + operators.join('+');
+ },
+
+ /**
+ * @param {!Object} params
+ * @return {string}
+ */
+ _generateChangeUrl(params) {
+ let range = this._getPatchRangeExpression(params);
+ if (range.length) { range = '/' + range; }
+ let suffix = `${range}`;
+ if (params.querystring) {
+ suffix += '?' + params.querystring;
+ }
+ if (params.project) {
+ return `/c/${params.project}/+/${params.changeNum}${suffix}`;
+ } else {
+ return `/c/${params.changeNum}${suffix}`;
+ }
+ },
+
+ /**
+ * @param {!Object} params
+ * @return {string}
+ */
+ _generateDashboardUrl(params) {
+ if (params.sections) {
+ // Custom dashboard.
+ const queryParams = params.sections.map(section => {
+ return encodeURIComponent(section.name) + '=' +
+ encodeURIComponent(section.query);
+ });
+ if (params.title) {
+ queryParams.push('title=' + encodeURIComponent(params.title));
+ }
+ const user = params.user ? params.user : '';
+ return `/dashboard/${user}?${queryParams.join('&')}`;
+ } else {
+ // User dashboard.
+ return `/dashboard/${params.user || 'self'}`;
+ }
+ },
+
+ /**
+ * @param {!Object} params
+ * @return {string}
+ */
+ _generateDiffOrEditUrl(params) {
+ let range = this._getPatchRangeExpression(params);
+ if (range.length) { range = '/' + range; }
+
+ let suffix = `${range}/${this.encodeURL(params.path, true)}`;
+
+ if (params.view === Gerrit.Nav.View.EDIT) { suffix += ',edit'; }
+
+ if (params.lineNum) {
+ suffix += '#';
+ if (params.leftSide) { suffix += 'b'; }
+ suffix += params.lineNum;
+ }
+
+ if (params.project) {
+ return `/c/${params.project}/+/${params.changeNum}${suffix}`;
+ } else {
+ return `/c/${params.changeNum}${suffix}`;
+ }
+ },
+
+ /**
* Given an object of parameters, potentially including a `patchNum` or a
* `basePatchNum` or both, return a string representation of that range. If
* no range is indicated in the params, the empty string is returned.
@@ -517,7 +581,6 @@
this._mapRoute(RoutePattern.ADMIN_PLACEHOLDER,
'_handleAdminPlaceholderRoute', true);
- this._mapRoute(RoutePattern.QUERY_OFFSET, '_handleQueryRoute');
this._mapRoute(RoutePattern.QUERY, '_handleQueryRoute');
this._mapRoute(RoutePattern.CHANGE_NUMBER_LEGACY,
@@ -593,19 +656,101 @@
});
},
- _handleDashboardRoute(data) {
- if (!data.params[0]) {
- this._redirect('/dashboard/self');
- return;
+ /**
+ * Decode an application/x-www-form-urlencoded string.
+ *
+ * @param {string} qs The application/x-www-form-urlencoded string.
+ * @return {string} The decoded string.
+ */
+ _decodeQueryString(qs) {
+ return decodeURIComponent(qs.replace(PLUS_PATTERN, ' '));
+ },
+
+ /**
+ * Parse a query string (e.g. window.location.search) into an array of
+ * name/value pairs.
+ *
+ * @param {string} qs The application/x-www-form-urlencoded query string.
+ * @return {!Array<!Array<string>>} An array of name/value pairs, where each
+ * element is a 2-element array.
+ */
+ _parseQueryString(qs) {
+ qs = qs.replace(QUESTION_PATTERN, '');
+ if (!qs) {
+ return [];
+ }
+ const params = [];
+ qs.split('&').forEach(param => {
+ const idx = param.indexOf('=');
+ let name;
+ let value;
+ if (idx < 0) {
+ name = this._decodeQueryString(param);
+ value = '';
+ } else {
+ name = this._decodeQueryString(param.substring(0, idx));
+ value = this._decodeQueryString(param.substring(idx + 1));
+ }
+ if (name) {
+ params.push([name, value]);
+ }
+ });
+ return params;
+ },
+
+ /**
+ * Handle dashboard routes. These may be user, custom, or project
+ * dashboards.
+ *
+ * @param {!Object} data The parsed route data.
+ * @param {string=} opt_qs Optional query string associated with the route.
+ * If not given, window.location.search is used. (Used by tests).
+ */
+ _handleDashboardRoute(data, opt_qs) {
+ // opt_qs may be provided by a test, and it may have a falsy value
+ const qs = opt_qs !== undefined ? opt_qs : window.location.search;
+ const queryParams = this._parseQueryString(qs);
+ let title = 'Custom Dashboard';
+ const titleParam = queryParams.find(
+ elem => elem[0].toLowerCase() === 'title');
+ if (titleParam) {
+ title = titleParam[1];
+ }
+ const sectionParams = queryParams.filter(
+ elem => elem[0] && elem[1] && elem[0].toLowerCase() !== 'title');
+ const sections = sectionParams.map(elem => {
+ return {
+ name: elem[0],
+ query: elem[1],
+ };
+ });
+
+ if (sections.length > 0) {
+ // Custom dashboard view.
+ this._setParams({
+ view: Gerrit.Nav.View.DASHBOARD,
+ user: data.params[0] || 'self',
+ sections,
+ title,
+ });
+ return Promise.resolve();
}
+ if (!data.params[0] && sections.length === 0) {
+ // Redirect /dashboard/ -> /dashboard/self.
+ this._redirect('/dashboard/self');
+ return Promise.resolve();
+ }
+
+ // User dashboard. We require viewing user to be logged in, else we
+ // redirect to login for self dashboard or simple owner search for
+ // other user dashboard.
return this.$.restAPI.getLoggedIn().then(loggedIn => {
if (!loggedIn) {
if (data.params[0].toLowerCase() === 'self') {
this._redirectToLogin(data.canonicalPath);
} else {
- // TODO: encode user or use _generateUrl.
- this._redirect('/q/owner:' + data.params[0]);
+ this._redirect('/q/owner:' + encodeURIComponent(data.params[0]));
}
} else {
this._setParams({
@@ -842,8 +987,11 @@
},
_handleQueryRoute(data) {
- data.params.view = Gerrit.Nav.View.SEARCH;
- this._setParams(data.params);
+ this._setParams({
+ view: Gerrit.Nav.View.SEARCH,
+ query: data.params[0],
+ offset: data.params[2],
+ });
},
_handleChangeNumberLegacyRoute(ctx) {
@@ -881,6 +1029,7 @@
basePatchNum: ctx.params[3],
patchNum: ctx.params[5],
view: Gerrit.Nav.View.CHANGE,
+ querystring: ctx.querystring,
};
this._normalizeLegacyRouteParams(params);
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
index ca00e19..b4fec5b 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router_test.html
@@ -237,13 +237,28 @@
changeNum: '1234',
project: 'test',
};
+ const paramsWithQuery = {
+ view: Gerrit.Nav.View.CHANGE,
+ changeNum: '1234',
+ project: 'test',
+ querystring: 'revert&foo=bar',
+ };
+
assert.equal(element._generateUrl(params), '/c/test/+/1234');
+ assert.equal(element._generateUrl(paramsWithQuery),
+ '/c/test/+/1234?revert&foo=bar');
params.patchNum = 10;
assert.equal(element._generateUrl(params), '/c/test/+/1234/10');
+ paramsWithQuery.patchNum = 10;
+ assert.equal(element._generateUrl(paramsWithQuery),
+ '/c/test/+/1234/10?revert&foo=bar');
params.basePatchNum = 5;
assert.equal(element._generateUrl(params), '/c/test/+/1234/5..10');
+ paramsWithQuery.basePatchNum = 5;
+ assert.equal(element._generateUrl(paramsWithQuery),
+ '/c/test/+/1234/5..10?revert&foo=bar');
});
test('diff', () => {
@@ -280,6 +295,17 @@
'/c/test/+/42/2/file.cpp#b123');
});
+ test('edit', () => {
+ const params = {
+ view: Gerrit.Nav.View.EDIT,
+ changeNum: '42',
+ project: 'test',
+ path: 'x+y/path.cpp',
+ };
+ assert.equal(element._generateUrl(params),
+ '/c/test/+/42/x%252By/path.cpp,edit');
+ });
+
test('_getPatchRangeExpression', () => {
const params = {};
let actual = element._getPatchRangeExpression(params);
@@ -297,6 +323,48 @@
actual = element._getPatchRangeExpression(params);
assert.equal(actual, '2..');
});
+
+ suite('dashboard', () => {
+ test('self dashboard', () => {
+ const params = {
+ view: Gerrit.Nav.View.DASHBOARD,
+ };
+ assert.equal(element._generateUrl(params), '/dashboard/self');
+ });
+
+ test('user dashboard', () => {
+ const params = {
+ view: Gerrit.Nav.View.DASHBOARD,
+ user: 'user',
+ };
+ assert.equal(element._generateUrl(params), '/dashboard/user');
+ });
+
+ test('custom self dashboard, no title', () => {
+ const params = {
+ view: Gerrit.Nav.View.DASHBOARD,
+ sections: [
+ {name: 'section 1', query: 'query 1'},
+ {name: 'section 2', query: 'query 2'},
+ ],
+ };
+ assert.equal(
+ element._generateUrl(params),
+ '/dashboard/?section%201=query%201§ion%202=query%202');
+ });
+
+ test('custom user dashboard, with title', () => {
+ const params = {
+ view: Gerrit.Nav.View.DASHBOARD,
+ user: 'user',
+ sections: [{name: 'name', query: 'query'}],
+ title: 'custom dashboard',
+ };
+ assert.equal(
+ element._generateUrl(params),
+ '/dashboard/user?name=query&title=custom%20dashboard');
+ });
+ });
});
suite('param normalization', () => {
@@ -478,6 +546,22 @@
'/c/test/+/42#foo');
});
+ test('_handleQueryRoute', () => {
+ const data = {params: ['project:foo/bar/baz']};
+ assertDataToParams(data, '_handleQueryRoute', {
+ view: Gerrit.Nav.View.SEARCH,
+ query: 'project:foo/bar/baz',
+ offset: undefined,
+ });
+
+ data.params.push(',123', '123');
+ assertDataToParams(data, '_handleQueryRoute', {
+ view: Gerrit.Nav.View.SEARCH,
+ query: 'project:foo/bar/baz',
+ offset: '123',
+ });
+ });
+
suite('_handleRegisterRoute', () => {
test('happy path', () => {
const ctx = {params: ['/foo/bar']};
@@ -514,7 +598,7 @@
assert.isFalse(redirectStub.called);
});
- test('redirects to dahsboard if logged in', () => {
+ test('redirects to dashboard if logged in', () => {
sandbox.stub(element.$.restAPI, 'getLoggedIn')
.returns(Promise.resolve(true));
const data = {
@@ -625,35 +709,31 @@
});
test('no user specified', () => {
- const data = {canonicalPath: '/dashboard', params: {}};
- const result = element._handleDashboardRoute(data);
- assert.isNotOk(result);
- assert.isFalse(setParamsStub.called);
- assert.isFalse(redirectToLoginStub.called);
- assert.isTrue(redirectStub.called);
- assert.equal(redirectStub.lastCall.args[0], '/dashboard/self');
+ const data = {canonicalPath: '/dashboard/', params: {0: ''}};
+ return element._handleDashboardRoute(data, '').then(() => {
+ assert.isFalse(setParamsStub.called);
+ assert.isFalse(redirectToLoginStub.called);
+ assert.isTrue(redirectStub.called);
+ assert.equal(redirectStub.lastCall.args[0], '/dashboard/self');
+ });
});
- test('own dahsboard but signed out redirects to login', () => {
+ test('own dashboard but signed out redirects to login', () => {
sandbox.stub(element.$.restAPI, 'getLoggedIn')
.returns(Promise.resolve(false));
- const data = {canonicalPath: '/dashboard', params: {0: 'seLF'}};
- const result = element._handleDashboardRoute(data);
- assert.isOk(result);
- return result.then(() => {
+ const data = {canonicalPath: '/dashboard/', params: {0: 'seLF'}};
+ return element._handleDashboardRoute(data, '').then(() => {
assert.isTrue(redirectToLoginStub.calledOnce);
assert.isFalse(redirectStub.called);
assert.isFalse(setParamsStub.called);
});
});
- test('non-self dahsboard but signed out does not redirect', () => {
+ test('non-self dashboard but signed out does not redirect', () => {
sandbox.stub(element.$.restAPI, 'getLoggedIn')
.returns(Promise.resolve(false));
- const data = {canonicalPath: '/dashboard', params: {0: 'foo'}};
- const result = element._handleDashboardRoute(data);
- assert.isOk(result);
- return result.then(() => {
+ const data = {canonicalPath: '/dashboard/', params: {0: 'foo'}};
+ return element._handleDashboardRoute(data, '').then(() => {
assert.isFalse(redirectToLoginStub.called);
assert.isFalse(setParamsStub.called);
assert.isTrue(redirectStub.calledOnce);
@@ -661,13 +741,11 @@
});
});
- test('dahsboard while signed in sets params', () => {
+ test('dashboard while signed in sets params', () => {
sandbox.stub(element.$.restAPI, 'getLoggedIn')
.returns(Promise.resolve(true));
- const data = {canonicalPath: '/dashboard', params: {0: 'foo'}};
- const result = element._handleDashboardRoute(data);
- assert.isOk(result);
- return result.then(() => {
+ const data = {canonicalPath: '/dashboard/', params: {0: 'foo'}};
+ return element._handleDashboardRoute(data, '').then(() => {
assert.isFalse(redirectToLoginStub.called);
assert.isFalse(redirectStub.called);
assert.isTrue(setParamsStub.calledOnce);
@@ -677,6 +755,42 @@
});
});
});
+
+ test('custom dashboard without title', () => {
+ const data = {canonicalPath: '/dashboard/', params: {0: ''}};
+ return element._handleDashboardRoute(data, '?a=b&c&d=e').then(() => {
+ assert.isFalse(redirectToLoginStub.called);
+ assert.isFalse(redirectStub.called);
+ assert.isTrue(setParamsStub.calledOnce);
+ assert.deepEqual(setParamsStub.lastCall.args[0], {
+ view: Gerrit.Nav.View.DASHBOARD,
+ user: 'self',
+ sections: [
+ {name: 'a', query: 'b'},
+ {name: 'd', query: 'e'},
+ ],
+ title: 'Custom Dashboard',
+ });
+ });
+ });
+
+ test('custom dashboard with title', () => {
+ const data = {canonicalPath: '/dashboard/', params: {0: ''}};
+ return element._handleDashboardRoute(data, '?a=b&c&d=&=e&title=t')
+ .then(() => {
+ assert.isFalse(redirectToLoginStub.called);
+ assert.isFalse(redirectStub.called);
+ assert.isTrue(setParamsStub.calledOnce);
+ assert.deepEqual(setParamsStub.lastCall.args[0], {
+ view: Gerrit.Nav.View.DASHBOARD,
+ user: 'self',
+ sections: [
+ {name: 'a', query: 'b'},
+ ],
+ title: 't',
+ });
+ });
+ });
});
suite('group routes', () => {
@@ -1020,6 +1134,7 @@
null, // 4 Unused
9, // 5 Patch number
],
+ querystring: '',
};
element._handleChangeLegacyRoute(ctx);
assert.isTrue(normalizeRouteStub.calledOnce);
@@ -1028,6 +1143,7 @@
basePatchNum: 6,
patchNum: 9,
view: Gerrit.Nav.View.CHANGE,
+ querystring: '',
});
});
@@ -1170,5 +1286,31 @@
});
});
});
+
+ suite('_parseQueryString', () => {
+ test('empty queries', () => {
+ assert.deepEqual(element._parseQueryString(''), []);
+ assert.deepEqual(element._parseQueryString('?'), []);
+ assert.deepEqual(element._parseQueryString('??'), []);
+ assert.deepEqual(element._parseQueryString('&&&'), []);
+ });
+
+ test('url decoding', () => {
+ assert.deepEqual(element._parseQueryString('+'), [[' ', '']]);
+ assert.deepEqual(element._parseQueryString('???+%3d+'), [[' = ', '']]);
+ assert.deepEqual(
+ element._parseQueryString('%6e%61%6d%65=%76%61%6c%75%65'),
+ [['name', 'value']]);
+ });
+
+ test('multiple parameters', () => {
+ assert.deepEqual(
+ element._parseQueryString('a=b&c=d&e=f'),
+ [['a', 'b'], ['c', 'd'], ['e', 'f']]);
+ assert.deepEqual(
+ element._parseQueryString('&a=b&&&e=f&'),
+ [['a', 'b'], ['e', 'f']]);
+ });
+ });
});
</script>
diff --git a/polygerrit-ui/app/elements/diff/gr-syntax-lib-loader/gr-syntax-lib-loader.js b/polygerrit-ui/app/elements/diff/gr-syntax-lib-loader/gr-syntax-lib-loader.js
index bfd8e90..ca3cf62 100644
--- a/polygerrit-ui/app/elements/diff/gr-syntax-lib-loader/gr-syntax-lib-loader.js
+++ b/polygerrit-ui/app/elements/diff/gr-syntax-lib-loader/gr-syntax-lib-loader.js
@@ -26,6 +26,7 @@
// NOTE: intended singleton.
value: {
+ configured: false,
loading: false,
callbacks: [],
},
@@ -60,12 +61,13 @@
},
_getHighlightLib() {
- return window.hljs;
- },
+ const lib = window.hljs;
+ if (lib && !this._state.configured) {
+ this._state.configured = true;
- _configureHighlightLib() {
- this._getHighlightLib().configure(
- {classPrefix: 'gr-diff gr-syntax gr-syntax-'});
+ lib.configure({classPrefix: 'gr-diff gr-syntax gr-syntax-'});
+ }
+ return lib;
},
_getLibRoot() {
@@ -93,10 +95,8 @@
}
script.src = src;
- script.onload = function() {
- this._configureHighlightLib();
- resolve();
- }.bind(this);
+ script.onload = resolve;
+ script.onerror = reject;
Polymer.dom(document.head).appendChild(script);
});
},
diff --git a/polygerrit-ui/app/elements/diff/gr-syntax-lib-loader/gr-syntax-lib-loader_test.html b/polygerrit-ui/app/elements/diff/gr-syntax-lib-loader/gr-syntax-lib-loader_test.html
index 6ddde46..6e88ed1 100644
--- a/polygerrit-ui/app/elements/diff/gr-syntax-lib-loader/gr-syntax-lib-loader_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-syntax-lib-loader/gr-syntax-lib-loader_test.html
@@ -55,6 +55,7 @@
loadStub.restore();
// Because the element state is a singleton, clean it up.
+ element._state.configured = false;
element._state.loading = false;
element._state.callbacks = [];
});
@@ -88,8 +89,13 @@
});
suite('preloaded', () => {
+ let hljsStub;
+
setup(() => {
- window.hljs = 'test-object';
+ hljsStub = {
+ configure: sinon.stub(),
+ };
+ window.hljs = hljsStub;
});
teardown(() => {
@@ -101,7 +107,14 @@
element.get().then(firstCallHandler);
flush(() => {
assert.isTrue(firstCallHandler.called);
- assert.isTrue(firstCallHandler.calledWith('test-object'));
+ assert.isTrue(firstCallHandler.calledWith(hljsStub));
+ done();
+ });
+ });
+
+ test('configures hljs', done => {
+ element.get().then(() => {
+ assert.isTrue(window.hljs.configure.calledOnce);
done();
});
});
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-constants.html b/polygerrit-ui/app/elements/edit/gr-edit-constants.html
new file mode 100644
index 0000000..1941dc8
--- /dev/null
+++ b/polygerrit-ui/app/elements/edit/gr-edit-constants.html
@@ -0,0 +1,31 @@
+<!--
+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.
+-->
+<script>
+ (function(window) {
+ 'use strict';
+
+ const GrEditConstants = window.GrEditConstants || {};
+
+ GrEditConstants.Actions = {
+ EDIT: {label: 'Edit', id: 'edit'},
+ DELETE: {label: 'Delete', id: 'delete'},
+ RENAME: {label: 'Rename', id: 'rename'},
+ RESTORE: {label: 'Restore', id: 'restore'},
+ };
+
+ window.GrEditConstants = GrEditConstants;
+ })(window);
+</script>
\ No newline at end of file
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.html b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.html
new file mode 100644
index 0000000..9eb4974
--- /dev/null
+++ b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.html
@@ -0,0 +1,149 @@
+<!--
+Copyright (C) 2017 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+
+<link rel="import" href="../../../behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html">
+<link rel="import" href="../../../behaviors/gr-path-list-behavior/gr-path-list-behavior.html">
+<link rel="import" href="../../../bower_components/iron-input/iron-input.html">
+<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
+<link rel="import" href="../../shared/gr-autocomplete/gr-autocomplete.html">
+<link rel="import" href="../../shared/gr-button/gr-button.html">
+<link rel="import" href="../../shared/gr-confirm-dialog/gr-confirm-dialog.html">
+<link rel="import" href="../../shared/gr-dropdown/gr-dropdown.html">
+<link rel="import" href="../../shared/gr-overlay/gr-overlay.html">
+<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
+<link rel="import" href="../gr-edit-constants.html">
+
+<link rel="import" href="../../../styles/shared-styles.html">
+
+<dom-module id="gr-edit-controls">
+ <template>
+ <style include="shared-styles">
+ :host {
+ align-items: center;
+ display: flex;
+ justify-content: flex-end;
+ }
+ .invisible {
+ display: none;
+ }
+ gr-button {
+ margin-left: 1em;
+ text-decoration: none;
+ }
+ gr-confirm-dialog {
+ width: 50em;
+ }
+ gr-confirm-dialog .main {
+ width: 100%;
+ }
+ gr-autocomplete {
+ --gr-autocomplete: {
+ border: 1px solid #d1d2d3;
+ border-radius: 2px;
+ font-size: 1em;
+ height: 2em;
+ padding: 0 .15em;
+ }
+ }
+ input {
+ border: 1px solid #d1d2d3;
+ border-radius: 2px;
+ font-size: 1em;
+ height: 2em;
+ margin: .5em 0;
+ padding: 0 .15em;
+ width: 100%;
+ }
+ </style>
+ <template is="dom-repeat" items="[[_actions]]" as="action">
+ <gr-button
+ id$="[[action.id]]"
+ class$="[[_computeIsInvisible(action.id, hiddenActions)]]"
+ link
+ on-tap="_handleTap">[[action.label]]</gr-button>
+ </template>
+ <gr-overlay id="overlay" with-backdrop>
+ <gr-confirm-dialog
+ id="editDialog"
+ class="invisible dialog"
+ disabled$="[[!_isValidPath(_path)]]"
+ confirm-label="Edit"
+ on-confirm="_handleEditConfirm"
+ on-cancel="_handleDialogCancel">
+ <div class="header">Edit a file</div>
+ <div class="main">
+ <gr-autocomplete
+ placeholder="Enter an existing or new full file path."
+ query="[[_query]]"
+ text="{{_path}}"></gr-autocomplete>
+ </div>
+ </gr-confirm-dialog>
+ <gr-confirm-dialog
+ id="deleteDialog"
+ class="invisible dialog"
+ disabled$="[[!_isValidPath(_path)]]"
+ confirm-label="Delete"
+ on-confirm="_handleDeleteConfirm"
+ on-cancel="_handleDialogCancel">
+ <div class="header">Delete a file</div>
+ <div class="main">
+ <gr-autocomplete
+ placeholder="Enter an existing full file path."
+ query="[[_query]]"
+ text="{{_path}}"></gr-autocomplete>
+ </div>
+ </gr-confirm-dialog>
+ <gr-confirm-dialog
+ id="renameDialog"
+ class="invisible dialog"
+ disabled$="[[!_computeRenameDisabled(_path, _newPath)]]"
+ confirm-label="Rename"
+ on-confirm="_handleRenameConfirm"
+ on-cancel="_handleDialogCancel">
+ <div class="header">Rename a file</div>
+ <div class="main">
+ <gr-autocomplete
+ placeholder="Enter an existing full file path."
+ query="[[_query]]"
+ text="{{_path}}"></gr-autocomplete>
+ <input
+ class="newPathInput"
+ is="iron-input"
+ bind-value="{{_newPath}}"
+ placeholder="Enter the new path."/>
+ </div>
+ </gr-confirm-dialog>
+ <gr-confirm-dialog
+ id="restoreDialog"
+ class="invisible dialog"
+ confirm-label="Restore"
+ on-confirm="_handleRestoreConfirm"
+ on-cancel="_handleDialogCancel">
+ <div class="header">Restore this file?</div>
+ <div class="main">
+ <input
+ is="iron-input"
+ disabled
+ bind-value="{{_path}}"/>
+ </div>
+ </gr-confirm-dialog>
+ </gr-overlay>
+ <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
+ </template>
+ <script src="gr-edit-controls.js"></script>
+</dom-module>
\ No newline at end of file
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
new file mode 100644
index 0000000..c04df74
--- /dev/null
+++ b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls.js
@@ -0,0 +1,193 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+(function() {
+ 'use strict';
+
+ Polymer({
+ is: 'gr-edit-controls',
+ properties: {
+ change: Object,
+ /**
+ * TODO(kaspern): by default, the RESTORE action should be hidden in the
+ * file-list as it is a per-file action only. Remove this default value
+ * when the Actions dictionary is moved to a shared constants file and
+ * use the hiddenActions property in the parent component.
+ */
+ hiddenActions: {
+ type: Array,
+ value() { return [GrEditConstants.Actions.RESTORE.id]; },
+ },
+
+ _actions: {
+ type: Array,
+ value() { return Object.values(GrEditConstants.Actions); },
+ },
+ _path: {
+ type: String,
+ value: '',
+ },
+ _newPath: {
+ type: String,
+ value: '',
+ },
+ _query: {
+ type: Function,
+ value() {
+ return this._queryFiles.bind(this);
+ },
+ },
+ },
+
+ behaviors: [
+ Gerrit.PatchSetBehavior,
+ ],
+
+ _handleTap(e) {
+ e.preventDefault();
+ const action = Polymer.dom(e).localTarget.id;
+ switch (action) {
+ case GrEditConstants.Actions.EDIT.id:
+ this.openEditDialog();
+ return;
+ case GrEditConstants.Actions.DELETE.id:
+ this.openDeleteDialog();
+ return;
+ case GrEditConstants.Actions.RENAME.id:
+ this.openRenameDialog();
+ return;
+ case GrEditConstants.Actions.RESTORE.id:
+ this.openRestoreDialog();
+ return;
+ }
+ },
+
+ openEditDialog(opt_path) {
+ if (opt_path) { this._path = opt_path; }
+ return this._showDialog(this.$.editDialog);
+ },
+
+ openDeleteDialog(opt_path) {
+ if (opt_path) { this._path = opt_path; }
+ return this._showDialog(this.$.deleteDialog);
+ },
+
+ openRenameDialog(opt_path) {
+ if (opt_path) { this._path = opt_path; }
+ return this._showDialog(this.$.renameDialog);
+ },
+
+ openRestoreDialog(opt_path) {
+ if (opt_path) { this._path = opt_path; }
+ return this._showDialog(this.$.restoreDialog);
+ },
+
+ /**
+ * Given a path string, checks that it is a valid file path.
+ * @param {string} path
+ * @return {boolean}
+ */
+ _isValidPath(path) {
+ // Double negation needed for strict boolean return type.
+ return !!path.length && !path.endsWith('/');
+ },
+
+ _computeRenameDisabled(path, newPath) {
+ return this._isValidPath(path) && this._isValidPath(newPath);
+ },
+
+ /**
+ * Given a dom event, gets the dialog that lies along this event path.
+ * @param {!Event} e
+ * @return {!Element}
+ */
+ _getDialogFromEvent(e) {
+ return Polymer.dom(e).path.find(element => {
+ if (!element.classList) { return false; }
+ return element.classList.contains('dialog');
+ });
+ },
+
+ _showDialog(dialog) {
+ return this.$.overlay.open().then(() => {
+ dialog.classList.toggle('invisible', false);
+ const autocomplete = dialog.querySelector('gr-autocomplete');
+ if (autocomplete) { autocomplete.focus(); }
+ this.async(() => { this.$.overlay.center(); }, 1);
+ });
+ },
+
+ _closeDialog(dialog, clearInputs) {
+ if (clearInputs) {
+ // Dialog may have autocompletes and plain inputs -- as these have
+ // different properties representing their bound text, it is easier to
+ // just make two separate queries.
+ dialog.querySelectorAll('gr-autocomplete')
+ .forEach(input => { input.text = ''; });
+ dialog.querySelectorAll('input')
+ .forEach(input => { input.bindValue = ''; });
+ }
+
+ dialog.classList.toggle('invisible', true);
+ return this.$.overlay.close();
+ },
+
+ _handleDialogCancel(e) {
+ this._closeDialog(this._getDialogFromEvent(e));
+ },
+
+ _handleEditConfirm(e) {
+ const url = Gerrit.Nav.getEditUrlForDiff(this.change, this._path);
+ Gerrit.Nav.navigateToRelativeUrl(url);
+ this._closeDialog(this._getDialogFromEvent(e), true);
+ },
+
+ _handleDeleteConfirm(e) {
+ this.$.restAPI.deleteFileInChangeEdit(this.change._number, this._path)
+ .then(res => {
+ if (!res.ok) { return; }
+ this._closeDialog(this._getDialogFromEvent(e), true);
+ Gerrit.Nav.navigateToChange(this.change);
+ });
+ },
+
+ _handleRestoreConfirm(e) {
+ this.$.restAPI.restoreFileInChangeEdit(this.change._number, this._path)
+ .then(res => {
+ if (!res.ok) { return; }
+ this._closeDialog(this._getDialogFromEvent(e), true);
+ Gerrit.Nav.navigateToChange(this.change);
+ });
+ },
+
+ _handleRenameConfirm(e) {
+ return this.$.restAPI.renameFileInChangeEdit(this.change._number,
+ this._path, this._newPath).then(res => {
+ if (!res.ok) { return; }
+ this._closeDialog(this._getDialogFromEvent(e), true);
+ Gerrit.Nav.navigateToChange(this.change);
+ });
+ },
+
+ _queryFiles(input) {
+ return this.$.restAPI.queryChangeFiles(this.change._number,
+ this.EDIT_NAME, input).then(res => res.map(file => {
+ return {name: file};
+ }));
+ },
+
+ _computeIsInvisible(id, hiddenActions) {
+ return hiddenActions.includes(id) ? 'invisible' : '';
+ },
+ });
+})();
\ No newline at end of file
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
new file mode 100644
index 0000000..7f2f9bb
--- /dev/null
+++ b/polygerrit-ui/app/elements/edit/gr-edit-controls/gr-edit-controls_test.html
@@ -0,0 +1,349 @@
+<!--
+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-edit-controls</title>
+
+<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
+
+<link rel="import" href="gr-edit-controls.html">
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+ <template>
+ <gr-edit-controls></gr-edit-controls>
+ </template>
+</test-fixture>
+
+<script>
+suite('gr-edit-controls tests', () => {
+ let element;
+ let sandbox;
+ let showDialogSpy;
+ let closeDialogSpy;
+ let queryStub;
+
+ setup(() => {
+ sandbox = sinon.sandbox.create();
+ element = fixture('basic');
+ element.change = {_number: '42'};
+ showDialogSpy = sandbox.spy(element, '_showDialog');
+ closeDialogSpy = sandbox.spy(element, '_closeDialog');
+ queryStub = sandbox.stub(element.$.restAPI, 'queryChangeFiles')
+ .returns(Promise.resolve([]));
+ flushAsynchronousOperations();
+ });
+
+ teardown(() => { sandbox.restore(); });
+
+ test('all actions exist', () => {
+ assert.equal(Polymer.dom(element.root).querySelectorAll('gr-button').length,
+ element._actions.length);
+ });
+
+ suite('edit button CUJ', () => {
+ let navStubs;
+
+ setup(() => {
+ navStubs = [
+ sandbox.stub(Gerrit.Nav, 'getEditUrlForDiff'),
+ sandbox.stub(Gerrit.Nav, 'navigateToRelativeUrl'),
+ ];
+ });
+
+ test('_isValidPath', () => {
+ assert.isFalse(element._isValidPath(''));
+ assert.isFalse(element._isValidPath('test/'));
+ assert.isFalse(element._isValidPath('/'));
+ assert.isTrue(element._isValidPath('test/path.cpp'));
+ assert.isTrue(element._isValidPath('test.js'));
+ });
+
+ test('edit', () => {
+ MockInteractions.tap(element.$$('#edit'));
+ return showDialogSpy.lastCall.returnValue.then(() => {
+ assert.isTrue(element.$.editDialog.disabled);
+ assert.isFalse(queryStub.called);
+ element.$.editDialog.querySelector('gr-autocomplete').text =
+ 'src/test.cpp';
+ assert.isTrue(queryStub.called);
+ assert.isFalse(element.$.editDialog.disabled);
+ MockInteractions.tap(element.$.editDialog.$$('gr-button[primary]'));
+ for (const stub of navStubs) { assert.isTrue(stub.called); }
+ assert.isTrue(closeDialogSpy.called);
+ });
+ });
+
+ test('cancel', () => {
+ MockInteractions.tap(element.$$('#edit'));
+ return showDialogSpy.lastCall.returnValue.then(() => {
+ assert.isTrue(element.$.editDialog.disabled);
+ element.$.editDialog.querySelector('gr-autocomplete').text =
+ 'src/test.cpp';
+ assert.isFalse(element.$.editDialog.disabled);
+ MockInteractions.tap(element.$.editDialog.$$('gr-button'));
+ for (const stub of navStubs) { assert.isFalse(stub.called); }
+ assert.isTrue(closeDialogSpy.called);
+ assert.equal(element._path, 'src/test.cpp');
+ });
+ });
+ });
+
+ suite('delete button CUJ', () => {
+ let navStub;
+ let deleteStub;
+
+ setup(() => {
+ navStub = sandbox.stub(Gerrit.Nav, 'navigateToChange');
+ deleteStub = sandbox.stub(element.$.restAPI, 'deleteFileInChangeEdit');
+ });
+
+ test('delete', () => {
+ deleteStub.returns(Promise.resolve({ok: true}));
+ MockInteractions.tap(element.$$('#delete'));
+ return showDialogSpy.lastCall.returnValue.then(() => {
+ assert.isTrue(element.$.deleteDialog.disabled);
+ assert.isFalse(queryStub.called);
+ element.$.deleteDialog.querySelector('gr-autocomplete').text =
+ 'src/test.cpp';
+ assert.isTrue(queryStub.called);
+ assert.isFalse(element.$.deleteDialog.disabled);
+ MockInteractions.tap(element.$.deleteDialog.$$('gr-button[primary]'));
+ flushAsynchronousOperations();
+
+ assert.isTrue(deleteStub.called);
+
+ return deleteStub.lastCall.returnValue.then(() => {
+ assert.equal(element._path, '');
+ assert.isTrue(navStub.called);
+ assert.isTrue(closeDialogSpy.called);
+ });
+ });
+ });
+
+ test('delete fails', () => {
+ deleteStub.returns(Promise.resolve({ok: false}));
+ MockInteractions.tap(element.$$('#delete'));
+ return showDialogSpy.lastCall.returnValue.then(() => {
+ assert.isTrue(element.$.deleteDialog.disabled);
+ assert.isFalse(queryStub.called);
+ element.$.deleteDialog.querySelector('gr-autocomplete').text =
+ 'src/test.cpp';
+ assert.isTrue(queryStub.called);
+ assert.isFalse(element.$.deleteDialog.disabled);
+ MockInteractions.tap(element.$.deleteDialog.$$('gr-button[primary]'));
+ flushAsynchronousOperations();
+
+ assert.isTrue(deleteStub.called);
+
+ return deleteStub.lastCall.returnValue.then(() => {
+ assert.isFalse(navStub.called);
+ assert.isFalse(closeDialogSpy.called);
+ });
+ });
+ });
+
+ test('cancel', () => {
+ MockInteractions.tap(element.$$('#delete'));
+ return showDialogSpy.lastCall.returnValue.then(() => {
+ assert.isTrue(element.$.deleteDialog.disabled);
+ element.$.deleteDialog.querySelector('gr-autocomplete').text =
+ 'src/test.cpp';
+ assert.isFalse(element.$.deleteDialog.disabled);
+ MockInteractions.tap(element.$.deleteDialog.$$('gr-button'));
+ assert.isFalse(navStub.called);
+ assert.isTrue(closeDialogSpy.called);
+ assert.equal(element._path, 'src/test.cpp');
+ });
+ });
+ });
+
+ suite('rename button CUJ', () => {
+ let navStub;
+ let renameStub;
+
+ setup(() => {
+ navStub = sandbox.stub(Gerrit.Nav, 'navigateToChange');
+ renameStub = sandbox.stub(element.$.restAPI, 'renameFileInChangeEdit');
+ });
+
+ test('rename', () => {
+ renameStub.returns(Promise.resolve({ok: true}));
+ MockInteractions.tap(element.$$('#rename'));
+ return showDialogSpy.lastCall.returnValue.then(() => {
+ assert.isTrue(element.$.renameDialog.disabled);
+ assert.isFalse(queryStub.called);
+ element.$.renameDialog.querySelector('gr-autocomplete').text =
+ 'src/test.cpp';
+ assert.isTrue(queryStub.called);
+ assert.isTrue(element.$.renameDialog.disabled);
+
+ element.$.renameDialog.querySelector('.newPathInput').bindValue =
+ 'src/test.newPath';
+
+ assert.isFalse(element.$.renameDialog.disabled);
+ MockInteractions.tap(element.$.renameDialog.$$('gr-button[primary]'));
+ flushAsynchronousOperations();
+
+ assert.isTrue(renameStub.called);
+
+ return renameStub.lastCall.returnValue.then(() => {
+ assert.equal(element._path, '');
+ assert.isTrue(navStub.called);
+ assert.isTrue(closeDialogSpy.called);
+ });
+ });
+ });
+
+ test('rename fails', () => {
+ renameStub.returns(Promise.resolve({ok: false}));
+ MockInteractions.tap(element.$$('#rename'));
+ return showDialogSpy.lastCall.returnValue.then(() => {
+ assert.isTrue(element.$.renameDialog.disabled);
+ assert.isFalse(queryStub.called);
+ element.$.renameDialog.querySelector('gr-autocomplete').text =
+ 'src/test.cpp';
+ assert.isTrue(queryStub.called);
+ assert.isTrue(element.$.renameDialog.disabled);
+
+ element.$.renameDialog.querySelector('.newPathInput').bindValue =
+ 'src/test.newPath';
+
+ assert.isFalse(element.$.renameDialog.disabled);
+ MockInteractions.tap(element.$.renameDialog.$$('gr-button[primary]'));
+ flushAsynchronousOperations();
+
+ assert.isTrue(renameStub.called);
+
+ return renameStub.lastCall.returnValue.then(() => {
+ assert.isFalse(navStub.called);
+ assert.isFalse(closeDialogSpy.called);
+ });
+ });
+ });
+
+ test('cancel', () => {
+ MockInteractions.tap(element.$$('#rename'));
+ return showDialogSpy.lastCall.returnValue.then(() => {
+ assert.isTrue(element.$.renameDialog.disabled);
+ element.$.renameDialog.querySelector('gr-autocomplete').text =
+ 'src/test.cpp';
+ element.$.renameDialog.querySelector('.newPathInput').bindValue =
+ 'src/test.newPath';
+ assert.isFalse(element.$.renameDialog.disabled);
+ MockInteractions.tap(element.$.renameDialog.$$('gr-button'));
+ assert.isFalse(navStub.called);
+ assert.isTrue(closeDialogSpy.called);
+ assert.equal(element._path, 'src/test.cpp');
+ assert.equal(element._newPath, 'src/test.newPath');
+ });
+ });
+ });
+
+ suite('restore button CUJ', () => {
+ let navStub;
+ let restoreStub;
+
+ setup(() => {
+ navStub = sandbox.stub(Gerrit.Nav, 'navigateToChange');
+ restoreStub = sandbox.stub(element.$.restAPI, 'restoreFileInChangeEdit');
+ });
+
+ test('restore hidden by default', () => {
+ assert.isTrue(element.$$('#restore').classList.contains('invisible'));
+ });
+
+ test('restore', () => {
+ restoreStub.returns(Promise.resolve({ok: true}));
+ element._path = 'src/test.cpp';
+ MockInteractions.tap(element.$$('#restore'));
+ return showDialogSpy.lastCall.returnValue.then(() => {
+ MockInteractions.tap(element.$.restoreDialog.$$('gr-button[primary]'));
+ flushAsynchronousOperations();
+
+ assert.isTrue(restoreStub.called);
+ assert.equal(restoreStub.lastCall.args[1], 'src/test.cpp');
+ return restoreStub.lastCall.returnValue.then(() => {
+ assert.equal(element._path, '');
+ assert.isTrue(navStub.called);
+ assert.isTrue(closeDialogSpy.called);
+ });
+ });
+ });
+
+ test('restore fails', () => {
+ restoreStub.returns(Promise.resolve({ok: false}));
+ element._path = 'src/test.cpp';
+ MockInteractions.tap(element.$$('#restore'));
+ return showDialogSpy.lastCall.returnValue.then(() => {
+ MockInteractions.tap(element.$.restoreDialog.$$('gr-button[primary]'));
+ flushAsynchronousOperations();
+
+ assert.isTrue(restoreStub.called);
+ assert.equal(restoreStub.lastCall.args[1], 'src/test.cpp');
+ return restoreStub.lastCall.returnValue.then(() => {
+ assert.isFalse(navStub.called);
+ assert.isFalse(closeDialogSpy.called);
+ });
+ });
+ });
+
+ test('cancel', () => {
+ element._path = 'src/test.cpp';
+ MockInteractions.tap(element.$$('#restore'));
+ return showDialogSpy.lastCall.returnValue.then(() => {
+ MockInteractions.tap(element.$.restoreDialog.$$('gr-button'));
+ assert.isFalse(navStub.called);
+ assert.isTrue(closeDialogSpy.called);
+ assert.equal(element._path, 'src/test.cpp');
+ });
+ });
+ });
+
+ test('openEditDialog', () => {
+ return element.openEditDialog('test/path.cpp').then(() => {
+ assert.isFalse(element.$.editDialog.hasAttribute('hidden'));
+ assert.equal(element.$.editDialog.querySelector('gr-autocomplete').text,
+ 'test/path.cpp');
+ });
+ });
+
+ test('_getDialogFromEvent', () => {
+ const spy = sandbox.spy(element, '_getDialogFromEvent');
+ element.addEventListener('tap', element._getDialogFromEvent);
+
+ MockInteractions.tap(element.$.editDialog);
+ flushAsynchronousOperations();
+ assert.equal(spy.lastCall.returnValue.id, 'editDialog');
+
+ MockInteractions.tap(element.$.deleteDialog);
+ flushAsynchronousOperations();
+ assert.equal(spy.lastCall.returnValue.id, 'deleteDialog');
+
+ MockInteractions.tap(
+ element.$.deleteDialog.querySelector('gr-autocomplete'));
+ flushAsynchronousOperations();
+ assert.equal(spy.lastCall.returnValue.id, 'deleteDialog');
+
+ MockInteractions.tap(element);
+ flushAsynchronousOperations();
+ assert.notOk(spy.lastCall.returnValue);
+ });
+});
+</script>
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.html b/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.html
new file mode 100644
index 0000000..bfbe11a
--- /dev/null
+++ b/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.html
@@ -0,0 +1,48 @@
+<!--
+Copyright (C) 2017 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+
+<link rel="import" href="../../shared/gr-button/gr-button.html">
+<link rel="import" href="../../shared/gr-dropdown/gr-dropdown.html">
+
+<link rel="import" href="../../../styles/shared-styles.html">
+
+<dom-module id="gr-edit-file-controls">
+ <template>
+ <style include="shared-styles">
+ :host {
+ align-items: center;
+ display: flex;
+ justify-content: flex-end;
+ }
+ #edit {
+ margin-right: .5em;
+ text-decoration: none;
+ }
+ </style>
+ <gr-button
+ id="edit"
+ link
+ on-tap="_handleEditTap">Edit</gr-button>
+ <!-- TODO(kaspern): implement more menu. -->
+ <gr-dropdown
+ id="more"
+ hidden
+ link>More</gr-dropdown>
+ </template>
+ <script src="gr-edit-file-controls.js"></script>
+</dom-module>
\ No newline at end of file
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.js b/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.js
new file mode 100644
index 0000000..1c87621
--- /dev/null
+++ b/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.js
@@ -0,0 +1,34 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+(function() {
+ 'use strict';
+
+ Polymer({
+ is: 'gr-edit-file-controls',
+
+ /**
+ * Fired when the edit button is pressed.
+ *
+ * @event edit-tap
+ */
+
+ properties: {
+ filePath: String,
+ },
+
+ _handleEditTap() {
+ this.fire('edit-tap', {path: this.filePath});
+ },
+ });
+})();
\ No newline at end of file
diff --git a/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls_test.html b/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls_test.html
new file mode 100644
index 0000000..250e208
--- /dev/null
+++ b/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls_test.html
@@ -0,0 +1,56 @@
+<!--
+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-edit-file-controls</title>
+
+<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
+
+<link rel="import" href="gr-edit-file-controls.html">
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+ <template>
+ <gr-edit-file-controls></gr-edit-file-controls>
+ </template>
+</test-fixture>
+
+<script>
+suite('gr-edit-file-controls tests', () => {
+ let element;
+ let sandbox;
+
+ setup(() => {
+ sandbox = sinon.sandbox.create();
+ element = fixture('basic');
+ });
+
+ teardown(() => { sandbox.restore(); });
+
+ test('edit tap emits event', () => {
+ const handler = sandbox.stub();
+ element.addEventListener('edit-tap', handler);
+ element.filePath = 'foo';
+
+ MockInteractions.tap(element.$.edit);
+ assert.isTrue(handler.called);
+ assert.equal(handler.lastCall.args[0].detail.path, 'foo');
+ });
+});
+</script>
\ No newline at end of file
diff --git a/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.html b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.html
new file mode 100644
index 0000000..df2ac93
--- /dev/null
+++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.html
@@ -0,0 +1,102 @@
+<!--
+Copyright (C) 2017 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+
+<link rel="import" href="../../../behaviors/gr-patch-set-behavior/gr-patch-set-behavior.html">
+<link rel="import" href="../../../behaviors/gr-path-list-behavior/gr-path-list-behavior.html">
+<link rel="import" href="../../../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html">
+<link rel="import" href="../../core/gr-navigation/gr-navigation.html">
+<link rel="import" href="../../shared/gr-button/gr-button.html">
+<link rel="import" href="../../shared/gr-editable-label/gr-editable-label.html">
+<link rel="import" href="../../shared/gr-fixed-panel/gr-fixed-panel.html">
+<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
+<link rel="import" href="../../../styles/shared-styles.html">
+
+
+<dom-module id="gr-editor-view">
+ <template>
+ <style include="shared-styles">
+ :host {
+ background-color: var(--view-background-color);
+ }
+ gr-fixed-panel {
+ background-color: #fff;
+ border-bottom: 1px #eee solid;
+ z-index: 10;
+ }
+ header,
+ .subHeader {
+ align-items: center;
+ display: flex;
+ justify-content: space-between;
+ padding: .75em var(--default-horizontal-margin);
+ }
+ header gr-editable-label {
+ font-size: 1.2em;
+ font-weight: bold;
+ }
+ .textareaWrapper {
+ margin: var(--default-horizontal-margin);
+ }
+ .textareaWrapper textarea {
+ border: 1px solid #ddd;
+ border-radius: 3px;
+ box-sizing: border-box;
+ font-family: var(--monospace-font-family);
+ min-height: 60vh;
+ resize: none;
+ white-space: pre;
+ width: 100%;
+ }
+ .textareaWrapper textarea:focus {
+ outline: none;
+ }
+ .textareaWrapper .editButtons {
+ display: none;
+ }
+ .rightControls {
+ justify-content: flex-end
+ }
+ </style>
+ <gr-fixed-panel
+ class$="[[_computeContainerClass(_editLoaded)]]"
+ floating-disabled="[[_panelFloatingDisabled]]"
+ keep-on-scroll
+ ready-for-measure="[[!_loading]]">
+ <header>
+ <gr-editable-label
+ label-text="File path"
+ value="[[_path]]"
+ placeholder="File path..."
+ on-changed="_handlePathChanged"></gr-editable-label>
+ <span class="rightControls">
+ <gr-button
+ id="save"
+ disabled$="[[_saveDisabled]]"
+ primary
+ on-tap="_saveEdit">Save</gr-button>
+ <gr-button id="cancel" on-tap="_handleCancelTap">Cancel</gr-button>
+ </span>
+ </header>
+ </gr-fixed-panel>
+ <div class="textareaWrapper">
+ <textarea value="{{_newContent::input}}" id="file"></textarea>
+ </div>
+ <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
+ </template>
+ <script src="gr-editor-view.js"></script>
+</dom-module>
\ No newline at end of file
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
new file mode 100644
index 0000000..5652793
--- /dev/null
+++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view.js
@@ -0,0 +1,129 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+(function() {
+ 'use strict';
+
+ Polymer({
+ is: 'gr-editor-view',
+
+ /**
+ * Fired when the title of the page should change.
+ *
+ * @event title-change
+ */
+
+ properties: {
+ /**
+ * URL params passed from the router.
+ */
+ params: {
+ type: Object,
+ observer: '_paramsChanged',
+ },
+
+ _change: Object,
+ _changeEditDetail: Object,
+ _changeNum: String,
+ _loggedIn: Boolean,
+ _path: String,
+ _content: String,
+ _newContent: String,
+ _saveDisabled: {
+ type: Boolean,
+ value: true,
+ computed: '_computeSaveDisabled(_content, _newContent)',
+ },
+ },
+
+ behaviors: [
+ Gerrit.KeyboardShortcutBehavior,
+ Gerrit.PatchSetBehavior,
+ Gerrit.PathListBehavior,
+ ],
+
+ attached() {
+ this._getLoggedIn().then(loggedIn => { this._loggedIn = loggedIn; });
+ },
+
+ _getLoggedIn() {
+ return this.$.restAPI.getLoggedIn();
+ },
+
+ _paramsChanged(value) {
+ if (value.view !== Gerrit.Nav.View.EDIT) { return; }
+
+ this._changeNum = value.changeNum;
+ this._path = value.path;
+
+ // NOTE: This may be called before attachment (e.g. while parentElement is
+ // null). Fire title-change in an async so that, if attachment to the DOM
+ // has been queued, the event can bubble up to the handler in gr-app.
+ this.async(() => {
+ const title = `Editing ${this.computeTruncatedPath(this._path)}`;
+ this.fire('title-change', {title});
+ });
+
+ const promises = [];
+
+ promises.push(this._getChangeDetail(this._changeNum));
+ promises.push(this._getFileContent(this._changeNum, this._path)
+ .then(fileContent => {
+ this._content = fileContent;
+ this._newContent = fileContent;
+ }));
+ return Promise.all(promises);
+ },
+
+ _getChangeDetail(changeNum) {
+ return this.$.restAPI.getDiffChangeDetail(changeNum).then(change => {
+ this._change = change;
+ });
+ },
+
+ _handlePathChanged(e) {
+ const path = e.detail;
+ if (path === this._path) { return Promise.resolve(); }
+ return this.$.restAPI.renameFileInChangeEdit(this._changeNum,
+ this._path, path).then(res => {
+ if (!res.ok) { return; }
+ this._viewEditInChangeView();
+ });
+ },
+
+ _viewEditInChangeView() {
+ Gerrit.Nav.navigateToChange(this._change, this.EDIT_NAME);
+ },
+
+ _getFileContent(changeNum, path) {
+ return this.$.restAPI.getFileInChangeEdit(changeNum, path);
+ },
+
+ _saveEdit() {
+ return this.$.restAPI.saveChangeEdit(this._changeNum, this._path,
+ this._newContent).then(res => {
+ if (!res.ok) { return; }
+ this._viewEditInChangeView();
+ });
+ },
+
+ _computeSaveDisabled(content, newContent) {
+ return content === newContent;
+ },
+
+ _handleCancelTap() {
+ // TODO(kaspern): Add a confirm dialog if there are unsaved changes.
+ this._viewEditInChangeView();
+ },
+ });
+})();
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
new file mode 100644
index 0000000..e3e6474
--- /dev/null
+++ b/polygerrit-ui/app/elements/edit/gr-editor-view/gr-editor-view_test.html
@@ -0,0 +1,183 @@
+<!--
+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-editor-view</title>
+
+<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
+
+<link rel="import" href="gr-editor-view.html">
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+ <template>
+ <gr-editor-view></gr-editor-view>
+ </template>
+</test-fixture>
+
+<script>
+suite('gr-editor-view tests', () => {
+ let element;
+ let sandbox;
+ let savePathStub;
+ let saveFileStub;
+ let changeDetailStub;
+ let navigateStub;
+ const mockParams = {
+ changeNum: '42',
+ path: 'foo/bar.baz',
+ };
+
+ setup(() => {
+ stub('gr-rest-api-interface', {
+ getLoggedIn() { return Promise.resolve(true); },
+ });
+ sandbox = sinon.sandbox.create();
+ element = fixture('basic');
+ savePathStub = sandbox.stub(element.$.restAPI, 'renameFileInChangeEdit');
+ saveFileStub = sandbox.stub(element.$.restAPI, 'saveChangeEdit');
+ changeDetailStub = sandbox.stub(element.$.restAPI, 'getDiffChangeDetail');
+ navigateStub = sandbox.stub(element, '_viewEditInChangeView');
+ });
+
+ teardown(() => { sandbox.restore(); });
+
+ suite('_paramsChanged', () => {
+ test('incorrect view returns immediately', () => {
+ element._paramsChanged(
+ Object.assign({}, mockParams, {view: Gerrit.Nav.View.DIFF}));
+ assert.notOk(element._changeNum);
+ });
+
+ test('good params proceed', () => {
+ changeDetailStub.returns(Promise.resolve({}));
+ const fileStub = sandbox.stub(element, '_getFileContent')
+ .returns(Promise.resolve('text'));
+
+ const promises = element._paramsChanged(
+ Object.assign({}, mockParams, {view: Gerrit.Nav.View.EDIT}));
+
+ flushAsynchronousOperations();
+ assert.equal(element._changeNum, mockParams.changeNum);
+ assert.equal(element._path, mockParams.path);
+ assert.deepEqual(changeDetailStub.lastCall.args[0],
+ mockParams.changeNum);
+ assert.deepEqual(fileStub.lastCall.args,
+ [mockParams.changeNum, mockParams.path]);
+
+ return promises.then(() => {
+ assert.equal(element._content, 'text');
+ assert.equal(element._newContent, 'text');
+ });
+ });
+ });
+
+ test('edit file path', done => {
+ element._changeNum = mockParams.changeNum;
+ element._path = mockParams.path;
+ savePathStub.onFirstCall().returns(Promise.resolve({}));
+ savePathStub.onSecondCall().returns(Promise.resolve({ok: true}));
+
+ // Calling with the same path should not navigate.
+ element._handlePathChanged({detail: mockParams.path}).then(() => {
+ assert.isFalse(savePathStub.called);
+ // !ok response
+ element._handlePathChanged({detail: 'newPath'}).then(() => {
+ assert.isTrue(savePathStub.called);
+ assert.isFalse(navigateStub.called);
+ // ok response
+ element._handlePathChanged({detail: 'newPath'}).then(() => {
+ assert.isTrue(navigateStub.called);
+ done();
+ });
+ });
+ });
+ });
+
+ suite('edit file content', () => {
+ const originalText = 'file text';
+ const newText = 'file text changed';
+
+ setup(() => {
+ element._changeNum = mockParams.changeNum;
+ element._path = mockParams.path;
+ element._content = originalText;
+ element._newContent = originalText;
+ flushAsynchronousOperations();
+ });
+
+ test('initial load', () => {
+ assert.equal(element.$.file.value, originalText);
+ assert.isTrue(element.$.save.hasAttribute('disabled'));
+ });
+
+ test('file modification and save, !ok response', done => {
+ const saveSpy = sandbox.spy(element, '_saveEdit');
+ saveFileStub.returns(Promise.resolve({ok: false}));
+ element._newContent = newText;
+ flushAsynchronousOperations();
+
+ assert.equal(element.$.file.value, newText);
+ assert.isFalse(element.$.save.hasAttribute('disabled'));
+
+ MockInteractions.tap(element.$.save);
+ assert(saveSpy.called);
+ saveSpy.lastCall.returnValue.then(() => {
+ assert.isTrue(saveFileStub.called);
+ assert.deepEqual(saveFileStub.lastCall.args,
+ [mockParams.changeNum, mockParams.path, newText]);
+ assert.isFalse(navigateStub.called);
+ done();
+ });
+ });
+
+ test('file modification and save', done => {
+ const saveSpy = sandbox.spy(element, '_saveEdit');
+ saveFileStub.returns(Promise.resolve({ok: true}));
+ element._newContent = newText;
+ flushAsynchronousOperations();
+
+ assert.equal(element.$.file.value, newText);
+ assert.isFalse(element.$.save.hasAttribute('disabled'));
+
+ MockInteractions.tap(element.$.save);
+ assert.isTrue(saveSpy.called);
+ saveSpy.lastCall.returnValue.then(() => {
+ assert.isTrue(saveFileStub.called);
+ assert.isTrue(navigateStub.called);
+ done();
+ });
+ });
+
+ test('file modification and cancel', () => {
+ const cancelSpy = sandbox.spy(element, '_handleCancelTap');
+ element._newContent = newText;
+ flushAsynchronousOperations();
+
+ assert.equal(element.$.file.value, newText);
+ assert.isFalse(element.$.save.hasAttribute('disabled'));
+
+ MockInteractions.tap(element.$.cancel);
+ assert.isTrue(cancelSpy.called);
+ assert.isFalse(saveFileStub.called);
+ assert.isTrue(navigateStub.called);
+ });
+ });
+});
+</script>
\ No newline at end of file
diff --git a/polygerrit-ui/app/elements/gr-app.html b/polygerrit-ui/app/elements/gr-app.html
index 6f8a4a1..eac4131 100644
--- a/polygerrit-ui/app/elements/gr-app.html
+++ b/polygerrit-ui/app/elements/gr-app.html
@@ -46,6 +46,7 @@
<link rel="import" href="./core/gr-reporting/gr-reporting.html">
<link rel="import" href="./core/gr-router/gr-router.html">
<link rel="import" href="./diff/gr-diff-view/gr-diff-view.html">
+<link rel="import" href="./edit/gr-editor-view/gr-editor-view.html">
<link rel="import" href="./plugins/gr-endpoint-decorator/gr-endpoint-decorator.html">
<link rel="import" href="./plugins/gr-external-style/gr-external-style.html">
<link rel="import" href="./plugins/gr-plugin-host/gr-plugin-host.html">
@@ -151,11 +152,15 @@
view-state="{{_viewState.changeView}}"
back-page="[[_lastSearchPage]]"></gr-change-view>
</template>
- <template is="dom-if" if="[[_showDiffView]]" restamp="true">
- <gr-diff-view
- params="[[params]]"
- change-view-state="{{_viewState.changeView}}"></gr-diff-view>
+ <template is="dom-if" if="[[_showEditorView]]" restamp="true">
+ <gr-editor-view
+ params="[[params]]"></gr-editor-view>
</template>
+ <template is="dom-if" if="[[_showDiffView]]" restamp="true">
+ <gr-diff-view
+ params="[[params]]"
+ change-view-state="{{_viewState.changeView}}"></gr-diff-view>
+ </template>
<template is="dom-if" if="[[_showSettingsView]]" restamp="true">
<gr-settings-view
params="[[params]]"
diff --git a/polygerrit-ui/app/elements/gr-app.js b/polygerrit-ui/app/elements/gr-app.js
index 4a38b85..8da8e16 100644
--- a/polygerrit-ui/app/elements/gr-app.js
+++ b/polygerrit-ui/app/elements/gr-app.js
@@ -56,6 +56,7 @@
_showSettingsView: Boolean,
_showAdminView: Boolean,
_showCLAView: Boolean,
+ _showEditorView: Boolean,
/** @type {?} */
_viewState: Object,
/** @type {?} */
@@ -139,6 +140,7 @@
this.set('_showSettingsView', view === Gerrit.Nav.View.SETTINGS);
this.set('_showAdminView', view === Gerrit.Nav.View.ADMIN);
this.set('_showCLAView', view === Gerrit.Nav.View.AGREEMENTS);
+ this.set('_showEditorView', view === Gerrit.Nav.View.EDIT);
if (this.params.justRegistered) {
this.$.registration.open();
}
diff --git a/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper.js b/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper.js
index e750c07..18eeb87 100644
--- a/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper.js
+++ b/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper.js
@@ -20,6 +20,17 @@
}
/**
+ * Add a callback to arbitrary event.
+ * The callback may return false to prevent event bubbling.
+ * @param {string} event Event name
+ * @param {function(Event):boolean} callback
+ * @return {function()} Unsubscribe function.
+ */
+ GrEventHelper.prototype.on = function(event, callback) {
+ return this._listen(this.element, callback, {event});
+ };
+
+ /**
* Add a callback to element click or touch.
* The callback may return false to prevent event bubbling.
* @param {function(Event):boolean} callback
@@ -43,6 +54,7 @@
GrEventHelper.prototype._listen = function(container, callback, opt_options) {
const capture = opt_options && opt_options.capture;
+ const event = opt_options && opt_options.event || 'tap';
const handler = e => {
if (e.path.indexOf(this.element) !== -1) {
let mayContinue = true;
@@ -58,9 +70,9 @@
}
}
};
- container.addEventListener('tap', handler, capture);
+ container.addEventListener(event, handler, capture);
const unsubscribe = () =>
- container.removeEventListener('tap', handler, capture);
+ container.removeEventListener(event, handler, capture);
this._unsubscribers.push(unsubscribe);
return unsubscribe;
};
diff --git a/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper_test.html b/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper_test.html
index 9d42851..43c42a9 100644
--- a/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper_test.html
+++ b/polygerrit-ui/app/elements/plugins/gr-event-helper/gr-event-helper_test.html
@@ -92,5 +92,12 @@
flushAsynchronousOperations();
assert.isFalse(tapStub.called);
});
+
+ test('on()', done => {
+ instance.on('foo', () => {
+ done();
+ });
+ element.fire('foo');
+ });
});
</script>
diff --git a/polygerrit-ui/app/elements/plugins/gr-project-api/gr-plugin-project-command.html b/polygerrit-ui/app/elements/plugins/gr-project-api/gr-plugin-project-command.html
new file mode 100644
index 0000000..87d11ad
--- /dev/null
+++ b/polygerrit-ui/app/elements/plugins/gr-project-api/gr-plugin-project-command.html
@@ -0,0 +1,34 @@
+<!--
+Copyright (C) 2017 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../admin/gr-project-command/gr-project-command.html">
+
+<dom-module id="gr-plugin-project-command">
+ <template>
+ <gr-project-command title="[[title]]">
+ </gr-project-command>
+ </template>
+ <script>
+ Polymer({
+ is: 'gr-plugin-project-command',
+ properties: {
+ title: String,
+ projectName: String,
+ config: Object,
+ },
+ });
+ </script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/plugins/gr-project-api/gr-project-api.html b/polygerrit-ui/app/elements/plugins/gr-project-api/gr-project-api.html
new file mode 100644
index 0000000..0106533
--- /dev/null
+++ b/polygerrit-ui/app/elements/plugins/gr-project-api/gr-project-api.html
@@ -0,0 +1,23 @@
+<!--
+Copyright (C) 2017 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../shared/gr-js-api-interface/gr-js-api-interface.html">
+<link rel="import" href="gr-plugin-project-command.html">
+
+<dom-module id="gr-project-api">
+ <script src="gr-project-api.js"></script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/plugins/gr-project-api/gr-project-api.js b/polygerrit-ui/app/elements/plugins/gr-project-api/gr-project-api.js
new file mode 100644
index 0000000..a173edd
--- /dev/null
+++ b/polygerrit-ui/app/elements/plugins/gr-project-api/gr-project-api.js
@@ -0,0 +1,60 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+(function(window) {
+ 'use strict';
+
+ // Prevent redefinition.
+ if (window.GrProjectApi) { return; }
+
+ function GrProjectApi(plugin) {
+ this._hook = null;
+ this.plugin = plugin;
+ }
+
+ GrProjectApi.prototype._createHook = function(title) {
+ this._hook = this.plugin.hook('project-command').onAttached(element => {
+ const pluginCommand =
+ document.createElement('gr-plugin-project-command');
+ pluginCommand.title = title;
+ element.appendChild(pluginCommand);
+ });
+ };
+
+ GrProjectApi.prototype.createCommand = function(title, callback) {
+ if (this._hook) {
+ console.warn('Already set up.');
+ return this._hook;
+ }
+ this._createHook(title);
+ this._hook.onAttached(element => {
+ if (callback(element.projectName, element.config) === false) {
+ element.hidden = true;
+ }
+ });
+ return this;
+ };
+
+ GrProjectApi.prototype.onTap = function(callback) {
+ if (!this._hook) {
+ console.warn('Call createCommand first.');
+ return this;
+ }
+ this._hook.onAttached(element => {
+ this.plugin.eventHelper(element).on('command-tap', callback);
+ });
+ return this;
+ };
+
+ window.GrProjectApi = GrProjectApi;
+})(window);
diff --git a/polygerrit-ui/app/elements/plugins/gr-project-api/gr-project-api_test.html b/polygerrit-ui/app/elements/plugins/gr-project-api/gr-project-api_test.html
new file mode 100644
index 0000000..b0719f5
--- /dev/null
+++ b/polygerrit-ui/app/elements/plugins/gr-project-api/gr-project-api_test.html
@@ -0,0 +1,80 @@
+<!DOCTYPE html>
+<!--
+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-project-api</title>
+
+<script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
+<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<link rel="import" href="../../../test/common-test-setup.html"/>
+<link rel="import" href="../gr-endpoint-decorator/gr-endpoint-decorator.html">
+<link rel="import" href="gr-project-api.html">
+
+<script>void(0);</script>
+
+<test-fixture id="basic">
+ <template>
+ <gr-endpoint-decorator name="project-command">
+ </gr-endpoint-decorator>
+ </template>
+</test-fixture>
+
+<script>
+ suite('gr-project-api tests', () => {
+ let sandbox;
+ let projectApi;
+
+ setup(() => {
+ sandbox = sinon.sandbox.create();
+ let plugin;
+ Gerrit.install(p => { plugin = p; }, '0.1',
+ 'http://test.com/plugins/testplugin/static/test.js');
+ sandbox.stub(Gerrit, '_arePluginsLoaded').returns(true);
+ projectApi = plugin.project();
+ });
+
+ teardown(() => {
+ projectApi = null;
+ sandbox.restore();
+ });
+
+ test('exists', () => {
+ assert.isOk(projectApi);
+ });
+
+ test('works', done => {
+ const attachedStub = sandbox.stub();
+ const tapStub = sandbox.stub();
+ projectApi
+ .createCommand('foo', attachedStub)
+ .onTap(tapStub);
+ const element = fixture('basic');
+ flush(() => {
+ assert.isTrue(attachedStub.called);
+ const pluginCommand = element.$$('gr-plugin-project-command');
+ assert.isOk(pluginCommand);
+ const command = pluginCommand.$$('gr-project-command');
+ assert.isOk(command);
+ assert.equal(command.title, 'foo');
+ assert.isFalse(tapStub.called);
+ MockInteractions.tap(command.$$('gr-button'));
+ assert.isTrue(tapStub.called);
+ done();
+ });
+ });
+ });
+</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.js b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.js
index c0449be..431ac0e 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.js
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete-dropdown/gr-autocomplete-dropdown.js
@@ -46,6 +46,7 @@
},
suggestions: {
type: Array,
+ value: () => [],
observer: '_resetCursorStops',
},
_suggestionEls: {
@@ -151,8 +152,12 @@
},
_resetCursorStops() {
- Polymer.dom.flush();
- this._suggestionEls = this.$.suggestions.querySelectorAll('li');
+ if (this.suggestions.length > 0) {
+ Polymer.dom.flush();
+ this._suggestionEls = this.$.suggestions.querySelectorAll('li');
+ } else {
+ this._suggestionEls = [];
+ }
},
_resetCursorIndex() {
diff --git a/polygerrit-ui/app/elements/shared/gr-button/gr-button.html b/polygerrit-ui/app/elements/shared/gr-button/gr-button.html
index c0b17af..7efd897 100644
--- a/polygerrit-ui/app/elements/shared/gr-button/gr-button.html
+++ b/polygerrit-ui/app/elements/shared/gr-button/gr-button.html
@@ -116,7 +116,10 @@
color: #aaa;
}
</style>
- <paper-button raised="[[!link]]" disabled="[[disabled]]">
+ <paper-button
+ raised="[[!link]]"
+ disabled="[[disabled]]"
+ tabindex="-1">
<content></content>
<i class="downArrow"></i>
</paper-button>
diff --git a/polygerrit-ui/app/elements/shared/gr-button/gr-button.js b/polygerrit-ui/app/elements/shared/gr-button/gr-button.js
index 551fd7f..911f49d 100644
--- a/polygerrit-ui/app/elements/shared/gr-button/gr-button.js
+++ b/polygerrit-ui/app/elements/shared/gr-button/gr-button.js
@@ -18,6 +18,7 @@
is: 'gr-button',
properties: {
+ tooltip: String,
downArrow: {
type: Boolean,
reflectToAttribute: true,
diff --git a/polygerrit-ui/app/elements/shared/gr-confirm-dialog/gr-confirm-dialog.html b/polygerrit-ui/app/elements/shared/gr-confirm-dialog/gr-confirm-dialog.html
index b06b962..efa2b35 100644
--- a/polygerrit-ui/app/elements/shared/gr-confirm-dialog/gr-confirm-dialog.html
+++ b/polygerrit-ui/app/elements/shared/gr-confirm-dialog/gr-confirm-dialog.html
@@ -45,20 +45,23 @@
footer {
padding: .5em 1.5em;
}
+ gr-button {
+ margin-left: 1em;
+ }
footer {
display: flex;
flex-shrink: 0;
- justify-content: space-between;
+ justify-content: flex-end;
}
</style>
<div class="container">
<header><content select=".header"></content></header>
<main><content select=".main"></content></main>
<footer>
- <gr-button primary on-tap="_handleConfirmTap" disabled="[[disabled]]">
+ <gr-button link on-tap="_handleCancelTap">Cancel</gr-button>
+ <gr-button link primary on-tap="_handleConfirmTap" disabled="[[disabled]]">
[[confirmLabel]]
</gr-button>
- <gr-button on-tap="_handleCancelTap">Cancel</gr-button>
</footer>
</div>
</template>
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.html b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.html
index b821909..6d7df94 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.html
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown/gr-dropdown.html
@@ -48,9 +48,6 @@
width: 2em;
vertical-align: middle;
}
- gr-button[link] {
- padding: 0.5em;
- }
gr-button[link]:focus {
outline: 5px auto -webkit-focus-ring-color;
}
@@ -66,6 +63,9 @@
display: block;
padding: .85em 1em;
}
+ li .itemAction {
+ @apply --gr-dropdown-item;
+ }
li .itemAction.disabled {
color: #ccc;
cursor: default;
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.html b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.html
index 6eb7c8d..ef78f3a 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.html
+++ b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.html
@@ -92,8 +92,8 @@
label="[[labelText]]"
value="{{_inputText}}"></paper-input>
<div class="buttons">
- <gr-button id="cancelBtn" on-tap="_cancel">cancel</gr-button>
- <gr-button id="saveBtn" on-tap="_save">save</gr-button>
+ <gr-button link id="cancelBtn" on-tap="_cancel">cancel</gr-button>
+ <gr-button link id="saveBtn" on-tap="_save">save</gr-button>
</div>
</div>
</div>
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 4133600..c5b0441 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
@@ -21,6 +21,7 @@
<link rel="import" href="../../plugins/gr-dom-hooks/gr-dom-hooks.html">
<link rel="import" href="../../plugins/gr-event-helper/gr-event-helper.html">
<link rel="import" href="../../plugins/gr-popup-interface/gr-popup-interface.html">
+<link rel="import" href="../../plugins/gr-project-api/gr-project-api.html">
<link rel="import" href="../../plugins/gr-theme-api/gr-theme-api.html">
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
<link rel="import" href="../gr-rest-api-interface/gr-rest-api-interface.html">
diff --git a/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js b/polygerrit-ui/app/elements/shared/gr-js-api-interface/gr-public-js-api.js
index 467e012..4e6b8cf 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
@@ -198,6 +198,10 @@
return new GrThemeApi(this);
};
+ Plugin.prototype.project = function() {
+ return new GrProjectApi(this);
+ };
+
Plugin.prototype.attributeHelper = function(element) {
return new GrAttributeHelper(element);
};
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 5153fb0..837552e 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
@@ -765,6 +765,11 @@
// Response may be an array of changes OR an array of arrays of
// changes.
if (opt_query instanceof Array) {
+ // Normalize the response to look like a multi-query response
+ // when there is only one query.
+ if (opt_query.length === 1) {
+ response = [response];
+ }
for (const arr of response) {
iterateOverChanges(arr);
}
@@ -899,6 +904,17 @@
patchRange.patchNum);
},
+ /**
+ * @param {number|string} changeNum
+ * @param {number|string} patchNum
+ * @param {string} query
+ * @return {!Promise<!Object>}
+ */
+ queryChangeFiles(changeNum, patchNum, query) {
+ return this._getChangeURLAndFetch(changeNum,
+ `/files?q=${encodeURIComponent(query)}`, patchNum);
+ },
+
getChangeFilesAsSpeciallySortedArray(changeNum, patchRange) {
return this.getChangeFiles(changeNum, patchRange).then(
this._normalizeChangeFilesResponse.bind(this));
@@ -1249,9 +1265,19 @@
.then(response => this.getResponseObject(response));
},
+ /**
+ * Gets a file in a change edit.
+ * @param {number|string} changeNum
+ * @param {string} path
+ */
getFileInChangeEdit(changeNum, path) {
const e = '/edit/' + encodeURIComponent(path);
- return this.getChangeURLAndSend(changeNum, 'GET', null, e);
+ const headers = {Accept: 'application/json'};
+ return this.getChangeURLAndSend(changeNum, 'GET', null, e, null, null,
+ null, null, headers).then(res => {
+ if (!res.ok) { return res; }
+ return this.getResponseObject(res);
+ });
},
rebaseChangeEdit(changeNum) {
@@ -1279,7 +1305,8 @@
saveChangeEdit(changeNum, path, contents) {
const e = '/edit/' + encodeURIComponent(path);
- return this.getChangeURLAndSend(changeNum, 'PUT', null, e, contents);
+ return this.getChangeURLAndSend(changeNum, 'PUT', null, e, contents, null,
+ null, 'text/plain');
},
// Deprecated, prefer to use putChangeCommitMessage instead.
@@ -1315,8 +1342,10 @@
* passed as null sometimes.
* @param {?=} opt_ctx
* @param {?string=} opt_contentType
+ * @param {Object=} opt_headers
*/
- send(method, url, opt_body, opt_errFn, opt_ctx, opt_contentType) {
+ send(method, url, opt_body, opt_errFn, opt_ctx, opt_contentType,
+ opt_headers) {
const options = {method};
if (opt_body) {
options.headers = new Headers();
@@ -1327,6 +1356,13 @@
}
options.body = opt_body;
}
+ if (opt_headers) {
+ if (!options.headers) { options.headers = new Headers(); }
+ for (const header in opt_headers) {
+ if (!opt_headers.hasOwnProperty(header)) { continue; }
+ options.headers.set(header, opt_headers[header]);
+ }
+ }
return this._auth.fetch(this.getBaseUrl() + url, options)
.then(response => {
if (!response.ok) {
@@ -1335,7 +1371,6 @@
}
this.fire('server-error', {response});
}
-
return response;
}).catch(err => {
this.fire('network-error', {error: err});
@@ -1845,16 +1880,17 @@
* @param {?string} endpoint gets passed as null.
* @param {?Object|number|string=} opt_payload gets passed as null, string,
* Object, or number.
- * @param {function(?Response, string=)=} opt_errFn
+ * @param {?function(?Response, string=)=} opt_errFn
* @param {?=} opt_ctx
* @param {?=} opt_contentType
+ * @param {Object=} opt_headers
* @return {!Promise<!Object>}
*/
getChangeURLAndSend(changeNum, method, patchNum, endpoint, opt_payload,
- opt_errFn, opt_ctx, opt_contentType) {
+ opt_errFn, opt_ctx, opt_contentType, opt_headers) {
return this._changeBaseURL(changeNum, patchNum).then(url => {
return this.send(method, url + endpoint, opt_payload, opt_errFn,
- opt_ctx, opt_contentType);
+ opt_ctx, opt_contentType, opt_headers);
});
},
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 eb4f418..aa7f9a0 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
@@ -701,6 +701,15 @@
assert.equal(sendStub.lastCall.args[1], '/projects/x%2Fy');
});
+ test('queryChangeFiles', () => {
+ const fetchStub = sandbox.stub(element, '_getChangeURLAndFetch')
+ .returns(Promise.resolve());
+ return element.queryChangeFiles('42', 'edit', 'test/path.js').then(() => {
+ assert.deepEqual(fetchStub.lastCall.args,
+ ['42', '/files?q=test%2Fpath.js', 'edit']);
+ });
+ });
+
test('getProjects', () => {
sandbox.stub(element, '_fetchSharedCacheURL');
element.getProjects('test', 25);
diff --git a/polygerrit-ui/app/samples/project-command.html b/polygerrit-ui/app/samples/project-command.html
new file mode 100644
index 0000000..8131a02
--- /dev/null
+++ b/polygerrit-ui/app/samples/project-command.html
@@ -0,0 +1,42 @@
+<dom-module id="sample-project-command">
+ <script>
+ Gerrit.install(plugin => {
+ // High-level API
+ plugin.project()
+ .createCommand('Bork', (projectName, projectConfig) => {
+ if (projectName !== 'All-Projects') {
+ return false;
+ }
+ }).onTap(() => {
+ alert('Bork, bork!');
+ });
+
+ // Low-level API
+ plugin.registerCustomComponent(
+ 'project-command', 'project-command-low');
+ });
+ </script>
+</dom-module>
+
+<!-- Low-level custom component for project command. -->
+<dom-module id="project-command-low">
+ <template>
+ <gr-project-command
+ title="Low-level bork"
+ on-command-tap="_handleCommandTap">
+ </gr-project-command>
+ </template>
+ <script>
+ Polymer({
+ is: 'project-command-low',
+ attached() {
+ console.log(this.projectName);
+ console.log(this.config);
+ this.hidden = this.projectName !== 'All-Projects';
+ },
+ _handleCommandTap() {
+ alert('(softly) bork, bork.');
+ },
+ });
+ </script>
+</dom-module>
diff --git a/polygerrit-ui/app/template_test_srcs/template_test.js b/polygerrit-ui/app/template_test_srcs/template_test.js
index dffcaf9..401f0aa 100644
--- a/polygerrit-ui/app/template_test_srcs/template_test.js
+++ b/polygerrit-ui/app/template_test_srcs/template_test.js
@@ -22,7 +22,9 @@
'GrDiffGroup',
'GrDiffLine',
'GrDomHooks',
+ 'GrEditConstants',
'GrEtagDecorator',
+ 'GrFileListConstants',
'GrGapiAuth',
'GrGerritAuth',
'GrLinkTextParser',
diff --git a/polygerrit-ui/app/test/index.html b/polygerrit-ui/app/test/index.html
index 7080eb7..a690192 100644
--- a/polygerrit-ui/app/test/index.html
+++ b/polygerrit-ui/app/test/index.html
@@ -26,6 +26,7 @@
const behaviorsPath = '../behaviors/';
// Elements tests.
+ /* eslint-disable max-len */
const elements = [
// This seemed to be flakey when it was farther down the list. Keep at the
// beginning.
@@ -44,6 +45,7 @@
'admin/gr-permission/gr-permission_test.html',
'admin/gr-plugin-list/gr-plugin-list_test.html',
'admin/gr-project-access/gr-project-access_test.html',
+ 'admin/gr-project-command/gr-project-command_test.html',
'admin/gr-project-commands/gr-project-commands_test.html',
'admin/gr-project-detail-list/gr-project-detail-list_test.html',
'admin/gr-project-list/gr-project-list_test.html',
@@ -102,10 +104,14 @@
'diff/gr-selection-action-box/gr-selection-action-box_test.html',
'diff/gr-syntax-layer/gr-syntax-layer_test.html',
'diff/gr-syntax-lib-loader/gr-syntax-lib-loader_test.html',
+ 'edit/gr-edit-controls/gr-edit-controls_test.html',
+ 'edit/gr-edit-file-controls/gr-edit-file-controls_test.html',
+ 'edit/gr-editor-view/gr-editor-view_test.html',
'plugins/gr-attribute-helper/gr-attribute-helper_test.html',
'plugins/gr-endpoint-decorator/gr-endpoint-decorator_test.html',
'plugins/gr-event-helper/gr-event-helper_test.html',
'plugins/gr-external-style/gr-external-style_test.html',
+ 'plugins/gr-project-api/gr-project-api_test.html',
'plugins/gr-plugin-host/gr-plugin-host_test.html',
'plugins/gr-popup-interface/gr-plugin-popup_test.html',
'plugins/gr-popup-interface/gr-popup-interface_test.html',
@@ -153,6 +159,7 @@
'shared/gr-tooltip-content/gr-tooltip-content_test.html',
'shared/gr-tooltip/gr-tooltip_test.html',
];
+ /* eslint-enable max-len */
for (let file of elements) {
file = elementsPath + file;
testFiles.push(file);
@@ -160,6 +167,7 @@
}
// Behaviors tests.
+ /* eslint-disable max-len */
const behaviors = [
'async-foreach-behavior/async-foreach-behavior_test.html',
'base-url-behavior/base-url-behavior_test.html',
@@ -173,6 +181,7 @@
'gr-path-list-behavior/gr-path-list-behavior_test.html',
'gr-tooltip-behavior/gr-tooltip-behavior_test.html',
];
+ /* eslint-enable max-len */
for (let file of behaviors) {
// Behaviors do not utilize the DOM, so no shadow DOM test is necessary.
file = behaviorsPath + file;
diff --git a/gerrit-acceptance-framework/pom.xml b/tools/maven/gerrit-acceptance-framework_pom.xml
similarity index 97%
rename from gerrit-acceptance-framework/pom.xml
rename to tools/maven/gerrit-acceptance-framework_pom.xml
index fc90095..a0f2e67 100644
--- a/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>2.15-rc1</version>
+ <version>2.16-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Gerrit Code Review - Acceptance Test Framework</name>
<description>Framework for Gerrit's acceptance tests</description>
diff --git a/gerrit-extension-api/pom.xml b/tools/maven/gerrit-extension-api_pom.xml
similarity index 97%
rename from gerrit-extension-api/pom.xml
rename to tools/maven/gerrit-extension-api_pom.xml
index 4724776..a8ae2e6 100644
--- a/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>2.15-rc1</version>
+ <version>2.16-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Gerrit Code Review - Extension API</name>
<description>API for Gerrit Extensions</description>
diff --git a/gerrit-plugin-api/pom.xml b/tools/maven/gerrit-plugin-api_pom.xml
similarity index 97%
rename from gerrit-plugin-api/pom.xml
rename to tools/maven/gerrit-plugin-api_pom.xml
index 9027450..84df44a 100644
--- a/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>2.15-rc1</version>
+ <version>2.16-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Gerrit Code Review - Plugin API</name>
<description>API for Gerrit Plugins</description>
diff --git a/gerrit-plugin-gwtui/pom.xml b/tools/maven/gerrit-plugin-gwtui_pom.xml
similarity index 97%
rename from gerrit-plugin-gwtui/pom.xml
rename to tools/maven/gerrit-plugin-gwtui_pom.xml
index e0b23aa..cc9aafc 100644
--- a/gerrit-plugin-gwtui/pom.xml
+++ b/tools/maven/gerrit-plugin-gwtui_pom.xml
@@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.google.gerrit</groupId>
<artifactId>gerrit-plugin-gwtui</artifactId>
- <version>2.15-rc1</version>
+ <version>2.16-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Gerrit Code Review - Plugin GWT UI</name>
<description>Common Classes for Gerrit GWT UI Plugins</description>
diff --git a/gerrit-war/pom.xml b/tools/maven/gerrit-war_pom.xml
similarity index 97%
rename from gerrit-war/pom.xml
rename to tools/maven/gerrit-war_pom.xml
index aca36b7..c43c098 100644
--- a/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>2.15-rc1</version>
+ <version>2.16-SNAPSHOT</version>
<packaging>war</packaging>
<name>Gerrit Code Review - WAR</name>
<description>Gerrit WAR</description>
diff --git a/tools/maven/mvn.py b/tools/maven/mvn.py
index f7b5aa8..2e1c1a9 100755
--- a/tools/maven/mvn.py
+++ b/tools/maven/mvn.py
@@ -56,7 +56,7 @@
for spec in args.s:
artifact, packaging_type, src = spec.split(':')
exe = cmd + [
- '-DpomFile=%s' % path.join(root, '%s/pom.xml' % artifact),
+ '-DpomFile=%s' % path.join(root, 'tools', 'maven', '%s_pom.xml' % artifact),
'-Dpackaging=%s' % packaging_type,
'-Dfile=%s' % src,
]
diff --git a/tools/release-announcement-template.txt b/tools/release-announcement-template.txt
index 87f5d49..2702f57 100644
--- a/tools/release-announcement-template.txt
+++ b/tools/release-announcement-template.txt
@@ -7,7 +7,7 @@
http://gerrit-documentation.storage.googleapis.com/Documentation/{{ data.version }}/index.html
{% if data.previous %}
Log of changes since {{ data.previous }}:
-https://gerrit.googlesource.com/gerrit/+log/v{{ data.previous }}..v{{ data.version }}
+https://gerrit.googlesource.com/gerrit/+log/v{{ data.previous }}..v{{ data.version }}?no-merges
{% endif %}
Download:
https://gerrit-releases.storage.googleapis.com/gerrit-{{ data.version }}.war
diff --git a/tools/version.py b/tools/version.py
index fed6d5d..72b0134 100755
--- a/tools/version.py
+++ b/tools/version.py
@@ -48,7 +48,7 @@
for project in ['gerrit-acceptance-framework', 'gerrit-extension-api',
'gerrit-plugin-api', 'gerrit-plugin-gwtui',
'gerrit-war']:
- pom = os.path.join(project, 'pom.xml')
+ pom = os.path.join('tools', 'maven', '%s_pom.xml' % project)
replace_in_file(pom, src_pattern)
src_pattern = re.compile(r'^(GERRIT_VERSION = ")([-.\w]+)(")$', re.MULTILINE)
diff --git a/version.bzl b/version.bzl
index 9f582b3..9a97721 100644
--- a/version.bzl
+++ b/version.bzl
@@ -2,4 +2,8 @@
# Used by :api_install and :api_deploy targets
# when talking to the destination repository.
#
-GERRIT_VERSION = "2.15-rc1"
+GERRIT_VERSION = "2.16-SNAPSHOT"
+
+def check_version(x):
+ if native.bazel_version < x:
+ fail("\nERROR: Current Bazel version is {}, expected at least {}\n".format(native.bazel_version, x))