Merge branch 'stable-2.15' * stable-2.15: Fix new comments from disappearing when expand/collapse diff Allow to copy text from comment box in Chrome v62 Change-Id: Id84831144732a52834618593911576c8eec30949
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..2909fee 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,20 +350,20 @@ 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" +MAIL_VERS = "1.6.0" maven_jar( name = "mail", artifact = "com.sun.mail:javax.mail:" + MAIL_VERS, - sha1 = "ab5daef2f881c42c8e280cbe918ec4d7fdfd7efe", + sha1 = "a055c648842c4954c1f7db7254f45d9ad565e278", ) MIME4J_VERS = "0.8.0" @@ -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/contrib/abandon_stale.py b/contrib/abandon_stale.py index 99022aa..cb1cc23 100755 --- a/contrib/abandon_stale.py +++ b/contrib/abandon_stale.py
@@ -71,6 +71,9 @@ action='store_true', help='enable dry-run mode: show stale changes but do ' 'not abandon them') + parser.add_option('-t', '--test', dest='testmode', action='store_true', + help='test mode: query changes with the `test-abandon` ' + 'topic and ignore age option') parser.add_option('-a', '--age', dest='age', metavar='AGE', default="6months", @@ -114,13 +117,16 @@ logging.error("Gerrit URL is required") return 1 - pattern = re.compile(r"^([\d]+)(month[s]?|year[s]?|week[s]?)") - match = pattern.match(options.age) - if not match: - logging.error("Invalid age: %s", options.age) - return 1 - message = "Abandoning after %s %s or more of inactivity." % \ - (match.group(1), match.group(2)) + if options.testmode: + message = "Abandoning in test mode" + else: + pattern = re.compile(r"^([\d]+)(month[s]?|year[s]?|week[s]?)") + match = pattern.match(options.age) + if not match: + logging.error("Invalid age: %s", options.age) + return 1 + message = "Abandoning after %s %s or more of inactivity." % \ + (match.group(1), match.group(2)) if options.digest_auth: auth_type = HTTPDigestAuthFromNetrc @@ -139,7 +145,10 @@ stale_changes = [] offset = 0 step = 500 - query_terms = ["status:new", "age:%s" % options.age] + if options.testmode: + query_terms = ["status:new", "owner:self", "topic:test-abandon"] + else: + query_terms = ["status:new", "age:%s" % options.age] if options.branches: query_terms += ["branch:%s" % b for b in options.branches] elif options.exclude_branches: @@ -148,7 +157,7 @@ query_terms += ["project:%s" % p for p in options.projects] elif options.exclude_projects: query_terms = ["-project:%s" % p for p in options.exclude_projects] - if options.owner: + if options.owner and not options.testmode: query_terms += ["owner:%s" % options.owner] query = "%20".join(query_terms) while True: @@ -191,7 +200,7 @@ try: gerrit.post("/changes/" + change_id + "/abandon", - json={"message" : "%s" % abandon_message}) + json={"message": "%s" % abandon_message}) abandoned += 1 except Exception as e: errors += 1 @@ -200,5 +209,6 @@ if not options.dry_run: logging.info("Abandoned %d changes. %d errors.", abandoned, errors) + if __name__ == "__main__": sys.exit(_main())
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..4b855f3 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; @@ -717,10 +722,6 @@ return gApi.changes().id(id).info(); } - protected ChangeInfo get(String id) throws RestApiException { - return gApi.changes().id(id).get(); - } - protected Optional<EditInfo> getEdit(String id) throws RestApiException { return gApi.changes().id(id).edit().get(); } @@ -1007,7 +1008,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 +1383,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/AbandonIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/AbandonIT.java index 2c1a5b3..a7ca55e 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/AbandonIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/AbandonIT.java
@@ -15,6 +15,7 @@ package com.google.gerrit.acceptance.api.change; import static com.google.common.truth.Truth.assertThat; +import static com.google.gerrit.extensions.client.ListChangesOption.MESSAGES; import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS; import static java.util.concurrent.TimeUnit.HOURS; import static java.util.concurrent.TimeUnit.SECONDS; @@ -50,7 +51,7 @@ String changeId = r.getChangeId(); assertThat(info(changeId).status).isEqualTo(ChangeStatus.NEW); gApi.changes().id(changeId).abandon(); - ChangeInfo info = get(changeId); + ChangeInfo info = get(changeId, MESSAGES); assertThat(info.status).isEqualTo(ChangeStatus.ABANDONED); assertThat(Iterables.getLast(info.messages).message.toLowerCase()).contains("abandoned"); @@ -68,12 +69,12 @@ changeAbandoner.batchAbandon( batchUpdateFactory, a.getChange().project(), user, list, "deadbeef"); - ChangeInfo info = get(a.getChangeId()); + ChangeInfo info = get(a.getChangeId(), MESSAGES); assertThat(info.status).isEqualTo(ChangeStatus.ABANDONED); assertThat(Iterables.getLast(info.messages).message.toLowerCase()).contains("abandoned"); assertThat(Iterables.getLast(info.messages).message.toLowerCase()).contains("deadbeef"); - info = get(b.getChangeId()); + info = get(b.getChangeId(), MESSAGES); assertThat(info.status).isEqualTo(ChangeStatus.ABANDONED); assertThat(Iterables.getLast(info.messages).message.toLowerCase()).contains("abandoned"); assertThat(Iterables.getLast(info.messages).message.toLowerCase()).contains("deadbeef"); @@ -155,7 +156,7 @@ assertThat(info(changeId).status).isEqualTo(ChangeStatus.ABANDONED); gApi.changes().id(changeId).restore(); - ChangeInfo info = get(changeId); + ChangeInfo info = get(changeId, MESSAGES); assertThat(info.status).isEqualTo(ChangeStatus.NEW); assertThat(Iterables.getLast(info.messages).message.toLowerCase()).contains("restored");
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..faede93 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; @@ -1105,7 +1107,7 @@ String changeId = r.getChangeId(); assertThat(info(changeId).status).isEqualTo(ChangeStatus.NEW); gApi.changes().id(changeId).abandon(); - ChangeInfo info = get(changeId); + ChangeInfo info = info(changeId); assertThat(info.status).isEqualTo(ChangeStatus.ABANDONED); exception.expect(ResourceConflictException.class); @@ -1124,7 +1126,7 @@ String changeId = r.getChangeId(); assertThat(info(changeId).status).isEqualTo(ChangeStatus.NEW); gApi.changes().id(changeId).abandon(); - ChangeInfo info = get(changeId); + ChangeInfo info = info(changeId); assertThat(info.status).isEqualTo(ChangeStatus.ABANDONED); RebaseInput ri = new RebaseInput(); @@ -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); @@ -2108,10 +2109,10 @@ gApi.changes().id(r.getChangeId()).addReviewer(in); setApiUser(user); - assertThat(get(r.getChangeId()).reviewed).isNull(); + assertThat(get(r.getChangeId(), REVIEWED).reviewed).isNull(); revision(r).review(ReviewInput.recommend()); - assertThat(get(r.getChangeId()).reviewed).isTrue(); + assertThat(get(r.getChangeId(), REVIEWED).reviewed).isTrue(); } @Test @@ -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/api/revision/RevisionIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java index a2814c3..7703d363 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java
@@ -21,6 +21,7 @@ import static com.google.gerrit.acceptance.PushOneCommit.PATCH; import static com.google.gerrit.acceptance.PushOneCommit.PATCH_FILE_ONLY; import static com.google.gerrit.acceptance.PushOneCommit.SUBJECT; +import static com.google.gerrit.extensions.client.ListChangesOption.ALL_REVISIONS; import static com.google.gerrit.extensions.client.ListChangesOption.DETAILED_LABELS; import static com.google.gerrit.reviewdb.client.Patch.COMMIT_MSG; import static com.google.gerrit.reviewdb.client.Patch.MERGE_LIST; @@ -523,7 +524,7 @@ gApi.changes().id(t2).restore(); gApi.changes().id(t1).current().cherryPick(in); - assertThat(get(t2).revisions).hasSize(2); + assertThat(get(t2, ALL_REVISIONS).revisions).hasSize(2); assertThat(gApi.changes().id(t2).current().file(FILE_NAME).content().asString()).isEqualTo("a"); }
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/edit/ChangeEditIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/ChangeEditIT.java index 4ca4498..8e74b7e 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/ChangeEditIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/ChangeEditIT.java
@@ -16,6 +16,10 @@ import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.common.truth.Truth.assertThat; +import static com.google.gerrit.extensions.client.ListChangesOption.CURRENT_COMMIT; +import static com.google.gerrit.extensions.client.ListChangesOption.CURRENT_REVISION; +import static com.google.gerrit.extensions.client.ListChangesOption.DETAILED_LABELS; +import static com.google.gerrit.extensions.client.ListChangesOption.MESSAGES; import static com.google.gerrit.extensions.common.EditInfoSubject.assertThat; import static com.google.gerrit.extensions.restapi.BinaryResultSubject.assertThat; import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS; @@ -166,7 +170,7 @@ // The tag for the publish edit change message should vary according // to whether the change was WIP at the time of publishing. - ChangeInfo info = get(changeId); + ChangeInfo info = get(changeId, MESSAGES); assertThat(info.messages).isNotEmpty(); assertThat(Iterables.getLast(info.messages).tag) .isEqualTo(ChangeMessagesUtil.TAG_UPLOADED_PATCH_SET); @@ -176,7 +180,7 @@ createEmptyEditFor(changeId); gApi.changes().id(changeId).edit().modifyFile(FILE_NAME, RawInputUtil.create(CONTENT_NEW2)); gApi.changes().id(changeId).edit().publish(); - info = get(changeId); + info = get(changeId, MESSAGES); assertThat(info.messages).isNotEmpty(); assertThat(Iterables.getLast(info.messages).tag) .isEqualTo(ChangeMessagesUtil.TAG_UPLOADED_WIP_PATCH_SET); @@ -415,7 +419,7 @@ adminRestSession.get(urlEdit(changeId)).assertNoContent(); createArbitraryEditFor(changeId); EditInfo editInfo = getEditInfo(changeId, false); - ChangeInfo changeInfo = get(changeId); + ChangeInfo changeInfo = get(changeId, CURRENT_REVISION, CURRENT_COMMIT); assertThat(editInfo.commit.commit).isNotEqualTo(changeInfo.currentRevision); assertThat(editInfo).commit().parents().hasSize(1); assertThat(editInfo).baseRevision().isEqualTo(changeInfo.currentRevision); @@ -615,7 +619,7 @@ publishInput.notify = NotifyHandling.NONE; gApi.changes().id(changeId).edit().publish(publishInput); - ChangeInfo info = get(changeId); + ChangeInfo info = get(changeId, DETAILED_LABELS); assertThat(info.subject).isEqualTo(newSubj); List<ApprovalInfo> approvals = info.labels.get(cr).all; assertThat(approvals).hasSize(1); @@ -839,7 +843,7 @@ private void assertChangeMessages(String changeId, List<String> expectedMessages) throws Exception { - ChangeInfo ci = get(changeId); + ChangeInfo ci = get(changeId, MESSAGES); assertThat(ci.messages).isNotNull(); assertThat(ci.messages).hasSize(expectedMessages.size()); List<String> actualMessages =
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java index 928cd7e..b9b7503 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
@@ -23,6 +23,9 @@ import static com.google.gerrit.acceptance.PushOneCommit.FILE_NAME; import static com.google.gerrit.common.FooterConstants.CHANGE_ID; import static com.google.gerrit.extensions.client.ListChangesOption.ALL_REVISIONS; +import static com.google.gerrit.extensions.client.ListChangesOption.CURRENT_REVISION; +import static com.google.gerrit.extensions.client.ListChangesOption.DETAILED_ACCOUNTS; +import static com.google.gerrit.extensions.client.ListChangesOption.DETAILED_LABELS; import static com.google.gerrit.extensions.client.ListChangesOption.MESSAGES; import static com.google.gerrit.extensions.common.EditInfoSubject.assertThat; import static com.google.gerrit.server.git.receive.ReceiveConstants.PUSH_OPTION_SKIP_VALIDATION; @@ -672,7 +675,7 @@ PushOneCommit.Result r = pushTo("refs/for/master/%m=my_test_message"); r.assertOkStatus(); r.assertChange(Change.Status.NEW, null); - ChangeInfo ci = get(r.getChangeId()); + ChangeInfo ci = get(r.getChangeId(), MESSAGES, ALL_REVISIONS); Collection<ChangeMessageInfo> changeMessages = ci.messages; assertThat(changeMessages).hasSize(1); for (ChangeMessageInfo cm : changeMessages) { @@ -707,7 +710,7 @@ r = push.to("refs/for/master/%m=new_test_message"); r.assertOkStatus(); - ChangeInfo ci = get(r.getChangeId()); + ChangeInfo ci = get(r.getChangeId(), ALL_REVISIONS); Collection<RevisionInfo> revisions = ci.revisions.values(); assertThat(revisions).hasSize(2); for (RevisionInfo ri : revisions) { @@ -723,7 +726,7 @@ public void pushForMasterWithApprovals() throws Exception { PushOneCommit.Result r = pushTo("refs/for/master/%l=Code-Review"); r.assertOkStatus(); - ChangeInfo ci = get(r.getChangeId()); + ChangeInfo ci = get(r.getChangeId(), DETAILED_LABELS, MESSAGES, DETAILED_ACCOUNTS); LabelInfo cr = ci.labels.get("Code-Review"); assertThat(cr.all).hasSize(1); assertThat(cr.all.get(0).name).isEqualTo("Administrator"); @@ -742,7 +745,7 @@ r.getChangeId()); r = push.to("refs/for/master/%l=Code-Review+2"); - ci = get(r.getChangeId()); + ci = get(r.getChangeId(), DETAILED_LABELS, MESSAGES, DETAILED_ACCOUNTS); cr = ci.labels.get("Code-Review"); assertThat(Iterables.getLast(ci.messages).message) .isEqualTo("Uploaded patch set 2: Code-Review+2."); @@ -763,7 +766,7 @@ "moreContent", r.getChangeId()); r = push.to("refs/for/master/%l=Code-Review+2"); - ci = get(r.getChangeId()); + ci = get(r.getChangeId(), MESSAGES); assertThat(Iterables.getLast(ci.messages).message).isEqualTo("Uploaded patch set 3."); } @@ -783,7 +786,7 @@ r.getChangeId()); r = push.to("refs/for/master/%l=Code-Review+2"); - ChangeInfo ci = get(r.getChangeId()); + ChangeInfo ci = get(r.getChangeId(), DETAILED_LABELS, MESSAGES, DETAILED_ACCOUNTS); LabelInfo cr = ci.labels.get("Code-Review"); assertThat(Iterables.getLast(ci.messages).message) .isEqualTo("Uploaded patch set 2: Code-Review+2."); @@ -828,7 +831,8 @@ // 2. +1 from Administrator (uploader): // On push Code-Review+1 was specified, hence we expect a +1 vote from // the uploader. - ChangeInfo ci = get(GitUtil.getChangeId(testRepo, c).get()); + ChangeInfo ci = + get(GitUtil.getChangeId(testRepo, c).get(), DETAILED_LABELS, MESSAGES, DETAILED_ACCOUNTS); LabelInfo cr = ci.labels.get("Code-Review"); assertThat(cr.all).hasSize(2); int indexAdmin = admin.fullName.equals(cr.all.get(0).name) ? 0 : 1; @@ -864,7 +868,7 @@ pushHead(testRepo, "refs/for/master/%l=Code-Review+1,l=Custom-Label-1", false); - ChangeInfo ci = get(GitUtil.getChangeId(testRepo, c).get()); + ChangeInfo ci = get(GitUtil.getChangeId(testRepo, c).get(), DETAILED_LABELS, DETAILED_ACCOUNTS); LabelInfo cr = ci.labels.get("Code-Review"); assertThat(cr.all).hasSize(1); cr = ci.labels.get("Custom-Label"); @@ -1161,8 +1165,8 @@ private void assertTwoChangesWithSameRevision(PushOneCommit.Result result) throws Exception { List<ChangeInfo> changes = query(result.getCommit().name()); assertThat(changes).hasSize(2); - ChangeInfo c1 = get(changes.get(0).id); - ChangeInfo c2 = get(changes.get(1).id); + ChangeInfo c1 = get(changes.get(0).id, CURRENT_REVISION); + ChangeInfo c2 = get(changes.get(1).id, CURRENT_REVISION); assertThat(c1.project).isEqualTo(c2.project); assertThat(c1.branch).isNotEqualTo(c2.branch); assertThat(c1.changeId).isEqualTo(c2.changeId);
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/AbstractSubmit.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java index 682b5bc..c77f83c 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
@@ -1112,7 +1112,7 @@ } protected void assertNew(String changeId) throws Exception { - assertThat(get(changeId).status).isEqualTo(ChangeStatus.NEW); + assertThat(info(changeId).status).isEqualTo(ChangeStatus.NEW); } protected void assertApproved(String changeId) throws Exception {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmitByRebase.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmitByRebase.java index 5dfc76d..309f8ea 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmitByRebase.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmitByRebase.java
@@ -403,7 +403,7 @@ assertThat(headAfterChange2.getShortMessage()).isEqualTo("Change 2"); assertThat(headAfterChange1).isEqualTo(headAfterChange2.getParent(0)); - ChangeInfo info2 = get(change2.getChangeId()); + ChangeInfo info2 = info(change2.getChangeId()); assertThat(info2.status).isEqualTo(ChangeStatus.MERGED); }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AssigneeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AssigneeIT.java index a905d38..cc0a1cd 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AssigneeIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AssigneeIT.java
@@ -16,6 +16,7 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.TruthJUnit.assume; +import static com.google.gerrit.extensions.client.ListChangesOption.DETAILED_LABELS; import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS; import static java.util.concurrent.TimeUnit.SECONDS; @@ -178,7 +179,7 @@ private Iterable<AccountInfo> getReviewers(PushOneCommit.Result r, ReviewerState state) throws Exception { - return get(r.getChangeId()).reviewers.get(state); + return get(r.getChangeId(), DETAILED_LABELS).reviewers.get(state); } private AccountInfo setAssignee(PushOneCommit.Result r, String identifieer) throws Exception {
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/ChangeMessagesIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java index 4c49e4c..2179fa0 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java
@@ -15,6 +15,7 @@ package com.google.gerrit.acceptance.rest.change; import static com.google.common.truth.Truth.assertThat; +import static com.google.gerrit.extensions.client.ListChangesOption.MESSAGES; import static java.util.concurrent.TimeUnit.SECONDS; import com.google.gerrit.acceptance.AbstractDaemonTest; @@ -56,7 +57,7 @@ @Test public void defaultMessage() throws Exception { String changeId = createChange().getChangeId(); - ChangeInfo c = get(changeId); + ChangeInfo c = get(changeId, MESSAGES); assertThat(c.messages).isNotNull(); assertThat(c.messages).hasSize(1); assertThat(c.messages.iterator().next().message).isEqualTo("Uploaded patch set 1."); @@ -69,7 +70,7 @@ postMessage(changeId, firstMessage); String secondMessage = "I like this feature."; postMessage(changeId, secondMessage); - ChangeInfo c = get(changeId); + ChangeInfo c = get(changeId, MESSAGES); assertThat(c.messages).isNotNull(); assertThat(c.messages).hasSize(3); Iterator<ChangeMessageInfo> it = c.messages.iterator(); @@ -84,7 +85,7 @@ String tag = "jenkins"; String msg = "Message with tag."; postMessage(changeId, msg, tag); - ChangeInfo c = get(changeId); + ChangeInfo c = get(changeId, MESSAGES); assertThat(c.messages).isNotNull(); assertThat(c.messages).hasSize(2); Iterator<ChangeMessageInfo> it = c.messages.iterator();
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/change/SubmitByCherryPickIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java index a385932..d895bc7 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByCherryPickIT.java
@@ -16,6 +16,8 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.TruthJUnit.assume; +import static com.google.gerrit.extensions.client.ListChangesOption.CURRENT_REVISION; +import static com.google.gerrit.extensions.client.ListChangesOption.MESSAGES; import com.google.common.collect.Iterables; import com.google.gerrit.acceptance.PushOneCommit; @@ -111,7 +113,7 @@ handle.remove(); } testRepo.git().fetch().setRemote("origin").call(); - ChangeInfo info = get(change.getChangeId()); + ChangeInfo info = get(change.getChangeId(), CURRENT_REVISION); RevCommit c = testRepo.getRevWalk().parseCommit(ObjectId.fromString(info.currentRevision)); testRepo.getRevWalk().parseBody(c); assertThat(c.getFooterLines("Custom")).containsExactly("refs/heads/master"); @@ -374,7 +376,7 @@ assertThat(getRemoteHead()).isEqualTo(headAfterFirstSubmit); - ChangeInfo info2 = get(change2.getChangeId()); + ChangeInfo info2 = get(change2.getChangeId(), MESSAGES); assertThat(info2.status).isEqualTo(ChangeStatus.MERGED); assertThat(Iterables.getLast(info2.messages).message) .isEqualTo(CommitMergeStatus.SKIPPED_IDENTICAL_TREE.getMessage());
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByRebaseAlwaysIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByRebaseAlwaysIT.java index e4c929a..e8b8fe8 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByRebaseAlwaysIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByRebaseAlwaysIT.java
@@ -15,6 +15,7 @@ package com.google.gerrit.acceptance.rest.change; import static com.google.common.truth.Truth.assertThat; +import static com.google.gerrit.extensions.client.ListChangesOption.CURRENT_REVISION; import com.google.gerrit.acceptance.PushOneCommit; import com.google.gerrit.acceptance.TestProjectInput; @@ -126,7 +127,7 @@ private RevCommit getCurrentCommit(PushOneCommit.Result change) throws Exception { testRepo.git().fetch().setRemote("origin").call(); - ChangeInfo info = get(change.getChangeId()); + ChangeInfo info = get(change.getChangeId(), CURRENT_REVISION); RevCommit c = testRepo.getRevWalk().parseCommit(ObjectId.fromString(info.currentRevision)); testRepo.getRevWalk().parseBody(c); return c;
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 49588e7a..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/event/CommentAddedEventIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/event/CommentAddedEventIT.java index 56c55e4..ad970ca 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/event/CommentAddedEventIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/event/CommentAddedEventIT.java
@@ -15,6 +15,7 @@ package com.google.gerrit.acceptance.server.event; import static com.google.common.truth.Truth.assertThat; +import static com.google.gerrit.extensions.client.ListChangesOption.DETAILED_LABELS; 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; @@ -124,7 +125,7 @@ revision(r).review(reviewInput); // push a new revision with +1 vote - ChangeInfo c = get(r.getChangeId()); + ChangeInfo c = info(r.getChangeId()); r = amendChange(c.changeId); reviewInput = new ReviewInput().label(label.getName(), (short) 1); revision(r).review(reviewInput); @@ -206,7 +207,7 @@ reviewInput.message = label.getName(); revision(r).review(reviewInput); - ChangeInfo c = get(r.getChangeId()); + ChangeInfo c = get(r.getChangeId(), DETAILED_LABELS); LabelInfo q = c.labels.get(label.getName()); assertThat(q.all).hasSize(1); ApprovalValues labelAttr = getApprovalValues(label); @@ -233,7 +234,7 @@ reviewInput.message = pLabel.getName(); revision(r).review(reviewInput); - c = get(r.getChangeId()); + c = get(r.getChangeId(), DETAILED_LABELS); q = c.labels.get(label.getName()); assertThat(q.all).hasSize(1); pLabelAttr = getApprovalValues(pLabel);
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..314c614 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,14 @@ 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.MAX_WITH_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.extensions.client.ListChangesOption.DETAILED_LABELS; +import static com.google.gerrit.extensions.client.ListChangesOption.LABELS; +import static com.google.gerrit.extensions.client.ListChangesOption.SUBMITTABLE; 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; @@ -26,7 +34,6 @@ import com.google.gerrit.common.data.Permission; import com.google.gerrit.extensions.api.changes.AddReviewerInput; import com.google.gerrit.extensions.api.changes.ReviewInput; -import com.google.gerrit.extensions.client.ListChangesOption; import com.google.gerrit.extensions.common.ChangeInfo; import com.google.gerrit.extensions.common.LabelInfo; import com.google.gerrit.extensions.events.CommentAddedListener; @@ -80,52 +87,83 @@ @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.approved).isNull(); + assertThat(q.recommended).isNull(); + assertThat(q.disliked).isNull(); assertThat(q.rejected).isNotNull(); assertThat(q.blocking).isNull(); } @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.approved).isNull(); + assertThat(q.recommended).isNull(); + assertThat(q.disliked).isNull(); assertThat(q.rejected).isNotNull(); assertThat(q.blocking).isNull(); } @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.approved).isNull(); + assertThat(q.recommended).isNull(); + assertThat(q.disliked).isNull(); assertThat(q.rejected).isNotNull(); assertThat(q.blocking).isNull(); } @Test + public void customLabelMaxNoBlock_MaxVoteSubmittable() throws Exception { + label.setFunction(MAX_NO_BLOCK); + P.setFunction(NO_OP); + saveLabelConfig(); + PushOneCommit.Result r = createChange(); + assertThat(info(r.getChangeId()).submittable).isNull(); + revision(r).review(ReviewInput.approve().label(label.getName(), 1)); + + ChangeInfo c = getWithLabels(r); + assertThat(c.submittable).isTrue(); + LabelInfo q = c.labels.get(label.getName()); + assertThat(q.all).hasSize(1); + assertThat(q.approved).isNotNull(); + assertThat(q.recommended).isNull(); + assertThat(q.disliked).isNull(); + assertThat(q.rejected).isNull(); + assertThat(q.blocking).isNull(); + } + + @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.approved).isNull(); + assertThat(q.recommended).isNull(); assertThat(q.disliked).isNull(); assertThat(q.rejected).isNotNull(); assertThat(q.blocking).isTrue(); @@ -133,7 +171,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,9 +182,11 @@ 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.approved).isNull(); + assertThat(q.recommended).isNull(); assertThat(q.disliked).isNull(); assertThat(q.rejected).isNull(); assertThat(q.blocking).isNull(); @@ -155,29 +195,52 @@ @Test public void customLabelMaxWithBlock_NegativeVoteBlock() throws Exception { + label.setFunction(MAX_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.approved).isNull(); + assertThat(q.recommended).isNull(); assertThat(q.disliked).isNull(); assertThat(q.rejected).isNotNull(); assertThat(q.blocking).isTrue(); } @Test + public void customLabelMaxWithBlock_MaxVoteSubmittable() throws Exception { + label.setFunction(MAX_WITH_BLOCK); + P.setFunction(NO_OP); + saveLabelConfig(); + PushOneCommit.Result r = createChange(); + assertThat(info(r.getChangeId()).submittable).isNull(); + revision(r).review(ReviewInput.approve().label(label.getName(), 1)); + + ChangeInfo c = getWithLabels(r); + assertThat(c.submittable).isTrue(); + LabelInfo q = c.labels.get(label.getName()); + assertThat(q.all).hasSize(1); + assertThat(q.approved).isNotNull(); + assertThat(q.recommended).isNull(); + assertThat(q.disliked).isNull(); + assertThat(q.rejected).isNull(); + assertThat(q.blocking).isNull(); + } + + @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 +262,8 @@ cfg.getLabelSections().put(P.getName(), P); saveProjectConfig(project, cfg); } + + private ChangeInfo getWithLabels(PushOneCommit.Result r) throws Exception { + return get(r.getChangeId(), LABELS, DETAILED_LABELS, SUBMITTABLE); + } }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/AbandonRestoreIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/AbandonRestoreIT.java index 96852b3..f0b937c 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/AbandonRestoreIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/AbandonRestoreIT.java
@@ -16,6 +16,7 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; +import static com.google.gerrit.extensions.client.ListChangesOption.MESSAGES; import com.google.common.collect.ImmutableList; import com.google.gerrit.acceptance.AbstractDaemonTest; @@ -67,7 +68,7 @@ } private void assertChangeMessages(String changeId, List<String> expected) throws Exception { - ChangeInfo c = get(changeId); + ChangeInfo c = get(changeId, MESSAGES); Iterable<ChangeMessageInfo> messages = c.messages; assertThat(messages).isNotNull(); assertThat(messages).hasSize(expected.size());
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-index/src/main/java/com/google/gerrit/index/query/InternalQuery.java b/gerrit-index/src/main/java/com/google/gerrit/index/query/InternalQuery.java index 0f8948b..3a4b372 100644 --- a/gerrit-index/src/main/java/com/google/gerrit/index/query/InternalQuery.java +++ b/gerrit-index/src/main/java/com/google/gerrit/index/query/InternalQuery.java
@@ -14,15 +14,19 @@ package com.google.gerrit.index.query; +import static com.google.common.base.Preconditions.checkArgument; +import static java.util.stream.Collectors.toSet; + import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; +import com.google.gerrit.index.FieldDef; import com.google.gerrit.index.Index; import com.google.gerrit.index.IndexCollection; import com.google.gerrit.index.IndexConfig; import com.google.gerrit.index.Schema; import com.google.gwtorm.server.OrmException; +import java.util.Arrays; import java.util.List; -import java.util.Set; /** * Execute a single query over a secondary index, for use by Gerrit internals. @@ -59,13 +63,16 @@ return this; } - public InternalQuery<T> setRequestedFields(Set<String> fields) { - queryProcessor.setRequestedFields(fields); + @SuppressWarnings("unchecked") // Can't set @SafeVarargs on a non-final method. + public InternalQuery<T> setRequestedFields(FieldDef<T, ?>... fields) { + checkArgument(fields.length > 0, "requested field list is empty"); + queryProcessor.setRequestedFields( + Arrays.stream(fields).map(FieldDef::getName).collect(toSet())); return this; } public InternalQuery<T> noFields() { - queryProcessor.setRequestedFields(ImmutableSet.<String>of()); + queryProcessor.setRequestedFields(ImmutableSet.of()); return this; }
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/ReviewerRecommender.java b/gerrit-server/src/main/java/com/google/gerrit/server/ReviewerRecommender.java index 2427d30..402a8eb 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/ReviewerRecommender.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/ReviewerRecommender.java
@@ -20,7 +20,6 @@ import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; import com.google.gerrit.common.data.LabelType; import com.google.gerrit.extensions.registration.DynamicMap; import com.google.gerrit.index.query.Predicate; @@ -205,7 +204,7 @@ queryProvider .get() .setLimit(25) - .setRequestedFields(ImmutableSet.of(ChangeField.APPROVAL.getName())) + .setRequestedFields(ChangeField.APPROVAL) .query(changeQueryBuilder.owner("self")); Map<Account.Id, MutableDouble> suggestions = new HashMap<>(); for (ChangeData cd : result) { @@ -267,8 +266,7 @@ } } - List<List<ChangeData>> result = - queryProvider.get().setLimit(25).setRequestedFields(ImmutableSet.of()).query(predicates); + List<List<ChangeData>> result = queryProvider.get().setLimit(25).noFields().query(predicates); Iterator<List<ChangeData>> queryResultIterator = result.iterator(); Iterator<Account.Id> reviewersIterator = reviewers.keySet().iterator();
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..e2c08ce 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
@@ -284,9 +284,11 @@ public ImmutableListMultimap<Account.Id, String> byChangeFromIndex(Change.Id changeId) throws OrmException { - Set<String> fields = ImmutableSet.of(ChangeField.ID.getName(), ChangeField.STAR.getName()); List<ChangeData> changeData = - queryProvider.get().setRequestedFields(fields).byLegacyChangeId(changeId); + queryProvider + .get() + .setRequestedFields(ChangeField.ID, ChangeField.STAR) + .byLegacyChangeId(changeId); if (changeData.size() != 1) { throw new NoSuchChangeException(changeId); } @@ -420,7 +422,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..be4add0 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,17 @@ 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 +35,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 +51,31 @@ * 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(). + return q.setRequestedFields(ChangeField.CHANGE, ChangeField.PATCH_SET, ChangeField.MERGEABLE); } 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 +86,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 +106,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 +127,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 +142,7 @@ */ private ChangeSet topicClosure( ReviewDb db, - ChangeSet cs, + ChangeSet changeSet, CurrentUser user, Set<String> topicsSeen, Set<String> visibleTopicsSeen) @@ -363,13 +150,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 +166,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 +181,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/SearchingChangeCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/SearchingChangeCacheImpl.java index abce2d2..6ae8b91 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/SearchingChangeCacheImpl.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/SearchingChangeCacheImpl.java
@@ -17,7 +17,6 @@ import com.google.auto.value.AutoValue; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; -import com.google.common.collect.ImmutableSet; import com.google.gerrit.common.Nullable; import com.google.gerrit.extensions.events.GitReferenceUpdatedListener; import com.google.gerrit.extensions.registration.DynamicSet; @@ -149,8 +148,7 @@ List<ChangeData> cds = queryProvider .get() - .setRequestedFields( - ImmutableSet.of(ChangeField.CHANGE.getName(), ChangeField.REVIEWER.getName())) + .setRequestedFields(ChangeField.CHANGE, ChangeField.REVIEWER) .byProject(key); List<CachedChange> result = new ArrayList<>(cds.size()); for (ChangeData cd : cds) {
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..723fef4 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
@@ -16,12 +16,12 @@ import com.google.auto.value.AutoValue; import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.ImmutableSet; import com.google.common.collect.Maps; import com.google.common.collect.Sets; 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; @@ -90,14 +90,6 @@ r, advertiseOpenChanges(allPatchSets)); } - private static final ImmutableSet<String> OPEN_CHANGES_FIELDS = - ImmutableSet.of( - // Required for ChangeIsVisibleToPrdicate. - ChangeField.CHANGE.getName(), - ChangeField.REVIEWER.getName(), - // Required during advertiseOpenChanges. - ChangeField.PATCH_SET.getName()); - private Set<ObjectId> advertiseOpenChanges(Set<ObjectId> allPatchSets) { // Advertise some recent open changes, in case a commit is based on one. int limit = 32; @@ -106,7 +98,12 @@ for (ChangeData cd : queryProvider .get() - .setRequestedFields(OPEN_CHANGES_FIELDS) + .setRequestedFields( + // Required for ChangeIsVisibleToPrdicate. + ChangeField.CHANGE, + ChangeField.REVIEWER, + // Required during advertiseOpenChanges. + ChangeField.PATCH_SET) .enforceVisibility(true) .setLimit(limit) .byProjectOpen(projectName)) {
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/notedb/PrimaryStorageMigrator.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/PrimaryStorageMigrator.java index aaa1c2f..f919efb 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/PrimaryStorageMigrator.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/PrimaryStorageMigrator.java
@@ -27,7 +27,6 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSet; import com.google.gerrit.common.Nullable; import com.google.gerrit.common.TimeUtil; import com.google.gerrit.extensions.restapi.RestApiException; @@ -479,10 +478,7 @@ private Project.NameKey getProject(Change.Id id) throws OrmException { List<ChangeData> cds = - queryProvider - .get() - .setRequestedFields(ImmutableSet.of(ChangeField.PROJECT.getName())) - .byLegacyChangeId(id); + queryProvider.get().setRequestedFields(ChangeField.PROJECT).byLegacyChangeId(id); Set<Project.NameKey> projects = new TreeSet<>(); for (ChangeData cd : cds) { projects.add(cd.project());
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..0e62b3fdc 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/account/InternalAccountQuery.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/account/InternalAccountQuery.java index 1169a25..fdb4aa9 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/query/account/InternalAccountQuery.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/account/InternalAccountQuery.java
@@ -21,6 +21,7 @@ import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; +import com.google.gerrit.index.FieldDef; import com.google.gerrit.index.IndexConfig; import com.google.gerrit.index.Schema; import com.google.gerrit.index.query.InternalQuery; @@ -66,8 +67,9 @@ return this; } + @SafeVarargs @Override - public InternalAccountQuery setRequestedFields(Set<String> fields) { + public final InternalAccountQuery setRequestedFields(FieldDef<AccountState, ?>... fields) { super.setRequestedFields(fields); return this; }
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/InternalChangeQuery.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java index 4d10c0e..e7ccc5a 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
@@ -24,6 +24,7 @@ import com.google.common.base.Strings; import com.google.common.collect.Lists; import com.google.common.collect.Sets; +import com.google.gerrit.index.FieldDef; import com.google.gerrit.index.IndexConfig; import com.google.gerrit.index.query.InternalQuery; import com.google.gerrit.index.query.Predicate; @@ -100,8 +101,9 @@ return this; } + @SafeVarargs @Override - public InternalChangeQuery setRequestedFields(Set<String> fields) { + public final InternalChangeQuery setRequestedFields(FieldDef<ChangeData, ?>... fields) { super.setRequestedFields(fields); return this; }
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/README.md b/polygerrit-ui/README.md index e47d246..6ccdbdf 100644 --- a/polygerrit-ui/README.md +++ b/polygerrit-ui/README.md
@@ -1,25 +1,46 @@ # PolyGerrit -## Installing [Node.js](https://nodejs.org/en/download/) +## Installing [Bazel](https://bazel.build/) + +Follow the instructions +[here](https://gerrit-review.googlesource.com/Documentation/dev-bazel.html#_installation) +to get and install Bazel. + +## Installing [Node.js](https://nodejs.org/en/download/) and npm packages The minimum nodejs version supported is 6.x+ ```sh # Debian experimental sudo apt-get install nodejs-legacy +sudo apt-get install npm # OS X with Homebrew brew install node +brew install npm ``` All other platforms: [download from nodejs.org](https://nodejs.org/en/download/). -## Installing [Bazel](https://bazel.build/) +Various steps below require installing additional npm packages. The full list of +dependencies can be installed with: -Follow the instructions -[here](https://gerrit-review.googlesource.com/Documentation/dev-bazel.html#_installation) -to get and install Bazel. +```sh +sudo npm install -g \ + eslint \ + eslint-config-google \ + eslint-plugin-html \ + fried-twinkie \ + polylint \ + typescript \ + web-component-tester +``` + +It may complain about a missing `typescript@2.3.4` peer dependency, which is +harmless. + +If you're interested in the details, keep reading. ## Local UI, Production Data @@ -78,18 +99,7 @@ ## Running Tests -One-time setup: - -```sh -# Debian/Ubuntu -sudo apt-get install npm - -# OS X with Homebrew -brew install npm - -# All platforms (including those above) -sudo npm install -g web-component-tester -``` +This step requires the `web-component-tester` npm module. Run all web tests: @@ -123,11 +133,7 @@ In addition, we encourage the use of [ESLint](http://eslint.org/). It is available as a command line utility, as well as a plugin for most editors -and IDEs. It, along with a few dependencies, can also be installed through NPM: - -```sh -sudo npm install -g eslint eslint-config-google eslint-plugin-html -``` +and IDEs. `eslint-config-google` is a port of the Google JS Style Guide to an ESLint config module, and `eslint-plugin-html` allows ESLint to lint scripts inside @@ -149,13 +155,9 @@ * To run the linter on all of your local changes: `git diff --name-only master | xargs eslint --ext .html,.js` -We also use the polylint tool to lint use of Polymer. To install polylint, +We also use the `polylint` tool to lint use of Polymer. To install polylint, execute the following command. -```sh -npm install -g polylint -``` - To run polylint, execute the following command. ```sh @@ -168,10 +170,7 @@ - Any functions with optional parameters will need closure annotations. - Any Polymer parameters that are nullable or can be multiple types (other than the one explicitly delared) will need type annotations. -A few dependencies are necessary to run these tests: -``` sh -npm install -g typescript fried-twinkie -``` +These tests require the `typescript` and `fried-twinkie` npm packages. To run on all files, execute the following command: @@ -191,4 +190,4 @@ ```sh bazel test //polygerrit-ui/app:template_test_change-list --test_arg=gr-change-list-view --test_output errors -``` \ No newline at end of file +```
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..31cfb41 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,16 @@ 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="../../edit/gr-edit-constants.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 +33,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 +438,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 +458,13 @@ 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> + on-files-shown-changed="_setShownFiles" + on-file-action-tap="_handleFileActionTap"></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..f84fd13 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, @@ -1287,5 +1288,26 @@ const patchRange = patchRangeRecord.base || {}; return this.patchNumEquals(patchRange.patchNum, this.EDIT_NAME); }, + + _handleFileActionTap(e) { + e.preventDefault(); + const controls = this.$.fileListHeader.$.editControls; + const path = e.detail.path; + switch (e.detail.action) { + case GrEditConstants.Actions.DELETE.id: + controls.openDeleteDialog(path); + break; + case GrEditConstants.Actions.EDIT.id: + Gerrit.Nav.navigateToRelativeUrl( + Gerrit.Nav.getEditUrlForDiff(this._change, path)); + break; + case GrEditConstants.Actions.RENAME.id: + controls.openRenameDialog(path); + break; + case GrEditConstants.Actions.RESTORE.id: + controls.openRestoreDialog(path); + break; + } + }, }); })();
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html index 6b86756..820ad3d 100644 --- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html +++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
@@ -23,6 +23,7 @@ <link rel="import" href="../../../test/common-test-setup.html"/> <script src="../../../bower_components/page/page.js"></script> +<link rel="import" href="../../edit/gr-edit-constants.html"> <link rel="import" href="gr-change-view.html"> <script>void(0);</script> @@ -1186,5 +1187,49 @@ assert.isFalse(element._editLoaded); }); + + test('file-action-tap handling', () => { + const fileList = element.$.fileList; + const Actions = GrEditConstants.Actions; + const controls = element.$.fileListHeader.$.editControls; + sandbox.stub(controls, 'openDeleteDialog'); + sandbox.stub(controls, 'openRenameDialog'); + sandbox.stub(controls, 'openRestoreDialog'); + sandbox.stub(Gerrit.Nav, 'getEditUrlForDiff'); + sandbox.stub(Gerrit.Nav, 'navigateToRelativeUrl'); + + // Delete + fileList.dispatchEvent(new CustomEvent('file-action-tap', + {detail: {action: Actions.DELETE.id, path: 'foo'}, bubbles: true})); + flushAsynchronousOperations(); + + assert.isTrue(controls.openDeleteDialog.called); + assert.equal(controls.openDeleteDialog.lastCall.args[0], 'foo'); + + // Restore + fileList.dispatchEvent(new CustomEvent('file-action-tap', + {detail: {action: Actions.RESTORE.id, path: 'foo'}, bubbles: true})); + flushAsynchronousOperations(); + + assert.isTrue(controls.openRestoreDialog.called); + assert.equal(controls.openRestoreDialog.lastCall.args[0], 'foo'); + + // Rename + fileList.dispatchEvent(new CustomEvent('file-action-tap', + {detail: {action: Actions.RENAME.id, path: 'foo'}, bubbles: true})); + flushAsynchronousOperations(); + + assert.isTrue(controls.openRenameDialog.called); + assert.equal(controls.openRenameDialog.lastCall.args[0], 'foo'); + + // Edit + fileList.dispatchEvent(new CustomEvent('file-action-tap', + {detail: {action: Actions.EDIT.id, path: 'foo'}, bubbles: true})); + flushAsynchronousOperations(); + + assert.isTrue(Gerrit.Nav.getEditUrlForDiff.called); + assert.equal(Gerrit.Nav.getEditUrlForDiff.lastCall.args[1], 'foo'); + assert.isTrue(Gerrit.Nav.navigateToRelativeUrl.called); + }); }); </script>
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 ace880ad..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..7080fc6 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: 10em; + } @media screen and (max-width: 50em) { .desktop { display: none; @@ -296,6 +308,11 @@ <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]]"></gr-edit-file-controls> + </div> </div> <template is="dom-if" if="[[_isFileExpanded(file.__path, _expandedFilePaths.*)]]"> @@ -335,6 +352,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 a14fe58..f7b1af9 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 => {
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 5a4f58d..be03e38 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
@@ -702,7 +702,6 @@ assert.isTrue(element._updateDiffPreferences.called); }); - test('expanded attribute not set on path when not expanded', () => { element._files = [ {__path: '/COMMIT_MSG'}, @@ -793,6 +792,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() {}}; @@ -1240,6 +1262,16 @@ }); }); }); + + test('editing actions', () => { + element.editLoaded = true; + 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')); + }); }); 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..f0d7f6f --- /dev/null +++ b/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.html
@@ -0,0 +1,64 @@ +<!-- +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="../gr-edit-constants.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 { + text-decoration: none; + } + #edit, + #more { + margin-right: 1em; + } + gr-dropdown { + --gr-dropdown-item: { + background-color: transparent; + border: none; + color: #2a66d9; + font-size: inherit; + text-transform: uppercase; + } + } + </style> + <gr-button + id="edit" + link + on-tap="_handleEditTap">Edit</gr-button> + <!-- TODO(kaspern): implement more menu. --> + <gr-dropdown + id="more" + items="[[_fileActions]]" + down-arrow + vertical-offset="20" + on-tap-item="_handleActionTap" + 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..ad25c0d --- /dev/null +++ b/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls.js
@@ -0,0 +1,63 @@ +// 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 an action in the overflow menu is tapped. + * + * @event file-action-tap + */ + + properties: { + filePath: String, + // Edit action not needed in the overflow. + _allFileActions: { + type: Array, + value: () => Object.values(GrEditConstants.Actions) + .filter(action => action !== GrEditConstants.Actions.EDIT), + }, + _fileActions: { + type: Array, + computed: '_computeFileActions(_allFileActions)', + }, + }, + + _handleEditTap() { + this._dispatchFileAction(GrEditConstants.Actions.EDIT.id, this.filePath); + }, + + _handleActionTap(e) { + this._dispatchFileAction(e.detail.id, this.filePath); + }, + + _dispatchFileAction(action, path) { + this.dispatchEvent(new CustomEvent('file-action-tap', + {detail: {action, path}, bubbles: true})); + }, + + _computeFileActions(actions) { + // TODO(kaspern): conditionally disable some actions based on file status. + return actions.map(action => { + return { + name: action.label, + id: action.id, + }; + }); + }, + }); +})(); \ 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..42fb466 --- /dev/null +++ b/polygerrit-ui/app/elements/edit/gr-edit-file-controls/gr-edit-file-controls_test.html
@@ -0,0 +1,101 @@ +<!-- +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-constants.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; + let fileActionHandler; + + setup(() => { + sandbox = sinon.sandbox.create(); + element = fixture('basic'); + fileActionHandler = sandbox.stub(); + element.addEventListener('file-action-tap', fileActionHandler); + }); + + teardown(() => { sandbox.restore(); }); + + test('edit tap emits event', () => { + element.filePath = 'foo'; + + MockInteractions.tap(element.$.edit); + assert.isTrue(fileActionHandler.called); + assert.deepEqual(fileActionHandler.lastCall.args[0].detail, + {action: GrEditConstants.Actions.EDIT.id, path: 'foo'}); + }); + + test('delete tap emits event', () => { + const more = element.$.more; + element.filePath = 'foo'; + more._open(); + flushAsynchronousOperations(); + + MockInteractions.tap(more.$$('li [data-id="delete"]')); + assert.isTrue(fileActionHandler.called); + assert.deepEqual(fileActionHandler.lastCall.args[0].detail, + {action: GrEditConstants.Actions.DELETE.id, path: 'foo'}); + }); + + test('restore tap emits event', () => { + const more = element.$.more; + element.filePath = 'foo'; + more._open(); + flushAsynchronousOperations(); + + MockInteractions.tap(more.$$('li [data-id="restore"]')); + assert.isTrue(fileActionHandler.called); + assert.deepEqual(fileActionHandler.lastCall.args[0].detail, + {action: GrEditConstants.Actions.RESTORE.id, path: 'foo'}); + }); + + test('rename tap emits event', () => { + const more = element.$.more; + element.filePath = 'foo'; + more._open(); + flushAsynchronousOperations(); + + MockInteractions.tap(more.$$('li [data-id="rename"]')); + assert.isTrue(fileActionHandler.called); + assert.deepEqual(fileActionHandler.lastCall.args[0].detail, + {action: GrEditConstants.Actions.RENAME.id, path: 'foo'}); + }); + + test('computed properties', () => { + assert.equal(element._allFileActions.length, 3); + assert.notOk(element._allFileActions + .find(action => action.id === GrEditConstants.Actions.EDIT.id)); + }); +}); +</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..340ba87 100644 --- a/version.bzl +++ b/version.bzl
@@ -2,4 +2,11 @@ # 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 == "": + # experimental / unreleased Bazel. + return + if native.bazel_version < x: + fail("\nERROR: Current Bazel version is {}, expected at least {}\n".format(native.bazel_version, x))